io.macgyver.core.auth.UserManager.java Source code

Java tutorial

Introduction

Here is the source code for io.macgyver.core.auth.UserManager.java

Source

/**
 * Licensed 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 io.macgyver.core.auth;

import io.macgyver.neorx.rest.NeoRxClient;
import io.macgyver.neorx.rest.NeoRxFunctions;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.lambdaworks.crypto.SCryptUtil;

public class UserManager {

    Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    NeoRxClient neo4j;

    public Optional<User> getInternalUser(final String id) {

        String q = "match (u:User) where u.username={username} return u.username, 'dummy' as dummy";

        JsonNode n = neo4j.execCypher(q, "username", id.toLowerCase()).toBlocking().firstOrDefault(null);
        if (n != null) {

            User u = new User();
            u.username = n.get("u.username").asText();

            u.roles = ImmutableList.copyOf(findRolesForUser(id.toLowerCase()));

            return Optional.of(u);
        }

        return Optional.absent();
    }

    public boolean authenticate(String username, String password) {
        try {
            String q = "match (u:User) where u.username={username} return u.scryptHash";
            ObjectNode n = new ObjectMapper().createObjectNode();
            n.put("username", username);
            JsonNode userNode = neo4j.execCypher(q, "username", username).toBlocking().firstOrDefault(null);
            if (userNode != null) {

                String hashValue = Strings.emptyToNull(userNode.asText());
                if (hashValue == null) {
                    return false;
                }
                try {
                    return SCryptUtil.check(password, Strings.nullToEmpty(hashValue));
                } catch (IllegalArgumentException e) {
                    // if the hash is invalid, we'll get an
                    // IllegalArgumentException
                    // This could happen if the hashed password was set to
                    // something to prevent login
                    // no need to log a whole stack trace for this
                    logger.info("auth error: " + e.toString());
                    return false;
                }

            } else {
                return false;
            }
        }

        catch (Exception e) {
            logger.warn("auth error", e);
            return false;
        }

    }

    public void setPassword(String username, String password) {

        String hash = SCryptUtil.scrypt(password, 4096, 8, 1);

        String c = "match (u:User) where u.username={username} set u.scryptHash={hash}";

        neo4j.execCypher(c, "username", username, "hash", hash);

    }

    public void setRoles(String username, List<String> roles) {

        for (String role : roles) {
            addRoleToUser(username, role);
        }

    }

    public User createUser(String username, List<String> roles) {

        if (getInternalUser(username).isPresent()) {
            throw new IllegalArgumentException("user already exists: " + username);
        }
        username = username.trim().toLowerCase();

        String cypher = "create (u:User {username:{username}}) return u";
        neo4j.execCypher(cypher, "username", username);

        setRoles(username, roles);
        User u = new User();
        u.username = username;
        u.roles = Lists.newArrayList();

        return u;

    }

    @PostConstruct
    public void initializeGraphDatabase() {
        try {

            String cipher = "CREATE CONSTRAINT ON (u:User) ASSERT u.username IS UNIQUE";
            neo4j.execCypher(cipher);

        } catch (Exception e) {
            logger.warn(e.toString());
        }

        try {

            String cipher = "CREATE CONSTRAINT ON (r:Role) ASSERT r.name IS UNIQUE";
            neo4j.execCypher(cipher);

        } catch (Exception e) {
            logger.warn(e.toString());
        }

        if (neo4j.checkConnection()) {
            seedRoles();
            Optional<User> admin = getInternalUser("admin");
            if (admin.isPresent()) {
                logger.debug("admin user already exists");
            } else {
                logger.info("adding admin user");
                List<String> roleList = Lists.newArrayList();

                createUser("admin", roleList);

                addUserToGroup("MACGYVER_ADMIN", "admin");
                setPassword("admin", "admin");

            }

            migrateRolesForUser("admin");
        }

    }

    public Collection<String> findRolesForUser(String username) {

        HashSet<String> roles = new HashSet<>();

        // First grab all the users with roles directly attached to them
        neo4j.execCypher(
                "match (u:User{username:{username}})-[:HAS_ROLE]-(r:Role) return distinct r.name as role_name",
                "username", username).flatMap(NeoRxFunctions.jsonNodeToString()).forEach(role -> roles.add(role));

        // Now grab roles that are indirectly attached via groups
        neo4j.execCypher(
                "match (u:User {username:{username}})-[:HAS_MEMBER]-(g:Group)-[]-(r:Role) return r.name as role_name",
                "username", username).flatMap(NeoRxFunctions.jsonNodeToString()).forEach(role -> roles.add(role));

        // Add the groups themselves as well
        neo4j.execCypher("match (u:User {username:{username}})-[:HAS_MEMBER]-(g:Group) return g.name as group_name",
                "username", username).flatMap(NeoRxFunctions.jsonNodeToString())
                .forEach(role -> roles.add(InternalGroupRoleTranslator.normalizeUpperCase("GROUP_" + role)));
        // Note that this will NOT include roles for externally managed groups
        return roles;

    }

    public Collection<String> findRolesForGroup(String group) {

        return neo4j
                .execCypher("match (g:Group{name:{name}})-[:HAS_ROLE]-(r:Role) return distinct r.name as role_name",
                        "name", group)
                .flatMap(NeoRxFunctions.jsonNodeToString()).toList().toBlocking().first();

    }

    public void seedRoles() {
        addRole(MacGyverRole.ROLE_MACGYVER_ADMIN.toString(), "MacGyver Administrator");
        addRole(MacGyverRole.ROLE_MACGYVER_USER.toString(), "MacGyver User");
        addRole(MacGyverRole.ROLE_MACGYVER_SHELL.toString(), "MacGyver Shell Access");
        addRole(MacGyverRole.ROLE_NEO4J_READ.toString(), "Read Access to Neo4j Console");
        addRole(MacGyverRole.ROLE_NEO4J_WRITE.toString(), "Read-Write Access Neo4j Console");

        addGroup("MACGYVER_ADMIN", "Macgyver Admin");

        addRoleToGroup("MACGYVER_ADMIN", MacGyverRole.ROLE_MACGYVER_ADMIN.toString());
        addRoleToGroup("MACGYVER_ADMIN", MacGyverRole.ROLE_MACGYVER_SHELL.toString());
        addRoleToGroup("MACGYVER_ADMIN", MacGyverRole.ROLE_MACGYVER_USER.toString());
        addRoleToGroup("MACGYVER_ADMIN", MacGyverRole.ROLE_NEO4J_READ.toString());
        addRoleToGroup("MACGYVER_ADMIN", MacGyverRole.ROLE_NEO4J_WRITE.toString());

    }

    public void migrateRolesForUser(String username) {
        JsonNode n = neo4j.execCypher("match (u:User {username: {username}}) return u", "username", username)
                .toBlocking().first();

        for (JsonNode s : Lists.newArrayList(n.path("roles").iterator())) {
            String roleName = s.asText();
            logger.info("adding role={} to user={}", roleName, username);
            addRoleToUser(username, roleName);

        }

    }

    public void addGroup(String name, String description) {
        String cypher = "merge (g:Group {name:{name}}) ON CREATE SET g.description={description} return g";
        neo4j.execCypher(cypher, "name", name, "description", description);
    }

    public void addRole(String name, String description) {
        String cypher = "merge (r:Role {name:{name}}) ON CREATE SET r.description={description} return r";
        neo4j.execCypher(cypher, "name", name, "description", description);
    }

    public void addRoleToUser(String user, String role) {

        String cypher = "match (u:User {username:{username}}),(r:Role {name:{role}}) MERGE (u)-[x:HAS_ROLE]-(r) return u,r";

        neo4j.execCypher(cypher, "username", user, "role", role);
    }

    public void addRoleToGroup(String group, String role) {
        String cypher = "match (g:Group {name:{name}}),(r:Role {name:{role}}) MERGE (g)-[x:HAS_ROLE]-(r) return g,r";

        neo4j.execCypher(cypher, "name", group, "role", role);
    }

    public void addUserToGroup(String group, String username) {
        String cypher = "match (g:Group {name:{name}}),(u:User {username:{username}}) MERGE (g)-[x:HAS_MEMBER]-(u) return x";
        neo4j.execCypher(cypher, "username", username, "name", group);
    }
}