MVC 3 Not Recognizing Windows Role (Group)? - windows

Has anybody come across occurrences where MVC does not recognize roles from Windows? I thought that roles translated to groups in Windows, but for some reason when I add a user to a group, check in MVC (using windows authentication) if that user.IsInRole("GroupJustAddedTo") always returns false. I have no idea why....Working in Server 2003 R2 with Windows Authentication enabled around the board. Confused :~( ???

Without knowing anything else, it makes me wonder if perhaps your MVC is not really connected to your AD server. Also, perhaps the user that is fetching the groups doesn't have sufficient privileges? Just some initial thoughts.
EDIT
We ended up writing our own RoleProvider. Here is the overloaded GetRolesForUser code
public override string[] GetRolesForUser(string userName)
{
List<string> allRoles = new List<string>();
PrincipalContext context;
context = new PrincipalContext(ContextType.Domain, "hlpusd.k12.ca.us", "DC=hlpusd,DC=k12,DC=ca,DC=us");
UserPrincipal user = UserPrincipal.FindByIdentity(context, userName);
PrincipalSearchResult<Principal> usergroups = user.GetGroups(); // list of AD groups the user is member of
IEnumerator<Principal> eGroup = usergroups.GetEnumerator();
while (eGroup.MoveNext())
{
allRoles.Add(eGroup.Current.Name);
}
return allRoles.ToArray();
}

Related

AuthorizeAttribute does not work on ApiController inside Area

A few days ago, half of our Azure hosted ASP.NET Web API (.NET Framework) started playing up. It's been running fine for a number of months, then all of a sudden, customers couldn't log into one of our front end sites.
We have three applications that connect to the Web API. Two of them connect through the controllers in the top level Controllers folder, and one of them connects through the controllers inside an Area. It's this Area that is causing us pain.
public class MyAppAreaRegistration : AreaRegistration
{
public override string AreaName => "MyApp";
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute("MyAppApi", "myapp/api/{controller}/{action}");
}
}
Both parts of the Web API use the same code to generate Jwt Security Tokens, and unauthorized methods in both areas are okay.
private JwtSecurityToken GenerateSecurityToken(int userId, string username, int customerId)
{
var signingKey = GetSigningKey();
var audience = GetSiteUrl(); // Must match the url of the site
var issuer = GetSiteUrl(); // Must match the url of the site
var lifeTime = TimeSpan.FromHours(24);
var now = DateTime.Now;
var expiry = now.Add(lifeTime);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Prn, customerId.ToString()),
new Claim(JwtRegisteredClaimNames.Iat, now.Ticks.ToString()),
new Claim(JwtRegisteredClaimNames.Exp, expiry.Ticks.ToString())
};
var token = AppServiceLoginHandler.CreateToken(claims, signingKey, audience, issuer, lifeTime);
return token;
}
After much effort trying to work out what could have changed, we implemented a CustomAuthorizeAttribute and applied it to a controller inside the Area and a controller outside the Area. The one outside the Area appears to have the actionContext.ControllerContext.RequestContext.Principal.Identity set correctly, but the one inside the area is empty. Not null. Just empty. We logged both the actionContext.ControllerContext.RequestContext.Principal as well as the Thread.CurrentPrincipal, but both are empty.
I can't seem to work out what is responsible for managing this data, and why it now refuses to do it for controllers with the area.
I'm able to access the token and extract the claims successfully.
All help will be appreciated.
Cheers

How to add/manage user claims at runtime in IdentityServer4

I am trying to use IdentityServer4 in a new project. I have seen in the PluralSight video 'Understanding ASP.NET Core Security' that IdentityServer4 can be used with claims based security to secure a web API. I have setup my IdentityServer4 as a separate project/solution.
I have also seen that you can add an IProfileService to add custom claims to the token which is returned by IdentityServer4.
One plan is to add new claims to users to grant them access to different parts of the api. However I can't figure out how to manage the claims of the users on the IdentityServer from the api project. I assume I should be making calls to IdentotyServer4 to add and remove a users claims?
Additionally is this a good approach in general, as I'm not sure allowing clients to add claims to the IdentityServer for their own internal security purposes makes sense - and could cause conflicts (eg multiple clients using the 'role' claim with value 'admin'). Perhaps I should be handling the security locally inside the api project and then just using the 'sub' claim to look them up?
Does anyone have a good approach for this?
Thanks
Old question but still relevant. As leastprivilege said in the comments
claims are about identity - not permissions
This rings true, but identity can also entail what type of user it is (Admin, User, Manager, etc) which can be used to determine permissions in your API. Perhaps setting up user roles with specific permissions? Essentially you could also split up Roles between clients as well for more control if CLIENT1-Admin should not have same permissions as CLIENT2-Admin.
So pass your Roles as a claim in your IProfileService.
public class ProfileService : IProfileService
{
private readonly Services.IUserService _userService;
public ProfileService(Services.IUserService userService)
{
_userService = userService;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
switch (context.Client.ClientId)
{
//setup profile data for each different client
case "CLIENT1":
{
//sub is your userId.
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
//get the actual user object from the database
var user = await _userService.GetUserAsync(long.Parse(userId.Value));
// issue the claims for the user
if (user != null)
{
var claims = GetCLIENT1Claims(user);
//add the claims
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
}
break;
case "CLIENT2":
{
//...
}
}
}
catch (Exception ex)
{
//log your exceptions
}
}
// Gets all significant user claims that should be included
private static Claim[] GetCLIENT1Claims(User user)
{
var claims = new List<Claim>
{
new Claim("user_id", user.UserId.ToString() ?? ""),
new Claim(JwtClaimTypes.Name, user.Name),
new Claim(JwtClaimTypes.Email, user.Email ?? ""),
new Claim("some_other_claim", user.Some_Other_Info ?? "")
};
//----- THIS IS WHERE ROLES ARE ADDED ------
//user roles which are just string[] = { "CLIENT1-Admin", "CLIENT1-User", .. }
foreach (string role in user.Roles)
claims.Add(new Claim(JwtClaimTypes.Role, role));
return claims.ToArray();
}
}
Then add [Authorize] attribute to you controllers for your specific permissions. This only allow specific roles to access them, hence setting up your own permissions.
[Authorize(Roles = "CLIENT1-Admin, CLIENT2-Admin, ...")]
public class ValuesController : Controller
{
//...
}
These claims above can also be passed on authentication for example if you are using a ResourceOwner setup with custom ResourceOwnerPasswordValidator. You can just pass the claims the same way in the Validation method like so.
context.Result = new GrantValidationResult(
subject: user.UserId.ToString(),
authenticationMethod: "custom",
claims: GetClaims(user));
So like leastprivilege said, you dont want to use IdentityServer for setting up permissions and passing that as claims (like who can edit what record), as they are way too specific and clutter the token, however setting up Roles that -
grant them access to different parts of the api.
This is perfectly fine with User roles.
Hope this helps.

How to maintain session information across authentication

I using ServiceStack authentication with a custom session object. I've got everything set up with different authentication providers and everything is working fine.
Now a want to store some information in the session before the user is authenticated (Think shopping cart). But we loose that information when the user logs in later. Looking at the code in the documentation this makes sense:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new BasicAuthProvider(), //Sign-in with Basic Auth
new CredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}));
The authentication removes the existing session whenever a user logs in. This makes sense when the old login is a valid user, you want to make sure it's fully logged out. However when the current session isn't authenticated there doesn't seem to be much reason to do so.
I've been looking at a custom session factory, but that doesn't help me because as () => new AuthUserSession() shows, there isn't any context to use when creating the new session. Without a way to get the old session there I've got no way to copy any information.
I can work around it by overriding AuthProvider.Authenticate() and grab the required information before calling base. But that means doing so in every authentication provider we use and the ones we might use in the future. That doesn't really feel like the correct solution.
Is there a cleaner way to carry information across the authentication? Preferably something which works regardless of the AuthProvider used.
Whilst the Typed Sessions are re-created after authenticating, the Permanent and Temporary Session Ids themselves remain the same which lets you use ServiceStack's dynamic SessionBag to store information about a user which you can set in your Services with:
public class UnAuthInfo
{
public string CustomInfo { get; set; }
}
public class MyServices : Service
{
public object Any(Request request)
{
var unAuthInfo = SessionBag.Get<UnAuthInfo>(typeof(UnAuthInfo).Name)
?? new UnAuthInfo();
unAuthInfo.CustomInfo = request.CustomInfo;
SessionBag.Set(typeof(UnAuthInfo).Name, unAuthInfo);
}
}
You can then access the dynamic Session Bag in your Custom AuthUserSession Session Events with:
public class CustomUserSession : AuthUserSession
{
[DataMember]
public string CustomInfo { get; set; }
public override void OnAuthenticated(IServiceBase service, IAuthSession session,
IAuthTokens tokens, Dictionary<string, string> authInfo)
{
var sessionBag = new SessionFactory(service.GetCacheClient())
.GetOrCreateSession();
var unAuthInfo = sessionBag.Get<UnAuthInfo>(typeof(UnAuthInfo).Name);
if (unAuthInfo != null)
this.CustomInfo = unAuthInfo.CustomInfo;
}
}
New Session API's in v4.0.32+
Accessing the Session bag will be a little nicer in next v4.0.32+ of ServiceStack with the new GetSessionBag() and convenience ISession Get/Set extension methods which will let you rewrite the above like:
public object Any(Request request)
{
var unAuthInfo = SessionBag.Get<UnAuthInfo>() ?? new UnAuthInfo();
unAuthInfo.CustomInfo = request.CustomInfo;
SessionBag.Set(unAuthInfo);
}
//...
public override void OnAuthenticated(IServiceBase service, IAuthSession session,
IAuthTokens tokens, Dictionary<string, string> authInfo)
{
var unAuthInfo = service.GetSessionBag().Get<UnAuthInfo>();
if (unAuthInfo != null)
this.CustomInfo = unAuthInfo.CustomInfo;
}

Spring ACL adding ACE when the current user has no permission on the ACL

Short Question: When a new user signs up on my website I need to add a read permission to a domain object. Spring ACLs does a check using the current user's permissions to see if a permission can be added. This will always fail because the user has just signed up and has no permissions on the object that they need a read permission on. Is there a way to skip the security check in certain situations?
Long question: The website I'm working on has an invite system that is done using tokens and emails. User A creates an organization and becomes the owner of that organization. User A can then invite User B to the organization by email, an email will be sent telling them to signup, etc. When User B gets the invite they sign up and the token is looked up. This token has a relation to an organization and at this point I try to give the user a ReadPermission but I get the error:
"org.springframework.security.acls.model.NotFoundException: Unable to locate a matching ACE for passed permissions and SIDs"
Is there a way around this security check?
Or
How far into Spring Security do I need to go to change this setup?
Found the answer to this when reading spring docs. You can use a Runnable and DelegatingSecurityContextExecutor to run the required call as a different user.
User newUser = getCurrentUser();
User notOriginalUser = accountsService.findUserById(someId);
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new UsernamePasswordAuthenticationToken(notOriginalUser, "doesnotmatter", authoritiesList);
context.setAuthentication(authentication);
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor, context);
class PermissionRunnable implements Runnable {
String u;
Organization o;
PermissionRunnable(Organization org, String username) {
o = org;
u = username;
}
public void run() {
permissionsService.setReadableByPrincipal(o, new PrincipalSid(u));
}
}
Runnable originalRunnable = new PermissionRunnable(org, newUser.getUsername());
executor.execute(originalRunnable);

Google App Engine: Object with id “” is managed by a different Object Manager - Revisited

I'm getting the following error using GAE, JPA, and Spring
Object with id “” is managed by a different Object Manager
When I first create an account, I put the User object in the session. Then when I update the user profile during that initial session, I merge the detached User. All works great.
I then logout and later create a new session. This time, I load the User object and place into the session. Still OK, but problem is when I update the user profile, the merge fails with the above error.
public boolean loadProfile(String openId, String email) {
User user = null;
try {
user = userDao.findByOpenId(openId);
} catch (NoResultException e) {
}
if (user != null) {
logger.error(JDOHelper.getPersistenceManager(user));
getSessionBean().setUser(user);
return true;
} else {
user = createNewAccount(openId, email);
getSessionBean().setUser(user);
return false;
}
}
#Transactional(propagation=Propagation.REQUIRES_NEW)
private User createNewAccount(String openId, String email) {
User user = new User();
user.setDisplayName(Long.toString(System.currentTimeMillis()));
OpenIdentifier oid = new OpenIdentifier();
oid.setOpenId(openId);
oid.setEmail(email);
oid.setUser(user);
Set<OpenIdentifier> openIds = new HashSet<OpenIdentifier>();
openIds.add(oid);
user.setOpenIds(openIds);
user = userDao.merge(user);
return user;
}
#Transactional(propagation=Propagation.REQUIRED)
public void createOrUpdate(ActionEvent e) {
logger.error(JDOHelper.getPersistenceManager(userFacade.getDelegate()));
User user = userDao.merge(userFacade.getDelegate());
sessionBean.setUser(user);
}
I found these related questions, but I'm still not able to fix.
AppEngine datastore: "Object with id ... is managed by a different Object Manager"
Google App Engine - Object with id "" is managed by a different - JPA
Datanucleus: moving from #Transactional to non-transactional
http://www.atentia.net/2010/03/object-with-id-is-managed-by-a-different-object-manager/
WRT closing the PM (as per 1 & 2), I'm not able to explicitly close the PM since I'm using Spring
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter. From logs, it appears to be opening and closing on each page request.
WRT making the entity detachable (as per 3 & 4), first of all, I'm using JPA and it seems wrong to use a JDO-related annotation. Secondly, it didn't work when I tried.
For extra credit, how do you debug with JDOHelper.getPersistenceManager(obj)? I am getting null in this case, as the User was detached between page requests. That seems normal to me so I'm not clear how to debug with it.
You don't have a PM, you have an EM. No idea what you're referring to there.
Detachable : with JPA all classes are (enhanced as) detachable
You're using some ancient GAE JPA plugin there (v1.x?), and that uses old versions of DataNucleus that are not supported. Use GAE JPA v2.x. "ObjectManager" hasn't existed in DataNucleus for years.
You (or the software you're using) have to close the EM or you get resources leaked all over the place.
NucleusJPAHelper.getEntityManager(obj); is how you get the EntityManager that manages an object (in DataNucleus v3.x, used by GAE JPA v2.x)

Resources