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:
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.
public interface IdentityType
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.
public interface User extends IdentityType
// Returns user id (sbryzak, os890…)
// Methods exposing set of basic most commonly used attributes.
void setFirstName(String firstName);
void setEmail(String email);
void setExpirationDate(Date expirationDate);
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<Group> getGroups(Role role);
Collection<Group> getGroups(String role);
boolean hasRole(Role role, Group group);
boolean hasRole(String role, String groupId);
boolean validatePassword(String password);
void updatePassword(String password);
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.
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"
//If parent group it refers to root ("/") in a group tree getParentGroup() returns null.
void createChildGroup(String name);
void removeChildGroup(Group group);
void removeChildGroup(String name);
// 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();
boolean hasRole(Role role, User user);
boolean hasRole(String role, String user);
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
boolean exists(User user, Group group);
boolean exists(String user, String groupId);
void add(User user, Group group);
void add(String user, String groupId);
Collection<User> getUsers(Group group);
Collection<User> getUsers(String groupId);
Collection<Group> getGroups(User user);
Collection<Group> getGroups(String user);
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
IdentityManager is an entry point type of interface for all related IDM operations.
public interface IdentityManager
User createUser(String name);
void removeUser(User user);
void removeUser(String name);
User getUser(String name);
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);
Role createRole(String name);
void removeRole(Role role);
void removeRole(String name);
Role getRole(String name);
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);
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.
Also my personal experience is that for simplicity it would be wise to use QueryAPI interfaces in the future to pass queries to SPI
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.
|Free forum by Nabble||Edit this page|