I like the idea of a simplified user API as Bolek suggests, so something like this:
Getting a reference to the manager:
@Inject IdentityManager identityManager;
Creating users/groups etc:
Identity user = identityManager.createUser("shane");
Role admin = identityManager.createRole("admin");
Identity group = identityManager.createGroup("Head Office");
This API takes all the guesswork away, and IMHO is very intuitive for a new user.
I would actually go a little further with the role management, supporting both application roles and group roles.
Role admin = identityManager.createRole("admin");
Group headOffice = identityManager.createGroup("Head Office");
Role officeManager = identityManager.createRole("officeManager");
Or perhaps we could provide an overloaded version of addRole instead:
user.addRole(admin); // grants the user the "admin" application role
user.addRole(officeManager, headOffice); // grants the user the "officeManager" role in the "Head Office" group
The roles could then be checked like so:
boolean isAdmin = identityManager.hasRole("shane", "admin");
boolean isHeadOfficeManager = identityManager.hasRole("shane", "officeManager", "Head Office");
Shane I like the idea to have something intuitive - with User/Group/Role instead of Identity. What Anil proposed is very good for detyped model and could be used as some sort of SPI layer - it is however a matter of discussion if we need one. It has great value but also adds significant cost and complexity - will try to share more of my experience in this matter from current IDM component design. I would definitelly keep it in the lower layer.
I also like the idea about application and groups roles. If I understand it correctly one would a simpler notion usefull in app context while group role could map place of a user within organization. Together with group structure - I vote for simple group tree - it would be quite powerful. We could actually discuss doing a mapping between those two so having application roles auto resolved based on group roles.
I'm writing down my thoughts right now in a separte doc and will share soon something more. What I would like to point out is to keep performance in mind while maintaining simplicity. Lets think at last 1M users, 50k groups and quite nice number of relationships (roles) in underlaying storage. So please no .getUsers()/getAllUsers() for the sake of easy OOME We'll also need something nice and intuitive for paginated queries.
The minute you think of a large data set and paginated queries, you have brought in the complexity back into the API.
You should assume that there will be two sets of users- one with simple use cases and the other with complex scenarios.
Ideally the simple api is for the former and the current existing api can be for the latter.
I don't agree. Complexity is something slightly different. I'm the first one to admit that current API is too complex and now can easily throw few ideas about what to remove to easily cleanup 1/3 of methods and not loose that much in terms of capabilities. And I actually did some stats recently - 4 base interfaces contain total of 144 methods not including separate query API - so there is a lot to cleanup. We all probably agree that portal has fairly complex identity use case but I can assure you that it will be able to rely on simplified API that so far I can see is shaping in this discussion
If you plug in any LDAP server with existing company structure you will easily end up with several "kilos" of users at least. In case of new system when number of entries in DB grows over time this means that framework will stop to perform over time. Not desired situation.
However actually instead of discussing corner numbers lets discuss use cases.
What is the use case of such method? It seems tempting but even with fairly low number of entries I don't see any serious one.
In case of even simple management UI you want to show users in nice paginated table 20 entries per page for ex. What matters more is to have them sorted. getAllUsers + sort by attribute starts to be inefficient fairly quickly
In case of typical application request you rather deal with single user. Still in case of typical security check instead of
it is better to do
or some similar dedicated check.
In case of data migration you want to operate on entries in smaller portions surrounded by separate transaction or flush session in between. That way ORM doesn't start crazy calculations around auto flush or commit and you have more control on the state of operation. So paged results again.
2) getAllGroups(), getRoles() and etc.
It is the same case for management UI - paginated and sorted table.
I agree that there are more cases for security checks where such call seems natural but I think that for most of them it is possible to find simple and more efficient alternatives.
Just to be clear - I don't really want to focus on fighting with such type of methods in the API. I'm just for finding efficient alternatives that will cover most of natural usecases where those could be used. And I strongly believe that even for simple use cases those are rather needed alternatives.
Getting back to concern about API complexity. I think the key part is to not try to invent object model that can map possibly everything - like I wrote before I think User, Role and tree of Group etities is fairly good compromise. The second part is to not end up with insane number of all possible query types in the API but to choose the good 20/80 proportion.
I think I mentioned already that portal is using tiny fraction of current PLIDM API... I will be the last one here who wants to repeat the same mistake
Few quick thoughts around pagination - taking the sort problem aside for now
I think there are 3 basic ways to approach the problem
Range range = Range.of(0,10)
Collection results = user.getGroups(role, range);
results = user.getGroups(role,range.next());
results = user.getGroups(role, Range.of(50,100));
This is the way to explicitly pass the range. Good thing is that it is stateless - so think about REST use case or passing navigation param - back button would work
PagedResult page = user.getGroups(role);
The only issue with this approach is that it is stateful and imlementation of PagedResult need to keep internal Range object and reference to some IDMService which can cause issues for serialization and clustering. However if it works with CDI and service proxy than it will work
Collection results = user.getGroups(role);
Where we return internal implementation of Collection which actually does the job of PagedResult from example above. Same concern about keeping reference to service.
Thing is if we take the most intuitive way of having user.getGroup(role) instead of service.getGroup(user, role) then we need have User DAO with reference to service injected by CDI anyway. I lack experience with CDI so how does it look like for serialization/clustering issues in practice?
I think that that smart iterable Collection is probably the most user intuitive approach however taking REST like use case into account explicit Range is very compeling. My take on that is that exposing Collection to users is a bit dangerous as easy to abuse with simple getAll() every time. Even when internal implementation will perform several sub queries. Range forces more careful coding. Therefore in the end I would vote against such approach.
Here's some brief feedback I had from Bolek's presentation - essentially I feel that we're on the same wavelength - I agree that we should remove the SPI Model and most of the SPI interfaces (excluding the IdentityStore). I also agree that the IdentityStore should be built on the API classes such as User, Role, etc. Here's some of the other things I'd do:
- Remove all of the *Manager classes, replace with a single class with intuitive, simple API. I wouldn't include "Session" in the name, as this term is very overloaded.
- Enhance the rules around role and group membership - support for activation dates, expiry dates, time of day or other rule-based activations. Also support for "hard-coded" identity attributes, such as enabled/disabled, identity expiry date, etc that actually have a real effect on the behaviour of the identity store.
- Base this on the CDI component model - as far as I'm aware AS7 is our only target container, and we should be eating our own dog food. I also suggest that we combine this effort with the PicketLink CDI integration that we'll be commencing shortly when we migrate Seam Security to the PicketLink umbrella - we can discuss this in greater detail later on.
- Support for mapping between OpenID users/attributes and PicketLink users/attributes, and linking between identites, ala Sourceforge.
- Differentiation between human and non-human identities
- More flexible identity stores, support the ability to generate salted password hashes and generally provide more integration points where the user can assert greater control over the identity store operations. Integration with the CDI event bus might be a good way to achieve this.
- Support for Bill's use case, where he might want to authenticate using a key value instead of a user/password combination. I'd like to see more details about the actual requirements for this to ensure we get it right.
That's all I can think of right now.
We are certainly on the same wavelenght. Nothing that I would strongly dissagree with. Maybe one thing to clarify is the point about CDI and AS7 - if we would limit target environment to AS7 and if CDI would be core dependency. While I agree that CDI is the way to go it could be good to have some flexitbility here.
Like I wrote elsewhere for portal the bare minimum is notion of role, tree structure for groups and more powerful but still simple and intuitive query api - pagination, sort or search by email.
I kickstarted some prototyping around API. Here are the results:
At this stage I just tried to put in one place everything we discussed here so far or what was proposed in code snippets. I had a bit hard time trying to play with "application role" and ended with adding separate Application entity.
A lot of needed areas are not covered yet but I put a lot of //TODO marks to keep track of ideas