org.sakaiproject.nakamura.user.servlet.CreateSakaiGroupServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.nakamura.user.servlet.CreateSakaiGroupServlet.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sakaiproject.nakamura.user.servlet;

import static org.sakaiproject.nakamura.api.user.UserConstants.PROP_GROUP_MANAGERS;
import static org.sakaiproject.nakamura.api.user.UserConstants.PROP_GROUP_VIEWERS;
import static org.sakaiproject.nakamura.api.user.UserConstants.SYSTEM_USER_MANAGER_GROUP_PREFIX;

import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.core.security.SecurityConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.servlets.HtmlResponse;
import org.apache.sling.jackrabbit.usermanager.impl.resource.AuthorizableResourceProvider;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.base.util.AccessControlUtil;
import org.apache.sling.servlets.post.Modification;
import org.apache.sling.servlets.post.ModificationType;
import org.apache.sling.servlets.post.SlingPostConstants;
import org.apache.sling.servlets.post.impl.helper.RequestProperty;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.event.EventAdmin;
import org.sakaiproject.nakamura.api.doc.BindingType;
import org.sakaiproject.nakamura.api.doc.ServiceBinding;
import org.sakaiproject.nakamura.api.doc.ServiceDocumentation;
import org.sakaiproject.nakamura.api.doc.ServiceExtension;
import org.sakaiproject.nakamura.api.doc.ServiceMethod;
import org.sakaiproject.nakamura.api.doc.ServiceParameter;
import org.sakaiproject.nakamura.api.doc.ServiceResponse;
import org.sakaiproject.nakamura.api.doc.ServiceSelector;
import org.sakaiproject.nakamura.api.user.AuthorizablePostProcessService;
import org.sakaiproject.nakamura.api.user.UserConstants;
import org.sakaiproject.nakamura.user.NameSanitizer;
import org.sakaiproject.nakamura.util.osgi.EventUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.Principal;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>
 * Sling Post Servlet implementation for creating a group in the jackrabbit UserManager.
 * </p>
 * <h2>Rest Service Description</h2>
 * <p>
 * Creates a new group. Maps on to nodes of resourceType <code>sling/groups</code> like
 * <code>/rep:system/rep:userManager/rep:groups</code> mapped to a resource url
 * <code>/system/userManager/group</code>. This servlet responds at
 * <code>/system/userManager/group.create.html</code>
 * </p>
 * <h4>Methods</h4>
 * <ul>
 * <li>POST</li>
 * </ul>
 * <h4>Post Parameters</h4>
 * <dl>
 * <dt>:name</dt>
 * <dd>The name of the new group (required)</dd>
 * <dt>*</dt>
 * <dd>Any additional parameters become properties of the group node (optional)</dd>
 * </dl>
 * <h4>Response</h4>
 * <dl>
 * <dt>200</dt>
 * <dd>Success, a redirect is sent to the group resource locator. The redirect comes with
 * HTML describing the status.</dd>
 * <dt>500</dt>
 * <dd>Failure, including group already exists. HTML explains the failure.</dd>
 * </dl>
 * <h4>Example</h4>
 *
 * <code>
 * curl -F:name=newGroupA  -Fproperty1=value1 http://localhost:8080/system/userManager/group.create.html
 * </code>
 *
 * <h4>Notes</h4>
 *
 * @scr.component immediate="true"
 * @scr.service interface="javax.servlet.Servlet"
 * @scr.property name="sling.servlet.resourceTypes" value="sling/groups"
 * @scr.property name="sling.servlet.methods" value="POST"
 * @scr.property name="sling.servlet.selectors" value="create"
 *
 * @scr.property name="servlet.post.dateFormats"
 *               values.0="EEE MMM dd yyyy HH:mm:ss 'GMT'Z"
 *               values.1="yyyy-MM-dd'T'HH:mm:ss.SSSZ" values.2="yyyy-MM-dd'T'HH:mm:ss"
 *               values.3="yyyy-MM-dd" values.4="dd.MM.yyyy HH:mm:ss"
 *               values.5="dd.MM.yyyy"
 *
 *
 */
@ServiceDocumentation(name = "Create Group Servlet", description = "Creates a new group. Maps on to nodes of resourceType sling/groups like "
        + "/rep:system/rep:userManager/rep:groups mapped to a resource url "
        + "/system/userManager/group. This servlet responds at /system/userManager/group.create.html", shortDescription = "Creates a new group", bindings = @ServiceBinding(type = BindingType.PATH, bindings = "/system/userManager/group.create.html", selectors = @ServiceSelector(name = "create", description = "Creates a new group"), extensions = @ServiceExtension(name = "html", description = "Posts produce html containing the update status")), methods = @ServiceMethod(name = "POST", description = {
                "Creates a new group with a name :name, "
                        + "storing additional parameters as properties of the new group.",
                "Example<br>"
                        + "<pre>curl -F:name=g-groupname -Fproperty1=value1 http://localhost:8080/system/userManager/group.create.html</pre>" }, parameters = {
                                @ServiceParameter(name = ":name", description = "The name of the new group (required)"),
                                @ServiceParameter(name = "", description = "Additional parameters become group node properties, "
                                        + "except for parameters starting with ':', which are only forwarded to post-processors (optional)") }, response = {
                                                @ServiceResponse(code = 200, description = "Success, a redirect is sent to the groups resource locator with HTML describing status."),
                                                @ServiceResponse(code = 500, description = "Failure, including group already exists. HTML explains failure.") }))

public class CreateSakaiGroupServlet extends AbstractSakaiGroupPostServlet implements ManagedService {

    /**
     *
     */
    private static final long serialVersionUID = 6587376522316825454L;

    private static final Logger LOGGER = LoggerFactory.getLogger(CreateSakaiGroupServlet.class);

    /**
     * The JCR Repository we access to resolve resources
     *
     * @scr.reference
     */
    protected transient SlingRepository repository;

    /**
     * Used to launch OSGi events.
     *
     * @scr.reference
     */
    protected transient EventAdmin eventAdmin;

    /**
     * Used to create the group.
     *
     * @scr.reference
     */
    protected transient AuthorizablePostProcessService postProcessorService;

    /**
     *
     * @scr.property value="authenticated,everyone" type="String"
     *               name="Groups who are allowed to create other groups" description=
     *               "A comma separated list of groups who area allowed to create other groups"
     */
    public static final String GROUP_AUTHORISED_TOCREATE = "groups.authorized.tocreate";

    private String[] authorizedGroups = { "authenticated" };

    /*
     * (non-Javadoc)
     *
     * @seeorg.apache.sling.jackrabbit.usermanager.post.AbstractAuthorizablePostServlet#
     * handleOperation(org.apache.sling.api.SlingHttpServletRequest,
     * org.apache.sling.api.servlets.HtmlResponse, java.util.List)
     */
    @Override
    @edu.umd.cs.findbugs.annotations.SuppressWarnings(justification = "If there is an exception, the user is certainly not admin", value = {
            "REC_CATCH_EXCEPTION" })

    protected void handleOperation(SlingHttpServletRequest request, HtmlResponse response,
            List<Modification> changes) throws RepositoryException {

        // KERN-432 dont allow anon users to access create group.
        if (SecurityConstants.ANONYMOUS_ID.equals(request.getRemoteUser())) {
            response.setStatus(403, "AccessDenied");
            return;
        }

        // check that the submitted parameter values have valid values.
        final String principalName = request.getParameter(SlingPostConstants.RP_NODE_NAME);
        if (principalName == null) {
            throw new RepositoryException("Group name was not submitted");
        }

        NameSanitizer san = new NameSanitizer(principalName, false);
        san.validate();

        // check for allow create Group
        boolean allowCreateGroup = false;
        User currentUser = null;

        try {
            Session currentSession = request.getResourceResolver().adaptTo(Session.class);
            UserManager um = AccessControlUtil.getUserManager(currentSession);
            currentUser = (User) um.getAuthorizable(currentSession.getUserID());
            if (currentUser.isAdmin()) {
                LOGGER.debug("User is an admin ");
                allowCreateGroup = true;
            } else {
                LOGGER.debug("Checking for membership of one of {} ", Arrays.toString(authorizedGroups));
                PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(currentSession);
                PrincipalIterator pi = principalManager
                        .getGroupMembership(principalManager.getPrincipal(currentSession.getUserID()));
                Set<String> groups = new HashSet<String>();
                for (; pi.hasNext();) {
                    groups.add(pi.nextPrincipal().getName());
                }

                for (String groupName : authorizedGroups) {
                    if (groups.contains(groupName)) {
                        allowCreateGroup = true;
                        break;
                    }

                    // TODO: move this nasty hack into the PrincipalManager dynamic groups need to
                    // be in the principal manager for this to work.
                    if ("authenticated".equals(groupName)
                            && !SecurityConstants.ADMIN_ID.equals(currentUser.getID())) {
                        allowCreateGroup = true;
                        break;
                    }

                    // just check via the user manager for dynamic resolution.
                    Group group = (Group) um.getAuthorizable(groupName);
                    LOGGER.debug("Checking for group  {} {} ", groupName, group);
                    if (group != null && group.isMember(currentUser)) {
                        allowCreateGroup = true;
                        LOGGER.debug("User is a member  of {} {} ", groupName, group);
                        break;
                    }
                }
            }
        } catch (Exception ex) {
            LOGGER.warn("Failed to determin if the user is an admin, assuming not. Cause: " + ex.getMessage());
            allowCreateGroup = false;
        }

        if (!allowCreateGroup) {
            LOGGER.debug("User is not allowed to create groups ");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN, "User is not allowed to create groups");
            return;
        }

        Session session = getSession();

        try {
            UserManager userManager = AccessControlUtil.getUserManager(session);
            Authorizable authorizable = userManager.getAuthorizable(principalName);

            if (authorizable != null) {
                // principal already exists!
                response.setStatus(400, "A principal already exists with the requested name: " + principalName);
                return;
            } else {
                Group group = userManager.createGroup(new Principal() {
                    public String getName() {
                        return principalName;
                    }
                });
                String groupPath = AuthorizableResourceProvider.SYSTEM_USER_MANAGER_GROUP_PREFIX + group.getID();
                Map<String, RequestProperty> reqProperties = collectContent(request, response, groupPath);

                response.setPath(groupPath);
                response.setLocation(externalizePath(request, groupPath));
                response.setParentLocation(
                        externalizePath(request, AuthorizableResourceProvider.SYSTEM_USER_MANAGER_GROUP_PATH));
                changes.add(Modification.onCreated(groupPath));

                // It is not allowed to touch the rep:group-managers property directly.
                String key = SYSTEM_USER_MANAGER_GROUP_PREFIX + principalName + "/";
                reqProperties.remove(key + PROP_GROUP_MANAGERS);
                reqProperties.remove(key + PROP_GROUP_VIEWERS);

                // write content from form
                writeContent(session, group, reqProperties, changes);

                // update the group memberships, although this uses session from the request, it
                // only
                // does so for finding authorizables, so its ok that we are using an admin session
                // here.
                updateGroupMembership(request, group, changes);
                // TODO We should probably let the client decide whether the
                // current user belongs in the managers list or not.
                updateOwnership(request, group, new String[] { currentUser.getID() }, changes);

                try {
                    postProcessorService.process(group, session, ModificationType.CREATE, request);
                } catch (RepositoryException e) {
                    LOGGER.info("Failed to create Group  {}", e.getMessage());
                    response.setStatus(HttpServletResponse.SC_CONFLICT, e.getMessage());
                    return;
                } catch (Exception e) {
                    LOGGER.warn(e.getMessage(), e);
                    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
                    return;
                }

                // Launch an OSGi event for creating a group.
                try {
                    Dictionary<String, String> properties = new Hashtable<String, String>();
                    properties.put(UserConstants.EVENT_PROP_USERID, principalName);
                    EventUtils.sendOsgiEvent(properties, UserConstants.TOPIC_GROUP_CREATED, eventAdmin);
                } catch (Exception e) {
                    // Trap all exception so we don't disrupt the normal behaviour.
                    LOGGER.error("Failed to launch an OSGi event for creating a user.", e);
                }
            }
        } catch (RepositoryException re) {
            LOGGER.info("Failed to create Group  {}", re.getMessage());
            LOGGER.debug("Failed to create Group Cause {}", re, re.getMessage());
            response.setStatus(HttpServletResponse.SC_CONFLICT, re.getMessage());
            return;
        } finally {
            ungetSession(session);
        }
    }

    /** Returns the JCR repository used by this service. */
    @Override
    protected SlingRepository getRepository() {
        return repository;
    }

    /**
     * Returns an administrative session to the default workspace.
     */
    private Session getSession() throws RepositoryException {
        return getRepository().loginAdministrative(null);
    }

    /**
     * Return the administrative session and close it.
     */
    private void ungetSession(final Session session) {
        if (session != null) {
            try {
                session.logout();
            } catch (Throwable t) {
                LOGGER.error("Unable to log out of session: " + t.getMessage(), t);
            }
        }
    }

    // ---------- SCR integration ---------------------------------------------

    /**
     * Activates this component.
     *
     * @param componentContext
     *          The OSGi <code>ComponentContext</code> of this component.
     */
    @Override
    protected void activate(ComponentContext componentContext) {
        super.activate(componentContext);
        String groupList = (String) componentContext.getProperties().get(GROUP_AUTHORISED_TOCREATE);
        if (groupList != null) {
            authorizedGroups = StringUtils.split(groupList, ',');
        }
    }

    /**
     * {@inheritDoc}
     *
     * @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary)
     */
    @SuppressWarnings("rawtypes")
    public void updated(Dictionary dictionary) throws ConfigurationException {
        String groupList = (String) dictionary.get(GROUP_AUTHORISED_TOCREATE);
        if (groupList != null) {
            authorizedGroups = StringUtils.split(groupList, ',');
        }
    }

}