View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.restapi.v1;
20  
21  import java.io.IOException;
22  import java.util.List;
23  import java.util.Objects;
24  import java.util.Optional;
25  import java.util.stream.Collectors;
26  import java.util.stream.Stream;
27  
28  import org.apache.logging.log4j.LogManager;
29  import org.mycore.access.MCRAccessInterface;
30  import org.mycore.access.MCRAccessManager;
31  import org.mycore.access.MCRRuleAccessInterface;
32  import org.mycore.access.mcrimpl.MCRAccessControlSystem;
33  import org.mycore.frontend.jersey.access.MCRRequestScopeACL;
34  import org.mycore.restapi.converter.MCRDetailLevel;
35  import org.mycore.restapi.v1.errors.MCRRestAPIError;
36  import org.mycore.restapi.v1.errors.MCRRestAPIException;
37  import org.mycore.restapi.v1.errors.MCRRestAPIExceptionMapper;
38  
39  import jakarta.annotation.Priority;
40  import jakarta.ws.rs.HttpMethod;
41  import jakarta.ws.rs.Path;
42  import jakarta.ws.rs.Priorities;
43  import jakarta.ws.rs.container.ContainerRequestContext;
44  import jakarta.ws.rs.container.ContainerRequestFilter;
45  import jakarta.ws.rs.container.ResourceInfo;
46  import jakarta.ws.rs.core.Context;
47  import jakarta.ws.rs.core.MultivaluedMap;
48  import jakarta.ws.rs.core.Response;
49  
50  @Priority(Priorities.AUTHORIZATION)
51  public class MCRRestAuthorizationFilter implements ContainerRequestFilter {
52  
53      public static final String PARAM_CLASSID = "classid";
54  
55      public static final String PARAM_MCRID = "mcrid";
56  
57      public static final String PARAM_DERID = "derid";
58  
59      public static final String PARAM_DER_PATH = "path";
60  
61      @Context
62      ResourceInfo resourceInfo;
63  
64      /**
65       * checks if the given REST API operation is allowed
66       * @param permission "read" or "write"
67       * @param path - the REST API path, e.g. /v1/messages
68       *
69       * @throws MCRRestAPIException if access is restricted
70       */
71      private void checkRestAPIAccess(ContainerRequestContext requestContext, MCRRestAPIACLPermission permission,
72          String path)
73          throws MCRRestAPIException {
74          MCRRequestScopeACL aclProvider = MCRRequestScopeACL.getInstance(requestContext);
75          LogManager.getLogger().warn(path + ": Checking API access: " + permission);
76          String thePath = path.startsWith("/") ? path : "/" + path;
77  
78          MCRAccessInterface acl = MCRAccessManager.getAccessImpl();
79          String permStr = permission.toString();
80          boolean hasAPIAccess = aclProvider.checkPermission("restapi:/", permStr);
81          if (hasAPIAccess) {
82              String objId = "restapi:" + thePath;
83              boolean isRuleInterface = acl instanceof MCRRuleAccessInterface;
84              if (!isRuleInterface || ((MCRRuleAccessInterface) acl).hasRule(objId, permStr)) {
85                  if (aclProvider.checkPermission(objId, permStr)) {
86                      return;
87                  }
88              } else {
89                  return;
90              }
91          }
92          throw new MCRRestAPIException(Response.Status.FORBIDDEN,
93              new MCRRestAPIError(MCRRestAPIError.CODE_ACCESS_DENIED, "REST-API action is not allowed.",
94                  "Check access right '" + permission + "' on ACLs 'restapi:/' and 'restapi:" + path + "'!"));
95      }
96  
97      private void checkBaseAccess(ContainerRequestContext requestContext, MCRRestAPIACLPermission permission,
98          String objectId, String derId, String path)
99          throws MCRRestAPIException {
100         LogManager.getLogger().debug("Permission: {}, Object: {}, Derivate: {}, Path: {}", permission, objectId, derId,
101             path);
102         Optional<String> checkable = Optional.ofNullable(derId)
103             .filter(d -> path != null) //only check for derId if path is given
104             .map(Optional::of)
105             .orElseGet(() -> Optional.ofNullable(objectId));
106         checkable.ifPresent(id -> LogManager.getLogger().info("Checking " + permission + " access on " + id));
107         MCRRequestScopeACL aclProvider = MCRRequestScopeACL.getInstance(requestContext);
108         boolean allowed = checkable
109             .map(id -> aclProvider.checkPermission(id, permission.toString()))
110             .orElse(true);
111         if (allowed) {
112             return;
113         }
114         throw new MCRRestAPIException(Response.Status.FORBIDDEN,
115             new MCRRestAPIError(MCRRestAPIError.CODE_ACCESS_DENIED, "REST-API action is not allowed.",
116                 "Check access right '" + permission + "' on '" + checkable.orElse(null) + "'!"));
117     }
118 
119     private void checkDetailLevel(ContainerRequestContext requestContext, String... detail) throws MCRRestAPIException {
120         MCRRequestScopeACL aclProvider = MCRRequestScopeACL.getInstance(requestContext);
121         List<String> missedPermissions = Stream.of(detail)
122             .map(d -> "rest-detail-" + d)
123             .filter(d -> MCRAccessManager.hasRule(MCRAccessControlSystem.POOL_PRIVILEGE_ID, d))
124             .filter(d -> !aclProvider.checkPermission(d))
125             .collect(Collectors.toList());
126         if (!missedPermissions.isEmpty()) {
127             throw new MCRRestAPIException(Response.Status.FORBIDDEN,
128                 new MCRRestAPIError(MCRRestAPIError.CODE_ACCESS_DENIED, "REST-API action is not allowed.",
129                     "Check permission(s) " + missedPermissions + "!"));
130         }
131     }
132 
133     @Override
134     public void filter(ContainerRequestContext requestContext) throws IOException {
135         MCRRestAPIACLPermission permission;
136         switch (requestContext.getMethod()) {
137         case HttpMethod.OPTIONS:
138             return;
139         case HttpMethod.GET:
140         case HttpMethod.HEAD:
141             permission = MCRRestAPIACLPermission.READ;
142             break;
143         case HttpMethod.DELETE:
144             permission = MCRRestAPIACLPermission.DELETE;
145             break;
146         default:
147             permission = MCRRestAPIACLPermission.WRITE;
148         }
149         Optional.ofNullable(resourceInfo.getResourceClass().getAnnotation(Path.class))
150             .map(Path::value)
151             .ifPresent(path -> {
152                 try {
153                     checkRestAPIAccess(requestContext, permission, path);
154                     MultivaluedMap<String, String> pathParameters = requestContext.getUriInfo().getPathParameters();
155                     checkBaseAccess(requestContext, permission, pathParameters.getFirst(PARAM_MCRID),
156                         pathParameters.getFirst(PARAM_DERID), pathParameters.getFirst(PARAM_DER_PATH));
157                 } catch (MCRRestAPIException e) {
158                     LogManager.getLogger().warn("API Access denied!");
159                     requestContext.abortWith(new MCRRestAPIExceptionMapper().toResponse(e));
160                 }
161             });
162         try {
163             checkDetailLevel(requestContext,
164                 requestContext.getAcceptableMediaTypes()
165                     .stream()
166                     .map(m -> m.getParameters().get(MCRDetailLevel.MEDIA_TYPE_PARAMETER))
167                     .filter(Objects::nonNull)
168                     .toArray(String[]::new));
169         } catch (MCRRestAPIException e) {
170             LogManager.getLogger().warn("API Access denied!");
171             requestContext.abortWith(new MCRRestAPIExceptionMapper().toResponse(e));
172         }
173 
174     }
175 
176     /**
177      * The REST API access permissions (read, write, delete)
178      */
179     public enum MCRRestAPIACLPermission {
180         READ {
181             public String toString() {
182                 return MCRAccessManager.PERMISSION_READ;
183             }
184         },
185 
186         WRITE {
187             public String toString() {
188                 return MCRAccessManager.PERMISSION_WRITE;
189             }
190         },
191 
192         DELETE {
193             public String toString() {
194                 return MCRAccessManager.PERMISSION_DELETE;
195             }
196         }
197     }
198 }