View Javadoc

1   /*
2    * Copyright (C) 2017-2019 Centre National d'Etudes Spatiales (CNES).
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 3.0 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not, write to the Free Software
16   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17   * MA 02110-1301  USA
18   */
19  package fr.cnes.doi.server;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  import java.security.KeyStore;
26  import java.util.List;
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  import org.restlet.Application;
30  import org.restlet.Client;
31  import org.restlet.Component;
32  import org.restlet.Context;
33  import org.restlet.Server;
34  import org.restlet.data.LocalReference;
35  import org.restlet.data.Parameter;
36  import org.restlet.data.Protocol;
37  import org.restlet.engine.Engine;
38  import org.restlet.engine.connector.ConnectorHelper;
39  import org.restlet.ext.httpclient4.HttpDOIClientHelper;
40  import org.restlet.representation.Representation;
41  import org.restlet.resource.ClientResource;
42  import org.restlet.routing.Filter;
43  import org.restlet.service.LogService;
44  import org.restlet.service.Service;
45  import org.restlet.util.Series;
46  
47  import fr.cnes.doi.application.AdminApplication;
48  import fr.cnes.doi.application.DoiCrossCiteApplication;
49  import fr.cnes.doi.application.DoiMdsApplication;
50  import fr.cnes.doi.client.ClientMDS;
51  import fr.cnes.doi.db.AbstractUserRoleDBHelper;
52  import fr.cnes.doi.exception.AuthenticationAccessException;
53  import fr.cnes.doi.db.model.AuthSystemUser;
54  import fr.cnes.doi.logging.api.DoiLogDataServer;
55  import fr.cnes.doi.logging.business.JsonMessage;
56  import fr.cnes.doi.logging.security.DoiSecurityLogFilter;
57  import fr.cnes.doi.security.RoleAuthorizer;
58  import fr.cnes.doi.settings.Consts;
59  import fr.cnes.doi.settings.DoiSettings;
60  import fr.cnes.doi.settings.EmailSettings;
61  import fr.cnes.doi.settings.JettySettings;
62  import fr.cnes.doi.settings.ProxySettings;
63  import fr.cnes.doi.utils.Utils;
64  import fr.cnes.doi.utils.spec.Requirement;
65  import fr.cnes.doi.plugin.PluginFactory;
66  import static fr.cnes.doi.plugin.Utils.addPath;
67  import fr.cnes.doi.db.IAuthenticationDBHelper;
68  import fr.cnes.doi.exception.ClientMdsException;
69  import static fr.cnes.doi.settings.Consts.USE_FORWARDED_FOR_HEADER;
70  
71  /**
72   * DoiServer contains the configuration of this server and the methods to
73   * start/stop it.
74   *
75   * @author Jean-Christophe Malapert (jean-christophe.malapert@cnes.fr)
76   */
77  public class DoiServer extends Component {
78  
79      /**
80       * Default value for {@link #SSL_CTX_FACTORY} parameter :
81       * {@value #DEFAULT_SSL_CTX}.
82       */
83      public static final String DEFAULT_SSL_CTX = "org.restlet.engine.ssl.DefaultSslContextFactory";
84      /**
85       * SslContectFactory parameter {@value #SSL_CTX_FACTORY}.
86       */
87      public static final String SSL_CTX_FACTORY = "sslContextFactory";
88      /**
89       * Key store parameter {@value #KEY_STORE_PATH}.
90       */
91      public static final String KEY_STORE_PATH = "keyStorePath";
92      /**
93       * Key store password parameter {@value #KEY_STORE_PWD}.
94       */
95      public static final String KEY_STORE_PWD = "keyStorePassword";
96      /**
97       * Key store type parameter {@value #KEY_STORE_TYPE}.
98       */
99      public static final String KEY_STORE_TYPE = "keyStoreType";
100     /**
101      * Key password parameter {@value #KEY_PWD}.
102      */
103     public static final String KEY_PWD = "keyPassword";
104     /**
105      * Trust store path parameter {@value #TRUST_STORE_PATH}.
106      */
107     public static final String TRUST_STORE_PATH = "trustStorePath";
108     /**
109      * Trust store password parameter {@value #TRUST_STORE_PATH}.
110      */
111     public static final String TRUST_STORE_PWD = "trustStorePassword";
112     /**
113      * Trust store type parameter {@value #TRUST_STORE_PATH}.
114      */
115     public static final String TRUST_STORE_TYPE = "trustStoreType";
116     /**
117      * JKS file, which is stored in the JAR.
118      */
119     public static final String JKS_FILE = "doiServerKey.jks";
120 
121     /**
122      * Directory {@value #JKS_DIRECTORY}. where JKS_FILE is located.
123      */
124     public static final String JKS_DIRECTORY = "jks";
125 
126     /**
127      * URI of the Meta Data Store application.
128      */
129     public static final String MDS_URI = "/mds";
130 
131     /**
132      * URI of the Citation application.
133      */
134     public static final String CITATION_URI = "/citation";
135 
136     /**
137      * URI of the Datacite status.
138      */
139     public static final String STATUS_URI = "/status";
140 
141     /**
142      * Number total connections.
143      */
144     public static final String RESTLET_MAX_TOTAL_CONNECTIONS = "maxTotalConnections";
145 
146     /**
147      * Number connections per host.
148      */
149     public static final String RESTLET_MAX_CONNECTIONS_PER_HOST = "maxConnectionsPerHost";
150 
151     /**
152      * Default number for RESTLET_MAX_TOTAL_CONNECTIONS.
153      */
154     public static final String DEFAULT_MAX_TOTAL_CONNECTIONS = "-1";
155 
156     /**
157      * Default number for RESTLET_MAX_CONNECTIONS_PER_HOST.
158      */
159     public static final String DEFAULT_MAX_CONNECTIONS_PER_HOST = "-1";
160 
161     /**
162      * Logger.
163      */
164     private static final Logger LOG = LogManager.getLogger(DoiServer.class.getName());
165 
166     /**
167      * Template message {@value #MESSAGE_TPL}.
168      */
169     private static final String MESSAGE_TPL = "{} : {}";
170 
171     /**
172      * Text to display {@value #PARAMETERS} in log messages.
173      */
174     private static final String PARAMETERS = "Parameters";
175 
176     static {
177         final List<ConnectorHelper<Client>> registeredClients = Engine.getInstance().
178                 getRegisteredClients();
179         registeredClients.add(0, new HttpDOIClientHelper(null));
180     }
181 
182     /**
183      * Configuration.
184      */
185     private final DoiSettings settings;
186 
187     /**
188      * Creates an instance of the server with settings coming from the
189      * config.properties
190      *
191      * @param settings settings
192      * @throws fr.cnes.doi.exception.ClientMdsException It happens when the server
193      * cannot access to Datacite account
194      */
195     public DoiServer(final DoiSettings settings) throws ClientMdsException {
196         super();
197         this.settings = settings;
198         startWithProxy();
199     }
200 
201     /**
202      * Init log services.
203      */
204     private void initLogServices() {
205         LOG.traceEntry();
206 
207         final LogService logServiceApplication = new DoiLogDataServer(Utils.HTTP_LOGGER_NAME, true);
208         this.getServices().add(logServiceApplication);
209 
210         final Service logServiceSecurity = new LogService(true) {
211             /**
212              * Creates a filter
213              *
214              * @param context context
215              * @return Filter
216              * @see
217              * org.restlet.service.LogService#createInboundFilter(org.restlet.Context)
218              */
219             @Override
220             public Filter createInboundFilter(final Context context) {
221                 return new DoiSecurityLogFilter();
222             }
223         };
224         this.getServices().add(logServiceSecurity);
225         LOG.traceExit();
226     }
227 
228     /**
229      * Configures the Server in HTTP and HTTPS.
230      */
231     @Requirement(reqId = Requirement.DOI_ARCHI_010, reqName = Requirement.DOI_ARCHI_010_NAME)
232     private void configureServer() throws ClientMdsException {
233         LOG.traceEntry();
234         final boolean isHttpStarted = initHttpServer();
235         final boolean isHttpsStarted = initHttpsServer();
236         if (isHttpStarted || isHttpsStarted) {
237             initClients();
238             initAttachApplication();
239         } else {
240             LOG.warn("No server is configured, please check your configuration file");
241         }
242 
243         LOG.traceExit();
244     }
245 
246     /**
247      * Inits the HTTP server.
248      *
249      * @return True when the Http server is configured to start.
250      */
251     private boolean initHttpServer() {
252         LOG.traceEntry();
253         final boolean isConfigured;
254         if (settings.hasValue(Consts.SERVER_HTTP_PORT)) {
255             final String httpPort = settings.getString(Consts.SERVER_HTTP_PORT);
256             final Server serverHttp = startHttpServer(Integer.parseInt(httpPort));
257             this.getServers().add(serverHttp);
258             initJettyConfiguration(serverHttp);
259             isConfigured = true;
260         } else {
261             isConfigured = false;
262         }
263         return LOG.traceExit(isConfigured);
264     }
265 
266     /**
267      * Inits the HTTPS server.
268      *
269      * @return True when the Https server is configured to start.
270      */
271     private boolean initHttpsServer() {
272         LOG.traceEntry();
273         final boolean isConfigured;
274         if (settings.hasValue(Consts.SERVER_HTTPS_PORT)) {
275             final String httpsPort = settings.getString(Consts.SERVER_HTTPS_PORT);
276             final Server serverHttps = startHttpsServer(Integer.parseInt(httpsPort));
277             this.getServers().add(serverHttps);
278             initJettyConfiguration(serverHttps);
279             isConfigured = true;
280         } else {
281             isConfigured = false;
282         }
283         return LOG.traceExit(isConfigured);
284     }
285 
286     /**
287      * Init the Jetty configuration and applies it to the server.
288      *
289      * @param server HTTP or HTTPS server
290      */
291     private void initJettyConfiguration(final Server server) {
292         LOG.traceEntry(new JsonMessage(server));
293         final JettySettings jettyProps = new JettySettings(server, settings);
294         jettyProps.addParamsToServerContext();
295         LOG.traceExit();
296     }
297 
298     /**
299      * Inits supported protocols. Theses protocols are used by the server to
300      * access to resources
301      */
302     private void initClients() {
303         LOG.traceEntry();
304         this.getClients().add(Protocol.HTTP);
305         this.getClients().add(Protocol.HTTPS);
306         this.getClients().add(Protocol.CLAP);
307         this.getClients().add(Protocol.FILE);
308         LOG.traceExit();
309     }
310 
311     /**
312      * Routes the applications.
313      */
314     private void initAttachApplication() throws ClientMdsException {
315         LOG.traceEntry();
316         final DoiSettings doiConfig = DoiSettings.getInstance();
317         final String contextMode = doiConfig.getString(Consts.CONTEXT_MODE);
318         final ClientMDS client = new ClientMDS(ClientMDS.Context.valueOf(contextMode), 
319                 doiConfig.getSecret(Consts.INIST_LOGIN),
320                 doiConfig.getSecret(Consts.INIST_PWD));          
321         final Application appDoiProject = new DoiMdsApplication(client);
322         final Application appAdmin = new AdminApplication(client);
323         this.getDefaultHost().attach(MDS_URI, appDoiProject);
324         this.getDefaultHost().attach(CITATION_URI, new DoiCrossCiteApplication());
325         this.getDefaultHost().attachDefault(appAdmin);
326         // Set authentication 
327         RoleAuthorizer.getInstance().createRealmFor(appDoiProject);
328         RoleAuthorizer.getInstance().createRealmFor(appAdmin);
329         // Set authentication user as admin
330         final String doiAdmin = PluginFactory.getAuthenticationSystem().getDOIAdmin();
331         addAuthenticationUserAsAdmin(doiAdmin);
332         LOG.traceExit();
333     }
334 
335     /**
336      * Adds an authentication system user as administrator of the DOI server
337      *
338      * @param username username
339      */
340     private void addAuthenticationUserAsAdmin(final String username) {
341         LOG.traceEntry("Parameter\n   username: {}", username);
342         final IAuthenticationDBHelper authenticationService = PluginFactory.
343                 getAuthenticationSystem();
344         final AbstractUserRoleDBHelper manageUsers = PluginFactory.getUserManagement();
345         try {
346             boolean isFound = false;
347             final List<AuthSystemUser> authenticationUsers = authenticationService.
348                     getDOIProjectMembers();
349             for (final AuthSystemUser authenticationUser : authenticationUsers) {
350                 if (authenticationUser.getUsername().equals(username)) {
351                     manageUsers.setUserToAdminGroup(authenticationUser.getUsername());
352                     isFound = true;
353                     break;
354                 }
355             }
356             if (!isFound) {
357                 LOG.warn("{} is not registered in the authentication system - Cannot create "
358                         + "the administrator in DOI database",
359                         username
360                 );
361             }
362         } catch (AuthenticationAccessException ex) {
363             LOG.catching(ex);
364             LOG.warn("Cannot create an administrator: {}", ex);
365         }
366 
367         LOG.traceExit();
368     }
369 
370     /**
371      * Starts with proxy.
372      *
373      */
374     private void startWithProxy() throws ClientMdsException {
375         LOG.traceEntry();
376         initLogServices();
377         RoleAuthorizer.getInstance();
378         ProxySettings.getInstance();
379         EmailSettings.getInstance();
380         configureServer();
381         LOG.traceExit();
382     }
383 
384     /**
385      * Creates a HTTP server
386      *
387      * @param port HTTP port
388      * @return the HTTP server
389      */
390     private Server startHttpServer(final Integer port) {
391         LOG.traceEntry(MESSAGE_TPL, PARAMETERS, port);
392         final Server server = new Server(Protocol.HTTP, port, this);
393         return LOG.traceExit(server);
394     }
395 
396     /**
397      * Creates a HTTPS server
398      *
399      * @param port HTTPS port
400      * @return the HTTPS server
401      */
402     private Server startHttpsServer(final Integer port) {
403         LOG.traceEntry(MESSAGE_TPL, PARAMETERS, port);
404         final String pathKeyStore;
405         if (settings.hasValue(Consts.SERVER_HTTPS_KEYSTORE_PATH)) {
406             pathKeyStore = settings.getString(Consts.SERVER_HTTPS_KEYSTORE_PATH);
407             LOG.debug("path key store value loaded from a custom configuration file");
408         } else {
409             pathKeyStore = extractKeyStoreToPath();
410             LOG.debug("path key store value loaded from an internal configuration");
411         }
412 
413         final String pathKeyTrustStore;
414         if (settings.hasValue(Consts.SERVER_HTTPS_TRUST_STORE_PATH)) {
415             pathKeyTrustStore = settings.getString(Consts.SERVER_HTTPS_TRUST_STORE_PATH);
416         } else {
417             pathKeyTrustStore = extractKeyStoreToPath();
418         }
419 
420         // create embedding https jetty server
421         final Server server = new Server(new Context(), Protocol.HTTPS, port, this);
422         final Series<Parameter> parameters = server.getContext().getParameters();
423 
424         LOG.debug(MESSAGE_TPL, USE_FORWARDED_FOR_HEADER, "true", "true");
425         parameters.set(USE_FORWARDED_FOR_HEADER, "true");
426 
427         LOG.debug(MESSAGE_TPL, RESTLET_MAX_TOTAL_CONNECTIONS, DoiSettings.getInstance().getString(
428                 fr.cnes.doi.settings.Consts.RESTLET_MAX_TOTAL_CONNECTIONS,
429                 DEFAULT_MAX_TOTAL_CONNECTIONS));
430         parameters.set(RESTLET_MAX_TOTAL_CONNECTIONS, DoiSettings.getInstance().getString(
431                 fr.cnes.doi.settings.Consts.RESTLET_MAX_TOTAL_CONNECTIONS,
432                 DEFAULT_MAX_TOTAL_CONNECTIONS));
433 
434         LOG.debug(MESSAGE_TPL, RESTLET_MAX_CONNECTIONS_PER_HOST, DoiSettings.getInstance().
435                 getString(fr.cnes.doi.settings.Consts.RESTLET_MAX_CONNECTIONS_PER_HOST,
436                         DEFAULT_MAX_CONNECTIONS_PER_HOST));
437         parameters.set(RESTLET_MAX_CONNECTIONS_PER_HOST, DoiSettings.getInstance().getString(
438                 fr.cnes.doi.settings.Consts.RESTLET_MAX_CONNECTIONS_PER_HOST,
439                 DEFAULT_MAX_CONNECTIONS_PER_HOST));
440 
441         LOG.debug(MESSAGE_TPL, SSL_CTX_FACTORY, DEFAULT_SSL_CTX);
442         parameters.add(SSL_CTX_FACTORY, DEFAULT_SSL_CTX);
443 
444         // Specifies the path for the keystore used by the server
445         LOG.debug(MESSAGE_TPL, KEY_STORE_PATH, pathKeyStore);
446         parameters.add(KEY_STORE_PATH, pathKeyStore);
447 
448         // Specifies the password for the keystore containing several keys
449         LOG.debug(MESSAGE_TPL, KEY_STORE_PWD, "xxxxxxx");
450         parameters.add(KEY_STORE_PWD, settings.getSecret(Consts.SERVER_HTTPS_KEYSTORE_PASSWD));
451 
452         // Specifies the type of the keystore
453         LOG.debug(MESSAGE_TPL, KEY_STORE_TYPE, KeyStore.getDefaultType());
454         parameters.add(KEY_STORE_TYPE, KeyStore.getDefaultType());
455 
456         // Specifies the password of the specific key used
457         LOG.debug(MESSAGE_TPL, KEY_PWD, "xxxxxxxx");
458         parameters.add(KEY_PWD, settings.getSecret(Consts.SERVER_HTTPS_SECRET_KEY));
459 
460         // Specifies the path to the truststore
461         LOG.debug(MESSAGE_TPL, TRUST_STORE_PATH, pathKeyTrustStore);
462         parameters.add(TRUST_STORE_PATH, pathKeyTrustStore);
463 
464         // Specifies the password of the truststore
465         LOG.debug(MESSAGE_TPL, TRUST_STORE_PWD, "xxxxx");
466         parameters.add(TRUST_STORE_PWD, settings.getSecret(Consts.SERVER_HTTPS_TRUST_STORE_PASSWD));
467 
468         // Specifies the type of the truststore
469         LOG.debug(MESSAGE_TPL, TRUST_STORE_TYPE, KeyStore.getDefaultType());
470         parameters.add(TRUST_STORE_TYPE, KeyStore.getDefaultType());
471 
472         return LOG.traceExit(server);
473     }
474 
475     /**
476      * Extracts keystore for JAR and copy it in a directory in order to use it.
477      *
478      * @return the path of the new location of the keystore.
479      */
480     private String extractKeyStoreToPath() {
481         LOG.traceEntry();
482         String result;
483         final Representation jks = new ClientResource(
484                 LocalReference.createClapReference("class/" + JKS_FILE)
485         ).get();
486         try {
487             final Path outputDirectory = new File(JKS_DIRECTORY).toPath();
488             if (Files.notExists(outputDirectory)) {
489                 Files.createDirectory(outputDirectory);
490                 LOG.info("Creates {} directory to extract {} in it", outputDirectory, JKS_FILE);
491             }
492             final String outputJksLocation = outputDirectory.getFileName()
493                     + File.separator
494                     + JKS_FILE;
495 
496             final File outputFile = new File(outputJksLocation);
497             Files.copy(jks.getStream(),
498                     outputFile.toPath(),
499                     java.nio.file.StandardCopyOption.REPLACE_EXISTING
500             );
501             LOG.info("Copy/replace if exists {} in {}", JKS_FILE, outputDirectory, JKS_FILE);
502             result = outputJksLocation;
503         } catch (IOException ex) {
504             LOG.fatal("Unable to extract keystore from class/" + JKS_FILE, ex);
505             result = "";
506         }
507         return LOG.traceExit(result);
508     }
509 
510     /**
511      * {@inheritDoc}
512      */
513     @Override
514     public synchronized void start() throws Exception {
515         LOG.info("Starting server ...");
516         addPath(DoiSettings.getInstance().getPathApp() + File.separatorChar + "plugins");
517         super.start();
518         LOG.info("Server started");
519     }
520 
521     /**
522      * {@inheritDoc}
523      */
524     @Override
525     public synchronized void stop() throws Exception {
526         LOG.info("Stopping the server ...");
527         super.stop();
528         LOG.info("Stopping Authentication plugin");
529         PluginFactory.getAuthenticationSystem().release();
530         LOG.info("Stopping Project plugin");
531         PluginFactory.getProjectSuffix().release();
532         LOG.info("Stopping Token plugin");
533         PluginFactory.getToken().release();
534         LOG.info("Stopping UserManagement plugin");
535         PluginFactory.getUserManagement().release();
536         EmailSettings.getInstance().sendMessage("[DOI] Stopping Server",
537                 "The server has been interrupted");
538         LOG.info("Server stopped");
539     }
540 
541 }