package com.example.auth.service; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.ReturnableEvaluator; import org.neo4j.graphdb.StopEvaluator; import org.neo4j.graphdb.TraversalPosition; import org.neo4j.graphdb.Traverser.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.security.authentication.dao.SaltSource; import org.springframework.security.authentication.encoding.PasswordEncoder; import org.springframework.security.authentication.encoding.ShaPasswordEncoder; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.GrantedAuthorityImpl; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; public class NeoBasedUserService implements UserDetailsService, AuthenticationService { @Autowired private PasswordEncoder passwordEncoder; @Autowired private SaltSource saltSource; private class FindGroupEvaluator implements ReturnableEvaluator { private String groupname; /** * Return all group nodes. */ public FindGroupEvaluator() { } /** * Return a specific group node. * * @param groupname * The name of the requested group */ public FindGroupEvaluator(String groupname) { this.groupname = groupname; } @Override public boolean isReturnableNode(TraversalPosition currentPos) { if (currentPos.lastRelationshipTraversed() == null) { return false; } boolean isGroup = currentPos.lastRelationshipTraversed().getType() .equals(AuthRelation.PART_OF); String groupname = (String) currentPos.currentNode().getProperty( GROUP_NAME, ""); if (this.groupname == null) { return isGroup; } else { return (isGroup && groupname.equals(this.groupname)); } } } private class FindUserEvaluator implements ReturnableEvaluator { private String username; /** * Return all users nodes. */ public FindUserEvaluator() { } /** * Return a specific user node. * * @param username * The name of the requested user */ public FindUserEvaluator(String username) { this.username = username; } @Override public boolean isReturnableNode(TraversalPosition currentPos) { if (currentPos.lastRelationshipTraversed() == null) { return false; } boolean isUser = currentPos.lastRelationshipTraversed().getType() .equals(AuthRelation.MEMBER_OF); String username = (String) currentPos.currentNode().getProperty( USER_NAME, ""); if (this.username == null) { return isUser; } else { return (isUser && username.equals(this.username)); } } } private class FindUserGroupsEvaluator implements ReturnableEvaluator { @Override public boolean isReturnableNode(TraversalPosition currentPos) { Relationship lastRel = currentPos.lastRelationshipTraversed(); if (currentPos.lastRelationshipTraversed() == null) { return false; } return (lastRel.getType().equals(AuthRelation.MEMBER_OF) || lastRel .getType().equals(AuthRelation.PART_OF)); } } public class UsernameAlreadyExistsException extends Exception { private static final long serialVersionUID = 360331512759728867L; public UsernameAlreadyExistsException(String username) { super("Username " + username + " already exists"); } } private GraphDatabaseService graphDb; private final static String GROUP_NAME = "group_name", USER_NAME = "user_name", PASSWORD = "password"; public NeoBasedUserService(GraphDatabaseService graphDb) { this.graphDb = graphDb; } @Override public void addGroup(String groupname) { addGroup(groupname, null); } @Override public void addGroup(String groupname, String parentGroupname) { Assert.hasText(groupname, "groupname must not be empty"); // check if the group already exists boolean groupExists = (findGroup(groupname) != null); if (!groupExists) { Node parent; if (parentGroupname != null) { parent = findGroup(parentGroupname); if (parent == null) { throw new IllegalArgumentException("Parent group " + parentGroupname + " not found"); } } else { parent = getAuthNode(); } Node group = graphDb.createNode(); group.setProperty(GROUP_NAME, groupname); group.createRelationshipTo(parent, AuthRelation.PART_OF); } } @Override public void addUser(String username, String password) throws UsernameAlreadyExistsException { addUser(username, password, null); } @Override public void addUser(String username, String password, String groupname) throws UsernameAlreadyExistsException { Node userNode = findUser(username); if (userNode != null) { throw new UsernameAlreadyExistsException(username); } else { Node parent; if (groupname != null) { parent = findGroup(groupname); if (parent == null) { throw new IllegalArgumentException("Group " + groupname + " not found"); } } else { parent = getAuthNode(); } userNode = graphDb.createNode(); userNode.setProperty(USER_NAME, username); // this works only if the salt actually uses the username UserDetails user = new User(username, password, true, true, true, true, new HashSet()); userNode.setProperty(PASSWORD, passwordEncoder.encodePassword( password, saltSource.getSalt(user))); userNode.createRelationshipTo(parent, AuthRelation.MEMBER_OF); } } @Override public void addUserToGroup(String username, String groupname) { Node user = findUser(username); for (Relationship rel : user.getRelationships(AuthRelation.MEMBER_OF, Direction.OUTGOING)) { if (rel.getOtherNode(user).getProperty(GROUP_NAME, "").equals( groupname)) { return; } } Node group = findGroup(groupname); user.createRelationshipTo(group, AuthRelation.MEMBER_OF); } private Node findGroup(String groupname) { Node auth = getAuthNode(); // check if the group already exists Iterator it = auth.traverse(Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, new FindGroupEvaluator(groupname), AuthRelation.PART_OF, Direction.INCOMING).iterator(); if (!it.hasNext()) { return null; } else { return it.next(); } } private Node findUser(String username) { Assert.hasText(username, "username must not be empty"); Node auth = getAuthNode(); // check if the group already exists Iterator it = auth.traverse(Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, new FindUserEvaluator(username), AuthRelation.PART_OF, Direction.INCOMING, AuthRelation.MEMBER_OF, Direction.INCOMING).iterator(); if (!it.hasNext()) { return null; } else { return it.next(); } } private Node getAuthNode() { Node reference = graphDb.getReferenceNode(); Relationship rel = reference.getSingleRelationship(AuthRelation.AUTH, Direction.OUTGOING); if (rel != null) { Node auth = rel.getEndNode(); return auth; } else { return null; } } @Transactional @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { Node userNode = findUser(username); if (userNode == null) { throw new UsernameNotFoundException("User " + username + " not found"); } // now get all groups for this user Iterator it = userNode.traverse(Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, new FindUserGroupsEvaluator(), AuthRelation.MEMBER_OF, Direction.OUTGOING, AuthRelation.PART_OF, Direction.OUTGOING).iterator(); Collection groups = new HashSet(); Node auth = getAuthNode(); while (it.hasNext()) { Node node = it.next(); if (!node.equals(auth)) { groups.add(new GrantedAuthorityImpl((String) node.getProperty( GROUP_NAME, ""))); } } UserDetails user = new User(username, (String) userNode.getProperty( PASSWORD, ""), true, true, true, true, groups); return user; } @Override public void removeUser(String username) throws UsernameNotFoundException { Collection users = getAuthNode().traverse(Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, new FindUserEvaluator(username), AuthRelation.MEMBER_OF, Direction.INCOMING, AuthRelation.PART_OF, Direction.INCOMING).getAllNodes(); if (users.size() == 0) { throw new UsernameNotFoundException("User " + username + " not found"); } Node user = users.iterator().next(); for (Relationship rel : user.getRelationships()) { rel.delete(); } user.delete(); } @Override public int removeUsers() { int usersRemoved = 0; for (Node user : getAuthNode().traverse(Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, new FindUserEvaluator(), AuthRelation.MEMBER_OF, Direction.INCOMING, AuthRelation.PART_OF, Direction.INCOMING)) { for (Relationship rel : user.getRelationships()) { rel.delete(); } user.delete(); usersRemoved++; } return usersRemoved; } @Override public void setUp() { // create the sub-index node if it does not exist if (getAuthNode() == null) { Node auth = graphDb.createNode(); graphDb.getReferenceNode().createRelationshipTo(auth, AuthRelation.AUTH); } } @Override public boolean userExists(String username) { return (findUser(username) != null); } }