1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.authorization.xacml.manager.endpoint;
23
24 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
25 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
26 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
27 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
28 import static javax.servlet.http.HttpServletResponse.SC_OK;
29 import static org.opencastproject.util.RestUtil.R.conflict;
30 import static org.opencastproject.util.RestUtil.R.noContent;
31 import static org.opencastproject.util.RestUtil.R.notFound;
32 import static org.opencastproject.util.RestUtil.R.ok;
33 import static org.opencastproject.util.RestUtil.R.serverError;
34 import static org.opencastproject.util.doc.rest.RestParameter.Type.BOOLEAN;
35 import static org.opencastproject.util.doc.rest.RestParameter.Type.INTEGER;
36 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
37
38 import org.opencastproject.assetmanager.api.AssetManager;
39 import org.opencastproject.authorization.xacml.manager.api.AclService;
40 import org.opencastproject.authorization.xacml.manager.api.AclServiceException;
41 import org.opencastproject.authorization.xacml.manager.api.AclServiceFactory;
42 import org.opencastproject.authorization.xacml.manager.api.ManagedAcl;
43 import org.opencastproject.authorization.xacml.manager.impl.ManagedAclImpl;
44 import org.opencastproject.mediapackage.MediaPackage;
45 import org.opencastproject.mediapackage.MediaPackageException;
46 import org.opencastproject.security.api.AccessControlList;
47 import org.opencastproject.security.api.AccessControlParser;
48 import org.opencastproject.security.api.AccessControlUtil;
49 import org.opencastproject.security.api.AclScope;
50 import org.opencastproject.security.api.AuthorizationService;
51 import org.opencastproject.security.api.Organization;
52 import org.opencastproject.security.api.SecurityService;
53 import org.opencastproject.util.Jsons;
54 import org.opencastproject.util.NotFoundException;
55 import org.opencastproject.util.doc.rest.RestParameter;
56 import org.opencastproject.util.doc.rest.RestQuery;
57 import org.opencastproject.util.doc.rest.RestResponse;
58
59 import org.apache.commons.lang3.StringUtils;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import java.util.List;
64 import java.util.Optional;
65
66 import javax.ws.rs.DELETE;
67 import javax.ws.rs.FormParam;
68 import javax.ws.rs.GET;
69 import javax.ws.rs.POST;
70 import javax.ws.rs.PUT;
71 import javax.ws.rs.Path;
72 import javax.ws.rs.PathParam;
73 import javax.ws.rs.Produces;
74 import javax.ws.rs.WebApplicationException;
75 import javax.ws.rs.core.MediaType;
76 import javax.ws.rs.core.Response;
77
78 public abstract class AbstractAclServiceRestEndpoint {
79 private static final Logger logger = LoggerFactory.getLogger(AbstractAclServiceRestEndpoint.class);
80
81 protected abstract AclServiceFactory getAclServiceFactory();
82
83 protected abstract SecurityService getSecurityService();
84
85 protected abstract AssetManager getAssetManager();
86
87 protected abstract AuthorizationService getAuthorizationService();
88
89 @GET
90 @Path("/acl/{aclId}")
91 @Produces(MediaType.APPLICATION_JSON)
92 @RestQuery(
93 name = "getacl",
94 description = "Return the ACL by the given id",
95 returnDescription = "Return the ACL by the given id",
96 pathParameters = {
97 @RestParameter(name = "aclId", isRequired = true, description = "The ACL identifier", type = INTEGER)
98 },
99 responses = {
100 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been returned"),
101 @RestResponse(responseCode = SC_NOT_FOUND, description = "The ACL has not been found"),
102 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error during returning the ACL")
103 }
104 )
105 public String getAcl(@PathParam("aclId") long aclId) throws NotFoundException {
106 final Optional<ManagedAcl> managedAcl = aclService().getAcl(aclId);
107 if (managedAcl.isEmpty()) {
108 logger.info("No ACL with id '{}' could be found", aclId);
109 throw new NotFoundException();
110 }
111 return JsonConv.full(managedAcl.get()).toJson();
112 }
113
114 @POST
115 @Path("/acl/extend")
116 @Produces(MediaType.APPLICATION_JSON)
117 @RestQuery(
118 name = "extendacl",
119 description = "Return the given ACL with a new role and action in JSON format",
120 returnDescription = "Return the ACL with the new role and action in JSON format",
121 restParameters = {
122 @RestParameter(name = "acl", isRequired = true, description = "The access control list", type = STRING),
123 @RestParameter(name = "action", isRequired = true, description = "The action for the ACL", type = STRING),
124 @RestParameter(name = "role", isRequired = true, description = "The role for the ACL", type = STRING),
125 @RestParameter(
126 name = "allow",
127 isRequired = true,
128 description = "The allow status for the ACL",
129 type = BOOLEAN
130 )
131 },
132 responses = {
133 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been returned"),
134 @RestResponse(responseCode = SC_BAD_REQUEST, description = "The ACL, action or role was invalid or empty"),
135 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error during returning the ACL")
136 }
137 )
138 public String extendAcl(
139 @FormParam("acl") String accessControlList,
140 @FormParam("action") String action,
141 @FormParam("role") String role,
142 @FormParam("allow") boolean allow
143 ) {
144 if (StringUtils.isBlank(accessControlList) || StringUtils.isBlank(action) || StringUtils.isBlank(role)) {
145 throw new WebApplicationException(Response.Status.BAD_REQUEST);
146 }
147
148 AccessControlList acl = AccessControlUtil.extendAcl(parseAcl(accessControlList), role, action, allow);
149 return JsonConv.full(acl).toJson();
150 }
151
152 @POST
153 @Path("/acl/reduce")
154 @Produces(MediaType.APPLICATION_JSON)
155 @RestQuery(
156 name = "reduceacl",
157 description = "Return the given ACL without a role and action in JSON format",
158 returnDescription = "Return the ACL without the role and action in JSON format", restParameters = {
159 @RestParameter(name = "acl", isRequired = true, description = "The access control list", type = STRING),
160 @RestParameter(name = "action", isRequired = true, description = "The action for the ACL", type = STRING),
161 @RestParameter(name = "role", isRequired = true, description = "The role for the ACL", type = STRING)
162 },
163 responses = {
164 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been returned"),
165 @RestResponse(responseCode = SC_BAD_REQUEST, description = "The ACL, role or action was invalid or empty"),
166 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error during returning the ACL")
167 }
168 )
169 public String reduceAcl(
170 @FormParam("acl") String accessControlList,
171 @FormParam("action") String action,
172 @FormParam("role") String role
173 ) {
174 if (StringUtils.isBlank(accessControlList) || StringUtils.isBlank(action) || StringUtils.isBlank(role)) {
175 throw new WebApplicationException(Response.Status.BAD_REQUEST);
176 }
177
178 AccessControlList acl = AccessControlUtil.reduceAcl(parseAcl(accessControlList), role, action);
179 return JsonConv.full(acl).toJson();
180 }
181
182 @GET
183 @Path("/acl/acls.json")
184 @Produces(MediaType.APPLICATION_JSON)
185 @RestQuery(
186 name = "getacls",
187 description = "Lists the ACL's as JSON",
188 returnDescription = "The list of ACL's as JSON",
189 responses = {
190 @RestResponse(responseCode = SC_OK, description = "The list of ACL's has successfully been returned"),
191 @RestResponse(
192 responseCode = SC_INTERNAL_SERVER_ERROR,
193 description = "Error during returning the list of ACL's"
194 )
195 }
196 )
197 public String getAcls() {
198 List<Jsons.Val> acls = aclService().getAcls().stream()
199 .map(JsonConv.fullManagedAcl)
200 .toList();
201 return Jsons.arr(acls).toJson();
202 }
203
204 @POST
205 @Path("/acl")
206 @Produces(MediaType.APPLICATION_JSON)
207 @RestQuery(
208 name = "createacl",
209 description = "Create an ACL",
210 returnDescription = "Create an ACL",
211 restParameters = {
212 @RestParameter(name = "name", isRequired = true, description = "The ACL name", type = STRING),
213 @RestParameter(name = "acl", isRequired = true, description = "The access control list", type = STRING)
214 },
215 responses = {
216 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been added"),
217 @RestResponse(responseCode = SC_CONFLICT, description = "An ACL with the same name already exists"),
218 @RestResponse(responseCode = SC_BAD_REQUEST, description = "Unable to parse the ACL"),
219 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error during adding the ACL")
220 }
221 )
222 public String createAcl(
223 @FormParam("name") String name,
224 @FormParam("acl") String accessControlList
225 ) {
226 final AccessControlList acl = parseAcl(accessControlList);
227 final Optional<ManagedAcl> managedAcl = aclService().createAcl(acl, name);
228 if (managedAcl.isEmpty()) {
229 logger.info("An ACL with the same name '{}' already exists", name);
230 throw new WebApplicationException(Response.Status.CONFLICT);
231 }
232 return JsonConv.full(managedAcl.get()).toJson();
233 }
234
235 @PUT
236 @Path("/acl/{aclId}")
237 @Produces(MediaType.APPLICATION_JSON)
238 @RestQuery(
239 name = "updateacl",
240 description = "Update an ACL",
241 returnDescription = "Update an ACL",
242 pathParameters = {
243 @RestParameter(name = "aclId", isRequired = true, description = "The ACL identifier", type = INTEGER)
244 },
245 restParameters = {
246 @RestParameter(name = "name", isRequired = true, description = "The ACL name", type = STRING),
247 @RestParameter(name = "acl", isRequired = true, description = "The access control list", type = STRING)
248 },
249 responses = {
250 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been updated"),
251 @RestResponse(responseCode = SC_NOT_FOUND, description = "The ACL has not been found"),
252 @RestResponse(responseCode = SC_BAD_REQUEST, description = "Unable to parse the ACL"),
253 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error during updating the ACL")
254 }
255 )
256 public String updateAcl(
257 @PathParam("aclId") long aclId,
258 @FormParam("name") String name,
259 @FormParam("acl") String accessControlList
260 ) throws NotFoundException {
261 final Organization org = getSecurityService().getOrganization();
262 final AccessControlList acl = parseAcl(accessControlList);
263 final ManagedAclImpl managedAcl = new ManagedAclImpl(aclId, name, org.getId(), acl);
264 if (!aclService().updateAcl(managedAcl)) {
265 logger.info("No ACL with id '{}' could be found under organization '{}'", aclId, org.getId());
266 throw new NotFoundException();
267 }
268 return JsonConv.full(managedAcl).toJson();
269 }
270
271 @DELETE
272 @Path("/acl/{aclId}")
273 @RestQuery(
274 name = "deleteacl",
275 description = "Delete an ACL",
276 returnDescription = "Delete an ACL",
277 pathParameters = {
278 @RestParameter(name = "aclId", isRequired = true, description = "The ACL identifier", type = INTEGER)
279 },
280 responses = {
281 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been deleted"),
282 @RestResponse(responseCode = SC_NOT_FOUND, description = "The ACL has not been found"),
283 @RestResponse(
284 responseCode = SC_CONFLICT,
285 description = "The ACL could not be deleted, there are still references on it"
286 ),
287 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error during deleting the ACL")
288 }
289 )
290 public Response deleteAcl(@PathParam("aclId") long aclId) throws NotFoundException {
291 try {
292 if (!aclService().deleteAcl(aclId)) {
293 return conflict();
294 }
295 } catch (AclServiceException e) {
296 logger.warn("Error deleting manged acl with id '{}'", aclId, e);
297 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
298 }
299 return noContent();
300 }
301
302 @POST
303 @Path("/apply/episode/{episodeId}")
304 @RestQuery(
305 name = "applyAclToEpisode",
306 description = "Immediate application of an ACL to an episode (Attention: This endpoint is deprecated and "
307 + " will be removed in future versions!)",
308 returnDescription = "Status code",
309 pathParameters = {
310 @RestParameter(name = "episodeId", isRequired = true, description = "The episode ID", type = STRING)
311 },
312 restParameters = {
313 @RestParameter(
314 name = "aclId",
315 isRequired = false,
316 description = "The ID of the ACL to apply. If missing the episode ACL will be "
317 + "deleted to fall back to the series ACL",
318 type = INTEGER
319 )
320 },
321 responses = {
322 @RestResponse(responseCode = SC_OK, description = "The ACL has been successfully applied"),
323 @RestResponse(responseCode = SC_NOT_FOUND, description = "The ACL or the episode has not been found"),
324 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Internal error")
325 }
326 )
327 public Response applyAclToEpisode(@PathParam("episodeId") String episodeId, @FormParam("aclId") Long aclId) {
328 final AclService aclService = aclService();
329 Optional<ManagedAcl> macl = aclService.getAcl(aclId);
330 if (macl.isEmpty()) {
331 return notFound();
332 }
333 try {
334 Optional<AccessControlList> aclOpt = Optional.of(macl.get().getAcl());
335 Optional<MediaPackage> mediaPackage = getAssetManager().getMediaPackage(episodeId);
336
337 if (mediaPackage.isPresent()) {
338 MediaPackage episodeSvcMp = mediaPackage.get();
339 aclOpt.ifPresentOrElse(
340 acl -> {
341 try {
342 MediaPackage mp = getAuthorizationService()
343 .setAcl(episodeSvcMp, AclScope.Episode, acl)
344 .getA();
345 getAssetManager().takeSnapshot(mp);
346 } catch (MediaPackageException e) {
347 logger.error("Error getting ACL from media package", e);
348 }
349 },
350 () -> {
351 MediaPackage mp = getAuthorizationService()
352 .removeAcl(episodeSvcMp, AclScope.Episode);
353 getAssetManager().takeSnapshot(mp);
354 }
355 );
356 return ok();
357 }
358
359 return notFound();
360 } catch (Exception e) {
361 logger.error("Error applying acl to episode {}", episodeId);
362 return serverError();
363 }
364 }
365
366 private static AccessControlList parseAcl(String acl) {
367 try {
368 return AccessControlParser.parseAcl(acl);
369 } catch (Exception e) {
370 logger.warn("Unable to parse ACL");
371 throw new WebApplicationException(Response.Status.BAD_REQUEST);
372 }
373 }
374
375 private AclService aclService() {
376 return getAclServiceFactory().serviceFor(getSecurityService().getOrganization());
377 }
378 }