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