I have the following setup for an app I want to use on the local network:
Silverlight Client App -> Web Server (data objects etc) | -> Exchange
| -> SQL Database
Everything else works fine, I can get data from the DB, etc (might be worth noting I'm using SQL auth for SQL not windows auth), but when I try to get calendar entries for the current user from exchange using Exchange Web Services it gives me a 401 unauthorised error.
This is all running on my local dev server (on my machine) which is running under a classic ASP.NET 4 app pool using the default ApplicationPoolIdentity. I'm not coming in via SSL, just over port 81
I can connect to get my own calendar entries, but for other users I get the error
If I try it without the impersonation code, I get a Connection did not succeed. Please try again later error since I assume that EWS will be using ApplicationPoolIdentity which doesn't have a domain logon.
I've used the following code to impersonate (ignore any potentially unhandled exceptions/security holes for now I just want to get it working!):
var impersonationContext = ((System.Security.Principal.WindowsIdentity)System.Threading.Thread.CurrentPrincipal.Identity).Impersonate();
// Use default credentials
service.UseDefaultCredentials = true;
// Get the target folder ID using the email address
var folder = new FolderId(WellKnownFolderName.Calendar, new Mailbox(emailAddress));
// Get the appointments
var response = service.FindAppointments(folder, view);
impersonationContext.Undo();
// Return list of appointment entities
return response.Items;
Looking at System.Security.Principal.GetCurrent() shows that the user changes to the identity of the user I want to impersonate after the Impersonate method is called. The AuthenticationType is Kerberos and the ImpersonationLevel is Impersonate. As far as I can see this should work but it looks like my web server doesn't want to successfully auth with exchange.
Am I missing something in my setup or in exchange?
This ended up working in the end - it ended up being some exchange settings and using the current threads principal instead of the system principal
Related
I have an intranet application that authenticates users against active directory. In general it works fine when testing locally(i.e. from my dev machine using VS 2017) or running it off of IIS in app server ("Browse *:80") until I try to access it using its URL from my local machine. Then whatever user id I use to get user's detail info, nothing is displayed.
Also, this app is to be accessible to users in certain groups so application checks logged in user's group membership.
Here is how I have it set up in IIS and different scenarios I tested with:
I set authentication to "Windows Authentication", disabled anonymous
authentication and enabled AS.NET Impersonation. This works fine when
running from app server using localhost but when trying to access
from my local machine and supplying user's user id to get his/her
detail info, no details are displayed (fails to get any info from
AD).
If I enable anonymous authentication and set it to "Application Pool Identity" (i.e. Network Services), it displays my custom
"Access Denied" page, presumably because this user is not part of
allowed group.
If I enable anonymous authentication and select "Specific User", supplying my credentials, then everything works fine, from app server or from my local machine, with one caveat: no matter who is accessing the site, it shows my name as logged in user.
I am stumped ad would appreciate some hints.
Update - Added code to get user info
Code to get user's identity:
WindowsIdentity wiUser = WindowsIdentity.GetCurrent();
string sID = wiUser.Name.ToUpper();
Code to get user's AD info:
static string adDomain = "www.xxx.yyy.zzz";
static string adContainer = "DC=www,DC=xxx,DC=yyy,DC=zzz";
public static DataTable getUserADInfoDT(string sSearchStr)
{
DataTable dtResults = new DataTable();
dtResults.Columns.Add("ID");
dtResults.Columns.Add("FirstName");
...
dtResults.Columns.Add("Zip");
string adDomain = string.Empty;
string adContainer = string.Empty;
// create domain context
PrincipalContext adPrincipalContext = new PrincipalContext(ContextType.Domain, adDomain, adContainer);
using (adPrincipalContext)
{
// define a "query-by-example" principal
UserPrincipal qbeUser = new UserPrincipal(adPrincipalContext);
qbeUser.SamAccountName = sSearchStr.Trim().ToUpper();
// create principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
PrincipalSearchResult<Principal> psr = srch.FindAll();
// find all matches
foreach (var found in psr)
{
DataRow dr = dtResults.NewRow();
UserPrincipal up = (UserPrincipal)found;
DirectoryEntry de = (DirectoryEntry)up.GetUnderlyingObject();
dr["ID"] = de.Properties["SAMAccountName"].Value.ToString().ToUpper();
if (de.Properties["givenName"].Value != null)
dr["FirstName"] = de.Properties["givenName"].Value.ToString();
...
if (de.Properties["postalCode"].Value != null)
dr["Zip"] = de.Properties["postalCode"].Value.ToString();
dtResults.Rows.Add(dr);
//de.Close();
}
return dtResults;
}
}
WindowsIdentity.GetCurrent() will get the user account that the current process is running under. If the application is running in IIS, that will usually be the user account that the app pool is running under - not the user that logged in to your application.
If you use IIS Express for debugging locally, then IIS Express is running under your user account, so you wouldn't see a difference. But on the server you will.
That said, if you have impersonation turned on and working properly, then WindowsIdentity.GetCurrent() should return the user that logged in - since impersonation means that your app is now pretending to be that person. So this might mean that impersonation isn't setup right. But maybe you don't even need it. I've never personally found a need to use impersonation.
To get the user that logged into your app, you should use HttpRequest.Current.User or possibly just HttpContext.User if your app is ASP.NET MVC.
And there is actually an easier way to get a DirectoryEntry object for the user, without having to search for the username. You can bind directly to the SID of the user, which is information you already have:
var user = new DirectoryEntry(
$"LDAP://<SID={((WindowsIdentity) HttpContext.User.Identity).User.Value}>");
If your server is not joined to the same domain as your users, then you might need to include the domain name:
var user = new DirectoryEntry(
$"LDAP://www.xxx.yyy.zzz/<SID={((WindowsIdentity) HttpContext.User.Identity).User.Value}>");
Something else to keep in mind: DirectoryEntry keeps a cache of all the Properties. When you access a property that it doesn't have in the cache already, it will go out to AD and ask for every attribute with a value. That's a lot of useless data going over the network if you only need 3 attributes. So you can tell it to specifically ask for only those 4 attributes before you use them:
user.RefreshCache(new[] {"SAMAccountName", "givenName", "postalCode"});
I talked about this, and other points, in an article I wrote: Active Directory: Better Performance
Update: To verify if IIS is actually enforcing authentication for a page, you can do this in PowerShell (update the URL to what you need - and this requires at least PowerShell 3):
try { (Invoke-WebRequest "http://example.com/the/page").StatusCode }
catch { $_.Exception.Response.StatusCode.Value__}
That will output the status code of the reply. You should see 401, since that is how IIS challenges the browser to send credentials. If you see 200, then Windows Authentication is not working for that page.
I do this in PowerShell, since Chrome's dev tools won't even show you the 401 challenge.
I have Microsoft SharePoint 2013 onPremise Farm with Microsoft Project Server 2013 installed on it.
I am trying to build a WebPart which should connect to the Project Server through CSOM from a TeamSite to get Project Information.
This is my super simple code:
string pwaPath = "http://sp2013/PWA";
ProjectContext projContext;
projContext = new ProjectContext(pwaPath);
projContext.Load(projContext.Projects);
projContext.ExecuteQuery();
When trying to execute it i get the following error:
ServerUnauthorizedAccessException
When i add
projContext.Credentials = new NetworkCredential("administrator", "pass", "domain");
It works! But how can i make the code using the logged in user, cause the credentials passed above are the users credentials who is currently logged in.
Your code won't work because farm solution does not execute in the context of the SharePoint user. If you check System.Security.Principal.WindowsIdentity.GetCurrent() you'll see that current identity is IIS built-in IUSR. This is the identity ClientContext will use to issue the request.
If you elevate permissions using SPSecurity.RunWithElevatedPrivileges() code will be executed with app pool identity. This may be enough in some scenarios, but may also be not secure, as all users will be able to access the resources.
I am creating the asp.net web application. In that I used active directory for login. I have one group named Validusers, when user logged in it checks the user to that validusers group. If the user exists in the group then login is successful, if not login failed.
I did all the things, it works good in my local machine, but it is not working when I publishing the website. I got the following error
Logon failure: unknown user name or bad password.
On my machine it works good,while publishing i got an error.i used below code for your reference
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "DomainName");
System.Security.Principal.WindowsIdentity MyIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
string LogUser = MyIdentity.Name.ToString();
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, LogUser);
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "Validusers");Validusers---->Groupname
if (user.IsMemberOf(group))
{
Login success
}
else
{
Login Failed
}
it will check logged user with Validusers group.if the user exists in the group then login is success other wise failed login. I got an error when i published this website.please give some solution
Although you had supplied so little information about the domain and network relationship with your development machine and web server, I assume the web server has no physical connection to the Active Directory server that you depend your code on. Then it should be impossible for web server to query the AD directory.
If web and AD servers are on same network than you may need to work on the firewall settings of both web server and AD server to make sure that they can communicate.
If web and AD server have no communication problems you should check the availability of that "Validusers" to a code running at web server.
As IIS applications run with the user account that is defined for the application pool that hosts the application, you should make sure that the app pool identity has enough rights to access those delicate information.
We have 2 web-servers which are theoretically identical, but are producing different results when performing an AzMan authorisation check.
We have the same web-site running on both machines (literally the same web-site - it's been XCOPYed from one to the other, and it runs under the same service account). All this web-site does is perform an authorisation check against an AzMan database (sitting on a separate SQL server).
However, on the working web-site (WebA) this check returns 0 (i.e. "user is authorised"), while on the broken web-site (WebB) this check returns 5 (i.e. "user is NOT authorised"). We are expecting 0 on both web-sites. The same user is accessing both web-sites, from the same PC.
Does anyone have any ideas for things we can check?
Environment details
Windows Server 2008 R2
Same AD domain
IIS 7.5
.NET 3.5
AzMan Database runs on SQL Server 2005/Windows Server 2008 R2.
Code
AzAuthorizationStoreClass authStore = new AzAuthorizationStoreClass();
// initialise the store
authStore.Initialize(0, "mssql://Driver={SQL Server};Server={OURDBSERVER};Trusted_Connection={Yes};/OURDATABASE/OURAPPLICATION", null);
// open the store
IAzApplication2 authApp = authStore.OpenApplication2("OURAPPLICATION", null);
// get the identity of the user NOT the service account
WindowsIdentity identity = Thread.CurrentPrincipal.Identity as WindowsIdentity;
// and from that derive the token
ulong userToken = (ulong)identity.Token.ToInt64();
// get the context based on the token
IAzClientContext3 clientContext =
(IAzClientContext3)authApp.InitializeClientContextFromToken(userToken, null);
// get the operation object based on the id
IAzOperation2 azManOperation = (IAzOperation2)authApp.OpenOperation(operationId, null);
// generate an audit identifier
string auditIdentifer =
string.Format("{0}{1} : O:{2}", "{the_correct_id}", identity.Name, operationId);
uint accessResult = clientContext.AccessCheck2(auditIdentifer, string.Empty, azManOperation.OperationID);
return accessResult.ToString();
Many thanks,
RB.
Thanks to David Hall for pointing me in the right direction.
Investigation showed that both web-sites were enabled for both Windows authentication and anonymous access. However, on one web-site the user was being logged in correctly, while on the broken web-site it was falling back to anonymous mode.
Disabling anonymous access fixed this problem by ensuring the user logs in to both web-sites.
However, this leaves another question of why the browser logs in anonymously on one web-site but not the other - one for ServerFault I think.
In our case, we were using ASP.NET impersonation with Windows Authentication and not Anonymous. Tt was working on the Windows 7 Enterprise x64 Development machine and not on the Windows Server 2008 R2 x64 test server. Both Application Pools were set up exactly the same with the same domain account credentials.
It turns out that ASP.NET impersonation was the root cause of the issue. After disabling ASP.NET impersonation, the App Pool account was now being used as the credentials to connect to the AzMan store successfully. The same issue was occurring when connecting to an AzMan store in Active Directory or SQL Server.
For clarity, the error I was getting was: Value does not fall within the expected range. from AzAuthorizationStoreClass.Initialize()
My final connection string was:
<add name="AzPolicyStore" connectionString="mssql://Driver={SQL
Server};Server=sqlserver\instance;/DatabaseName/AzStore" />
I receive an "Access Deined" COMException when I try to connect to a remote IIS 6 server from my C# application that is running under IIS 5.1.
Any ideas? I am experiencing all the same issues with the original questions.
Update - 4/1/09
I found this solution (http://www.codeproject.com/KB/cs/Start_Stop_IIS_Website.aspx) that consists of a window application connecting to an IIS server to start and stop web sites. I am able to run it on my workstation and connect to the IIS server.
Ugh....why can I run this stand alone application but not my ASP.NET application?
Original
I receive an "Access Denied" COMException when I try to connect to IIS from a remote machine using the DirectoryEntry.Exist method to check to see if the IIS server is valid.
string path = string.Format("IIS://{0}/W3SVC", server);
if(DirectoryEntry.Exist(path))
{
//do something is valid....
}
I am a member of an active directory group that has been added to the Administrators groups to the IIS server I am trying to connect to.
Has anyone experience this issue and know how to resolve it?
UPDATE:
#Kev - It is an ASP.NET application. Also, I can connect without an username and password to the remote server through IIS6 Manager.
#Chris - I am trying to connect to the remote server to display the number of virtual directorys and determine the .NET framework version of each directory. See this SO question.
#dautzenb - My ASP.NET application is running under IIS 5.1 trying to connect to an IIS 6 server. I can see fault audits in the security log for my local ASPNET account on the remote server. When I try to debug the application, I am running under my domain account and still get the Access is denied.
UPDATE 2:
#Kev - I was able to establish to create a DirectoryEntry object using the following overload:
public DirectoryEntry
(
string path,
string username,
string password
)
But, all of the properties contain a " threw an exception of type 'System.Runtime.InteropServices.COMException'" while I debug the app.
Also, the AuthenticationType property is set to Secure.
UPDATE 3:
The following two failure audit entries were in the remote IIS server's security event log every time I tried to establish a connection:
First event:
Event Category: Account Logon
Event ID: 680
Log attempt by: MICROSOFT_AUTHENTICATION_PACKAGE_V1_0
Logon account: ASPNET
Source Workstation:
Error Code: 0xC0000234
Second event:
Event Category: Logon/Logoff
Event ID: 529
Logon Failure:
Reason: Unknown user name or bad password
User Name: ASPNET
Domain: (MyDomain)
Logon Type: 3
Logon Process: NtLmSsp
Authentication Package: NTLM
Workstation Name: (MyWorkstationId)
Caller User Name: -
Caller Domain: -
Caller Logon ID: -
Caller Process ID: -
Transited Services: -
Source Network Address: 10.12.13.35
Source Port: 1708
Impersonation is set to true and the username and password are blank. It is using the ASPNET account on the remote IIS server.
If it is an identity problem, you could try setting your IIS 5.1 application to use Integrated Windows Authentication, and then add the following to you web.config on your IIS5.1 web site under system.web to enable impersonation.
<identity impersonate="true"/>
<authentication mode="Windows" />
Since this is an ASP.NET application, it runs in an Application Pool of IIS. This Application Pool runs using a specific user("Local System", "Network Service" or another user).
Does this user have enough rights to connect to a remote server ?
See MSDN for more info.
This looks like it may be a double-hop issue. If you are impersonating the current user of a website using NTLM, that impersonation is only valid on that server (your IIS 5.1 server in this case). If you try to connect to another server using the web site, you are actually going to have issues as it cannot pass the token to another server that was used during impersonation. The same is true if you are debugging your site through your machine, going to another box. Your local machine is authenticating you, but it cannot impersonate you to another server.
All of the solutions I have used in the past require you to hard code the app pool to use an account that has permissions, set the annony. account to a domain account with permissions on the other machine, or use a windows service running on the IIS 5.1 machine, under a domain account, to connect to the other server.
If you are using Kerberos, this wouldn't apply, but AD uses NTLM by default.
Where exactly are you trying to read too? Is it in under the same path as your application?
When I had this problem, I found that simply authenticating my self on a Windows file share solved the problem. From experience, I think that WMI/ADSI/COM doesn't have great support for not-already-authenticated users. I believe this issue occurs when you're not associated with a Windows domain.
If it is indeed a NTLM doublehop issue you could use the SETSPN utility to create service principal named instances for your target IIS servers.
Then you could go into Active Directory, and then allow the computer object (basically the NETWORK SERVICE or LOCAL SERVICE principals) to delegate its credentials to a correctly registered SPN.
Then you could hop-hop-hop all over the place! But, be warned! People can hurt themselves on sharp pointy things when you enable double-hop!
Good KB articles to read:
http://support.microsoft.com/kb/929650
I believe that DirectoryEntry.Exists silently ignores any credentials supplied and uses the creds of the authenticated user. This seems to match the behaviour you've described. For AD work, we never use it for this reason.
I'm sort of stumped at the moment as to why you can't get this working. There is a temporary work around you could try. When instantiating the DirectoryEntry object you could use one of the following constructor overloads:
public DirectoryEntry(
string path,
string username,
string password
)
Documented at: MSDN: DirectoryEntry Constructor (String, String, String)
...or...
public DirectoryEntry(
string path,
string username,
string password,
AuthenticationTypes authenticationType
)
Documented at: MSDN: DirectoryEntry Constructor (String, String, String, AuthenticationTypes)
As it happens I'm building a test AD environment on my virtual server box for a new project to do similar stuff. When I get it up and running I'll have a play around to see if I can reproduce the problem you're encountering. In the meantime let us know what happens if you try these constructor overloads referenced above.
Update (In answer to Michaels comment):
For reasons that evade me just now, we couldn't use DirectoryEntry.Exists() in a particular scenario, there is this snippet of code that gets called now and again in one of our apps:
public static bool MetabasePathExists(string metabasePath)
{
try
{
using(DirectoryEntry site = new DirectoryEntry(metabasePath))
{
if(site.Name != String.Empty)
{
return true;
}
return false;
}
}
catch(COMException ex)
{
if(ex.Message.StartsWith("The system cannot find the path specified"))
{
return false;
}
LogError(ex, String.Format("metabasePath={0}", metabasePath));
throw;
}
catch(Exception ex)
{
LogError(ex, String.Format("metabasePath={0}", metabasePath));
throw;
}
}
You could replace the constructor with one of the ones from above. Admittedly it's a stab in the dark :).