org.sakaiproject.nakamura.user.lite.servlet.LiteCreateSakaiGroupServlet.java Source code

Java tutorial

Introduction

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

Source

/**
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF 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.lite.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 com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;

import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.servlets.HtmlResponse;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.servlets.post.Modification;
import org.apache.sling.servlets.post.ModificationType;
import org.apache.sling.servlets.post.SlingPostConstants;
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.lite.ClientPoolException;
import org.sakaiproject.nakamura.api.lite.Repository;
import org.sakaiproject.nakamura.api.lite.Session;
import org.sakaiproject.nakamura.api.lite.StorageClientException;
import org.sakaiproject.nakamura.api.lite.StorageClientUtils;
import org.sakaiproject.nakamura.api.lite.accesscontrol.AccessDeniedException;
import org.sakaiproject.nakamura.api.lite.authorizable.Authorizable;
import org.sakaiproject.nakamura.api.lite.authorizable.AuthorizableManager;
import org.sakaiproject.nakamura.api.lite.authorizable.Group;
import org.sakaiproject.nakamura.api.lite.authorizable.User;
import org.sakaiproject.nakamura.api.resource.RequestProperty;
import org.sakaiproject.nakamura.api.user.LiteAuthorizablePostProcessService;
import org.sakaiproject.nakamura.api.user.UserConstants;
import org.sakaiproject.nakamura.user.lite.resource.LiteAuthorizableResourceProvider;
import org.sakaiproject.nakamura.user.lite.resource.LiteNameSanitizer;
import org.sakaiproject.nakamura.util.osgi.EventUtils;
import org.sakaiproject.nakamura.util.parameters.ParameterMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;

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>
 *
 *
 */
@SlingServlet(resourceTypes = { "sparse/groups" }, methods = { "POST" }, selectors = { "create" })
@Properties(value = { @Property(name = "servlet.post.dateFormats", value = { "EEE MMM dd yyyy HH:mm:ss 'GMT'Z",
        "yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd", "dd.MM.yyyy HH:mm:ss",
        "dd.MM.yyyy" }) })
@ServiceDocumentation(name = "Create Group Servlet", okForVersion = "1.2", description = "Creates a new group. Maps on to nodes of resourceType sparse/groups like "
        + "/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", 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 LiteCreateSakaiGroupServlet extends LiteAbstractSakaiGroupPostServlet implements ManagedService {

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

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

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

    /**
     * Used to create the group.
     *
     */
    @Reference
    protected transient LiteAuthorizablePostProcessService postProcessorService;

    /**
     */
    @Property(value = "authenticated,everyone")
    public static final String GROUP_AUTHORISED_TOCREATE = "groups.authorized.tocreate";

    private Set<String> authorizedGroups = ImmutableSet.of();

    /*
     * (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
    protected void handleOperation(SlingHttpServletRequest request, HtmlResponse response,
            List<Modification> changes)
            throws AuthorizableExistsException, ClientPoolException, StorageClientException, AccessDeniedException {

        // KERN-432 dont allow anon users to access create group.
        if (User.ANON_USER.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 IllegalArgumentException("Group name was not submitted");
        }

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

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

        try {
            Session currentSession = StorageClientUtils
                    .adaptToSession(request.getResourceResolver().adaptTo(javax.jcr.Session.class));
            AuthorizableManager authorizableManager = currentSession.getAuthorizableManager();
            currentUser = (User) authorizableManager.findAuthorizable(currentSession.getUserId());
            if (currentUser.isAdmin()) {
                LOGGER.debug("User is an admin ");
                allowCreateGroup = true;
            } else {
                LOGGER.debug("Checking for membership of one of {} ", authorizedGroups);
                for (String groupName : currentUser.getPrincipals()) {
                    if (authorizedGroups.contains(groupName)) {
                        allowCreateGroup = true;
                        break;
                    }
                }
                // TODO: Implement Dynamic Group membership checks
            }
        } 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 {
            AuthorizableManager authorizableManager = session.getAuthorizableManager();
            Authorizable authorizable = authorizableManager.findAuthorizable(principalName);

            if (authorizable != null) {
                // principal already exists!
                response.setStatus(400, "A principal already exists with the requested name: " + principalName);
                return;
            } else {
                if (authorizableManager.createGroup(principalName, principalName, null)) {
                    Group group = (Group) authorizableManager.findAuthorizable(principalName);
                    String groupPath = LiteAuthorizableResourceProvider.SYSTEM_USER_MANAGER_GROUP_PREFIX
                            + group.getId();
                    Map<String, RequestProperty> reqProperties = collectContent(request, response, groupPath);

                    response.setPath(groupPath);
                    response.setLocation(groupPath);
                    response.setParentLocation(LiteAuthorizableResourceProvider.SYSTEM_USER_MANAGER_GROUP_PATH);
                    changes.add(Modification.onCreated(groupPath));

                    Map<String, Object> toSave = Maps.newLinkedHashMap();

                    // 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, toSave);

                    dumpToSave(toSave, "after write content");
                    // 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, session, group, changes, toSave);

                    dumpToSave(toSave, " after update group membership");

                    // 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, toSave);

                    dumpToSave(toSave, "before save");

                    saveAll(session, toSave);
                    try {
                        postProcessorService.process(group, session, ModificationType.CREATE,
                                ParameterMap.extractParameters(request));
                    } 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);
                        properties.put("path", 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);
                    }
                } else {
                    throw new AuthorizableExistsException("Failed to create group, already exists ");
                }
            }
        } finally {
            ungetSession(session);
        }
    }

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

    /**
     * Returns an administrative session to the default workspace.
     * @throws AccessDeniedException 
     * @throws StorageClientException 
     * @throws ClientPoolException 
     */
    private Session getSession() throws ClientPoolException, StorageClientException, AccessDeniedException {
        return getRepository().loginAdministrative();
    }

    /**
     * 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 = PropertiesUtil.toString(componentContext.getProperties().get(GROUP_AUTHORISED_TOCREATE),
                null);
        if (groupList != null) {
            authorizedGroups = ImmutableSet.copyOf(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 = ImmutableSet.copyOf(StringUtils.split(groupList, ','));
        }
    }

}