1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.restapi.v2;
20
21 import java.util.List;
22 import java.util.Objects;
23 import java.util.Optional;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
26
27 import org.apache.logging.log4j.LogManager;
28 import org.mycore.access.MCRAccessManager;
29 import org.mycore.access.mcrimpl.MCRAccessControlSystem;
30 import org.mycore.frontend.jersey.access.MCRRequestScopeACL;
31 import org.mycore.restapi.converter.MCRDetailLevel;
32 import org.mycore.restapi.v2.access.MCRRestAPIACLPermission;
33 import org.mycore.restapi.v2.access.MCRRestAccessManager;
34 import org.mycore.restapi.v2.annotation.MCRRestRequiredPermission;
35
36 import jakarta.annotation.Priority;
37 import jakarta.ws.rs.ForbiddenException;
38 import jakarta.ws.rs.HttpMethod;
39 import jakarta.ws.rs.Path;
40 import jakarta.ws.rs.Priorities;
41 import jakarta.ws.rs.container.ContainerRequestContext;
42 import jakarta.ws.rs.container.ContainerRequestFilter;
43 import jakarta.ws.rs.container.ResourceInfo;
44 import jakarta.ws.rs.core.Context;
45 import jakarta.ws.rs.core.MultivaluedMap;
46 import jakarta.ws.rs.core.Response;
47
48 @Priority(Priorities.AUTHORIZATION)
49 public class MCRRestAuthorizationFilter implements ContainerRequestFilter {
50
51 public static final String PARAM_CLASSID = "classid";
52
53 public static final String PARAM_MCRID = "mcrid";
54
55 public static final String PARAM_DERID = "derid";
56
57 public static final String PARAM_DER_PATH = "path";
58
59 @Context
60 ResourceInfo resourceInfo;
61
62
63
64
65
66
67
68
69 private void checkRestAPIAccess(final ContainerRequestContext requestContext,
70 final MCRRestAPIACLPermission permission, final String path) throws ForbiddenException {
71 LogManager.getLogger().warn(path + ": Checking API access: " + permission);
72 final MCRRequestScopeACL aclProvider = MCRRequestScopeACL.getInstance(requestContext);
73 if (MCRRestAccessManager.checkRestAPIAccess(aclProvider, permission, path)) {
74 return;
75 }
76 throw MCRErrorResponse.fromStatus(Response.Status.FORBIDDEN.getStatusCode())
77 .withErrorCode(MCRErrorCodeConstants.API_NO_PERMISSION)
78 .withMessage("REST-API action is not allowed.")
79 .withDetail("Check access right '" + permission + "' on ACLs 'restapi:/' and 'restapi:" + path + "'!")
80 .toException();
81 }
82
83 private void checkBaseAccess(ContainerRequestContext requestContext, MCRRestAPIACLPermission permission,
84 String objectId, String derId, String path)
85 throws ForbiddenException {
86 LogManager.getLogger().debug("Permission: {}, Object: {}, Derivate: {}, Path: {}", permission, objectId, derId,
87 path);
88 Optional<String> checkable = Optional.ofNullable(derId)
89 .filter(d -> path != null)
90 .map(Optional::of)
91 .orElseGet(() -> Optional.ofNullable(objectId));
92 checkable.ifPresent(id -> LogManager.getLogger().info("Checking " + permission + " access on " + id));
93 MCRRequestScopeACL aclProvider = MCRRequestScopeACL.getInstance(requestContext);
94 boolean allowed = checkable
95 .map(id -> aclProvider.checkPermission(id, permission.toString()))
96 .orElse(true);
97 if (allowed) {
98 return;
99 }
100 if (checkable.get().equals(objectId)) {
101 throw MCRErrorResponse.fromStatus(Response.Status.FORBIDDEN.getStatusCode())
102 .withErrorCode(MCRErrorCodeConstants.MCROBJECT_NO_PERMISSION)
103 .withMessage("You do not have " + permission + " permission on MCRObject " + objectId + ".")
104 .toException();
105 }
106 throw MCRErrorResponse.fromStatus(Response.Status.FORBIDDEN.getStatusCode())
107 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_NO_PERMISSION)
108 .withMessage("You do not have " + permission + " permission on MCRDerivate " + derId + ".")
109 .toException();
110 }
111
112 private void checkDetailLevel(ContainerRequestContext requestContext, String... detail) throws ForbiddenException {
113 MCRRequestScopeACL aclProvider = MCRRequestScopeACL.getInstance(requestContext);
114 List<String> missedPermissions = Stream.of(detail)
115 .map(d -> "rest-detail-" + d)
116 .filter(d -> MCRAccessManager.hasRule(MCRAccessControlSystem.POOL_PRIVILEGE_ID, d))
117 .filter(d -> !aclProvider.checkPermission(d))
118 .collect(Collectors.toList());
119 if (!missedPermissions.isEmpty()) {
120 throw MCRErrorResponse.fromStatus(Response.Status.FORBIDDEN.getStatusCode())
121 .withErrorCode(MCRErrorCodeConstants.API_NO_PERMISSION)
122 .withMessage("REST-API action is not allowed.")
123 .withDetail("Check access right(s) '" + missedPermissions + "' on "
124 + MCRAccessControlSystem.POOL_PRIVILEGE_ID + "'!")
125 .toException();
126 }
127 }
128
129 @Override
130 public void filter(ContainerRequestContext requestContext) {
131 final String method = requestContext.getMethod();
132 if (HttpMethod.OPTIONS.equals(method)) {
133 return;
134 }
135 final MCRRestRequiredPermission annotation
136 = resourceInfo.getResourceMethod().getAnnotation(MCRRestRequiredPermission.class);
137 final MCRRestAPIACLPermission permission = Optional.ofNullable(annotation)
138 .map(a -> a.value())
139 .orElseGet(() -> MCRRestAPIACLPermission.fromMethod(method));
140 Optional.ofNullable(resourceInfo.getResourceClass().getAnnotation(Path.class))
141 .map(Path::value)
142 .ifPresent(path -> {
143 checkRestAPIAccess(requestContext, permission, path);
144 MultivaluedMap<String, String> pathParameters = requestContext.getUriInfo().getPathParameters();
145 checkBaseAccess(requestContext, permission, pathParameters.getFirst(PARAM_MCRID),
146 pathParameters.getFirst(PARAM_DERID), pathParameters.getFirst(PARAM_DER_PATH));
147 });
148 checkDetailLevel(requestContext,
149 requestContext.getAcceptableMediaTypes()
150 .stream()
151 .map(m -> m.getParameters().get(MCRDetailLevel.MEDIA_TYPE_PARAMETER))
152 .filter(Objects::nonNull)
153 .toArray(String[]::new));
154 }
155 }