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