If you were trying to make it transparent then it would be more like
// This would return the user ID that the current user is running as
(whether it's themselves or a "run as" user)
String userId = ((UserPrincipal)subject.getPrincipal()).getUserId();
In your logging code - if you wanted to log the actual user ID (not
the "run as" user ID) you would do:
String authenticatedUserId =
((UserPrincipal)subject.getPrincipal()).getAuthenticatedUserId();
I'd compare it to:
User user =
getUser( ((UserPrincipal)subject.getPrincipal()).getUserId() );
But actually in my code it just looks like this - so it really doesn't
matter much if there's a few more characters:
User user = userManager.getCurrentUser();
Plus, my principal already has more than one ID encapsulated in it.
On Dec 15, 2008, at 2:29 PM, Les Hazlewood wrote:
On Mon, Dec 15, 2008 at 2:18 PM, Jeremy Haile <[email protected]>
wrote:
Well, I think you could structure it so the programmers making calls
on the subject don't have to know and still follow the approach
where everything is encapsulated in the principal. Just depends on
how your principal encapsulates the information.
I like the Principal approach because it seems simpler, more
transparent (i.e. no proxy classes pulling data from the session,
etc.), and requires less overriding/etc to accomplish since you just
implement the logic in your realm and return a principal that
encapsulates any necessary information.
So how do you deal with user-specific functionality pertaining to
the logged in user? Do you do something like this?
UserIdentity identity = (UserIdentity)Subject.getPrincipal();
Long userId;
if ( identity.isAssumed() ) {
userId = identity.getAssumedId();
} else {
userId = identity.getId();
}
User user = getUser(userId);
System.out.println( "Welcome " + user.getGivenName() + "!" );
Compare that to this:
User user = getUser( (Long)Subject.getPrincipal() );
System.out.println( "Welcome " + user.getGivenName() + "!" );
Thanks for any clarity - I'm still trying to understand your
approach...
I too like hearing about other approaches to this problem though.
On Dec 15, 2008, at 1:53 PM, Les Hazlewood wrote:
But this approach requires the GUI programmer (or the one making
calls on the subject) to 'know' about this 'run as' check,
everywhere in the application, right? Doesn't that sound a little
invasive as to opposed to just calling Subject.getPrincipal() in
all locations?
I surface this only for clarity - I'm not shooting down any
approaches. I just haven't seen a way to 1) offer transparent 'run
as' functionality while 2) still retaining accurate traceability.
My solution achieves both points, but I'm very open to any
suggestions on how else it may be done - if there is a cleaner way,
etc. If there's anything else up your sleeves, I'm all ears ;)
On Mon, Dec 15, 2008 at 11:14 AM, Jeremy Haile <[email protected]>
wrote:
Another "clean" way to do this would be to do it similar to my
approach, but actually have the principal object have the assumed
identity AND actual identity. In most cases these would be the
same, but in a "super user" situation, they could be different.
Most (non-auditing) code that needs to determine who the current
user is would always retrieve the principal and use the "assumed
identity". Any auditing or event logging code could then log both
of these fields by examining the principal.
On Dec 15, 2008, at 10:32 AM, Les Hazlewood wrote:
This is pretty cool - I like seeing how people come up with their
respective solutions. It is interesting to see others' thought
process for solving these things.
I do this functionality as well in my applications, but I humbly
offer my solution as a little more elegant and 'traceable' from a
security perspective.
The purpose of 'run as' functionality is almost always to see
things exactly as that particular user would see things, so you
can verify their experience, or to perform logic as that
individual. But it is still important in secure applications
(IMHO) to never lose track of who is actually performing the
button clicks, especially if they've assumed someone else's
identity.
And if it ever comes to government or financial related
applications or any application that has to adhere to government
regulations or oversight, you always want to be able to show any
government official/reviewer, "Although it appears User X did
this, it was _really_ User Y - don't blame User X".
Here's how I solve this problem:
A user logs in with their own username and password always.
They perform some sort of user search, which then shows a
paginated results page.
If the current user has the "assumeIdentity" permission, only then
is an additional 'assume identity' link available on each line-
item that is shown in the results page. If they don't have this
permission, they never see the link and thus can't click on it.
The current user (if permitted) clicks the 'assume identity' link,
which sends a request to the server and then adds the target
user's identity (in our system a Long primary key of the assumed
User) to the current user's session. Our session table in the
database has a 'assumed_identity' column that is a foreign key to
the user's table.
We also have event tracking in place, where any operation deemed
as noteworthy is logged and has a foreign key back to the sessions
table.
Therefore, we can always tell for *every* action (logged event)
which session it was attributed to. Then, if the entry in the
sessions table has an assumed identity, then we know that the
event was really attributed to the 'owning' or original user, not
the user of the assumed identity.
In our application, the Subject.getPrincipal() method returns the
user's long ID primary key. We added a little Subject wrapper/
proxy around the existing Subject implementation that first checks
the session, and if there is an assumed identity, returns that.
If there is no assumed identity, it returns the id of the user
that actually authenticated as normal.
This is to ensure that all application functionality built around
the Subject.getPrincipal code still returns the expected data so
further information can be looked up (user name, first name,
etc). Works perfectly if there is an assumed identity or not, but
the session always 'remember's who the 'real' user is executing
the logic for traceability purposes.
I hope that gives some insight how this works :) I've always
wanted to add this in a clean way to JSecurity. Maybe it should
be a 1.0 feature...
On Mon, Dec 15, 2008 at 9:18 AM, Jeremy Haile <[email protected]>
wrote:
Animesh,
You can definitely support super user authentication with
JSecurity - we do this in our application. The way we do it is by
having our realm accept multiple types of tokens - a regular
UsernamePasswordToken and also a SuperUserToken. The
SuperUserToken contains an additional field called "runAsUser".
(in other words, it has a username, password, and a "run as
username" property)
The realm will then authenticate the normal username and password,
but return back the principal of the "run as user". Since our
realm extends from AuthorizingRealm, it simply returns an instance
of SimpleAuthenticationInfo that contains the principal of the
"run as user" but the credentials of the user who is authenticating.
Since this is potentially a very dangerous feature, we only enable
it for accounts that have the admin flag set on them and ensure
that the password for this account is very secure, limited, and
changed on a regular basis. This functionality is also only
available from a secret URL that we don't link to in any way.
Here's an excerpt code snippet from our codebase:
User user = userManager.getActiveUserByEmail( organizationId,
token.getUsername());
if( user == null ) {
throw new UnknownAccountException( "No user account found for
[" + token.getUsername() + "] for org ID [" + organizationId +
"]" );
}
if( token instanceof SuperUserToken ) {
if( !user.isAdmin() ) {
final String message = "Attempt to login as
superuser by non-admin account: [" + token.getUsername() + "]";
log.error(message);
throw new UnauthorizedException( message );
}
Contact runAsContact =
contactManager
.getContactByEmail( ((SuperUserToken)token).getRunAsEmail() );
if( runAsContact == null ) {
throw new UnknownAccountException( "No user found
with email [" + ((SuperUserToken)token).getRunAsEmail() + "]" );
}
UserPrincipal runAsPrincipal = new
UserPrincipal(runAsContact.getUser().getId(), runAsContact.getId());
return new SimpleAuthenticationInfo( runAsPrincipal,
user.getEncryptedPassword(), getName() );
} else {
// Do regular authentication here...
}
Let me know if you have any questions or problems with this
approach.
Jeremy Haile
On Dec 15, 2008, at 4:57 AM, Animesh Jain wrote:
Hi
Is there some way to create a super user sort of entity which can
authenticate itself as any subject it wants. It probably is not
desired to have such functionality but I'm wondering if there's
some way to achieve that if needed.
Kind regards
Animesh