io.flowly.auth.manager.GroupManager.java Source code

Java tutorial

Introduction

Here is the source code for io.flowly.auth.manager.GroupManager.java

Source

/*
 * Copyright (c) 2015 The original author or authors.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Apache License v2.0 
 *  which accompanies this distribution.
 *
 *  The Apache License v2.0 is available at
 *  http://opensource.org/licenses/Apache-2.0
 *
 *  You may elect to redistribute this code under this license.
 */

package io.flowly.auth.manager;

import io.flowly.auth.graph.Schema;
import io.flowly.core.ObjectKeys;
import io.flowly.core.security.Group;
import io.flowly.core.security.Permission;
import io.flowly.core.security.User;
import io.vertx.core.Handler;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Defines CRUD operations on groups in flowly.
 *
 * @author <a>Uday Tatiraju</a>
 */
public class GroupManager extends BaseManager {
    private static final Logger logger = LoggerFactory.getLogger(GroupManager.class);

    public GroupManager(Graph graph) {
        super(graph);
    }

    /**
     * Create a group node in the auth graph.
     *
     * @param group JSON object representing a group's attributes, permissions, users and memberships.
     *              Ex: {
     *                  "groupId": "Group 1",
     *                  "usersToAdd": [
     *                      13,
     *                      14
     *                  ],
     *                  "groupsToAdd": [
     *                      12343,
     *                      34567
     *                  ],
     *                  "permissionsToAdd": [
     *                      {
     *                          "resourceVertexId": 555611
     *                          "rwx": 7
     *                      }
     *                  ]
     *              }
     * @return an empty list or a list of validation errors based on whether the group was created or not.
     */
    @Override
    public JsonArray create(JsonObject group) {
        Group newGroup = new Group(group);
        JsonArray errors = newGroup.validate();

        if (errors.size() == 0) {
            try {
                Vertex groupVertex = graph.addVertex(T.label, Schema.V_GROUP, Schema.V_P_GROUP_ID,
                        newGroup.getGroupId());
                setPropertyValue(groupVertex, Schema.V_P_DESCRIPTION, newGroup.getDescription());

                grantMemberships(groupVertex, true, group.getJsonArray(Group.USERS_TO_ADD), null);
                grantMemberships(groupVertex, false, group.getJsonArray(User.GROUPS_TO_ADD), null);
                grantPermissions(groupVertex, group.getJsonArray(Permission.PERMISSIONS_TO_ADD), null);

                commit();
            } catch (Exception ex) {
                rollback();
                String error = "Unable to create group: " + newGroup.getGroupId();
                logger.error(error, ex);
                errors.add(error);
            }
        }

        return errors;
    }

    /**
     * Update a group node in the auth graph.
     *
     * @param group JSON object representing a group's attributes, permissions, users and memberships.
     *              Ex: {
     *                  "id": 12345,
     *                  "description": "Represents flowly users.",
     *                  "usersToRemove": [
     *                      14
     *                  ],
     *                  "groupsToAdd": [
     *                      34567
     *                  ],
     *                  "groupsToRemove": [
     *                      12343
     *                  ],
     *                  "permissionsToRemove": [
     *                      555611
     *                  ]
     *              }
     * @return an empty list or a list of validation errors based on whether the group was updated or not.
     */
    @Override
    public JsonArray update(JsonObject group) {
        Group updatedGroup = new Group(group);
        JsonArray errors = updatedGroup.validate(true);

        if (errors.size() == 0) {
            try {
                Vertex groupVertex = getVertex(updatedGroup.getId());
                setGroupId(groupVertex, updatedGroup, true);
                setPropertyValue(groupVertex, Schema.V_P_DESCRIPTION, updatedGroup.getDescription());

                // Update group memberships.
                redoMemberships(groupVertex, false, updatedGroup.getJsonArray(User.GROUPS_TO_ADD),
                        updatedGroup.getJsonArray(User.GROUPS_TO_REMOVE));

                // Update group users.
                redoMemberships(groupVertex, true, updatedGroup.getJsonArray(Group.USERS_TO_ADD),
                        updatedGroup.getJsonArray(Group.USERS_TO_REMOVE));

                // Update permissions.
                redoPermissions(groupVertex, group);

                commit();
            } catch (IllegalArgumentException ex) {
                rollback();
                errors.add(ex.getMessage());
                logger.error(ex);
            } catch (Exception ex) {
                rollback();
                String error = "Unable to update group: " + updatedGroup.getGroupId();
                logger.error(error, ex);
                errors.add(error);
            }
        }

        return errors;
    }

    /**
     * Get the group based on the id.
     * Doesn't retrieve members, memberships and permissions.
     *
     * @param id unique id set by the auth graph.
     * @return JSON object representing a group.
     *         Ex: {
     *             "id": 12345,
     *             "groupId": "Group 1",
     *             "description": "Represents flowly users."
     *         }
     */
    @Override
    public JsonObject get(Long id) {
        try {
            Vertex groupVertex = getVertex(id);
            JsonObject group = makeGroupObject(groupVertex, null, false);
            commit();

            return group;
        } catch (Exception ex) {
            rollback();
            logger.error("Unable to retrieve group: " + id, ex);
            return null;
        }
    }

    /**
     * Get the group based on group Id.
     * Doesn't retrieve members, memberships and permissions.
     *
     * @param groupId the group id the uniquely identifies a group in the auth graph.
     * @return JSON object representing a group.
     *         Ex: {
     *             "id": 12345,
     *             "groupId": "Group 1",
     *             "description": "Represents flowly users."
     *         }
     */
    @Override
    public JsonObject get(String groupId) {
        return get(groupId, false, false, false, false, false);
    }

    /**
     * Get the group based on group Id.
     *
     * @param groupId the group id that uniquely identifies a group in the auth graph.
     * @param includeDirectMemberships indicates if the group's direct memberships are to be retrieved.
     * @param includeEffectiveMemberships indicates if all the group's memberships are to be retrieved.
     * @param includeDirectMembers indicates if the group's direct members are to be retrieved.
     * @param includeEffectiveUsers indicates if all the group's users are to be retrieved.
     * @param includeDirectPermissions indicates if the permissions directly granted to the group are to be retrieved.
     * @return JSON object representing a group and optional memberships and permissions.
     *         Ex: {
     *             "id": 12345,
     *             "groupId": "Group A",
     *             "description": "Represents flowly users."
     *             "directMemberships": [
     *                 {
     *                     "id": 12345,
     *                     "groupId": "Group 1",
     *                 }
     *             ],
     *             "effectiveMemberships": [
     *                 {
     *                     "id": 12345,
     *                     "groupId": "Group 1",
     *                 },
     *                 {
     *                     "id": 12350,
     *                     "groupId": "Group 2",
     *                 }
     *             ],
     *             "directMembers: [
     *                 {
     *                     "id": 1,
     *                     "userId": "aragorn",
     *                     "fullName": "Strider Wingfoot"
     *                 }
     *             ]
     *             "directPermissions": [
     *                 {
     *                     "resourceId": 555611
     *                     "rwx": 7
     *                 }
     *             ]
     *         }
     */
    public JsonObject get(String groupId, boolean includeDirectMemberships, boolean includeEffectiveMemberships,
            boolean includeDirectMembers, boolean includeEffectiveUsers, boolean includeDirectPermissions) {
        try {
            JsonObject group = null;
            GraphTraversal<Vertex, Vertex> traversal = graph.traversal().V().has(Schema.V_GROUP,
                    Schema.V_P_GROUP_ID, groupId);

            if (traversal.hasNext()) {
                Vertex groupVertex = traversal.next();
                group = makeGroupObject(groupVertex, null, false);
                getMembers(groupVertex, group, includeDirectMembers, includeEffectiveUsers);
                getMemberships(groupVertex, group, includeEffectiveMemberships, includeDirectMemberships, false);

                if (includeDirectPermissions) {
                    getDirectPermissions(groupVertex, group);
                }
            }

            commit();
            return group;
        } catch (Exception ex) {
            rollback();
            logger.error("Unable to retrieve group: " + groupId, ex);
            return null;
        }
    }

    @Override
    public Handler<Message<JsonObject>> getHandler() {
        return message -> {
            JsonObject args = message.body();
            message.reply(get(args.getString(Group.GROUP_ID), args.getBoolean("includeDirectMemberships"),
                    args.getBoolean("includeEffectiveMemberships"), args.getBoolean("includeDirectMembers"),
                    args.getBoolean("includeEffectiveUsers"), args.getBoolean("includeDirectPermissions")));
        };
    }

    /**
     * Search for groups based on provided criteria.
     * By default, groups are sorted by GROUP_ID in ascending order.
     *
     * @param pageNumber the page number used to retrieve groups.
     * @param pageSize the number of users that fill a page.
     * @return a list of groups along with their respective members and memberships.
     */
    @Override
    public JsonArray search(int pageNumber, int pageSize) {
        int low = (pageNumber - 1) * pageSize;
        int high = low + pageSize;

        try {
            List<Group> groups = graph.traversal().V().hasLabel(Schema.V_GROUP).order()
                    .by(Schema.V_P_GROUP_ID, Order.incr).range(low, high + ADDITIONAL_RECORDS).map(m -> {
                        Vertex groupVertex = m.get();
                        Group group = makeGroupObject(groupVertex, null, false);
                        getMembers(groupVertex, group, true, false);
                        getMemberships(groupVertex, group, false, true, false);

                        return group;
                    }).toList();

            commit();
            return new JsonArray(groups);
        } catch (Exception ex) {
            rollback();
            logger.error("Unable to search for groups.", ex);
            return null;
        }
    }

    @Override
    public JsonArray delete(Object id) {
        JsonArray errors;

        try {
            // Cannot delete administrators group.
            Vertex vertex = getVertex(id);
            if (!ObjectKeys.ADMIN_GROUP_ID.equalsIgnoreCase(getPropertyValue(vertex, Schema.V_P_GROUP_ID))) {
                errors = super.delete(id);
            } else {
                errors = new JsonArray().add("Cannot delete group: " + id);
            }

            commit();
        } catch (Exception ex) {
            rollback();
            errors = new JsonArray().add("Cannot delete group:" + id);
            logger.error(errors.getString(0), ex);
        }

        return errors;
    }

    private void getMembers(Vertex groupVertex, JsonObject group, boolean includeDirectMembers,
            boolean includeEffectiveUsers) {
        if (includeDirectMembers) {
            JsonArray directMembers = new JsonArray();
            group.put(Group.DIRECT_MEMBERS, directMembers);

            graph.traversal().V(groupVertex).outE(Schema.E_MEMBER).inV().sideEffect(m -> {
                Vertex vertex = m.get();
                if (vertex.label().equals(Schema.V_USER)) {
                    directMembers.add(makeUserObject(vertex));
                } else {
                    directMembers.add(makeGroupObject(vertex, null, false));
                }

            }).toList();
        }

        // Recursively retrieve all unique users.
        if (includeEffectiveUsers) {
            JsonArray allUsers = new JsonArray();
            group.put(Group.ALL_USERS, allUsers);

            getUsers(groupVertex, allUsers, new HashSet<>());
        }
    }

    private void getUsers(Vertex groupVertex, JsonArray users, Set<Long> userIds) {
        graph.traversal().V(groupVertex).repeat(__.outE(Schema.E_MEMBER).inV()).emit().sideEffect(m -> {
            Vertex vertex = m.get();

            if (vertex.label().equals(Schema.V_USER) && !userIds.contains(vertex.id())) {
                users.add(makeUserObject(vertex));
                userIds.add((Long) vertex.id());
            } else {
                getUsers(vertex, users, userIds);
            }

        }).toList();
    }

    private void setGroupId(Vertex groupVertex, Group group, boolean isUpdate) {
        if (isUpdate) {
            String oldGroupId = getPropertyValue(groupVertex, Schema.V_P_GROUP_ID);
            String newGroupId = group.getGroupId();

            if (oldGroupId.equalsIgnoreCase(ObjectKeys.ADMIN_GROUP_ID)) {
                if (!oldGroupId.equalsIgnoreCase(newGroupId)) {
                    throw new IllegalArgumentException("Cannot change the administrator group id.");
                }
            } else {
                setPropertyValue(groupVertex, Schema.V_P_GROUP_ID, group.getGroupId());
            }
        } else {
            setPropertyValue(groupVertex, Schema.V_P_GROUP_ID, group.getGroupId());
        }
    }
}