ba16ca76b5ae1b0c0a90e16711cea59e5f54dfff
[policy/common.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.common.endpoints.http.server.internal;
22
23 import com.fasterxml.jackson.annotation.JsonIgnore;
24 import java.util.EnumSet;
25 import javax.servlet.DispatcherType;
26 import org.eclipse.jetty.security.ConstraintMapping;
27 import org.eclipse.jetty.security.ConstraintSecurityHandler;
28 import org.eclipse.jetty.security.HashLoginService;
29 import org.eclipse.jetty.security.UserStore;
30 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
31 import org.eclipse.jetty.server.CustomRequestLog;
32 import org.eclipse.jetty.server.HttpConfiguration;
33 import org.eclipse.jetty.server.HttpConnectionFactory;
34 import org.eclipse.jetty.server.SecureRequestCustomizer;
35 import org.eclipse.jetty.server.Server;
36 import org.eclipse.jetty.server.ServerConnector;
37 import org.eclipse.jetty.server.Slf4jRequestLogWriter;
38 import org.eclipse.jetty.servlet.FilterHolder;
39 import org.eclipse.jetty.servlet.ServletContextHandler;
40 import org.eclipse.jetty.util.security.Constraint;
41 import org.eclipse.jetty.util.security.Credential;
42 import org.eclipse.jetty.util.ssl.SslContextFactory;
43 import org.onap.aaf.cadi.filter.CadiFilter;
44 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
45 import org.onap.policy.common.gson.annotation.GsonJsonIgnore;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  * Http Server implementation using Embedded Jetty.
51  */
52 public abstract class JettyServletServer implements HttpServletServer, Runnable {
53
54     /**
55      * Keystore/Truststore system property names.
56      */
57     public static final String SYSTEM_KEYSTORE_PROPERTY_NAME = "javax.net.ssl.keyStore";
58     public static final String SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.keyStorePassword";
59     public static final String SYSTEM_TRUSTSTORE_PROPERTY_NAME = "javax.net.ssl.trustStore";
60     public static final String SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.trustStorePassword";
61
62     /**
63      * Logger.
64      */
65     private static Logger logger = LoggerFactory.getLogger(JettyServletServer.class);
66
67     /**
68      * Server name.
69      */
70     protected final String name;
71
72     /**
73      * Server host address.
74      */
75     protected final String host;
76
77     /**
78      * Server port to bind.
79      */
80     protected final int port;
81
82     /**
83      * Server auth user name.
84      */
85     protected String user;
86
87     /**
88      * Server auth password name.
89      */
90     protected String password;
91
92     /**
93      * Server base context path.
94      */
95     protected final String contextPath;
96
97     /**
98      * Embedded jetty server.
99      */
100     protected final Server jettyServer;
101
102     /**
103      * Servlet context.
104      */
105     protected final ServletContextHandler context;
106
107     /**
108      * Jetty connector.
109      */
110     protected final ServerConnector connector;
111
112     /**
113      * Jetty thread.
114      */
115     protected volatile Thread jettyThread;
116
117     /**
118      * Start condition.
119      */
120     protected Object startCondition = new Object();
121
122     /**
123      * Constructor.
124      *
125      * @param name server name
126      * @param host server host
127      * @param port server port
128      * @param contextPath context path
129      *
130      * @throws IllegalArgumentException if invalid parameters are passed in
131      */
132     public JettyServletServer(String name, boolean https, String host, int port, String contextPath) {
133         String srvName = name;
134
135         if (srvName == null || srvName.isEmpty()) {
136             srvName = "http-" + port;
137         }
138
139         if (port <= 0 || port >= 65535) {
140             throw new IllegalArgumentException("Invalid Port provided: " + port);
141         }
142
143         String srvHost = host;
144         if (srvHost == null || srvHost.isEmpty()) {
145             srvHost = "localhost";
146         }
147
148         String ctxtPath = contextPath;
149         if (ctxtPath == null || ctxtPath.isEmpty()) {
150             ctxtPath = "/";
151         }
152
153         this.name = srvName;
154
155         this.host = srvHost;
156         this.port = port;
157
158         this.contextPath = ctxtPath;
159
160         this.context = new ServletContextHandler(ServletContextHandler.SESSIONS);
161         this.context.setContextPath(ctxtPath);
162
163         this.jettyServer = new Server();
164
165         CustomRequestLog requestLog =
166                         new CustomRequestLog(new Slf4jRequestLogWriter(), CustomRequestLog.EXTENDED_NCSA_FORMAT);
167         this.jettyServer.setRequestLog(requestLog);
168
169         if (https) {
170             this.connector = httpsConnector();
171         } else {
172             this.connector = httpConnector();
173         }
174
175         this.connector.setName(srvName);
176         this.connector.setReuseAddress(true);
177         this.connector.setPort(port);
178         this.connector.setHost(srvHost);
179
180         this.jettyServer.addConnector(this.connector);
181         this.jettyServer.setHandler(context);
182     }
183
184     public JettyServletServer(String name, String host, int port, String contextPath) {
185         this(name, false, host, port, contextPath);
186     }
187
188     @Override
189     public void addFilterClass(String filterPath, String filterClass) {
190         if (filterClass == null || filterClass.isEmpty()) {
191             throw new IllegalArgumentException("No filter class provided");
192         }
193
194         String tempFilterPath = filterPath;
195         if (filterPath == null || filterPath.isEmpty()) {
196             tempFilterPath = "/*";
197         }
198
199         context.addFilter(filterClass, tempFilterPath, EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
200     }
201
202     /**
203      * Returns the https connector.
204      *
205      * @return the server connector
206      */
207     public ServerConnector httpsConnector() {
208         SslContextFactory sslContextFactory = new SslContextFactory.Server();
209
210         String keyStore = System.getProperty(SYSTEM_KEYSTORE_PROPERTY_NAME);
211         if (keyStore != null) {
212             sslContextFactory.setKeyStorePath(keyStore);
213
214             String ksPassword = System.getProperty(SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME);
215             if (ksPassword != null) {
216                 sslContextFactory.setKeyStorePassword(ksPassword);
217             }
218         }
219
220         String trustStore = System.getProperty(SYSTEM_TRUSTSTORE_PROPERTY_NAME);
221         if (trustStore != null) {
222             sslContextFactory.setTrustStorePath(trustStore);
223
224             String tsPassword = System.getProperty(SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME);
225             if (tsPassword != null) {
226                 sslContextFactory.setTrustStorePassword(tsPassword);
227             }
228         }
229
230         HttpConfiguration https = new HttpConfiguration();
231         https.addCustomizer(new SecureRequestCustomizer());
232
233         return new ServerConnector(jettyServer, sslContextFactory, new HttpConnectionFactory(https));
234     }
235
236     public ServerConnector httpConnector() {
237         return new ServerConnector(this.jettyServer);
238     }
239
240     @Override
241     public void setAafAuthentication(String filterPath) {
242         this.addFilterClass(filterPath, CadiFilter.class.getName());
243     }
244
245     @Override
246     public boolean isAaf() {
247         for (FilterHolder filter : context.getServletHandler().getFilters()) {
248             if (CadiFilter.class.getName().equals(filter.getClassName())) {
249                 return true;
250             }
251         }
252         return false;
253     }
254
255     @Override
256     public void setBasicAuthentication(String user, String password, String servletPath) {
257         String srvltPath = servletPath;
258
259         if (user == null || user.isEmpty() || password == null || password.isEmpty()) {
260             throw new IllegalArgumentException("Missing user and/or password");
261         }
262
263         if (srvltPath == null || srvltPath.isEmpty()) {
264             srvltPath = "/*";
265         }
266
267         final HashLoginService hashLoginService = new HashLoginService();
268         final UserStore userStore = new UserStore();
269         userStore.addUser(user, Credential.getCredential(password), new String[] {"user"});
270         hashLoginService.setUserStore(userStore);
271         hashLoginService.setName(this.connector.getName() + "-login-service");
272
273         Constraint constraint = new Constraint();
274         constraint.setName(Constraint.__BASIC_AUTH);
275         constraint.setRoles(new String[] {"user"});
276         constraint.setAuthenticate(true);
277
278         ConstraintMapping constraintMapping = new ConstraintMapping();
279         constraintMapping.setConstraint(constraint);
280         constraintMapping.setPathSpec(srvltPath);
281
282         ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
283         securityHandler.setAuthenticator(new BasicAuthenticator());
284         securityHandler.setRealmName(this.connector.getName() + "-realm");
285         securityHandler.addConstraintMapping(constraintMapping);
286         securityHandler.setLoginService(hashLoginService);
287
288         this.context.setSecurityHandler(securityHandler);
289
290         this.user = user;
291         this.password = password;
292     }
293
294     /**
295      * jetty server execution.
296      */
297     @Override
298     public void run() {
299         try {
300             logger.info("{}: STARTING", this);
301
302             this.jettyServer.start();
303
304             if (logger.isTraceEnabled()) {
305                 logger.trace("{}: STARTED: {}", this, this.jettyServer.dump());
306             }
307
308             synchronized (this.startCondition) {
309                 this.startCondition.notifyAll();
310             }
311
312             this.jettyServer.join();
313         } catch (Exception e) {
314             logger.error("{}: error found while bringing up server", this, e);
315         }
316     }
317
318     @Override
319     public boolean waitedStart(long maxWaitTime) throws InterruptedException {
320         logger.info("{}: WAITED-START", this);
321
322         if (maxWaitTime < 0) {
323             throw new IllegalArgumentException("max-wait-time cannot be negative");
324         }
325
326         long pendingWaitTime = maxWaitTime;
327
328         if (!this.start()) {
329             return false;
330         }
331
332         synchronized (this.startCondition) {
333
334             while (!this.jettyServer.isRunning()) {
335                 try {
336                     long startTs = System.currentTimeMillis();
337
338                     this.startCondition.wait(pendingWaitTime);
339
340                     if (maxWaitTime == 0) {
341                         /* spurious notification */
342                         continue;
343                     }
344
345                     long endTs = System.currentTimeMillis();
346                     pendingWaitTime = pendingWaitTime - (endTs - startTs);
347
348                     logger.info("{}: pending time is {} ms.", this, pendingWaitTime);
349
350                     if (pendingWaitTime <= 0) {
351                         return false;
352                     }
353
354                 } catch (InterruptedException e) {
355                     logger.warn("{}: waited-start has been interrupted", this);
356                     throw e;
357                 }
358             }
359
360             return this.jettyServer.isRunning();
361         }
362     }
363
364     @Override
365     public boolean start() {
366         logger.info("{}: STARTING", this);
367
368         synchronized (this) {
369             if (jettyThread == null || !this.jettyThread.isAlive()) {
370
371                 this.jettyThread = new Thread(this);
372                 this.jettyThread.setName(this.name + "-" + this.port);
373                 this.jettyThread.start();
374             }
375         }
376
377         return true;
378     }
379
380     @Override
381     public boolean stop() {
382         logger.info("{}: STOPPING", this);
383
384         synchronized (this) {
385             if (jettyThread == null) {
386                 return true;
387             }
388
389             if (!jettyThread.isAlive()) {
390                 this.jettyThread = null;
391             }
392
393             try {
394                 this.connector.stop();
395             } catch (Exception e) {
396                 logger.error("{}: error while stopping management server", this, e);
397             }
398
399             try {
400                 this.jettyServer.stop();
401             } catch (Exception e) {
402                 logger.error("{}: error while stopping management server", this, e);
403                 return false;
404             }
405
406             Thread.yield();
407         }
408
409         return true;
410     }
411
412     @Override
413     public void shutdown() {
414         logger.info("{}: SHUTTING DOWN", this);
415
416         this.stop();
417
418         if (this.jettyThread == null) {
419             return;
420         }
421
422         Thread jettyThreadCopy = this.jettyThread;
423
424         if (jettyThreadCopy.isAlive()) {
425             try {
426                 jettyThreadCopy.join(2000L);
427             } catch (InterruptedException e) {
428                 logger.warn("{}: error while shutting down management server", this);
429                 Thread.currentThread().interrupt();
430             }
431             if (!jettyThreadCopy.isInterrupted()) {
432                 try {
433                     jettyThreadCopy.interrupt();
434                 } catch (Exception e) {
435                     // do nothing
436                     logger.warn("{}: exception while shutting down (OK)", this, e);
437                 }
438             }
439         }
440
441         this.jettyServer.destroy();
442     }
443
444     @Override
445     public boolean isAlive() {
446         if (this.jettyThread != null) {
447             return this.jettyThread.isAlive();
448         }
449
450         return false;
451     }
452
453     @Override
454     public int getPort() {
455         return this.port;
456     }
457
458     /**
459      * Get name.
460      *
461      * @return the name
462      */
463     public String getName() {
464         return name;
465     }
466
467     /**
468      * Get host.
469      *
470      * @return the host
471      */
472     public String getHost() {
473         return host;
474     }
475
476     /**
477      * Get user.
478      *
479      * @return the user
480      */
481     public String getUser() {
482         return user;
483     }
484
485     /**
486      * Get password.
487      *
488      * @return the password
489      */
490     @JsonIgnore
491     @GsonJsonIgnore
492     public String getPassword() {
493         return password;
494     }
495
496     @Override
497     public String toString() {
498         StringBuilder builder = new StringBuilder();
499         builder.append("JettyServer [name=").append(name).append(", host=").append(host).append(", port=").append(port)
500                 .append(", user=").append(user).append(", password=").append(password != null).append(", contextPath=")
501                 .append(contextPath).append(", jettyServer=").append(jettyServer).append(", context=")
502                 .append(this.context).append(", connector=").append(connector).append(", jettyThread=")
503                 .append(jettyThread).append("]");
504         return builder.toString();
505     }
506
507 }