private String[] getReadingPrincipals( RepositorySession repositorySession, String zone, String path) throws StorageClientException { Session session = repositorySession.adaptTo(Session.class); AccessControlManager accessControlManager = session.getAccessControlManager(); return accessControlManager.findPrincipals( zone, path, Permissions.CAN_READ.getPermission(), true); }
/** * Gets the principals that can read content at a given path. * * @param session * @param path The path to check. * @return {@link String[]} of principal names that can read {@link path}. An empty array is * returned if no principals can read the path. * @throws StorageClientException */ @SuppressWarnings("unused") private String[] getReadingPrincipals(Session session, String path) throws StorageClientException { AccessControlManager accessControlManager = session.getAccessControlManager(); return accessControlManager.findPrincipals( Security.ZONE_CONTENT, path, Permissions.CAN_READ.getPermission(), true); }
public void createActivity( Session session, Content targetLocation, String userId, ActivityServiceCallback callback) throws AccessDeniedException, StorageClientException, ServletException, IOException { if (userId == null) { userId = session.getUserId(); } if (!userId.equals(session.getUserId()) && !User.ADMIN_USER.equals(session.getUserId())) { throw new IllegalStateException( "Only Administrative sessions may act on behalf of another user for activities"); } ContentManager contentManager = session.getContentManager(); // create activityStore if it does not exist String path = StorageClientUtils.newPath(targetLocation.getPath(), ACTIVITY_STORE_NAME); if (!contentManager.exists(path)) { contentManager.update( new Content( path, ImmutableMap.<String, Object>of( SLING_RESOURCE_TYPE_PROPERTY, ACTIVITY_STORE_RESOURCE_TYPE))); // inherit ACL from the target node, but let logged-in users write activities session .getAccessControlManager() .setAcl( Security.ZONE_CONTENT, path, new AclModification[] { new AclModification( AclModification.grantKey(Group.EVERYONE), Permissions.CAN_WRITE.getPermission(), Operation.OP_AND) }); } // create activity within activityStore String activityPath = StorageClientUtils.newPath(path, ActivityUtils.createId()); String activityFeedPath = StorageClientUtils.newPath(targetLocation.getPath(), "activityFeed"); if (!contentManager.exists(activityFeedPath)) { contentManager.update(new Content(activityFeedPath, null)); } if (!contentManager.exists(activityPath)) { contentManager.update( new Content( activityPath, ImmutableMap.of( JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY, (Object) ActivityConstants.ACTIVITY_ITEM_RESOURCE_TYPE))); } Content activtyNode = contentManager.get(activityPath); callback.processRequest(activtyNode); activtyNode = contentManager.get(activityPath); activtyNode.setProperty(PARAM_ACTOR_ID, userId); activtyNode.setProperty(ActivityConstants.PARAM_SOURCE, targetLocation.getPath()); Session adminSession = repository.loginAdministrative(); List<String> routesStr = new LinkedList<String>(); List<String> readers = new LinkedList<String>(); try { List<ActivityRoute> routes = activityRouterManager.getActivityRoutes(activtyNode, adminSession); if (routes != null) { for (ActivityRoute route : routes) { routesStr.add(route.getDestination()); if (route.getReaders() != null && route.getReaders().length > 0) { readers.addAll(Arrays.asList(route.getReaders())); } } } // store the routes as child content of the activity so we may lock it down to admin. It's // common for // the activity to be stored within the context of the content to which it pertains (e.g., // within the // pooled content item on which the user performed the activity), therefore we could expose // user // activity routes there -- that is an exposure of potentially sensitive content such as who // the user's // connections are. String routesPath = StorageClientUtils.newPath(activtyNode.getPath(), ActivityConstants.PARAM_ROUTES); contentManager.update( new Content( routesPath, ImmutableMap.<String, Object>of( ActivityConstants.PARAM_ROUTES, routesStr.toArray(new String[routesStr.size()])))); adminSession .getAccessControlManager() .setAcl( Security.ZONE_CONTENT, routesPath, new AclModification[] { new AclModification( AclModification.denyKey(User.ANON_USER), Permissions.ALL.getPermission(), Operation.OP_REPLACE), new AclModification( AclModification.denyKey(Group.EVERYONE), Permissions.ALL.getPermission(), Operation.OP_REPLACE), new AclModification( AclModification.denyKey(userId), Permissions.ALL.getPermission(), Operation.OP_REPLACE) }); if (!readers.isEmpty()) { AclModification[] readerAcls = new AclModification[readers.size()]; int i = 0; for (String reader : readers) { // ensure all the necessary readers/routes can read the activity readerAcls[i] = new AclModification( AclModification.grantKey(reader), Permissions.CAN_READ.getPermission(), Operation.OP_OR); i++; } adminSession .getAccessControlManager() .setAcl(Security.ZONE_CONTENT, activtyNode.getPath(), readerAcls); } } finally { SparseUtils.logoutQuietly(adminSession); } // store the activity node contentManager.update(activtyNode); // post the asynchronous OSGi event final Dictionary<String, String> properties = new Hashtable<String, String>(); properties.put(UserConstants.EVENT_PROP_USERID, userId); properties.put(ActivityConstants.EVENT_PROP_PATH, activityPath); properties.put("path", activityPath); properties.put("resourceType", ActivityConstants.ACTIVITY_ITEM_RESOURCE_TYPE); EventUtils.sendOsgiEvent(properties, LITE_EVENT_TOPIC, eventAdmin); }
@SlingServlet( methods = {"GET", "POST"}, resourceTypes = {"sakai/pooled-content"}, selectors = {"members"}) @Properties( value = { @Property(name = "service.vendor", value = "The Sakai Foundation"), @Property( name = "service.description", value = "Manages the Managers, Editors, and Viewers for pooled content.") }) @ServiceDocumentation( name = "Manage Members Content Pool Servlet", okForVersion = "1.1", shortDescription = "List and manage the managers, editors, and viewers for a file in the content pool.", description = "List and manage the managers, editors, and viewers for a file in the content pool.", bindings = { @ServiceBinding( type = BindingType.TYPE, bindings = {"sakai/pooled-content"}, selectors = { @ServiceSelector(name = "members", description = "Binds to the selector members."), @ServiceSelector( name = "detailed", description = "(optional) Provides more detailed profile information."), @ServiceSelector( name = "tidy", description = "(optional) Provides formatted JSON output.") }) }, methods = { @ServiceMethod( name = "GET", description = { "Retrieves a list of managers, editors, and viewers for this pooled content item.", "<pre>curl http://localhost:8080/p/hESoXumAT.members.tidy.json</pre>", "<pre>{\n" + " \"managers\": [{\n" + " \"hash\": \"suzy\",\n" + " \"basic\": {\n" + " \"access\": \"everybody\",\n" + " \"elements\": {\n" + " \"picture\": {\n" + " \"value\": \"{\\\"name\\\":\\\"256x256_tmp1309269939493.jpg\\\",\\\"_name\\\":\\\"tmp1309269939493.jpg\\\",\\\"_charset_\\\":\\\"utf-8\\\",\\\"selectedx1\\\":0,\\\"selectedy1\\\":3,\\\"selectedx2\\\":85,\\\"selectedy2\\\":88}\"\n" + " },\n" + " \"lastName\": {\n" + " \"value\": \"Queue\"\n" + " },\n" + " \"email\": {\n" + " \"value\": \"[email protected]\"\n" + " },\n" + " \"firstName\": {\n" + " \"value\": \"Suzy\"\n" + " }\n" + " }\n" + " },\n" + " \"rep:userId\": \"suzy\",\n" + " \"userid\": \"suzy\",\n" + " \"counts\": {\n" + " \"contactsCount\": 0,\n" + " \"membershipsCount\": 0,\n" + " \"contentCount\": 3,\n" + " \"countLastUpdate\": 1309287542572\n" + " },\n" + " \"sakai:excludeSearch\": false\n" + " }],\n" + " \"editors\": [{\n" + " \"hash\": \"alice.b.toklas\",\n" + " \"basic\": {\n" + " \"access\": \"everybody\",\n" + " \"elements\": {\n" + " \"picture\": {\n" + " \"value\": \"{\\\"name\\\":\\\"256x256_tmp1309269939493.jpg\\\",\\\"_name\\\":\\\"tmp1309269939493.jpg\\\",\\\"_charset_\\\":\\\"utf-8\\\",\\\"selectedx1\\\":0,\\\"selectedy1\\\":3,\\\"selectedx2\\\":85,\\\"selectedy2\\\":88}\"\n" + " },\n" + " \"lastName\": {\n" + " \"value\": \"Toklas\"\n" + " },\n" + " \"email\": {\n" + " \"value\": \"[email protected]\"\n" + " },\n" + " \"firstName\": {\n" + " \"value\": \"Alice\"\n" + " }\n" + " }\n" + " },\n" + " \"rep:userId\": \"aliceb\",\n" + " \"userid\": \"aliceb\",\n" + " \"counts\": {\n" + " \"contactsCount\": 0,\n" + " \"membershipsCount\": 0,\n" + " \"contentCount\": 3,\n" + " \"countLastUpdate\": 1309287542572\n" + " },\n" + " \"sakai:excludeSearch\": false\n" + " }],\n" + " \"viewers\": [{\n" + " \"basic\": {\n" + " \"access\": \"everybody\",\n" + " \"elements\": {\n" + " \"lastName\": {\n" + " \"value\": \"User\"\n" + " },\n" + " \"email\": {\n" + " \"value\": \"[email protected]\"\n" + " },\n" + " \"firstName\": {\n" + " \"value\": \"Anonymous\"\n" + " }\n" + " }\n" + " },\n" + " \"rep:userId\": \"anonymous\"\n" + " },\n" + " {\n" + " \"sakai:category\": null,\n" + " \"sakai:group-description\": null,\n" + " \"sakai:group-id\": \"everyone\",\n" + " \"createdBy\": null,\n" + " \"lastModified\": null,\n" + " \"sakai:group-title\": null,\n" + " \"created\": null,\n" + " \"basic\": {\n" + " \"access\": \"everybody\",\n" + " \"elements\": {}\n" + " },\n" + " \"lastModifiedBy\": null,\n" + " \"groupid\": \"everyone\",\n" + " \"counts\": {},\n" + " \"sakai:excludeSearch\": false\n" + " }]\n" + "}</pre>" }, response = { @ServiceResponse( code = 200, description = "All processing finished successfully. Output is in the JSON format."), @ServiceResponse( code = 500, description = "Any exceptions encountered during processing.") }), @ServiceMethod( name = "POST", description = "Manipulate the member list for a pooled content item.", parameters = { @ServiceParameter( name = ":manager", description = "Set the managers on the ACL of a file."), @ServiceParameter( name = ":editor", description = "Set the editors on the ACL of a file."), @ServiceParameter( name = ":viewer", description = "Set the viewers on the ACL of a file.") }, response = { @ServiceResponse(code = 200, description = "All processing finished successfully."), @ServiceResponse( code = 401, description = "POST by anonymous user, or current user doesn't have permission to update this content."), @ServiceResponse( code = 500, description = "Any exceptions encountered during processing.") }) }) public class ManageMembersContentPoolServlet extends SlingAllMethodsServlet { private static final long serialVersionUID = 3385014961034481906L; private static final Logger LOGGER = LoggerFactory.getLogger(ManageMembersContentPoolServlet.class); private static final Permission PERMISSION_EDITOR = Permissions.CAN_READ.combine(Permissions.CAN_WRITE); @Reference protected transient ProfileService profileService; @Reference protected transient BasicUserInfoService basicUserInfoService; @Reference protected transient AuthorizableCountChanger authorizableCountChanger; /** * Retrieves the list of members. * * <p>{@inheritDoc} * * @see * org.apache.sling.api.servlets.SlingSafeMethodsServlet#doGet(org.apache.sling.api.SlingHttpServletRequest, * org.apache.sling.api.SlingHttpServletResponse) */ @Override protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException { try { // Get hold of the actual file. Resource resource = request.getResource(); javax.jcr.Session jcrSession = request.getResourceResolver().adaptTo(javax.jcr.Session.class); Session session = resource.adaptTo(Session.class); AuthorizableManager am = session.getAuthorizableManager(); AccessControlManager acm = session.getAccessControlManager(); Content node = resource.adaptTo(Content.class); Authorizable thisUser = am.findAuthorizable(session.getUserId()); if (!acm.can(thisUser, Security.ZONE_CONTENT, resource.getPath(), Permissions.CAN_READ)) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } Map<String, Object> properties = node.getProperties(); String[] managers = (String[]) properties.get(POOLED_CONTENT_USER_MANAGER); String[] editors = (String[]) properties.get(POOLED_CONTENT_USER_EDITOR); String[] viewers = (String[]) properties.get(POOLED_CONTENT_USER_VIEWER); boolean detailed = false; boolean tidy = false; for (String selector : request.getRequestPathInfo().getSelectors()) { if ("detailed".equals(selector)) { detailed = true; } else if ("tidy".equals(selector)) { tidy = true; } } // Loop over the sets and output it. ExtendedJSONWriter writer = new ExtendedJSONWriter(response.getWriter()); writer.setTidy(tidy); writer.object(); writer.key("managers"); writer.array(); for (String manager : StorageClientUtils.nonNullStringArray(managers)) { try { writeProfileMap(jcrSession, am, writer, manager, detailed); } catch (AccessDeniedException e) { LOGGER.debug("Skipping private manager [{}]", manager); } } writer.endArray(); writer.key("editors"); writer.array(); for (String editor : StorageClientUtils.nonNullStringArray(editors)) { try { writeProfileMap(jcrSession, am, writer, editor, detailed); } catch (AccessDeniedException e) { LOGGER.debug("Skipping private editor [{}]", editor); } } writer.endArray(); writer.key("viewers"); writer.array(); for (String viewer : StorageClientUtils.nonNullStringArray(viewers)) { try { writeProfileMap(jcrSession, am, writer, viewer, detailed); } catch (AccessDeniedException e) { LOGGER.debug("Skipping private viewer [{}]", viewer); } } writer.endArray(); writer.endObject(); } catch (JSONException e) { response.sendError(SC_INTERNAL_SERVER_ERROR, "Failed to generate proper JSON."); LOGGER.error(e.getMessage(), e); } catch (StorageClientException e) { response.sendError(SC_INTERNAL_SERVER_ERROR, "Failed to generate proper JSON."); LOGGER.error(e.getMessage(), e); } catch (AccessDeniedException e) { response.sendError(SC_INTERNAL_SERVER_ERROR, "Failed to generate proper JSON."); LOGGER.error(e.getMessage(), e); } catch (RepositoryException e) { response.sendError(SC_INTERNAL_SERVER_ERROR, "Failed to generate proper JSON."); LOGGER.error(e.getMessage(), e); } } private void writeProfileMap( javax.jcr.Session jcrSession, AuthorizableManager um, ExtendedJSONWriter writer, String user, boolean detailed) throws JSONException, AccessDeniedException, StorageClientException, RepositoryException { Authorizable au = um.findAuthorizable(user); if (au != null) { ValueMap profileMap; if (detailed) { profileMap = profileService.getProfileMap(au, jcrSession); } else { profileMap = new ValueMapDecorator(basicUserInfoService.getProperties(au)); } if (profileMap != null) { writer.valueMap(profileMap); } } else { writer.object(); writer.key("userid"); writer.value(user); writer.endObject(); } } /** * Manipulate the member list for this file. * * <p>{@inheritDoc} * * @see * org.apache.sling.api.servlets.SlingAllMethodsServlet#doPost(org.apache.sling.api.SlingHttpServletRequest, * org.apache.sling.api.SlingHttpServletResponse) */ @SuppressWarnings("unchecked") @Override protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException { // fail if anonymous String remoteUser = request.getRemoteUser(); if (User.ANON_USER.equals(remoteUser)) { response.sendError(SC_FORBIDDEN, "Anonymous users cannot update content members."); return; } Session session = null; boolean releaseSession = false; try { Resource resource = request.getResource(); session = resource.adaptTo(Session.class); Content pooledContent = resource.adaptTo(Content.class); AccessControlManager accessControlManager = session.getAccessControlManager(); AuthorizableManager authorizableManager = session.getAuthorizableManager(); User thisUser = authorizableManager.getUser(); if (!accessControlManager.can( thisUser, Security.ZONE_CONTENT, pooledContent.getPath(), Permissions.CAN_READ)) { response.sendError(SC_FORBIDDEN, "Insufficient permission to read this content."); } Map<String, Object> properties = pooledContent.getProperties(); String[] managers = StorageClientUtils.nonNullStringArray( (String[]) properties.get(POOLED_CONTENT_USER_MANAGER)); String[] editors = StorageClientUtils.nonNullStringArray( (String[]) properties.get(POOLED_CONTENT_USER_EDITOR)); String[] viewers = StorageClientUtils.nonNullStringArray( (String[]) properties.get(POOLED_CONTENT_USER_VIEWER)); Set<String> managerSet = Sets.newHashSet(managers); Set<String> editorSet = Sets.newHashSet(editors); Set<String> viewerSet = Sets.newHashSet(viewers); List<String> removeViewers = Arrays.asList( StorageClientUtils.nonNullStringArray(request.getParameterValues(":viewer@Delete"))); List<String> removeManagers = Arrays.asList( StorageClientUtils.nonNullStringArray(request.getParameterValues(":manager@Delete"))); List<String> removeEditors = Arrays.asList( StorageClientUtils.nonNullStringArray(request.getParameterValues(":editor@Delete"))); List<String> addViewers = Arrays.asList( StorageClientUtils.nonNullStringArray(request.getParameterValues(":viewer"))); List<String> addManagers = Arrays.asList( StorageClientUtils.nonNullStringArray(request.getParameterValues(":manager"))); List<String> addEditors = Arrays.asList( StorageClientUtils.nonNullStringArray(request.getParameterValues(":editor"))); if (!accessControlManager.can( thisUser, Security.ZONE_CONTENT, pooledContent.getPath(), Permissions.CAN_WRITE)) { if (!addManagers.isEmpty()) { response.sendError(SC_FORBIDDEN, "Non-managers may not add managers to content."); return; } for (String name : removeManagers) { // asking to remove managers who don't exist is harmless if (managerSet.contains(name)) { response.sendError(SC_FORBIDDEN, "Non-managers may not remove managers from content."); return; } } if (addViewers.contains(User.ANON_USER) || addViewers.contains(Group.EVERYONE)) { response.sendError( SC_FORBIDDEN, "Non-managers may not add 'anonymous' or 'everyone' as viewers."); return; } if (addEditors.contains(User.ANON_USER) || addEditors.contains(Group.EVERYONE)) { response.sendError( SC_FORBIDDEN, "Non-managers may not add 'anonymous' or 'everyone' as editors."); return; } for (String name : removeViewers) { if (!thisUser.getId().equals(name)) { Authorizable viewer = authorizableManager.findAuthorizable(name); if (viewer != null && !accessControlManager.can( thisUser, Security.ZONE_AUTHORIZABLES, name, Permissions.CAN_WRITE)) { response.sendError( SC_FORBIDDEN, "Non-managers may not remove any viewer other than themselves or a group which they manage."); } } } // the request has passed all the rules that govern non-manager users // so we'll grant an administrative session session = session.getRepository().loginAdministrative(); releaseSession = true; } List<AclModification> aclModifications = Lists.newArrayList(); for (String addManager : addManagers) { if ((addManager.length() > 0) && !managerSet.contains(addManager)) { managerSet.add(addManager); AclModification.addAcl(true, Permissions.CAN_MANAGE, addManager, aclModifications); } } for (String removeManager : removeManagers) { if ((removeManager.length() > 0) && managerSet.contains(removeManager)) { managerSet.remove(removeManager); AclModification.removeAcl(true, Permissions.CAN_MANAGE, removeManager, aclModifications); } } for (String addEditor : addEditors) { if ((addEditor.length() > 0) && !editorSet.contains(addEditor)) { editorSet.add(addEditor); AclModification.addAcl(true, PERMISSION_EDITOR, addEditor, aclModifications); } } for (String removeEditor : removeEditors) { if ((removeEditor.length() > 0) && editorSet.contains(removeEditor)) { editorSet.remove(removeEditor); AclModification.removeAcl(true, PERMISSION_EDITOR, removeEditor, aclModifications); } } for (String addViewer : addViewers) { if ((addViewer.length() > 0) && !viewerSet.contains(addViewer)) { viewerSet.add(addViewer); AclModification.addAcl(true, Permissions.CAN_READ, addViewer, aclModifications); } } for (String removeViewer : removeViewers) { removeViewer = removeViewer.trim(); if ((removeViewer.length() > 0) && viewerSet.contains(removeViewer)) { viewerSet.remove(removeViewer); if (!managerSet.contains(removeViewer)) { AclModification.removeAcl(true, Permissions.CAN_READ, removeViewer, aclModifications); } } } updateContentMembers(session, pooledContent, viewerSet, managerSet, editorSet); updateContentAccess(session, pooledContent, aclModifications); this.authorizableCountChanger.notify( UserConstants.CONTENT_ITEMS_PROP, addViewers, addEditors, addManagers, removeViewers, removeEditors, removeManagers); response.setStatus(SC_OK); } catch (StorageClientException e) { LOGGER.error(e.getMessage()); response.sendError( SC_INTERNAL_SERVER_ERROR, "StorageClientException: " + e.getLocalizedMessage()); } catch (AccessDeniedException e) { response.sendError( SC_FORBIDDEN, "Insufficient permission to update content members at " + request.getRequestURI()); } finally { if (session != null && releaseSession) { try { session.logout(); } catch (ClientPoolException e) { LOGGER.error(e.getMessage()); } } } } private void updateContentMembers( Session session, Content content, Set<String> viewerSet, Set<String> managerSet, Set<String> editorSet) throws StorageClientException, AccessDeniedException { content.setProperty( POOLED_CONTENT_USER_VIEWER, viewerSet.toArray(new String[viewerSet.size()])); content.setProperty( POOLED_CONTENT_USER_MANAGER, managerSet.toArray(new String[managerSet.size()])); content.setProperty( POOLED_CONTENT_USER_EDITOR, editorSet.toArray(new String[editorSet.size()])); LOGGER.debug( "Set Managers to {}", Arrays.toString(managerSet.toArray(new String[managerSet.size()]))); LOGGER.debug( "Set Editors to {}", Arrays.toString(editorSet.toArray(new String[editorSet.size()]))); LOGGER.debug( "Set Viewers to {}", Arrays.toString(viewerSet.toArray(new String[managerSet.size()]))); session.getContentManager().update(content); } private void updateContentAccess( Session session, Content content, List<AclModification> aclModifications) throws StorageClientException, AccessDeniedException { LOGGER.debug( "ACL Modifications {}", Arrays.toString(aclModifications.toArray(new AclModification[aclModifications.size()]))); session .getAccessControlManager() .setAcl( Security.ZONE_CONTENT, content.getPath(), aclModifications.toArray(new AclModification[aclModifications.size()])); } }