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.resource.admin;
20  
21  import fr.cnes.doi.application.AdminApplication;
22  import static fr.cnes.doi.application.AdminApplication.TOKEN_TEMPLATE;
23  import fr.cnes.doi.db.AbstractTokenDBHelper;
24  import fr.cnes.doi.db.model.DOIUser;
25  import fr.cnes.doi.exception.DOIDbException;
26  import fr.cnes.doi.exception.DoiRuntimeException;
27  import fr.cnes.doi.exception.TokenSecurityException;
28  import fr.cnes.doi.plugin.PluginFactory;
29  import fr.cnes.doi.resource.AbstractResource;
30  import fr.cnes.doi.security.RoleAuthorizer;
31  import fr.cnes.doi.security.TokenSecurity;
32  import fr.cnes.doi.security.TokenSecurity.TimeUnit;
33  import fr.cnes.doi.settings.Consts;
34  import fr.cnes.doi.settings.DoiSettings;
35  import fr.cnes.doi.settings.EmailSettings;
36  import fr.cnes.doi.utils.spec.Requirement;
37  import io.jsonwebtoken.Claims;
38  import io.jsonwebtoken.Jws;
39  import java.util.List;
40  import org.apache.logging.log4j.Level;
41  import org.apache.logging.log4j.Logger;
42  import org.restlet.data.Form;
43  import org.restlet.data.MediaType;
44  import org.restlet.data.Method;
45  import org.restlet.data.Status;
46  import org.restlet.ext.json.JsonRepresentation;
47  import org.restlet.ext.wadl.DocumentationInfo;
48  import org.restlet.ext.wadl.MethodInfo;
49  import org.restlet.ext.wadl.ParameterStyle;
50  import org.restlet.ext.wadl.RepresentationInfo;
51  import org.restlet.representation.Representation;
52  import org.restlet.resource.Delete;
53  import org.restlet.resource.Get;
54  import org.restlet.resource.Post;
55  import org.restlet.resource.ResourceException;
56  
57  /**
58   * Provides a resource to create token and to decrypt token
59   *
60   * @author Jean-Christophe Malapert (jean-christophe.malapert@cnes.fr)
61   */
62  @Requirement(reqId = Requirement.DOI_INTER_040, reqName = Requirement.DOI_INTER_040_NAME)
63  public class TokenResource extends AbstractResource {
64  
65      /**
66       * User ID of the operator that creates the token.
67       */
68      public static final String IDENTIFIER_PARAMETER = "identifier";
69  
70      /**
71       * Token for a specific project.
72       */
73      public static final String PROJECT_ID_PARAMETER = "projectID";
74  
75      /**
76       * Unit of time used to define the expiration time of the token.
77       */
78      public static final String UNIT_OF_TIME_PARAMETER = "typeOfTime";
79  
80      /**
81       * Amount of time for which the token is not expirated.
82       */
83      public static final String AMOUNT_OF_TIME_PARAMETER = "amountTime";
84  
85      /**
86       * Token parameter catched from the URL.
87       */
88      private volatile String tokenParam;
89  
90      /**
91       * The token database.
92       */
93      private volatile AbstractTokenDBHelper tokenDB;
94  
95      /**
96       * Logger.
97       */
98      private volatile Logger LOG;
99  
100     /**
101      * Set-up method that can be overridden in order to initialize the state of
102      * the resource
103      *
104      * @throws ResourceException - if a problem happens
105      */
106     @Override
107     protected void doInit() throws ResourceException {
108         super.doInit();
109         final AdminApplication app = (AdminApplication) getApplication();
110         LOG = app.getLog();
111         LOG.traceEntry();
112         setDescription("This resource handles the token");
113         this.tokenParam = getAttribute(TOKEN_TEMPLATE);
114         this.tokenDB = ((AdminApplication) this.getApplication()).getTokenDB();
115         LOG.debug("Token Param : {}", this.tokenParam);
116         LOG.traceExit();
117     }
118 
119     /**
120      * Creates and stores a token.
121      *
122      * The token creation is based on several actions :
123      * <ul>
124      * <li>creates the {@link fr.cnes.doi.security.TokenSecurity#generate}</li>
125      * <li>stores the token in
126      * {@link fr.cnes.doi.db.AbstractTokenDBHelper token database}</li>
127      * </ul>
128      *
129      * @param infoForm submitted information when requesting the token creation
130      * @return the token
131      */
132     @Requirement(reqId = Requirement.DOI_SRV_150, reqName = Requirement.DOI_SRV_150_NAME)
133     @Post
134     public String createToken(final Form infoForm) {
135         LOG.traceEntry("Paramater : {}", infoForm);
136         final Form info = (infoForm == null) ? new Form() : infoForm;
137         try {
138             final String user = this.getClientInfo().getUser().getIdentifier();
139             LOG.debug("Identified user : {}", user);
140             final String userID;
141             if (isInRole(RoleAuthorizer.ROLE_ADMIN)) {
142                 // The admin can generate for everybody
143                 LOG.debug("User {} is admin", user);
144                 userID = info.getFirstValue(IDENTIFIER_PARAMETER, user);
145             } else {
146                 // The token is generated for the identified user.
147                 userID = user;
148             }
149             final String projectID = info.getFirstValue(PROJECT_ID_PARAMETER, null);
150             final String defaultTimeUnit = DoiSettings.getInstance().getString(
151                     Consts.TOKEN_EXPIRATION_UNIT,
152                     String.valueOf(TokenSecurity.TimeUnit.HOUR.getTimeUnit())
153             );
154             final String defaultTimeDelay = DoiSettings.getInstance().getString(
155                     Consts.TOKEN_EXPIRATION_DELAY, "1");
156 
157             final String timeParam = info.getFirstValue(UNIT_OF_TIME_PARAMETER, defaultTimeUnit);
158             final int timeUnit = Integer.parseInt(timeParam);
159             final TimeUnit unit = TokenSecurity.TimeUnit.getTimeUnitFrom(timeUnit);
160 
161             final int amount = Integer.parseInt(defaultTimeDelay);
162 
163             final String tokenJwt;
164             if (projectID == null) {
165                 tokenJwt = TokenSecurity.getInstance().generate(
166                         userID,
167                         unit,
168                         amount
169                 );
170             } else {
171                 tokenJwt = TokenSecurity.getInstance().generate(
172                         userID,
173                         Integer.parseInt(projectID),
174                         unit,
175                         amount
176                 );
177             }
178 
179             LOG.info("Token created {} for project {} during {} {}",
180                     tokenJwt, projectID, amount, unit.name());
181 
182             if (this.tokenDB.addToken(tokenJwt)) {
183                 sendTokenToUser(user, userID, tokenJwt, amount, timeUnit);
184             }
185 
186             return LOG.traceExit(tokenJwt);
187         } catch (TokenSecurityException ex) {
188             throw LOG.throwing(Level.INFO, new ResourceException(ex.getStatus(), ex.getMessage(),
189                     ex));
190         }
191     }
192 
193     /**
194      * Sends the token to the user when the administrator creates a token for
195      * theuser
196      *
197      * @param userAdmin User administration
198      * @param userID user to send the message
199      * @param token created token for userID
200      * @param amount time
201      * @param timeUnit time unit
202      */
203     private void sendTokenToUser(final String userAdmin, final String userID, final String token,
204             final int amount, final int timeUnit) {
205         if (!userAdmin.equals(userID)) {
206             try {
207                 final StringBuilder builderMsg = new StringBuilder();
208                 builderMsg.append("The administrator ").append(userAdmin)
209                         .append(" has created a token for you:\n\n").append(token).append("\n\n")
210                         .append("This token is valid during ").append(amount).append(" ")
211                         .append(TokenSecurity.TimeUnit.getTimeUnitFrom(timeUnit).name());
212                 final List<DOIUser> doiUsers = PluginFactory.getUserManagement().getUsers();
213                 String email = "";
214                 for (final DOIUser doiUser : doiUsers) {
215                     if (doiUser.getUsername().equals(userID)) {
216                         email = doiUser.getEmail();
217                         break;
218                     }
219                 }
220                 if (email.isEmpty()) {
221                     LOG.error("Email is not set for {}", userID);
222                 }
223                 EmailSettings.getInstance().sendMessage("Creating Token", builderMsg.toString(),
224                         email);
225             } catch (DOIDbException ex) {
226                 LOG.error(ex);
227             }
228         }
229     }
230 
231     /**
232      * Returns the information from the token encoded as JSon format.
233      *
234      * @return the included information in the token
235      */
236     @Requirement(reqId = Requirement.DOI_SRV_180, reqName = Requirement.DOI_SRV_180_NAME)
237     @Get
238     public Representation getTokenInformation() {
239         LOG.traceEntry();
240         try {
241             final Jws<Claims> jws = TokenSecurity.getInstance()
242                     .getTokenInformation(this.tokenParam);
243             return LOG.traceExit(new JsonRepresentation(jws));
244         } catch (DoiRuntimeException ex) {
245             throw LOG.throwing(Level.INFO, new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST,
246                     ex));
247         }
248     }
249 
250     /**
251      * Deletes token.
252      */
253     @Requirement(reqId = Requirement.DOI_INTER_040, reqName = Requirement.DOI_INTER_040_NAME)
254     @Delete
255     public void deleteToken() {
256         LOG.traceEntry();
257         this.tokenDB.deleteToken(this.tokenParam);
258     }
259 
260     /**
261      * projects representation
262      *
263      * @return Wadl representation for projects
264      */
265     @Requirement(reqId = Requirement.DOI_DOC_010, reqName = Requirement.DOI_DOC_010_NAME)
266     private RepresentationInfo jsonRepresentation() {
267         final RepresentationInfo repInfo = new RepresentationInfo();
268         repInfo.setMediaType(MediaType.APPLICATION_JSON);
269         final DocumentationInfo docInfo = new DocumentationInfo();
270         docInfo.setTitle("Json Representation");
271         docInfo.setTextContent("The representation contains informations about the token.");
272         repInfo.setDocumentation(docInfo);
273         return repInfo;
274     }
275 
276     /**
277      * Describes GET method.
278      *
279      * @param info method info
280      */
281     @Requirement(reqId = Requirement.DOI_DOC_010, reqName = Requirement.DOI_DOC_010_NAME)
282     @Override
283     protected void describeGet(final MethodInfo info) {
284         info.setName(Method.GET);
285         info.setDocumentation("Get information about a specific token");
286         addRequestDocToMethod(info, createQueryParamDoc(
287                 TOKEN_TEMPLATE, ParameterStyle.TEMPLATE,
288                 "token", true, "xs:string")
289         );
290         addResponseDocToMethod(info, createResponseDoc(
291                 Status.SUCCESS_OK, "Operation successful", jsonRepresentation())
292         );
293         addResponseDocToMethod(info, createResponseDoc(
294                 Status.CLIENT_ERROR_BAD_REQUEST, "Wrong token")
295         );
296     }
297 
298     /**
299      * Describes POST method.
300      *
301      * @param info method info
302      */
303     @Requirement(reqId = Requirement.DOI_DOC_010, reqName = Requirement.DOI_DOC_010_NAME)
304     @Override
305     protected void describePost(final MethodInfo info) {
306         info.setName(Method.POST);
307         info.setDocumentation("Creates a token");
308         addRequestDocToMethod(info, createQueryParamDoc(
309                 IDENTIFIER_PARAMETER, ParameterStyle.MATRIX,
310                 "User ID of the operator that creates the token", true, "xs:string")
311         );
312         addRequestDocToMethod(info, createQueryParamDoc(
313                 PROJECT_ID_PARAMETER, ParameterStyle.MATRIX,
314                 "Token for a specific project", true, "xs:string")
315         );
316         addRequestDocToMethod(info, createQueryParamDoc(
317                 UNIT_OF_TIME_PARAMETER, ParameterStyle.MATRIX,
318                 "Unit of time used to define the expiration time of the token",
319                 false, "xs:int")
320         );
321         addResponseDocToMethod(info, createResponseDoc(
322                 Status.SUCCESS_OK, "Operation successful",
323                 stringRepresentation())
324         );
325         addResponseDocToMethod(info, createResponseDoc(
326                 Status.CLIENT_ERROR_BAD_REQUEST, "Submitted values are not valid")
327         );
328     }
329 
330 }