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.adminui.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_NOT_FOUND;
27 import static javax.servlet.http.HttpServletResponse.SC_OK;
28 import static org.apache.commons.lang3.StringUtils.trimToNull;
29 import static org.opencastproject.index.service.util.RestUtils.okJsonList;
30 import static org.opencastproject.userdirectory.UserIdRoleProvider.getUserRolePrefix;
31 import static org.opencastproject.userdirectory.UserIdRoleProvider.isSanitize;
32 import static org.opencastproject.util.RestUtil.R.conflict;
33 import static org.opencastproject.util.RestUtil.R.noContent;
34 import static org.opencastproject.util.doc.rest.RestParameter.Type.INTEGER;
35 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
36
37 import org.opencastproject.adminui.util.TextFilter;
38 import org.opencastproject.authorization.xacml.manager.api.AclService;
39 import org.opencastproject.authorization.xacml.manager.api.AclServiceException;
40 import org.opencastproject.authorization.xacml.manager.api.AclServiceFactory;
41 import org.opencastproject.authorization.xacml.manager.api.ManagedAcl;
42 import org.opencastproject.authorization.xacml.manager.endpoint.JsonConv;
43 import org.opencastproject.authorization.xacml.manager.impl.ManagedAclImpl;
44 import org.opencastproject.index.service.resources.list.query.AclsListQuery;
45 import org.opencastproject.index.service.util.RestUtils;
46 import org.opencastproject.security.api.AccessControlEntry;
47 import org.opencastproject.security.api.AccessControlList;
48 import org.opencastproject.security.api.AccessControlParser;
49 import org.opencastproject.security.api.Organization;
50 import org.opencastproject.security.api.Role;
51 import org.opencastproject.security.api.RoleDirectoryService;
52 import org.opencastproject.security.api.SecurityService;
53 import org.opencastproject.security.api.User;
54 import org.opencastproject.security.api.UserDirectoryService;
55 import org.opencastproject.util.NotFoundException;
56 import org.opencastproject.util.doc.rest.RestParameter;
57 import org.opencastproject.util.doc.rest.RestQuery;
58 import org.opencastproject.util.doc.rest.RestResponse;
59 import org.opencastproject.util.doc.rest.RestService;
60 import org.opencastproject.util.requests.SortCriterion;
61 import org.opencastproject.util.requests.SortCriterion.Order;
62
63 import com.google.gson.JsonArray;
64 import com.google.gson.JsonObject;
65
66 import org.apache.commons.lang3.ObjectUtils;
67 import org.apache.commons.lang3.StringUtils;
68 import org.json.simple.JSONArray;
69 import org.json.simple.JSONObject;
70 import org.osgi.service.component.ComponentContext;
71 import org.osgi.service.component.annotations.Activate;
72 import org.osgi.service.component.annotations.Component;
73 import org.osgi.service.component.annotations.Reference;
74 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
77
78 import java.io.IOException;
79 import java.util.ArrayList;
80 import java.util.Collections;
81 import java.util.Comparator;
82 import java.util.HashMap;
83 import java.util.LinkedHashSet;
84 import java.util.List;
85 import java.util.Map;
86 import java.util.Optional;
87 import java.util.Set;
88
89 import javax.ws.rs.DELETE;
90 import javax.ws.rs.FormParam;
91 import javax.ws.rs.GET;
92 import javax.ws.rs.POST;
93 import javax.ws.rs.PUT;
94 import javax.ws.rs.Path;
95 import javax.ws.rs.PathParam;
96 import javax.ws.rs.Produces;
97 import javax.ws.rs.QueryParam;
98 import javax.ws.rs.WebApplicationException;
99 import javax.ws.rs.core.MediaType;
100 import javax.ws.rs.core.Response;
101
102 @Path("/admin-ng/acl")
103 @RestService(
104 name = "acl",
105 title = "Acl service",
106 abstractText = "Provides operations for acl",
107 notes = { "This service offers the default acl CRUD Operations for the admin UI.",
108 "<strong>Important:</strong> "
109 + "<em>This service is for exclusive use by the module admin-ui. Its API might change "
110 + "anytime without prior notice. Any dependencies other than the admin UI will be strictly ignored. "
111 + "DO NOT use this for integration of third-party applications.<em>"})
112 @Component(
113 immediate = true,
114 service = AclEndpoint.class,
115 property = {
116 "service.description=Admin UI - ACL Endpoint",
117 "opencast.service.type=org.opencastproject.adminui.AclEndpoint",
118 "opencast.service.path=/admin-ng/acl",
119 }
120 )
121 @JaxrsResource
122 public class AclEndpoint {
123
124
125 private static final Logger logger = LoggerFactory.getLogger(AclEndpoint.class);
126
127
128 private AclServiceFactory aclServiceFactory;
129
130
131 private SecurityService securityService;
132
133
134 private RoleDirectoryService roleDirectoryService;
135
136
137 protected UserDirectoryService userDirectoryService;
138
139
140
141
142
143 @Reference
144 public void setAclServiceFactory(AclServiceFactory aclServiceFactory) {
145 this.aclServiceFactory = aclServiceFactory;
146 }
147
148
149 @Reference
150 public void setRoleDirectoryService(RoleDirectoryService roleDirectoryService) {
151 this.roleDirectoryService = roleDirectoryService;
152 }
153
154
155
156
157
158 @Reference
159 public void setSecurityService(SecurityService securityService) {
160 this.securityService = securityService;
161 }
162
163
164 @Activate
165 protected void activate(ComponentContext cc) {
166 logger.info("Activate the Admin ui - Acl facade endpoint");
167 }
168
169 private AclService aclService() {
170 return aclServiceFactory.serviceFor(securityService.getOrganization());
171 }
172
173 @Reference
174 public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
175 this.userDirectoryService = userDirectoryService;
176 }
177
178 @GET
179 @Path("acls.json")
180 @Produces(MediaType.APPLICATION_JSON)
181 @RestQuery(
182 name = "allaclasjson",
183 description = "Returns a list of acls",
184 returnDescription = "Returns a JSON representation of the list of acls available the current user's organization",
185 restParameters = {
186 @RestParameter(name = "filter", isRequired = false, description = "The filter used for the query. They "
187 + "should be formated like that: 'filter1:value1,filter2:value2'", type = STRING),
188 @RestParameter(name = "sort", isRequired = false, description = "The sort order. May include any of the "
189 + "following: NAME. Add '_DESC' to reverse the sort order (e.g. NAME_DESC).", type = STRING),
190 @RestParameter(defaultValue = "100", description = "The maximum number of items to return per page.",
191 isRequired = false, name = "limit", type = RestParameter.Type.STRING),
192 @RestParameter(defaultValue = "0", description = "The page number.", isRequired = false, name = "offset",
193 type = RestParameter.Type.STRING)
194 },
195 responses = {
196 @RestResponse(responseCode = SC_OK, description = "The list of ACL's has successfully been returned")
197 })
198 public Response getAclsAsJson(@QueryParam("filter") String filter, @QueryParam("sort") String sort,
199 @QueryParam("offset") int offset, @QueryParam("limit") int limit) throws IOException {
200 if (limit < 1) {
201 limit = 100;
202 }
203 Optional<String> optSort = Optional.ofNullable(trimToNull(sort));
204 Optional<String> filterName = Optional.empty();
205 Optional<String> filterText = Optional.empty();
206
207 Map<String, String> filters = RestUtils.parseFilter(filter);
208 for (String name : filters.keySet()) {
209 String value = filters.get(name);
210 if (AclsListQuery.FILTER_NAME_NAME.equals(name)) {
211 filterName = Optional.of(value);
212 } else if ((AclsListQuery.FILTER_TEXT_NAME.equals(name)) && (StringUtils.isNotBlank(value))) {
213 filterText = Optional.of(value);
214 }
215 }
216
217
218 List<ManagedAcl> filteredAcls = new ArrayList<>();
219 for (ManagedAcl acl : aclService().getAcls()) {
220
221 if ((filterName.isPresent() && !filterName.get().equals(acl.getName()))
222 || (filterText.isPresent() && !TextFilter.match(filterText.get(), acl.getName()))) {
223 continue;
224 }
225 filteredAcls.add(acl);
226 }
227 int total = filteredAcls.size();
228
229
230 if (optSort.isPresent()) {
231 final ArrayList<SortCriterion> sortCriteria = RestUtils.parseSortQueryParameter(optSort.get());
232 Collections.sort(filteredAcls, new Comparator<ManagedAcl>() {
233 @Override
234 public int compare(ManagedAcl acl1, ManagedAcl acl2) {
235 for (SortCriterion criterion : sortCriteria) {
236 Order order = criterion.getOrder();
237 switch (criterion.getFieldName()) {
238 case "name":
239 if (order.equals(Order.Descending)) {
240 return ObjectUtils.compare(acl2.getName(), acl1.getName());
241 }
242 return ObjectUtils.compare(acl1.getName(), acl2.getName());
243 default:
244 logger.info("Unkown sort type: {}", criterion.getFieldName());
245 return 0;
246 }
247 }
248 return 0;
249 }
250 });
251 }
252
253 int start = Math.min(offset, filteredAcls.size());
254 int end = Math.min(start + limit, filteredAcls.size());
255
256
257 List<ManagedAcl> subList = filteredAcls.subList(start, end);
258
259
260 List<JsonObject> aclJSON = new ArrayList<>();
261 for (ManagedAcl acl : subList) {
262 aclJSON.add(full(acl));
263 }
264
265 return okJsonList(aclJSON, offset, limit, total);
266 }
267
268 @GET
269 @Path("roles.json")
270 @Produces(MediaType.APPLICATION_JSON)
271 @RestQuery(
272 name = "getRoles",
273 description = "Returns a list of roles",
274 returnDescription = "Returns a JSON representation of the roles with the given parameters under the "
275 + "current user's organization.",
276 restParameters = {
277 @RestParameter(name = "query", isRequired = false, description = "The query.", type = STRING),
278 @RestParameter(name = "target", isRequired = false, description = "The target of the roles.",
279 type = STRING),
280 @RestParameter(name = "limit", defaultValue = "100",
281 description = "The maximum number of items to return per page.", isRequired = false,
282 type = RestParameter.Type.STRING),
283 @RestParameter(name = "offset", defaultValue = "0", description = "The page number.", isRequired = false,
284 type = RestParameter.Type.STRING)
285 },
286 responses = {
287 @RestResponse(responseCode = SC_OK, description = "The list of roles.")
288 })
289 public Response getRoles(@QueryParam("query") String query, @QueryParam("target") String target,
290 @QueryParam("offset") int offset, @QueryParam("limit") int limit) {
291
292 String roleQuery = "%";
293 if (StringUtils.isNotBlank(query)) {
294 roleQuery = query.trim() + "%";
295 }
296
297 Role.Target roleTarget = Role.Target.ALL;
298
299 if (StringUtils.isNotBlank(target)) {
300 try {
301 roleTarget = Role.Target.valueOf(target.trim());
302 } catch (Exception e) {
303 logger.warn("Invalid target filter value {}", target);
304 }
305 }
306
307 List<Role> roles = roleDirectoryService.findRoles(roleQuery, roleTarget, offset, limit);
308 Set<Role> uniqueRoles = new LinkedHashSet<>(roles);
309
310 JSONArray jsonRoles = new JSONArray();
311 for (Role role: uniqueRoles) {
312 JSONObject jsonRole = new JSONObject();
313 jsonRole.put("name", role.getName());
314 jsonRole.put("type", role.getType().toString());
315 jsonRole.put("description", role.getDescription());
316 jsonRole.put("organization", role.getOrganizationId());
317 jsonRole.put("isSanitize", isSanitize());
318 if (!isSanitize()) {
319 User user = userDirectoryService.loadUser(role.getName().replaceFirst(getUserRolePrefix(), ""));
320 if (user != null) {
321 jsonRole.put("user", generateJsonUser(user));
322 }
323 }
324 jsonRoles.add(jsonRole);
325 }
326
327 return Response.ok(jsonRoles.toJSONString()).build();
328 }
329
330 @DELETE
331 @Path("{id}")
332 @RestQuery(
333 name = "deleteacl",
334 description = "Delete an ACL",
335 returnDescription = "Delete an ACL",
336 pathParameters = {
337 @RestParameter(name = "id", isRequired = true, description = "The ACL identifier", type = INTEGER)
338 },
339 responses = {
340 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been deleted"),
341 @RestResponse(responseCode = SC_NOT_FOUND, description = "The ACL has not been found"),
342 @RestResponse(responseCode = SC_CONFLICT, description = "The ACL could not be deleted, there are still "
343 + "references on it")
344 })
345 public Response deleteAcl(@PathParam("id") long aclId) throws NotFoundException {
346 try {
347 if (!aclService().deleteAcl(aclId)) {
348 return conflict();
349 }
350 } catch (AclServiceException e) {
351 logger.warn("Error deleting manged acl with id '{}'", aclId, e);
352 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
353 }
354 return noContent();
355 }
356
357 @POST
358 @Path("")
359 @Produces(MediaType.APPLICATION_JSON)
360 @RestQuery(
361 name = "createacl",
362 description = "Create an ACL",
363 returnDescription = "Create an ACL",
364 restParameters = {
365 @RestParameter(name = "name", isRequired = true, description = "The ACL name", type = STRING),
366 @RestParameter(name = "acl", isRequired = true, description = "The access control list", type = STRING)
367 },
368 responses = {
369 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been added"),
370 @RestResponse(responseCode = SC_CONFLICT, description = "An ACL with the same name already exists"),
371 @RestResponse(responseCode = SC_BAD_REQUEST, description = "Unable to parse the ACL")
372 })
373 public Response createAcl(@FormParam("name") String name, @FormParam("acl") String accessControlList) {
374 final AccessControlList acl = parseAcl(accessControlList);
375 Optional<ManagedAcl> managedAcl = aclService().createAcl(acl, name);
376 if (managedAcl.isEmpty()) {
377 logger.info("An ACL with the same name '{}' already exists", name);
378 throw new WebApplicationException(Response.Status.CONFLICT);
379 }
380 return RestUtils.okJson(full(managedAcl.get()));
381 }
382
383 @PUT
384 @Path("{id}")
385 @Produces(MediaType.APPLICATION_JSON)
386 @RestQuery(
387 name = "updateacl",
388 description = "Update an ACL",
389 returnDescription = "Update an ACL",
390 pathParameters = {
391 @RestParameter(name = "id", isRequired = true, description = "The ACL identifier", type = INTEGER)
392 },
393 restParameters = {
394 @RestParameter(name = "name", isRequired = true, description = "The ACL name", type = STRING),
395 @RestParameter(name = "acl", isRequired = true, description = "The access control list", type = STRING)
396 },
397 responses = {
398 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been updated"),
399 @RestResponse(responseCode = SC_NOT_FOUND, description = "The ACL has not been found"),
400 @RestResponse(responseCode = SC_BAD_REQUEST, description = "Unable to parse the ACL")
401 })
402 public Response updateAcl(@PathParam("id") long aclId, @FormParam("name") String name,
403 @FormParam("acl") String accessControlList) throws NotFoundException {
404 final Organization org = securityService.getOrganization();
405 final AccessControlList acl = parseAcl(accessControlList);
406 final ManagedAclImpl managedAcl = new ManagedAclImpl(aclId, name, org.getId(), acl);
407 if (!aclService().updateAcl(managedAcl)) {
408 logger.info("No ACL with id '{}' could be found under organization '{}'", aclId, org.getId());
409 throw new NotFoundException();
410 }
411 return RestUtils.okJson(full(managedAcl));
412 }
413
414 @GET
415 @Path("{id}")
416 @Produces(MediaType.APPLICATION_JSON)
417 @RestQuery(
418 name = "getacl",
419 description = "Return the ACL by the given id",
420 returnDescription = "Return the ACL by the given id",
421 pathParameters = {
422 @RestParameter(name = "id", isRequired = true, description = "The ACL identifier", type = INTEGER)
423 },
424 responses = {
425 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been returned"),
426 @RestResponse(responseCode = SC_NOT_FOUND, description = "The ACL has not been found")
427 })
428 public Response getAcl(@PathParam("id") long aclId) throws NotFoundException {
429 Optional<ManagedAcl> managedAcl = aclService().getAcl(aclId);
430 if (managedAcl.isPresent()) {
431 return RestUtils.okJson(full(managedAcl.get()));
432 }
433 logger.info("No ACL with id '{}' could by found", aclId);
434 throw new NotFoundException();
435 }
436
437 @GET
438 @Path("acl/{name}")
439 @Produces(MediaType.APPLICATION_JSON)
440 @RestQuery(
441 name = "getaclbyname",
442 description = "Return the ACL by the given name",
443 returnDescription = "Return the ACL by the given name",
444 pathParameters = {
445 @RestParameter(name = "name", isRequired = true, description = "The ACL name", type = STRING)
446 },
447 responses = {
448 @RestResponse(responseCode = SC_OK, description = "The ACL has successfully been returned"),
449 @RestResponse(responseCode = SC_NOT_FOUND, description = "The ACL has not been found")
450 })
451 public Response getAcl(@PathParam("name") String aclName) throws NotFoundException {
452 Optional<ManagedAcl> managedAcl = aclService().getAcl(aclName);
453 if (managedAcl.isPresent()) {
454 return RestUtils.okJson(full(managedAcl.get()));
455 }
456 logger.info("No ACL with name '{}' could by found", aclName);
457 throw new NotFoundException();
458 }
459
460 private static AccessControlList parseAcl(String acl) {
461 try {
462 return AccessControlParser.parseAcl(acl);
463 } catch (Exception e) {
464 logger.warn("Unable to parse ACL", e);
465 throw new WebApplicationException(Response.Status.BAD_REQUEST);
466 }
467 }
468
469 public JsonObject full(AccessControlEntry ace) {
470 JsonObject json = new JsonObject();
471 json.addProperty(JsonConv.KEY_ROLE, ace.getRole());
472 json.addProperty(JsonConv.KEY_ACTION, ace.getAction());
473 json.addProperty(JsonConv.KEY_ALLOW, ace.isAllow());
474 return json;
475 }
476
477 private JsonObject fullAccessControlEntry(AccessControlEntry ace) {
478 return full(ace);
479 }
480
481 public JsonObject full(AccessControlList acl) {
482 JsonObject json = new JsonObject();
483 JsonArray aceArray = new JsonArray();
484
485 List<AccessControlEntry> entries = acl.getEntries();
486 if (entries != null) {
487 for (AccessControlEntry entry : entries) {
488 aceArray.add(fullAccessControlEntry(entry));
489 }
490 }
491
492 json.add(JsonConv.KEY_ACE, aceArray);
493 return json;
494 }
495
496 public JsonObject full(ManagedAcl acl) {
497 JsonObject json = new JsonObject();
498 json.addProperty(JsonConv.KEY_ID, acl.getId());
499 json.addProperty(JsonConv.KEY_NAME, acl.getName());
500 json.addProperty(JsonConv.KEY_ORGANIZATION_ID, acl.getOrganizationId());
501 json.add("acl", full(acl.getAcl()));
502 return json;
503 }
504
505 public Map<String, Object> generateJsonUser(User user) {
506
507 Map<String, Object> userData = new HashMap<>();
508 userData.put("username", user.getUsername());
509 userData.put("name", user.getName());
510 userData.put("email", user.getEmail());
511 return userData;
512 }
513
514 }