[DISCUSS] DELTASPIKE-79 Identity Management

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[DISCUSS] DELTASPIKE-79 Identity Management

Boleslaw Dawidowicz
As we are starting discussion around Identity Model for Authorization API I would like to also open one around Identity Management APIs. I was working on some prototype based on previous experience with PicketLink IDM API. Here is the code that is a reference to what I'm proposing in this email. It may be easier to follow code on github then the one pasted in the email:

https://github.com/bdaw/DeltaSpikeMirror/tree/fc7752409f289b6dfbd577292b70a786a4fb62f4/deltaspike/modules/security/api/src/main/java/org/apache/deltaspike/security/api/idm

https://github.com/bdaw/DeltaSpikeMirror/tree/fc7752409f289b6dfbd577292b70a786a4fb62f4/deltaspike/modules/security/api/src/main/java/org/apache/deltaspike/security/spi/idm

It contains also Query API and basic SPI which will be discussed separately but I'm sharing everything to show consistent vision behind proposed design.

There are 6 Key interfaces. IdentityType, User, Group, Role, Membership and IdentityManager.


1)
public interface IdentityType
{
  String getKey();

  void setAttribute(String name, String value);

  void setAttribute(String name, String[] values);

  void removeAttribute(String name);

  String getAttribute(String name);

  String[] getAttributeValues(String name);

  Map<String, String[]> getAttributes();
}

Base interface that User, Group and Role interface then extend. The getKey() method returns unique identifier.



2)
public interface User extends IdentityType
{
 
 // Returns user id (sbryzak, os890…)
 String getId();

 // Methods exposing set of basic most commonly used attributes.
 String getFirstName();

 void setFirstName(String firstName);

 String getLastName();

 void setLastName();

 String getFullName();

 String getEmail();

 void setEmail(String email);

 boolean isEnabled();

 void enable();

 void disable();

 Date getExpirationDate();

 void setExpirationDate(Date expirationDate);

 Date getCreationDate();

 // Roles
 void addRole(Role role, Group group);

 void addRole(String role, String groupId);

 Collection<Role> getRoles(Group group);

 Collection<Role> getRoles(String groupId);

 Map<Role, Set<Group>> getMembershipsMap();

 //?? Map<Group, Set<Role>> getMembershipMap() <-- or both?

 Collection<Membership> getMemberships();

 Collection<Group> getGroups(Role role);

 Collection<Group> getGroups(String role);

 boolean hasRole(Role role, Group group);

 boolean hasRole(String role, String groupId);

// Authentication
 boolean validatePassword(String password);

 void updatePassword(String password);
}



3)
Groups are structured in a tree. They can be stored flat under root of the tree in simplest use case or should have specified parent group reference during creation. Major reason behind tree structure is proper balance between simplicity and flexibility. My experience is that flat groups are just not enough and most commonly used LDAP structures in organizations are 2 levels deep (think Active Directory storing groups for Windows Domain). On the other hand anything more complex - for example graph with a notion of group type - is just non necessary complication of API.  

public interface Group extends IdentityType
{

 Groups are stored in tree hierarchy and therefore ID represents a path. ID string always
 begins with "/" element that represents root of the tree
 Example: Valid IDs are "/acme/departments/marketing", "/security/administrator" or "/administrator".
 Where "acme", "departments", "marketing", "security" and "administrator" are group names.
 
 String getId();

 Group name is unique identifier in specific group tree branch. For example
 group with id "/acme/departments/marketing" will have name "marketing" and
 parent group of id "/acme/departments"
 
 String getName();

 //If parent group it refers to root ("/") in a group tree getParentGroup() returns null.
 
 Group getParentGroup();

 void createChildGroup(String name);

 void removeChildGroup(Group group);

 void removeChildGroup(String name);

 Collection<Group> getChildGroups();

 // Methods related to roles

 void addRole(Role role, User user);

 void addRole(String role, String user);

 void removeRole(Role role, User user);

 void removeRole(String role, String user);

 Collection<Role> getRoles(User user);

 Collection<Role> getRoles(String user);

 Collection<User> getUsersWithRole(Role role);

 Collection<User> getUsersWithRole(String role);

 Map<Role, Set<User>> getMembershipsMap();

 Collection<Membership> getMemberships();

 boolean hasRole(Role role, User user);

 boolean hasRole(String role, String user);

}




4)
Concept of Role is a contextual mapping between User and Group. For example if we have roles in our application like "member", "manager" and "administrator" we can define relationships such as:

- User *john* has Role *manager* in Group */acme/departments/itsec*
- User *john* has Role *manager* in Group */acme/departments/engineering*
- User *eva* has Role *administrator* in Group */acme/departments/itsec*
- User "stefan" has Role "member" in Group "/acme/departments/engineering"

public interface Role extends IdentityType
{
 String getName();

 boolean exists(User user, Group group);

 boolean exists(String user, String groupId);

 void add(User user, Group group);

 void add(String user, String groupId);

 // Users
 Collection<User> getUsers(Group group);

 Collection<User> getUsers(String groupId);


 // Groups
 Collection<Group> getGroups(User user);

 Collection<Group> getGroups(String user);
}



5)
Membership is just a helper interface to let easier perform Role related queries. It is not used create/update/remove type of operations - just to easily retrieve combination of User/Role/Group. It expose more sense together with Query API so may be discussed later.

public interface Membership
{
 User getUser();

 Group getGroup();

 Role getRole();
}



6)
IdentityManager is an entry point type of interface for all related IDM operations.

public interface IdentityManager
{
   
 // User
 User createUser(String name);

 void removeUser(User user);

 void removeUser(String name);

 User getUser(String name);

 Collection<User> getAllUsers();

 // Group
 Group createGroup(String id);

 Group createGroup(String id, Group parent);

 Group createGroup(String id, String parent);

 void removeGroup(Group group);

 void removeGroup(String groupId);

 Group getGroup(String groupId);

 Group getGroup(String groupId, Group parent);

 Collection<Group> getAllGroups();

 // Roles

 Role createRole(String name);

 void removeRole(Role role);

 void removeRole(String name);

 Role getRole(String name);

 Collection<Role> getAllRoles();

 Collection<Role> getRoles(User user, Group group);

 Collection<Role> getRoles(String user, String groupId);

 boolean hasRole(Role role, User user, Group group);

 boolean hasRole(String role, String user, String groupId);

 // Queries
 UserQuery createUserQuery();

 GroupQuery createGroupQuery();

 RoleQuery createRoleQuery();

 MembershipQuery createMembershipQuery();

}



7)
I know that Query API and SPI is out of scope of current stage but I left references to those to give wider context of the design as it is important to keep consistency. As you can see there is no notion of more flexible getter methods, filters or pagination. Main decision here is to keep IdentityManager, User, Role and Group interfaces simple as short as possible. Then most of methods like "getAllUsers()" are performance nightmares when invoked in pair with huge identity store. All pagination, sorting and filtering capabilities are left to be covered in QueryAPI.

https://github.com/bdaw/DeltaSpikeMirror/blob/fc7752409f289b6dfbd577292b70a786a4fb62f4/deltaspike/modules/security/api/src/main/java/org/apache/deltaspike/security/api/idm/GroupQuery.java
https://github.com/bdaw/DeltaSpikeMirror/blob/fc7752409f289b6dfbd577292b70a786a4fb62f4/deltaspike/modules/security/api/src/main/java/org/apache/deltaspike/security/api/idm/MembershipQuery.java
https://github.com/bdaw/DeltaSpikeMirror/blob/fc7752409f289b6dfbd577292b70a786a4fb62f4/deltaspike/modules/security/api/src/main/java/org/apache/deltaspike/security/api/idm/RoleQuery.java
https://github.com/bdaw/DeltaSpikeMirror/blob/fc7752409f289b6dfbd577292b70a786a4fb62f4/deltaspike/modules/security/api/src/main/java/org/apache/deltaspike/security/api/idm/UserQuery.java


Also my personal experience is that for simplicity it would be wise to use QueryAPI interfaces in the future to pass queries to SPI

https://github.com/bdaw/DeltaSpikeMirror/blob/fc7752409f289b6dfbd577292b70a786a4fb62f4/deltaspike/modules/security/api/src/main/java/org/apache/deltaspike/security/spi/idm/IdentityStore.java
 
I'm mentioning only to cover questions about missing methods in interfaces proposed above and as it quite hard to fully decouple those parts during API design.

Bolek