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