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.mds;
20  
21  import fr.cnes.doi.application.DoiMdsApplication;
22  import fr.cnes.doi.application.DoiMdsApplication.API_MDS;
23  import fr.cnes.doi.exception.DoiServerException;
24  import fr.cnes.doi.resource.AbstractResource;
25  import static fr.cnes.doi.security.UtilsHeader.SELECTED_ROLE_PARAMETER;
26  
27  import fr.cnes.doi.utils.spec.Requirement;
28  import java.util.List;
29  
30  import org.apache.logging.log4j.Level;
31  import org.apache.logging.log4j.Logger;
32  import org.restlet.data.MediaType;
33  import org.restlet.ext.wadl.DocumentationInfo;
34  import org.restlet.ext.wadl.MethodInfo;
35  import org.restlet.ext.wadl.RepresentationInfo;
36  import org.restlet.security.Role;
37  import org.restlet.util.Series;
38  
39  /**
40   * Base resource for the different resources.
41   *
42   * @author Jean-Christophe Malapert (jean-christophe.malapert@cnes.fr)
43   */
44  public class BaseMdsResource extends AbstractResource {
45  
46      /**
47       * The parameter that describes the DOI name {@value #DOI_PARAMETER}.
48       */
49      public static final String DOI_PARAMETER = "doi";
50  
51      /**
52       * The parameter that describes the landing page related to the DOI
53       * {@value #URL_PARAMETER}.
54       */
55      public static final String URL_PARAMETER = "url";
56      /**
57       * Logger.
58       */
59      protected volatile Logger LOG;
60  
61      /**
62       * DOI Mds application.
63       */
64      private volatile DoiMdsApplication doiApp;
65  
66      /**
67       * Init.
68       *
69       * @throws DoiServerException - if a problem happens
70       */
71      @Override
72      protected void doInit() throws DoiServerException {
73          super.doInit();
74          this.doiApp = (DoiMdsApplication) getApplication();
75          LOG = this.doiApp.getLog();
76          LOG.traceEntry();
77          LOG.traceExit();
78      }
79  
80      /**
81       * Tests if the user has only one single role.
82       *
83       * @param roles roles
84       * @return True when the user has only one single role otherwise False
85       */
86      private boolean hasSingleRole(final List<Role> roles) {
87          LOG.traceEntry("Parameter : {}", roles);
88          return LOG.traceExit(roles.size() == 1);
89      }
90  
91      /**
92       * Tests if the user has no role.
93       *
94       * @param roles roles
95       * @return True when the user has no role otherwise False
96       */
97      private boolean hasNoRole(final List<Role> roles) {
98          LOG.traceEntry("Parameter : {}", roles);
99          return LOG.traceExit(roles.isEmpty());
100     }
101 
102     /**
103      * Tests if the user has selected a role.
104      *
105      * @param suffusedWithRole selected role
106      * @return True when the user has selected a role otherwise False
107      */
108     private boolean hasSelectedRole(final String suffusedWithRole) {
109         LOG.traceEntry("Parameter : {}", suffusedWithRole);
110         return LOG.traceExit(!suffusedWithRole.isEmpty());
111     }
112 
113     /**
114      * Returns the suffix project with which the user is associated. For each
115      * suffix project, a role is associated. To get the suffix projet, the role
116      * must be get in the HTTP header. When several roles are associated to a
117      * user, a
118      * <i>selectedRole</i> must be provided to select the role that the user
119      * wants to get.
120      *
121      * @param selectedRole selected role
122      * @return the project name associated to the user
123      * @throws DoiServerException - CLIENT_ERROR_FORBIDDEN if the role is not
124      * allowed to use this feature - CLIENT_ERROR_UNAUTHORIZED if no role is
125      * provided - CLIENT_ERROR_CONFLICT if a user is associated to more than one
126      * role
127      */
128     private String getRoleName(final String selectedRole) throws DoiServerException {
129         LOG.traceEntry("Parameter : {}", selectedRole);
130         final String roleName;
131         if (hasSelectedRole(selectedRole)) {
132             roleName = getRoleNameWhenRoleInHeader(selectedRole);
133         } else {
134             roleName = getRoleNameWhenNotProvidedInHeader();
135         }
136         return LOG.traceExit(roleName);
137     }
138 
139     /**
140      * Get the role when the role is not provided in the header.
141      *
142      * @return the role name
143      */
144     private String getRoleNameWhenNotProvidedInHeader() {
145         LOG.traceEntry();
146         // no role is provided in the header
147         final List<Role> roles = getClientInfo().getRoles();
148         if (hasNoRole(roles)) {
149             // the user has no role, go out
150             throw LOG.throwing(Level.DEBUG, new DoiServerException(getApplication(),
151                     API_MDS.SECURITY_USER_NO_ROLE, "User not contained in any role"));
152         } else if (hasSingleRole(roles)) {
153             // the user has only one role, ok do it
154             final Role role = roles.get(0);
155             LOG.debug("User has a single Role {}", role);
156             return LOG.traceExit(role.getName());
157         } else {
158             // the user has several roles, he has to select a profile, go out
159             LOG.info("DOIServer : Cannot know which role must be applied");
160             throw LOG.throwing(Level.DEBUG, new DoiServerException(getApplication(),
161                     API_MDS.SECURITY_USER_CONFLICT, "Cannot know which role must be applied"));
162         }
163     }
164 
165     /**
166      * Get the role when the role is in the header.
167      *
168      * @param selectedRole role from the header
169      * @return the role name
170      */
171     private String getRoleNameWhenRoleInHeader(final String selectedRole) {
172         LOG.traceEntry("Parameter : {}", selectedRole);
173         // the role is selected in the Header
174         LOG.debug("Role selected : {}", selectedRole);
175         if (isInRole(selectedRole)) {
176             // the selected role is well related to the user
177             LOG.debug("User is in Role : {}", selectedRole);
178             return LOG.traceExit(selectedRole);
179         } else {
180             // the user is not contained in the selected role => a possible hacking
181             LOG.debug("User is not in Role : {}", selectedRole);
182             LOG.info("DOIServer : The role {} is not allowed to use this feature", selectedRole);
183             throw LOG.throwing(Level.DEBUG,
184                     new DoiServerException(getApplication(),
185                             API_MDS.SECURITY_USER_NOT_IN_SELECTED_ROLE,
186                             "Fail to make this request with this role (" + selectedRole + ")"));
187         }
188     }
189 
190     /**
191      * Checks permissions to access according to the role of the user and the
192      * DOI name. The authorization in DOI server is based on <b>role</b>. Each
193      * projectSuffix is
194      * <ul>
195      * <li>a <b>role</b></li>
196      * <li>related to a projectName</li>
197      * </ul>
198      * When the DOI name does not start with <i>prefixDOI/role</i>, an exception
199      * is send.
200      *
201      * @param doiName DOI name
202      * @param selectedRole Selected role
203      * @throws DoiServerException
204      * <ul>
205      * <li>{@link API_MDS#SECURITY_USER_NO_ROLE}</li>
206      * <li>{@link API_MDS#SECURITY_USER_NOT_IN_SELECTED_ROLE}</li>
207      * <li>{@link API_MDS#SECURITY_USER_PERMISSION}</li>
208      * <li>{@link API_MDS#SECURITY_USER_CONFLICT}</li>
209      * </ul>
210      * @see fr.cnes.doi.resource.admin.SuffixProjectsResource Creates a
211      * suffixProject for a given project name
212      */
213     @Requirement(reqId = Requirement.DOI_AUTO_030, reqName = Requirement.DOI_AUTO_030_NAME)
214     protected void checkPermission(final String doiName, final String selectedRole)
215             throws DoiServerException {
216         LOG.traceEntry("Parameters : {} and {}", doiName, selectedRole);
217         final String prefixCNES = this.getDoiApp().getDataCentrePrefix();
218         final String projectRole = getRoleName(selectedRole);
219         if (!doiName.startsWith(prefixCNES + "/" + projectRole + "/")) {
220             LOG.debug("The DOI {}  does not match with {}", doiName,
221                     prefixCNES + "/" + projectRole);
222             throw LOG.throwing(Level.DEBUG,
223                     new DoiServerException(getApplication(), API_MDS.SECURITY_USER_PERMISSION,
224                             "The DOI " + doiName + " does not match with" + prefixCNES + "/"
225                             + projectRole));
226         }
227         LOG.traceExit();
228     }
229 
230     /**
231      * Extract <i>selectedRole</i> from HTTP header.
232      *
233      * @return the selected role or an empty string when there is no selected
234      * role
235      */
236     @Requirement(reqId = Requirement.DOI_AUTO_020, reqName = Requirement.DOI_AUTO_020_NAME)
237     public String extractSelectedRoleFromRequestIfExists() {
238         LOG.traceEntry();
239         final Series headers = (Series) getRequestAttributes().get("org.restlet.http.headers");
240         final String selectedRole = headers.getFirstValue(SELECTED_ROLE_PARAMETER, "");
241         return LOG.traceExit(selectedRole);
242     }
243 
244     /**
245      * Body representation.
246      *
247      * @return Wadl description for a body representation
248      */
249     protected RepresentationInfo explainRepresentation() {
250         final RepresentationInfo repInfo = new RepresentationInfo();
251         repInfo.setIdentifier("explainRepresentationID");
252         repInfo.setMediaType(MediaType.TEXT_PLAIN);
253         final DocumentationInfo docInfo = new DocumentationInfo();
254         docInfo.setTitle("Body representation");
255         docInfo.setTextContent("short explanation of status code");
256         repInfo.setDocumentation(docInfo);
257         return repInfo;
258     }
259 
260     /**
261      * Describes WADL representation for {@link #checkPermission} method. The
262      * different representations are the followings:
263      * <ul>
264      * <li>{@link API_MDS#SECURITY_USER_NO_ROLE}</li>
265      * <li>{@link API_MDS#SECURITY_USER_NOT_IN_SELECTED_ROLE}</li>
266      * <li>{@link API_MDS#SECURITY_USER_PERMISSION}</li>
267      * <li>{@link API_MDS#SECURITY_USER_CONFLICT}</li>
268      * </ul>
269      *
270      * @param info Method information
271      */
272     @Override
273     protected void describeDelete(final MethodInfo info) {
274         addResponseDocToMethod(info, createResponseDoc(API_MDS.SECURITY_USER_NO_ROLE.getStatus(),
275                 API_MDS.SECURITY_USER_NO_ROLE.getShortMessage(), "explainRepresentationID"));
276         addResponseDocToMethod(info,
277                 createResponseDoc(API_MDS.SECURITY_USER_NOT_IN_SELECTED_ROLE.getStatus(),
278                         API_MDS.SECURITY_USER_NOT_IN_SELECTED_ROLE.getShortMessage(),
279                         "explainRepresentationID"));
280         addResponseDocToMethod(info, createResponseDoc(API_MDS.SECURITY_USER_PERMISSION.getStatus(),
281                 API_MDS.SECURITY_USER_PERMISSION.getShortMessage(), "explainRepresentationID"));
282         addResponseDocToMethod(info, createResponseDoc(API_MDS.SECURITY_USER_CONFLICT.getStatus(),
283                 API_MDS.SECURITY_USER_CONFLICT.getShortMessage(), "explainRepresentationID"));
284     }
285 
286     /**
287      * Describes WADL representation for {@link #checkPermission} method. The
288      * different representations are the followings:
289      * <ul>
290      * <li>{@link API_MDS#SECURITY_USER_NO_ROLE}</li>
291      * <li>{@link API_MDS#SECURITY_USER_NOT_IN_SELECTED_ROLE}</li>
292      * <li>{@link API_MDS#SECURITY_USER_PERMISSION}</li>
293      * <li>{@link API_MDS#SECURITY_USER_CONFLICT}</li>
294      * </ul>
295      *
296      * @param info Method information
297      */
298     @Override
299     protected void describePost(final MethodInfo info) {
300         addResponseDocToMethod(info, createResponseDoc(API_MDS.SECURITY_USER_NO_ROLE.getStatus(),
301                 API_MDS.SECURITY_USER_NO_ROLE.getShortMessage(), explainRepresentation()));
302         addResponseDocToMethod(info,
303                 createResponseDoc(API_MDS.SECURITY_USER_NOT_IN_SELECTED_ROLE.getStatus(),
304                         API_MDS.SECURITY_USER_NOT_IN_SELECTED_ROLE.getShortMessage(),
305                         "explainRepresentationID"));
306         addResponseDocToMethod(info, createResponseDoc(API_MDS.SECURITY_USER_PERMISSION.getStatus(),
307                 API_MDS.SECURITY_USER_PERMISSION.getShortMessage(), "explainRepresentationID"));
308         addResponseDocToMethod(info, createResponseDoc(API_MDS.SECURITY_USER_CONFLICT.getStatus(),
309                 API_MDS.SECURITY_USER_CONFLICT.getShortMessage(), "explainRepresentationID"));
310     }
311 
312     /**
313      * Retrieves path by cleaning query parameters.
314      *
315      * @param basePath base path to remove
316      * @return the path value
317      */
318     public String getAttributePath(final String basePath) {
319         String resourcePath = getResourcePath();
320         int questionMark = resourcePath.indexOf("?");
321         questionMark = (questionMark == -1) ? resourcePath.length() : questionMark;
322         resourcePath = resourcePath.substring(0, questionMark);
323         return resourcePath.replace(basePath + "/", "");
324     }
325 
326     /**
327      * Returns the Mds application.
328      *
329      * @return the doiApp
330      */
331     public DoiMdsApplication getDoiApp() {
332         return doiApp;
333     }
334 
335 }