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.application;
20  
21  import static fr.cnes.doi.client.ClientMDS.SCHEMA_DATACITE;
22  
23  import org.apache.logging.log4j.LogManager;
24  import org.apache.logging.log4j.Logger;
25  import org.restlet.Context;
26  import org.restlet.Request;
27  import org.restlet.Response;
28  import org.restlet.Restlet;
29  import org.restlet.data.Method;
30  import org.restlet.data.Reference;
31  import org.restlet.data.Status;
32  import org.restlet.ext.wadl.ApplicationInfo;
33  import org.restlet.ext.wadl.DocumentationInfo;
34  import org.restlet.ext.wadl.GrammarsInfo;
35  import org.restlet.ext.wadl.IncludeInfo;
36  import org.restlet.routing.Filter;
37  import org.restlet.routing.Router;
38  import org.restlet.routing.Template;
39  import org.restlet.security.ChallengeAuthenticator;
40  import org.restlet.security.MethodAuthorizer;
41  
42  import fr.cnes.doi.client.ClientMDS;
43  import fr.cnes.doi.db.AbstractTokenDBHelper;
44  import fr.cnes.doi.exception.DoiRuntimeException;
45  import fr.cnes.doi.resource.mds.DoiResource;
46  import fr.cnes.doi.resource.mds.DoisResource;
47  import fr.cnes.doi.resource.mds.MediaResource;
48  import fr.cnes.doi.resource.mds.MetadataResource;
49  import fr.cnes.doi.resource.mds.MetadatasResource;
50  import fr.cnes.doi.security.TokenSecurity;
51  import fr.cnes.doi.settings.Consts;
52  import fr.cnes.doi.utils.spec.Requirement;
53  import org.apache.logging.log4j.ThreadContext;
54  import org.restlet.data.ClientInfo;
55  
56  /**
57   * Provides an application to handle Data Object Identifier within an
58   * organization. A Digital Object Identifier (DOI) is a persistent identifier or
59   * handle used to uniquely identify objects, standardized by the International
60   * Organization. A DOI aims to be "resolvable", usually to some form of access
61   * to the information object to which the DOI refers. This is achieved by
62   * binding the DOI to metadata about the object, such as a URL, where all the
63   * details about the object are accessible. Everytime a URL changes, the
64   * publisher has to update the metadata for the DOI to link to the new URL. It
65   * is the publisher's responsibility to update the DOI database. If he fails to
66   * do so, the DOI resolves to a dead link leaving the DOI useless.
67   * <p>
68   * Two methods of authentication are defined in this application and both is
69   * optional:
70   * <ul>
71   * <li>{@link #createAuthenticator authenticator} by login/password</li>
72   * <li>{@link #createTokenAuthenticator authenticator} by
73   * {@link fr.cnes.doi.resource.admin.TokenResource token}
74   * </ul>
75   * Only the GET can be anonymous whereas POST, PUT, DELETE
76   * {@link #createMethodAuthorizer methods need to be authenticated}.
77   *
78   * <p>
79   * <b>Security</b><br>
80   * <code>
81   * --------------<br>
82   * The authentication is done by the following pipeline:<br>
83   * |Method authorization|--&gt;|Authentication login/pwd|--&gt;|Authentication
84   * token|<br>
85   * Method authorization : Only GET method does not need an authorization
86   * </code>
87   *
88   * <p>
89   * <b>Routing</b><br>
90   * <code>
91   * --------------<br>
92   * <br>
93   * root<br>
94   * |<br>
95   * |__ dois (authorization)<br>
96   * |__ dois/{doiName} (authorization)<br>
97   * |__ metadata (authorization)<br>
98   * |__ metadata/{doiName} (authorization)<br>
99   * |__ media/{doiName} (authorization)<br>
100  * </code>
101  *
102  * @see #createInboundRoot the resources related to this application
103  * @see <a href="http://www.doi.org/hb.html">DOI Handbook</a>
104  * @see <a href="https://mds.datacite.org/static/apidoc">API Documentation</a>
105  * @see DoisResource List and create DOI
106  * @see DoiResource Handle a DOI
107  * @see MetadatasResource Create metadata
108  * @see MetadataResource Handle DOI metadata
109  * @see MediaResource Handle media related to metadata
110  *
111  * @author Jean-Christophe Malapert (jean-Christophe Malapert@cnes.fr)
112  */
113 @Requirement(reqId = Requirement.DOI_SRV_010, reqName = Requirement.DOI_SRV_010_NAME)
114 @Requirement(reqId = Requirement.DOI_SRV_020, reqName = Requirement.DOI_SRV_020_NAME)
115 @Requirement(reqId = Requirement.DOI_SRV_030, reqName = Requirement.DOI_SRV_030_NAME)
116 @Requirement(reqId = Requirement.DOI_SRV_040, reqName = Requirement.DOI_SRV_040_NAME)
117 @Requirement(reqId = Requirement.DOI_SRV_050, reqName = Requirement.DOI_SRV_050_NAME)
118 @Requirement(reqId = Requirement.DOI_SRV_060, reqName = Requirement.DOI_SRV_060_NAME)
119 @Requirement(reqId = Requirement.DOI_SRV_070, reqName = Requirement.DOI_SRV_070_NAME)
120 @Requirement(reqId = Requirement.DOI_SRV_080, reqName = Requirement.DOI_SRV_080_NAME)
121 @Requirement(reqId = Requirement.DOI_SRV_090, reqName = Requirement.DOI_SRV_090_NAME)
122 @Requirement(reqId = Requirement.DOI_MONIT_020, reqName = Requirement.DOI_MONIT_020_NAME)
123 public final class DoiMdsApplication extends AbstractApplication {
124 
125     /**
126      * Template Query for DOI : {@value #DOI_TEMPLATE}.
127      */
128     public static final String DOI_TEMPLATE = "doiName";
129 
130     /**
131      * URI to handle the collection of DOIs : {@value #DOI_URI}.
132      */
133     public static final String DOI_URI = "/dois";
134 
135     /**
136      * URI to handle a DOI : {@value #DOI_NAME_URI}.
137      */
138     public static final String DOI_NAME_URI = "/{" + DOI_TEMPLATE + "}";
139 
140     /**
141      * URI to handle metadata : {@value #METADATAS_URI}.
142      */
143     public static final String METADATAS_URI = "/metadata";
144 
145     /**
146      * URI to handle media : {@value #MEDIA_URI}.
147      */
148     public static final String MEDIA_URI = "/media";
149 
150     /**
151      * Application name : {@value #NAME}
152      */
153     public static final String NAME = "Metadata Store Application";
154 
155     /**
156      * Logger.
157      */
158     private static final Logger LOG = LogManager.getLogger(DoiMdsApplication.class.getName());
159 
160     /**
161      * Client to query Mds Datacite.
162      */
163     private final ClientMDS client;
164 
165     /**
166      * Token DB that contains the set of generated token.
167      */
168     private final AbstractTokenDBHelper tokenDB;
169 
170     /**
171      * Creates the Digital Object Identifier server application.
172      *
173      * @param client ClientMDS
174      * @throws DoiRuntimeException When the DataCite schema is not available
175      */
176     public DoiMdsApplication(final ClientMDS client) {
177         super();
178         setName(NAME);
179         setDescription(
180                 "Provides an application for handling Data Object Identifier at CNES<br/>"
181                 + "This application provides 3 API:" + "<ul>" + "<li>dois : DOI minting</li>"
182                 + "<li>metadata : Registration of the associated metadata</li>"
183                 + "<li>media : Possbility to obtain metadata in various formats and/or get "
184                 + "automatic, direct access to an object rather than via the \"landing page\"</li>"
185                 + "</ul>");
186         this.client = client;
187         this.tokenDB = TokenSecurity.getInstance().getTokenDB();
188     }
189 
190     /**
191      * Creates a router for the DoiMdsApplication.
192      *
193      * This router routes the resources for the Mds application, which is
194      * protected by two authentication mechanisms (optional mechanisms) and an
195      * authorization by method.
196      *
197      * @see DoiMdsApplication#createRouter the router that contains the Mds
198      * resources
199      * @see DoiMdsApplication#createAuthenticator the authentication mechanism
200      * by login/password
201      * @see DoiMdsApplication#createTokenAuthenticator the authentication
202      * mechanism by token
203      * @see DoiMdsApplication#createMethodAuthorizer the method authorization
204      * mechanism
205      * @return Router
206      */
207     @Override
208     public Restlet createInboundRoot() {
209         LOG.traceEntry();
210 
211         final Filter logContext = new Filter() {
212             /**
213              * Get the IP client from proxy (if there is one) and set it in
214              * the header.
215              * 
216              * @param request request
217              * @param response response
218              * @return 0
219              */            
220             @Override
221             protected int beforeHandle(final Request request, final Response response) {
222                 final ClientInfo clientInfo = request.getClientInfo();
223                 final String ipAddress = request.getHeaders().getFirstValue(
224                         Consts.PROXIFIED_IP, clientInfo.getUpstreamAddress()
225                 );
226                 ThreadContext.put(Consts.LOG_IP_ADDRESS, ipAddress);
227                 return Filter.CONTINUE;
228             }
229         };
230 
231         // Defines the strategy of authentication (authentication is not required)
232         //   - authentication with login/pwd
233         final ChallengeAuthenticator challAuth = createAuthenticatorLoginBased();
234         challAuth.setOptional(true);
235 
236         //   - authentication with token
237         final ChallengeAuthenticator challTokenAuth = createTokenAuthenticator();
238         challTokenAuth.setOptional(true);
239 
240         // Set specific authorization on method after checking authentication
241         final MethodAuthorizer methodAuth = createMethodAuthorizer();
242 
243         // set information available for Log4j
244         logContext.setNext(challAuth);
245 
246         //  create a pipeline of authentication
247         challAuth.setNext(challTokenAuth);
248         challTokenAuth.setNext(methodAuth);
249 
250         // Router
251         methodAuth.setNext(createRouter());
252 
253         final Filter filter = new SecurityPostProcessingFilter(getContext(), logContext);
254         return LOG.traceExit(filter);
255     }
256 
257     /**
258      * Creates the router. The router routes the following resources:
259      * <ul>
260      * <li>{@link DoiMdsApplication#DOI_URI} to create/update a DOI and its
261      * landing page</li>
262      * <li>{@link DoiMdsApplication#DOI_URI} {@link DoiMdsApplication#DOI_NAME_URI}
263      * to get the URL of the landing page related to a given DOI</li>
264      * <li>{@link DoiMdsApplication#METADATAS_URI} to create/update DOI
265      * metadata</li>
266      * <li>{@link DoiMdsApplication#METADATAS_URI} {@link DoiMdsApplication#DOI_NAME_URI}
267      * to get DOI's metadata or delete a given DOI</li>
268      * <li>{@link DoiMdsApplication#MEDIA_URI} {@link DoiMdsApplication#DOI_NAME_URI}
269      * to handle media related to a DOI
270      * </ul>
271      *
272      * @see DoiResource Handles a DOI and its landing page
273      * @see MetadatasResource Handles DOI metadata
274      * @see MetadataResource Handles DOI metadata
275      * @see MediaResource Handles media related to a DOI
276      * @return the router
277      */
278     private Router createRouter() {
279         LOG.traceEntry();
280 
281         final Router router = new Router(getContext());
282         router.attach(DOI_URI, DoisResource.class);
283         router.attach(DOI_URI + DOI_NAME_URI, DoiResource.class)
284                 .getTemplate().setMatchingMode(Template.MODE_STARTS_WITH);
285         router.attach(METADATAS_URI, MetadatasResource.class);
286         router.attach(METADATAS_URI + DOI_NAME_URI, MetadataResource.class)
287                 .getTemplate().setMatchingMode(Template.MODE_STARTS_WITH);
288         router.attach(MEDIA_URI + DOI_NAME_URI, MediaResource.class)
289                 .getTemplate().setMatchingMode(Template.MODE_STARTS_WITH);
290 
291         return LOG.traceExit(router);
292     }
293 
294     /**
295      * Creates the method authorizer. GET method can be anonymous. The verbs
296      * (POST, PUT, DELETE) need to be authenticated.
297      *
298      * @return Authorizer based on authorized methods
299      */
300     private MethodAuthorizer createMethodAuthorizer() {
301         LOG.traceEntry();
302 
303         final MethodAuthorizer methodAuth = new MethodAuthorizer();
304         methodAuth.getAnonymousMethods().add(Method.GET);
305         methodAuth.getAnonymousMethods().add(Method.OPTIONS);
306         methodAuth.getAuthenticatedMethods().add(Method.GET);
307         methodAuth.getAuthenticatedMethods().add(Method.POST);
308         methodAuth.getAuthenticatedMethods().add(Method.PUT);
309         methodAuth.getAuthenticatedMethods().add(Method.DELETE);
310 
311         return LOG.traceExit(methodAuth);
312     }
313 
314     /**
315      * Returns the decrypted login for DataCite.
316      *
317      * @return the DataCite's login
318      */
319     private String getLoginMds() {
320         LOG.traceEntry();
321         return LOG.traceExit(this.getConfig().getSecret(Consts.INIST_LOGIN));
322     }
323 
324     /**
325      * Returns the decrypted password for DataCite.
326      *
327      * @return the DataCite's pwd
328      */
329     private String getPwdMds() {
330         LOG.traceEntry();
331         return LOG.traceExit(this.getConfig().getSecret(Consts.INIST_PWD));
332     }
333 
334     /**
335      * Returns the DOI prefix.
336      *
337      * @return the DOI prefix
338      */
339     public String getDataCentrePrefix() {
340         LOG.traceEntry();
341         return LOG.traceExit(this.getConfig().getString(Consts.INIST_DOI));
342     }
343 
344     /**
345      * Returns the client.
346      *
347      * @return the client
348      */
349     public ClientMDS getClient() {
350         LOG.traceEntry();
351         return LOG.traceExit(this.client);
352     }
353 
354     /**
355      * Returns the token database.
356      *
357      * @return the token database
358      */
359     @Override
360     public AbstractTokenDBHelper getTokenDB() {
361         LOG.traceEntry();
362         return LOG.traceExit(this.tokenDB);
363     }
364 
365     /**
366      * Returns the logger.
367      *
368      * @return the logger
369      */
370     @Override
371     public Logger getLog() {
372         return LOG;
373     }
374 
375     /**
376      * Method to describe application in the WADL.
377      *
378      * @param request Request
379      * @param response Response
380      * @return the application description for WADL
381      */
382     @Override
383     public final ApplicationInfo getApplicationInfo(final Request request,
384             final Response response) {
385         final ApplicationInfo result = super.getApplicationInfo(request, response);
386         final DocumentationInfo docInfo = new DocumentationInfo(
387                 "DOI server application provides is central service that registers DOI at DataCite"
388         );
389         docInfo.setTitle(this.getName());
390         docInfo.setTextContent(this.getDescription());
391         result.setDocumentation(docInfo);
392         result.getNamespaces().put(SCHEMA_DATACITE, "default");
393         result.getNamespaces().put("http://www.w3.org/2001/XMLSchema", "xsi");
394         final GrammarsInfo grammar = new GrammarsInfo();
395         final IncludeInfo include = new IncludeInfo();
396         include.setTargetRef(new Reference(SCHEMA_DATACITE));
397         grammar.getIncludes().add(include);
398         result.setGrammars(grammar);
399         return result;
400     }
401 
402     /**
403      * Post processing for specific authorization. Specific class to handle the
404      * case where the user is authorized by oauth but non authorized by the
405      * service because the user's role is not related to any projects
406      */
407     public static class SecurityPostProcessingFilter extends Filter {
408 
409         /**
410          * Constructor
411          *
412          * @param context context
413          * @param next gard
414          */
415         public SecurityPostProcessingFilter(final Context context, final Restlet next) {
416             super(context, next);
417         }
418 
419         /**
420          * Fixed problem with Jetty response.
421          *
422          * @param request request
423          * @param response response
424          */
425         @Override
426         protected void afterHandle(final Request request, final Response response) {
427             final Status status = response.getStatus();
428             final String reason = status.getReasonPhrase();
429             if (status.getCode() == Status.CLIENT_ERROR_UNAUTHORIZED.getCode()
430                     && API_MDS.SECURITY_USER_NO_ROLE.getShortMessage().equals(reason)) {
431                 response.getHeaders().add("WWW-Authenticate",
432                         "Basic realm=\"DOI Server access\", charset=\"UTF-8\"");
433             }
434         }
435     }
436 
437     /**
438      * API related only to the code in this webservice. Other codes can be
439      * returned by the {@link ClientMDS}
440      */
441     public enum API_MDS {
442         /**
443          * Create metadata. SUCCESS_CREATED is used as status meaning "Operation
444          * successful"
445          */
446         CREATE_METADATA(Status.SUCCESS_CREATED, "Operation successful"),
447         /**
448          * Forbidden to use this role. It happens when a user provides a role to
449          * the server whereas he is unknown in this role. CLIENT_ERROR_FORBIDDEN
450          * is used as status meaning "Forbidden to use this role"
451          */
452         SECURITY_USER_NOT_IN_SELECTED_ROLE(Status.CLIENT_ERROR_FORBIDDEN,
453                 "Forbidden to use this role"),
454         /**
455          * Fail to authorize the user. It happens when a client is authentified
456          * but unauthorized to use the resource. CLIENT_ERROR_UNAUTHORIZED is
457          * used as status meaning "Fail to authorize the user"
458          */
459         SECURITY_USER_NO_ROLE(Status.CLIENT_ERROR_UNAUTHORIZED, "Fail to authorize the user"),
460         /**
461          * Fail to know privileges of a user. It happens when an user is
462          * associated to several roles without selecting one.
463          * CLIENT_ERROR_CONFLICT is used as status meaning "Error when an user
464          * is associated to more than one role without setting selectedRole
465          * parameter"
466          */
467         SECURITY_USER_CONFLICT(Status.CLIENT_ERROR_CONFLICT,
468                 "Error when an user is associated to more than one role without setting selectedRole "
469                 + "parameter"),
470         /**
471          * Fail to create a DOI. It happens when a user try to create a DOI with
472          * the wrong role. Actually, the role is contained in the DOI name. So
473          * if a user is authentified with a right role and try to create a DOI
474          * for another role, an exception is raised. SECURITY_USER_PERMISSION is
475          * used as status meaning "User is not allowed to make this operation"
476          */
477         SECURITY_USER_PERMISSION(Status.CLIENT_ERROR_FORBIDDEN,
478                 "User is not allowed to make this operation"),
479         /**
480          * Cannot access to Datacite. It happens when a network problem may
481          * happen with Datacite. SERVER_ERROR_GATEWAY_TIMEOUT is used as status
482          * meaning "Cannot access to Datacite"
483          */
484         NETWORK_PROBLEM(Status.CONNECTOR_ERROR_COMMUNICATION, "Cannot access to Datacite"),
485         /**
486          * Fail to validate user input parameter for creating the DOI. It
487          * happens in the following cases:
488          * <ul>
489          * <li>the DOI or metadata are not provided</li>
490          * <li>the prefix is not allowed</li>
491          * <li>some characters are not allowed in the DOI name</li>
492          * </ul>
493          * CLIENT_ERROR_BAD_REQUEST is used as status meaning "Failed to
494          * validate the user inputs parameters"
495          */
496         METADATA_VALIDATION(Status.CLIENT_ERROR_BAD_REQUEST,
497                 "Failed to validate the user inputs parameters"),
498         /**
499          * Fail to create the media related to the DOI. CLIENT_ERROR_BAD_REQUEST
500          * is used as status meaning "DOI not provided or one or more of the
501          * specified mime-types or urls are invalid (e.g. non supported
502          * mime-type, not allowed url domain, etc.)"
503          */
504         MEDIA_VALIDATION(Status.CLIENT_ERROR_BAD_REQUEST,
505                 "DOI not provided or one or more of the specified mime-types or urls are invalid "
506                 + "(e.g. non supported mime-type, not allowed url domain, etc.)"),
507         /**
508          * Fail to create the landing page related to the DOI.
509          * CLIENT_ERROR_BAD_REQUEST is used as status meaning "Validation error
510          * when defining the DOI and its landing page"
511          */
512         LANGING_PAGE_VALIDATION(Status.CLIENT_ERROR_BAD_REQUEST,
513                 "Validation error when defining the DOI and its landing page"),
514         /**
515          * DOI validation error. CLIENT_ERROR_BAD_REQUEST is used as status
516          * meaning "Character or prefix not allowed in the DOI"
517          */
518         DOI_VALIDATION(Status.CLIENT_ERROR_BAD_REQUEST, "Character or prefix not allowed in the "
519                 + "DOI"),
520         /**
521          * Internal server error. Fail to communicate with DataCite using the
522          * interface specification meaning "Interface problem between Datacite
523          * and DOI-Server"
524          */
525         DATACITE_PROBLEM(Status.SERVER_ERROR_INTERNAL,
526                 "Interface problem between Datacite and DOI-Server");
527 
528         /**
529          * Message status.
530          */
531         private final Status status;
532         /**
533          * Short message.
534          */
535         private final String shortMessage;
536 
537         /**
538          * Constructor.
539          *
540          * @param status status
541          * @param shortMessage short message related to the status
542          */
543         API_MDS(final Status status, final String shortMessage) {
544             this.status = status;
545             this.shortMessage = shortMessage;
546         }
547 
548         /**
549          * Returns the status
550          *
551          * @return the status
552          */
553         public Status getStatus() {
554             return this.status;
555         }
556 
557         /**
558          * Returns the short message.
559          *
560          * @return the short message
561          */
562         public String getShortMessage() {
563             return this.shortMessage;
564         }
565 
566     }
567 }