My approach might not be ideal, but I find it working quite well. Thing what I did - I decided not to use dependency injection pass current user everywhere directly because that was getting too cumbersome and switched to static context. Problem with contexts - they are a bit difficult to manage.
This one is defined in my domain:
public static class UserContext{
private static Func<User> _getCurrentUser;
private static bool _initialized;
public static User Current{
get{
if(!_initialized)
throw new Exception("Can i haz getCurrentUser delegate?");
var user=_getCurrentUser();
return user??User.Anonymous;
}
}
public static void Initialize(Func<User> getCurrentUser){
_getCurrentUser=getCurrentUser;
_initialized=true;
}
}
Note that delegate is static - for whole app only one at a time. And I'm not 100% sure about it's life cycle, possible memory leaks or whatnot.
Client application is responsible to initialize context. My web application does that on every request:
public class UserContextTask:BootstrapperTask{
private readonly IUserSession _userSession;
public UserContextTask(IUserSession userSession){
Guard.AgainstNull(userSession);
_userSession=userSession;
}
public override TaskContinuation Execute(){
UserContext.Initialize(()=>_userSession.GetCurrentUser());
return TaskContinuation.Continue;
}
}
Using mvcextensions library to stream-line bootstrapping tasks. You can just subscribe for according events in global.asax for that.
In client side (web app), I implement application service named IUserSession:
public User GetCurrentUser(){
if(HttpContext.Current.User==null) return null;
var identity=HttpContext.Current.User.Identity;
if(!identity.IsAuthenticated) return null;
var user=_repository.ByUserName(identity.Name);
if(user==null) throw new Exception("User not found. It should be. Looks bad.");
return user;
}
There is some more lame code necessary in order to use forms auth with roles w/o membership provider and role provider. But that's not the point of this question.
At domain level - I'm explicitly describing permissions that users might have like this one:
public class AcceptApplications:IUserRights{
public bool IsSatisfiedBy(User u){
return u.IsInAnyRole(Role.JTS,Role.Secretary);
}
public void CheckRightsFor(User u){
if(!IsSatisfiedBy(u)) throw new ApplicationException
("User is not authorized to accept applications.");
}
}
Cool thing is - those permissions can be made more sophisticated. E.g.:
public class FillQualityAssessment:IUserRights{
private readonly Application _application;
public FillQualityAssessment(Application application){
Guard.AgainstNull(application,
"User rights check failed. Application not specified.");
_application=application;
}
public bool IsSatisfiedBy(User u){
return u.IsInRole(Role.Assessor)&&_application.Assessors.Contains(u);
}
public void CheckRightsFor(User u){
if(!IsSatisfiedBy(u))
throw new ApplicationException
("User is not authorized to fill quality assessment.");
}
}
Permissions can be checked vica versa too - User has these fellas:
public virtual bool HasRightsTo<T>(T authorizationSpec) where T:IUserRights{
return authorizationSpec.IsSatisfiedBy(this);
}
public virtual void CheckRightsFor<T>(T authorizationSpec) where T:IUserRights{
authorizationSpec.CheckRightsFor(this);
}
Here's my aggregate root base class:
public class Root:Entity,IRoot{
public virtual void Authorize(IUserRights rights){
UserContext.Current.CheckRightsFor(rights);
}
}
And here's how I check permissions:
public class Application{
public virtual void Accept(){
Authorize(new AcceptApplications());
OpeningStatus=OpeningStatus.Accepted;
}
}
I hope that helps...