View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
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       // the episode service is the source of authority for the retrieval of media packages
338       if (mediaPackage.isPresent()) {
339         MediaPackage episodeSvcMp = mediaPackage.get();
340         aclOpt.fold(new Option.EMatch<AccessControlList>() {
341           // set the new episode ACL
342           @Override
343           public void esome(final AccessControlList acl) {
344             // update in episode service
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           // if none EpisodeACLTransition#isDelete returns true so delete the episode ACL
354           @Override
355           public void enone() {
356             // update in episode service
357             MediaPackage mp = getAuthorizationService().removeAcl(episodeSvcMp, AclScope.Episode);
358             getAssetManager().takeSnapshot(mp);
359           }
360 
361         });
362         return ok();
363       }
364       // not found
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 }