I've an application that connects to Reporting Services in SQL Server 2008 R2.
The error is the following:
System.Net.WebException: The request failed with HTTP status 401: Unauthorized.
at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse
(SoapClientMessage message, WebResponse response, Stream responseStream,
Boolean asyncCall)
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke
(String methodName, Object[] parameters)
at Microsoft.SqlServer.ReportingServices2005.Execution.ReportExecutionService.LoadReport
(String Report, String HistoryID)
The application is running in production fine in 2 different customers, so it's not a codeing issue.
I'm trying to install it now on a customer's server, which is using AD. The SQL Server and the IIS is all in the same machine though, so I don't really care about AD.
It runs if I run IE as Administrator, but it doesn't work with other users. The ASP.NET app is connecting to SSRS using a user created in the local machine (called ReportingServicesUser), member of the ReportingServicesUser group.
Things I've tried:
Adding ReportingServicesUser to the Site Settings in the RS website (did the same for Network Service, IUSR, the Authenticated Users group, Local Service, etc)
Adding ReportingServicesUser to the folder permissions in the RS website (did the same for Network Service, IUSR, the Authenticated Users group, Local Service, etc)
Added permissions for that users to the databases (app database and RS related dbs)
Added NTFS permissions to the RS folders (I will double check though).
Connecting to the RS using http://localhost, http://computername and http://domain.com
For reference, the code is this (simplified version):
var service = new ReportExecutionService();
service.Credentials = new NetworkCredential("ReportingServicesUser", "password");
service.Url = "http://computername:90/ReportServer/ReportExecution2005.asmx";
service.ExecutionHeaderValue = new ExecutionHeader();
var execInfo = new ExecutionInfo();
execInfo = service.LoadReport("path-to-the-report", null);
===> Here it throws the exception
I've read a lot of posts and pages about this but I cannot get an answer that works for me.
OK, I've finally had to change the code to:
rs.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
and it worked. Probably there's another solution but I couldn't find it out.
Are you sure you gave "ReportingServicesUser" browse permissions for the specific report in ssrs? The request never made it to the server it seems but I would check \Reporting Services\LogFiles just to be certain.
Also, Your "report user" user needs to be defined on the reporting server with the credentials you send.
Related
Before I go into the issue let me first explain the architecture I'm working with.
DB-Server - SQL Server 2019, Windows 2019
SSRS-Server - SSRS 2019, Windows 2019
Web-Server - IIS v10, Windows 2019
All servers are on the same domain.
The application rendering the report is written in ASP.NET using the rsweb:ReportViewer control.
Requirement: I need the ability to access reports from the ASP.NET application using a machine level account.
Current Error: The request failed with HTTP status 401: Unauthorized.
What I've done so far...
Added machine account to SSRS. I've given it permissions at all levels: site settings, folders, reports. Once I get it working I'll dumb down the rights but for now I need it working. This machine level account is the Web-Server. My assumption is any access from the Web-Server to the SSRS-Server will be granted.
Changed the application pool's identity to LocalSystem. Yes this may violate least privilege but again I'm viewing this as a POC at this point. I've also tried NetworkService.
Web.config. There may be something I need to do here to make this work. I've tried using identity impersonate but I had no success but it's possible I didn't use it in combination with another setting. When I have the authentication mode set to Windows the Windows Identity used by ASP.NET is NT AUTHORITY\IUSR. Removing this line gives me NT AUTHORITY\NETWORK SERVICE as the Windows Identity. I would think the NETWORK SERVICE account would be granted; however, this still results in a 401 error.
Modified the "rsreportserver.config" file (C:\Program Files\Microsoft SQL Server Reporting Services\SSRS\ReportServer\rsreportserver.config)
ASP.NET ReportViewer setup:
ReportViewer1.ServerReport.ReportPath = rc.report_ssrs_path;
ReportViewer1.ServerReport.ReportServerUrl = new System.Uri(rc.report_ssrs_url);
SqlConnectionStringBuilder conString = new Database().conBuilder;
List<ReportParameter> parameters = new List<ReportParameter>();
parameters.Add(new ReportParameter("ConString", conString.ConnectionString));
ReportViewer1.ServerReport.SetParameters(parameters);
DataSourceCredentials dsc = new DataSourceCredentials();
dsc.Name = ReportViewer1.ServerReport.GetDataSources()[0].Name;
dsc.UserId = conString.UserID;
dsc.Password = conString.Password;
ReportViewer1.ServerReport.SetDataSourceCredentials(new DataSourceCredentials[] {
dsc });
ReportViewer1.ShowCredentialPrompts = false;
ReportViewer1.ServerReport.Refresh();
If anyone could help it would be greatly appreciated. I've scoured the internet for a solution to this with no luck. It seems like it should be possible and a lot of the things I've read make it sound like it is. I'm sure I'm just missing something minor.
I had to create a new dev environment with less VMs as my IDE kept crashing, so I created a VM with AD and IIS on the same server.
I was using the following code fine in my old environment:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
Environment.GetEnvironmentVariable("DOMAIN"),
Environment.GetEnvironmentVariable("USER_OU"),
Environment.GetEnvironmentVariable("SERVICE_USERNAME"),
Environment.GetEnvironmentVariable("SERVICE_PASSWORD"));
UserPrincipalEx usr = new UserPrincipalEx(ctx);
usr.Name = ticket.FirstName + " " + ticket.LastName;
usr.SamAccountName = ticket.Username;
usr.GivenName = ticket.FirstName;
usr.Surname = ticket.LastName;
usr.DisplayName = ticket.FirstName + " " + ticket.Account.LastName;
usr.UserPrincipalName = ticket.Username + "#" + Environment.GetEnvironmentVariable("DOMAIN");
usr.Enabled = enabled;
try
{
usr.Save();
usr.SetPassword(temppwd);
usr.ExpirePasswordNow();
}
I can still save the user and it appears in AD, however SetPassword no longer works:
[IIS EXPRESS] Request started: "POST" https://localhost:5001/create
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
--- End of inner exception stack trace ---
at System.DirectoryServices.DirectoryEntry.Invoke(String methodName, Object[] args)
at System.DirectoryServices.AccountManagement.SDSUtils.SetPassword(DirectoryEntry de, String newPassword)
at System.DirectoryServices.AccountManagement.ADStoreCtx.SetPassword(AuthenticablePrincipal p, String newPassword)
at System.DirectoryServices.AccountManagement.PasswordInfo.SetPassword(String newPassword)
at System.DirectoryServices.AccountManagement.AuthenticablePrincipal.SetPassword(String newPassword)
My service account is a Domain Admin and I get the same error if I try my own AD creds.
I have tired calling SetPassword() before Save() but it fails at the same point.
The only difference is that I have AD and IIS on the same server. I have tired both Jetbrains Rider and VS2019. I am getting very close to my project deadline and I am really stuck.
None of the users have 'User Cannot Change Password Set' and the new users don't have any options under 'Account Options' set.
SetPassword sets the unicodePwd attribute. That has some restrictions on when it can be updated. The documentation for that says:
Windows 2000 operating system servers require that the client have a 128-bit (or better) SSL/TLS-encrypted connection to the DC in order to modify this attribute. On Windows Server 2003 operating system and later, the DC also permits modification of the unicodePwd attribute on a connection protected by 128-bit (or better) Simple Authentication and Security Layer (SASL)-layer encryption instead of SSL/TLS.
It should setup a secure connection by default (it does for me), but it's possible that it can't in your setup for whatever reason.
You can pass a ContextOptions object in the constructor to your PrincipalContext. By default that is automatically set to ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing, which should be secure. But ContextOptions.Negotiate uses "either Kerberos or NTLM", and ContextOptions.Signing (the encryption) depends on Kerberos. So maybe it's falling back to NTLM and can't encrypt.
You might be able to confirm this by inspecting these values, after you create the account:
Console.WriteLine(ctx.Options);
Console.WriteLine(((DirectoryEntry) usr.GetUnderlyingObject()).AuthenticationType);
The values you'd be looking for are:
Negotiate, Signing, Sealing
Secure, Signing, Sealing
That's what I have when SetPassword works. But I'm not sure if it actually changes those values if it falls back to NTLM. Sometimes it does that pretty silently.
In any case, if Kerberos isn't happening, you can either troubleshoot that, or attempt to connect via LDAPS (LDAP over SSL). That would look something like this:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
Environment.GetEnvironmentVariable("DOMAIN") + ":636",
Environment.GetEnvironmentVariable("USER_OU"),
ContextOptions.Negotiate | ContextOptions.SecureSocketLayer,
Environment.GetEnvironmentVariable("SERVICE_USERNAME"),
Environment.GetEnvironmentVariable("SERVICE_PASSWORD"));
But that can cause other issues since your DC needs to have a certificate that you trust.
I'm tried to pull some SharePoint 2013 list data I created which works fine when running locally on my machine and when run locally one the server. I'm user the same credentials when running both locally and locally on the server. The issue is when I publish and navigate to my ASP.NET app on the server I get the "The remote server returned an error: (401) Unauthorized." Error...
I've looked at a bunch of the posts on stackoverflow and some other articles on the web
This points out that the context seems to be using IUSR:
http://blogs.msdn.com/b/sridhara/archive/2014/02/06/sharepoint-2013-csom-call-from-web-part-fails-with-401-for-all-users.aspx
This one mentions to try setting the default network credentials:
https://sharepoint.stackexchange.com/questions/10364/http-401-unauthorized-using-the-managed-client-object-model
I've tried using the fixes mentioned in the article as well as trying to force the context to use DefaultNetworkCredentials but no luck. I would like for the app to use the credentials of the logged in user and not the machine...
Here is the code I'm using:
SP.ClientContext context = new SP.ClientContext("MySPDevInstance");
context.Credentials = CredentialCache.DefaultNetworkCredentials;
Entity entity = context.Web.GetEntity(collectionNamespace, collectionName);
LobSystem lobSystem = entity.GetLobSystem();
LobSystemInstanceCollection lobSystemInstanceCollection = lobSystem.GetLobSystemInstances();
context.Load(lobSystemInstanceCollection);
context.ExecuteQuery();
LobSystemInstance lobSystemInstance = lobSystemInstanceCollection[0];
FilterCollection filterCollection = entity.GetFilters(filter);
filterCollection.SetFilterValue("LimitFilter", 0, 1000);
EntityInstanceCollection items = entity.FindFiltered(filterCollection, filter, lobSystemInstance);
The server is running IIS 6.0
Any advice would be much appreciated!
Thank you
I presume your ASP.NET web site is using Windows Integrated (NTLM) authentication. A user authenticated this way cannot authenticate to a second location from the server side (the web server.) You are experiencing what is known as the "double-hop" (1) limitation of NTLM. You must use a dedicated account on the server side, or if you really do want to use the logged-in user's identity, you must use an authentication scheme that permits delegation, such as Kerberos.
If you really need the user's identity to access SharePoint data and you cannot change the authentication scheme, then the best way to do this is to use the JavaScript CSOM. This means the user is authenticating directly to the SharePoint server (a single hop, not double) and your ASP.NET site serves the page containing this script to the user.
(1) http://blogs.msdn.com/b/knowledgecast/archive/2007/01/31/the-double-hop-problem.aspx
Use Default Credentials worked for me:
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.UseDefaultCredentials = true;
Setup the crendentials by code:
SP.ClientContext context = new SP.ClientContext("MySPDevInstance");
context.Credentials = new NetworkCredential("username", "password");
You should put this at the configuration file to change it without publishing or recompiling the application.
Just to add one more setting that I encountered. If the account is restricted to access only certain servers than add the client machine to that account as well. For example if a web application is hosted on Server A and trying to connect to SharePoint 2010 on Server B with account ABC then make sure that account has access to Server A in Active Directory. Normally the AD account doesn't have restrictions to connect to machines but in my case the account was restricted to only certain machines. I added my web application hosted server to the account and it worked.
I have a WebForms app that uses the WindowsAzure.Storage API v3. It works fine in development and in one production environment, but I'm rolling out a new instance and any code that calls out Azure Blob Storage gives me a 403 error.
I've been fiddling with this for awhile, and it fails on any call out to Blob Storage, so rather than show my code I'll show my stack trace:
[WebException: The remote server returned an error: (403) Forbidden.]
System.Net.HttpWebRequest.GetResponse() +8525404
Microsoft.WindowsAzure.Storage.Core.Executor.Executor.ExecuteSync(RESTCommand`1 cmd, IRetryPolicy policy, OperationContext operationContext) +1541
[StorageException: The remote server returned an error: (403) Forbidden.]
Microsoft.WindowsAzure.Storage.Core.Executor.Executor.ExecuteSync(RESTCommand`1 cmd, IRetryPolicy policy, OperationContext operationContext) +2996
Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer.CreateIfNotExists(BlobContainerPublicAccessType accessType, BlobRequestOptions requestOptions, OperationContext operationContext) +177
ObsidianData.Azure.Storage.GetContainer(CloudBlobClient client, Containers targetContainer) in D:\Dev\nSource\Obsidian\Source\ObsidianData\Azure\Storage.vb:84
ObsidianWeb.Leads.HandleListenLink(String fileName, HyperLink link) in D:\Dev\nSource\Obsidian\Source\ObsidianWeb\Bdc\Leads.aspx.vb:188
ObsidianWeb.Leads.LoadEntity_ContactDetails(BoLead lead) in D:\Dev\nSource\Obsidian\Source\ObsidianWeb\Bdc\Leads.aspx.vb:147
ObsidianWeb.Leads.LoadEntity(BoLead Lead) in D:\Dev\nSource\Obsidian\Source\ObsidianWeb\Bdc\Leads.aspx.vb:62
EntityPages.EntityPage`1.LoadEntity() +91
EntityPages.EntityPage`1.Page_LoadComplete(Object sender, EventArgs e) +151
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +4018
Here's what I've tried...
The AzureStorageConnectionString that fails in this environment definitely works in production
Other connection strings (from the other production environment, which works) also get a 403 here
There seemed to be an issue with timestamps in some old versions of the REST api (which I am not directly using...) so I made certain the times are correct, even tried switching the server to UTC time.
Tried toggling the connection string between http/https.
Upgraded to the latest version of the API (v3.1)
Tried fiddling with the code to ensure that every call out to Azure Storage gets 403. It does.
In desperation, Installed Azure Powershell on the server just to verify that some type of communication with Azure is working. And that worked fine.
Browsed to the azure management portal as well and that works fine.
Any ideas? This should just be using port 80 or 443, right? So there should be no way this is some kind of network issue. Let me know if that's wrong.
The working production machine is an Azure VM (Server 2008 R2 with IIS 7.5)
There are also some differences with the server:
This new machine is physical hardware (Server 2012 and IIS 8)
This IS using a different storage account inside my azure subscription, however I've tried a total of 3 connection strings and none of them work here.
UPDATE: someone asked to see the code. Okay, I wrote a class called Azure.Storage, which just abstracts my cloud storage code. We are failing on a call to Storage.Exists, so here's the part of that class that feels relevant:
Public Shared Function Exists(container As Containers, blobName As String) As Boolean
Dim Dir As CloudBlobContainer = GetContainer(container)
Dim Blob As CloudBlockBlob = Dir.GetBlockBlobReference(blobName.ToLower())
Return Blob.Exists()
End Function
Private Shared Function GetContainer(client As CloudBlobClient, targetContainer As Containers)
Dim Container As CloudBlobContainer = client.GetContainerReference(targetContainer.ToString.ToLower())
Container.CreateIfNotExists()
Container.SetPermissions(New BlobContainerPermissions() With {.PublicAccess = BlobContainerPublicAccessType.Blob})
Return Container
End Function
Private Shared Function GetCloudBlobClient() As CloudBlobClient
Dim Account As CloudStorageAccount = CloudStorageAccount.Parse(Settings.Cloud.AzureStorageConnectionString())
Return Account.CreateCloudBlobClient()
End Function
...Containers is just an enum of container names (there are several):
Public Enum Containers
CallerWavs
CampaignImports
Delve
Exports
CampaignImages
Logos
ReportLogos
WebLinkImages
End Enum
...Yes, they have upper-case characters, which causes problems. Everything is forced to lowercase before it goes out.
Also I did verify that the correct AzureConnectionString is coming out of my settings class. Again, I tried a few that work elsewhere. And this one works elsewhere also!
Please check the clock on the servers in question. Apart from the incorrect account key, you can also get 403 error if the time on the server is not in sync with the time on storage servers (Give or take +/- 15 minutes deviation is allowed).
I also ran into this error. My problem was that I had turned ON dynamic IP security restrictions in my web.config and the number of files being downloaded in some cases (e.g. with pages with lots of images) was exceeding the max thresholds I had defined in my web.config.
In my case Access key is not same as connection string using by the source code.
So try to recheck on your Azure -> [Storage Account Name] -> Access Keys -> key1 -> Key & Connection string.
I have a web application in a separate server than Active Directory and I want to change a user password. The code is the next:
string newPassword = Membership.GeneratePassword(int.Parse(WebConfigurationManager.AppSettings["passLenght"]),
int.Parse(WebConfigurationManager.AppSettings["passNonAlpha"]));
DirectoryEntry de = new DirectoryEntry(WebConfigurationManager.ConnectionStrings["ADConnString"].ConnectionString,
WebConfigurationManager.AppSettings["ADAdmin"], WebConfigurationManager.AppSettings["ADAdminPass"]);
DirectorySearcher deSearch = new DirectorySearcher(de);
deSearch.Filter = "(&(objectClass=user) (userPrincipalName=" + name + "))";
SearchResultCollection results = deSearch.FindAll();
if (results.Count == 1)
{
foreach (SearchResult OneSearchResult in results)
{
DirectoryEntry AlterUser = OneSearchResult.GetDirectoryEntry();
AlterUser.AuthenticationType = AuthenticationTypes.Secure;
AlterUser.Invoke("SetPassword", newPassword);
AlterUser.CommitChanges();
AlterUser.Close();
}
}
When I run this in my development environment (where Active Directory and the web application are on the same server) it is working. But when I try to run it in the production environment I am having the next error:
Exception has been thrown by the target of an invocation
What am I missing?
Thanks.
EDIT:
I could go deep in the exception error and I get this:
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
Permissions are the issue. The account under which your ASP.NET code is running doesn't have the permission to set the account password.
Either:
Run the AppPool under a user that has the required permissions, or
Use impersonation to elevate the permissions for the SetPassword call
The reason it is working in your dev environment/failing in production is likely due to a combination of:
You are running the app under the Visual Studio development web server that runs under your user account, which has the necessary permissions. Running it under "real" IIS will run it under a less privileged account.
In the live environment there's another machine hop from the web server to the AD server, and the credentials don't get passed along. The web server needs to have network credentials (either as part of the AppPool identity, or a call to LogonUser) in order to authenticate to AD.
The code looks correct. This could be happening because the password your sending though Active Directory does not meet the minimum requirements. Trying using a more complex password such as "M2k3ThisWork!"
If you want to change the password of AD then you use this
AlterUser.Invoke("ChangePassword", OldPassword, newPassword);