co.mitro.core.servlets.AddPendingGroupServlet.java Source code

Java tutorial

Introduction

Here is the source code for co.mitro.core.servlets.AddPendingGroupServlet.java

Source

/*******************************************************************************
 * Copyright (c) 2013, 2014 Lectorius, Inc.
 * Authors:
 * Vijay Pandurangan (vijayp@mitro.co)
 * Evan Jones (ej@mitro.co)
 * Adam Hilss (ahilss@mitro.co)
 *
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *     
 *     You can contact the authors at inbound@mitro.co.
 *******************************************************************************/
package co.mitro.core.servlets;

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

import javax.servlet.annotation.WebServlet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import co.mitro.core.accesscontrol.AuthenticatedDB;
import co.mitro.core.exceptions.MitroServletException;
import co.mitro.core.server.data.DBAcl;
import co.mitro.core.server.data.DBGroup;
import co.mitro.core.server.data.DBIdentity;
import co.mitro.core.server.data.DBPendingGroup;
import co.mitro.core.server.data.RPC.AddPendingGroupRequest;
import co.mitro.core.server.data.RPC.AddPendingGroupRequest.AdminInfo;
import co.mitro.core.server.data.RPC.AddPendingGroupRequest.MemberList;
import co.mitro.core.server.data.RPC.AddPendingGroupRequest.PendingGroup;
import co.mitro.core.server.data.RPC.AddPendingGroupResponse;
import co.mitro.core.server.data.RPC.GroupDiff;
import co.mitro.core.server.data.RPC.GroupDiff.GroupModificationType;
import co.mitro.core.server.data.RPC.MitroRPC;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.j256.ormlite.stmt.DeleteBuilder;
import com.j256.ormlite.stmt.SelectArg;

@WebServlet("/api/internal/AddPendingGroups")
public class AddPendingGroupServlet extends MitroServlet {
    private static final long serialVersionUID = -6743381126604749211L;
    private static final Logger logger = LoggerFactory.getLogger(AddPendingGroupServlet.class);
    private static final SecureRandom secureRng = new SecureRandom();

    @Override
    protected MitroRPC processCommand(MitroRequestContext context)
            throws IOException, SQLException, MitroServletException {
        AddPendingGroupRequest in = gson.fromJson(context.jsonRequest, AddPendingGroupRequest.class);

        /** This is generated once per push per scope. Used to prevent race conditions*/
        final String nonce = new BigInteger(128, secureRng).toString(32);

        assert (in.pendingGroups != null);
        assert (in.adminInfo != null);
        final AdminInfo adminInfo = in.adminInfo;
        assert (adminInfo.domainAdminEmail != null);
        DBIdentity domainAdmin = DBIdentity.getIdentityForUserName(context.manager, adminInfo.domainAdminEmail);
        if (domainAdmin == null) {
            throw new MitroServletException("unknown user:" + adminInfo.domainAdminEmail);
        }
        if (Strings.isNullOrEmpty(in.scope)) {
            throw new MitroServletException("scope must not be null or empty");
        }

        @SuppressWarnings("deprecation")
        AuthenticatedDB syncAdminDb = AuthenticatedDB.deprecatedNew(context.manager, domainAdmin);
        Set<DBGroup> orgs = syncAdminDb.getOrganizations();
        if (orgs.isEmpty()) {
            throw new MitroServletException("no org:" + adminInfo.domainAdminEmail);
        } else if (orgs.size() > 1) {
            throw new MitroServletException(
                    "user " + adminInfo.domainAdminEmail + " is an admin of multiple orgs. aborting");
        }
        DBGroup org = orgs.iterator().next();
        Map<String, DBGroup> existingGroups = Maps.newHashMap();
        Map<String, GroupDiff> diffs = Maps.newHashMap();
        Map<String, MemberList> pendingGroupMap = Maps.newHashMap();
        Map<String, PendingGroup> inMap = Maps.newHashMap();
        for (PendingGroup pg : in.pendingGroups) {
            inMap.put(pg.groupName, pg);
        }

        calculatePendingGroupDiffs(context, in.pendingGroups, org, existingGroups, diffs, pendingGroupMap,
                in.scope);

        // send email if there are no existing sync requests.
        boolean alreadySentEmail = (null != context.manager.pendingGroupDao.queryBuilder().where()
                .eq(DBPendingGroup.SCOPE_NAME, new SelectArg(in.scope)).queryForFirst());
        if (!(alreadySentEmail || diffs.isEmpty())) {
            // TODO SEND EMAIL;
        }

        // TODO: send email!
        DeleteBuilder<DBPendingGroup, Integer> deleter = context.manager.pendingGroupDao.deleteBuilder();
        deleter.where().eq(DBPendingGroup.SCOPE_NAME, in.scope);
        deleter.delete();

        if (!diffs.isEmpty()) {
            // if there are _any_ diffs, we have to add everything.
            for (String groupName : pendingGroupMap.keySet()) {
                PendingGroup pg = inMap.get(groupName);
                DBPendingGroup dbg = new DBPendingGroup(context.requestor, groupName, in.scope, pg.memberListJson,
                        pg.signature, org);
                dbg.setSyncNonce(nonce);
                context.manager.pendingGroupDao.create(dbg);
            }
        }

        AddPendingGroupResponse out = new AddPendingGroupResponse();
        out.syncNonce = nonce;
        out.diffs = diffs;
        return out;
    }

    /**
     * Too many inout parameters.
     * TODO: change this to use a parameter object
     * @param context in
     * @param pendingGroupsList in
     * @param org in
     * @param existingGroups in/out
     * @param diffs in/out
     * @param pendingGroupMap in/out
     * @return the scope
     */
    public static void calculatePendingGroupDiffs(MitroRequestContext context,
            Collection<? extends PendingGroup> pendingGroupsList, DBGroup org, Map<String, DBGroup> existingGroups,
            Map<String, GroupDiff> diffs, Map<String, MemberList> pendingGroupMap, String scope)
            throws MitroServletException, SQLException {
        assert scope != null || pendingGroupsList.isEmpty();
        for (PendingGroup pg : pendingGroupsList) {
            AddPendingGroupRequest.MemberList ml = gson.fromJson(pg.memberListJson,
                    AddPendingGroupRequest.MemberList.class);
            assert (null != ml) : "you cannot have a null MemberList";
            for (String user : ml.memberList) {
                assert Util.isEmailAddress(user) : "invalid email: '" + user + "'";
            }
            pendingGroupMap.put(pg.groupName, ml);
        }

        for (DBGroup g : org.getAllOrgGroups(context.manager)) {
            if (scope != null && scope.equals(g.getScope())) {
                existingGroups.put(g.getName(), g);
            }
        }

        Set<String> allGroupNames = Sets.union(pendingGroupMap.keySet(), existingGroups.keySet());
        for (String groupName : allGroupNames) {
            diffGroupPair(context, diffs, groupName, existingGroups.get(groupName), pendingGroupMap.get(groupName));
        }
    }

    private static void diffGroupPair(MitroRequestContext context, Map<String, GroupDiff> diffs, String groupName,
            DBGroup existing, MemberList pending) throws SQLException {
        GroupDiff gd = new GroupDiff();
        gd.groupName = groupName;
        Set<String> syncSourceMembers = null;
        Set<String> existingUsers = null;
        if (existing != null) {
            Set<Integer> userIds = Sets.newHashSet();
            existing.putDirectUsersIntoSet(userIds, DBAcl.modifyGroupSecretsAccess());
            // TODO: this will pull in users n*m times where n is number of users and m
            //       is number of groups the user is in.
            existingUsers = DBIdentity.getUserNamesFromIds(context.manager, userIds);
        }
        if (pending != null) {
            syncSourceMembers = Sets.newHashSet(pending.memberList);
        }
        assert (syncSourceMembers != null || existingUsers != null);
        if (syncSourceMembers == null) {
            gd.groupModification = GroupModificationType.IS_DELETED;
            syncSourceMembers = Collections.emptySet();
        } else if (existingUsers == null) {
            gd.groupModification = GroupModificationType.IS_NEW;
            existingUsers = Collections.emptySet();
        }
        gd.deletedUsers = Lists.newArrayList(Sets.difference(existingUsers, syncSourceMembers));
        gd.newUsers = Lists.newArrayList(Sets.difference(syncSourceMembers, existingUsers));
        if (gd.groupModification.equals(GroupModificationType.IS_UNCHANGED)
                && !(gd.deletedUsers.isEmpty() && gd.newUsers.isEmpty())) {
            gd.groupModification = GroupModificationType.MEMBERSHIP_MODIFIED;
        }
        if (gd.isDifferent()) {
            // TODO: move this outside to make sure this doesn't have so many mutable parameters.
            diffs.put(groupName, gd);
        }
    }
}