Check other user's role membership (IsInRole, WindowsIdentity/Principal) - asp.net

I'm writing ASP.NET code to run on an internal network where Windows Authentication will be used. Certain operations will require me to run a group membership check on other users (not the current user)
NOTE: I am NOT trying to impersonate this account, or access any information in the context of this other user. Just trying to find out what kind of user they are for internal business logic.
My first thought was to use
new WindowsPrincipal(new WindowsIdentity("MACHINENAME\\username"))
.IsInRole("MACHINENAME\\Group1")
However, the WindowsIdentity constructor fails with a SecurityException "The name provided is not a properly formed account name".
If I strip MACHINENAME\ from the parameter, I get a different error: There are currently no logon servers available to service the logon request
The WindowsTokenRoleProvider role provider explicitly only works with the current user, and will not check other user accounts.
Are there security restrictions to checking roles of other users? Would it make a difference if the web server was on a domain and I were checking domain accounts?
In the end, I'll need to have this work on an AD domain, but would prefer a solution that will work on either local or AD accounts.
Thank you
UPDATE: I've been able to test this on a domain now -- the code does work in an AD context so long as I don't use the domain name (test "username" against "Group1", not "DOMAIN\username" against "DOMAIN\Group1")
So how would I get this to work in the context of local users and groups?

Based on Rob A's comment, PrincipalContext and UserPrincipal are the classes I apparently need to use:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Machine))
{
var u = UserPrincipal.FindByIdentity(ctx, IdentityType.Name, "username");
var b = u.IsMemberOf(ctx, IdentityType.Name, "Group1");
var groups = u.GetAuthorizationGroups();
}
And by altering the ContextType, can switch between local accounts and AD accounts. I wish this was built into a RoleProvider, but I guess that's something I'd have to do for myself.

Related

User assigned managed identity does not work with DefaultAzureCredential

In my Az Function I have system assigned managed identity enabled and a user assigned managed identity assigned.
I would like to use the user assigned managed identity to access some resources, so I put the following code to initialize ARMClient.
DefaultAzureCredentialOptions option = new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = "xxxxxxx"
};
var credential = new DefaultAzureCredential(option);
_armClientUAI = new ArmClient(credential, subscriptionId);
Also, would like to grant access to the function itself to some resources, so I do not set AZURE_CLIENT_ID in App Setting as this will route to user assigned identity instead.
After deployed my function to Azure, it failed to access those resources that user assigned managed identity has been grant. Looks like it uses system assigne managed identity even I set client id explicitly.
From the doc here, it mentioned the check order for DefaultAzureCredential as follows, but did not say how it determines when both system assigned and user assigned are enabled.
EnvironmentCredential
ManagedIdentityCredential
SharedTokenCacheCredential
IntelliJCredential
AzureCliCredential
AzurePowerShellCredential
Fails if none of the credentials above could be created.
Tried to set the client id explicitly, but not working.

Add Roles fetched from SQL Server as Claims to AD FS Relying Party Trust

I'm authenticating users of an asp.net mvc web site by using ADFS Server 2016 passive redirection, and I cannot get claims from a SQL attribute store. I'm interested in discovering what I am doing wrong or missing.
Side note: I'm using the System.Identity libraries from Framework 4.5 (I'm not referencing the Microsoft.Identity libraries created for older framework versions; most ADFS code samples that I stumble across use these old libraries).
The basics are working well. All of this is in one domain. I have my asp.net web.config set up to redirect users to my ADFS server for authentication. The ADFS server successfully authenticates and redirects users back to my asp.net web site. On the ADFS I have one Claim Issuance Policy rule where I simply pass back all claims from the Active Directory.
On the web site I am able to iterate through the user's Claims collection and display them. Here is the code from the *.cshtml page where I iterate though the claims, it works fine:
#using System.Security.Claims;
#{
var currentPrincipalIdentity = (ClaimsIdentity)System.Threading.Thread.CurrentPrincipal.Identity;
}
#foreach (Claim claim in currentPrincipalIdentity.Claims)
{
<br/>#claim.Type : #claim.Value
}
In addition to these claims from Active Directory, I want to fetch a bunch of roles from a SQL Server database and add them to the Claims collection as roles. I'm fetching the roles from a legacy asp.net Membership database. As step 1 I just want to hard-code the username in the SQL statement (eventually I will need to figure out how to pass the username as a parameter to the SQL statement, but that will be step 2).
First, I gave the identity that the ADFS server runs under read/write/execute permissions on my SQL Server (when I take these permissions away I get a permissions error, which gives me confidence that my SQL statement is executing).
In my AD FS I added a SQL Server Attribute Store by right-clicking the "Attribute Stores" node, selecting an Attribute store type of "SQL", named is "SQLServer", and added a connection string like so:
Server=SqlDev01; Database=MyLegacyMembershipDatabase; Integrated Security=SSPI;
I then select the "Relying Party Trusts" folder, select the trust I am interested in, and select "Edit Claim Issuance Policies." I have one rule there that works; it simply passes back all Active Directory claims. I can see all of these claims on my web page (upn, name, windowsaccountname, all of my group sids, and etc):
c:[]
=> issue(claim = c);
I'm trying to add a 2nd custom rule to read a legacy membership database. In my ADFS I click "Add Rule", "Send Claims Using a Custom Rule", and add this as the rule:
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"]
=> add(store = "SQLServer", types =
("http://schemas.microsoft.com/ws/2008/06/identity/claims/role"), query =
"select r.RoleName AS Role from dbo.aspnet_Roles r INNER JOIN
dbo.aspnet_UsersInRoles uir ON r.RoleId = uir.RoleId INNER JOIN
dbo.aspnet_Users u ON uir.UserId = u.UserId WHERE u.UserName = '[hard-coded
value here]' OR u.UserName={0}", param = c.Value);
It saves fine, but when I re-run the page nothing changes; I still get the original collection of Active Directory claims, but not the data from SQL Server.
I am confident the SQL Server statement is executing, because if I remove permissions for the identity that ADFS runs under from the SQL Server I get an error, and if I deliberately garble the SQL syntax I get an error. If I reverse these deliberate mistakes then the page functions properly again. But I never see the Roles that I want to see in the Claims collection.
From my understanding of custom rules, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" is passed as a parameter into the query, that is why I have the OR statement above; my ultimate goal is to pass the user's UPN as a parameter into the SQL query.
Am I missing something or doing something wrong? Bonus question--assuming I get this working, can you tell me how to pass the user's UPN as a parameter into the SQL query?
Try an "issue" rule rather than an "add".

Hosting WIF Identity Provider and generating self signed certificates for it

For my final year project I've developed an ASP.NET website and I've implemented a Single Sign On Login System using Windows Identity Foundation (in a similar manner to the tutorial shown here: http://www.primaryobjects.com/2013/08/08/using-single-sign-on-with-windows-identity-foundation-in-mvc-net )
This means that I currently have a 2 Websites, my Identity Provider Site and the site that uses the IP and contains most of the functionality.The IP uses X509 certificate to generate the token and for this I've been able so far to use a self signed certificate. This is the code that I've been using to retrieve the certificate from the local machine:
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
X509Certificate2Collection certificates = null;
store.Open(OpenFlags.ReadOnly);
try
{
certificates = store.Certificates;
var certs = certificates.OfType<X509Certificate2>().Where(x => x.SubjectName.Name.Equals(subjectName, StringComparison.OrdinalIgnoreCase)).ToList();
if (certs.Count == 0)
throw new ApplicationException(string.Format("No certificate was found for subject Name {0}", subjectName));
else if (certs.Count > 1)
throw new ApplicationException(string.Format("There are multiple certificates for subject Name {0}", subjectName));
return new X509Certificate2(certs[0]);
}
In order to be able to present my project I will be asked to host it on the web, but this will mean that I'll need a substitute for my self signed certificate.
I could use something like azure websites to host my websites but I wasn't able to find a solution so far that would allow me to generate a self signed certificate on a service like azure and retrieve it programatically.
Could you please suggest me a solution for this problem?
Ideally, you wouldn't have to create your own IDP. You could use something like ADFS.
Azure AD is an IDP and has its own certificates so you don't need to create any.
All you need is the metadata and you can find the URL under AppServices / Endpoints.
There's an example here but note that this uses WS-Fed OWIN not WIF. (WIF is somewhat old school).
To make your life easier, you can update your web.config programmatically by adding code to global.asax.
Or just move your whole solution to a VM in Azure.

ASP Identity : Binding Sessions To Request Url (Sub Domains)

I don't think the title of the question is particularly accurate but that's how best i could title it.
Without summarizing, I have an MVC app hosted on Microsoft Azure. The app was built for multiple institutions (each connecting to a separate database) but the Login Module (Asp Identity) is in a central database (users are identified by their institution code). So during deployment, a sub domain is created (still pointing to the app on azure).
My problem is, the app has no regard for the Request Url, the sessions are maintained across domains. This is a serious problem because i cache User data (by session). So if a user Logs in on "domain1.myapp.com" , then opens another tab , logs into "domain2.myapp.com" , all data cached for the user logged in for "domain1" will be used for the user logged in at "domain2". The app doesn't bother to get data for the user in "domain2" since the key for that data value is already present in the session cache.
Okay, I hope the problem is understood. How do i get past this.
Ideas ? implementation ?
EDIT 1
I insert data into the cache by,
HttpRuntime.Cache.Insert("KEY", "VALUE", null, DateTime.Now.AddMinutes(30),Cache.NoSlidingExpiration);
Your caching strategy needs to change when you cache per user and per domain. The simplest approach is just to add the domain and user name to the cache key, which will keep each cache in a separate per user and per domain bucket. Make sure you put a delimiter between the values to ensure uniqueness of the key.
var domain = HttpContext.Request.Url.DnsSafeHost;
var user = HttpContext.User.Identity.Name;
var key = "__" + domain + "_" + user + "_" + "KEY";
HttpRuntime.Cache.Insert(key, "VALUE", null, DateTime.Now.AddMinutes(30),Cache.NoSlidingExpiration);
Note that if you use HttpContext.Session, it will automatically put different domain information into separate buckets because it is based on a cookie (which by default is domain specific). However, HttpContext.Session comes with its own set of problems.

How to pass HostingEnvironment.Impersonate credentials to ExchangeService EWS?

Is it possible to pass the credentials of the user browsing my asp.net web application to the EWS FindAppointments call?
I'm only trying to return calendar details for the active browsing user, who will without doubt have permission to read their own calendar, so the issue should not relate to Exchange impersonation with the EWS api discussed here.
The code below works just fine when running localhost, but running from the web server, despite Windows Authentication and Identity Impersonation being configured it throws an access denied error.
using (HostingEnvironment.Impersonate())
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
service.UseDefaultCredentials = true;
service.AutodiscoverUrl(UserEmailAddress);
Mailbox mb = new Mailbox(UserEmailAddress);
FolderId cfCalendarFolderID = new FolderId(WellKnownFolderName.Calendar, mb);
CalendarView cvCalendarView = new CalendarView(DateTime.Now, DateTime.Now.AddDays(30), 1000);
cvCalendarView.MaxItemsReturned = 3;
Perhaps I'm missing a simple way to pass the HostingEnvironment credentials to my ExchangeService object?
Is there a way to check what the service.UseDefaultCredentials are?
I'm not able to use the following as there isn't a way to get the password from the windows authenticated impersonated user.
service.Credentials = new System.Net.NetworkCredential(username, password, domain);
I've also tried the following, but get the same ServiceResponseException access denied errot.
service.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
service.PreAuthenticate = true;
Thanks in advance of your kind assistance.
Additional info which may or may not be relevant:
The Application Pool Identity for the website is NetworkService.
The UserEmailAddress variable is set from an AD lookup based on System.Security.Principal.WindowsIdentity.GetCurrent().Name
EDIT (14th Aug 2012)
To achieve what I'd like to do above, I believe the HostingEnvironment.Impersonate isn't required.
Instead I need to use the ExchangeService's ImpersonatedUserId property.
More details on that here
Only problem though is we're running Exchange 2007 and the power shell command for enabling a service account to impersonate all users (that you would use pass in to the .Credentials parameter) only appears to be compatible with Exchange 2010.
You should try using WebCredentials instead of NetworkCredential - see related SO post. There seems to be an issue with EWS and AutoDiscover + NetworkCredentials

Resources