NullReferenceException in DotNetOpenAuth - asp.net-mvc

I tracked down what appears to be a defect causing a NullReferenceException in my ASP.NET MVC app. Did I break this, or should this break DotNetOpenAuth for the majority of installations?
I get this:
[NullReferenceException: Object reference not set to an instance of an object.]
DotNetOpenAuth.AspNet.OpenAuthSecurityManager.GetUsername(HttpContextBase context) +27
DotNetOpenAuth.AspNet.OpenAuthSecurityManager.RequestAuthentication(String returnUrl) +341
Controllers.LoginController.Index() +226
Here's the Index() method. Note it currently returns User in production and OK in development. Those are obviously temporary. The commented code is from the last URL I referenced below, where I reduced the amount of code between me and the problem. The stack trace was still similar, though.
public virtual ActionResult Index()
{
if (HttpContext == null) return Content("HttpContext");
if (HttpContext.User == null) return Content("User");
if (HttpContext.User.Identity == null) return Content("Identity");
return Content("OK");
new OpenAuthSecurityManager(HttpContext, s_fbClient, OAuthDataProvider.Instance)
.RequestAuthentication(Url.Action(Actions.Callback()));
// OAuthWebSecurity
// .RequestAuthentication("facebook", Url.Action(Actions.Callback()));
return null;
}
The exception arises because HttpContext.User is null. Here's the DotNetOpenAuth.AspNet library source for that failing method.
private static string GetUsername(HttpContextBase context) {
string username = null;
if (context.User.Identity.IsAuthenticated) {
username = context.User.Identity.Name;
}
return username ?? string.Empty;
}
User has apparently has been nullable ever since IIS Integrated Mode was available. This explains why I don't see it in development, as I'm running IIS Express with defaults, but it doesn't explain why I can't find any information about the defect. Integrated mode was released in 2007, and DotNetOpenAuth is still maintained. Microsoft docs say this about the setting:
Classic Mode: Use this mode only when the applications in the
application pool cannot run in Integrated mode.
I must be missing something, because it seems like everyone should have this issue.
Have I NuGet'ed a non-maintained library somehow? It seems odd, since it shows it was just updated a week ago. But when I follow the documentation link from NuGet, the source code I arrive at doesn't seem to even have an AspNet namespace, where my exception arose.
EDIT: The only related package I use is currently DotNetOpenAuth.AspNet (and it has 8 dependencies), last published less than a week ago. I've tried other packages also. I don't need SimpleAuth or any WebMatrix jazz. In the process of resolving this, I tried switching libraries as described http://techblog.dorogin.com/2013/06/Microsoft.AspNet.WebPages.OAuth.html
EDIT: Logged a defect related to this, but it seems this library may not be maintained. https://github.com/DotNetOpenAuth/DotNetOpenAuth/issues/317#issuecomment-29580565
EDIT: Pending stack trace, it may be the same defect as MVC 5 Owin Facebook Auth results in Null Reference Exception

This is indeed a defect in the DotNetOpenAuth.AspNet code. Unfortunately that DotNetOpenAuth library is no longer maintained. Modifying the source with:
private static string GetUsername(HttpContextBase context) {
string username = null;
if (context.User != null && context.User.Identity.IsAuthenticated) {
username = context.User.Identity.Name;
}
return username ?? string.Empty;
}
Of course does the trick.

Related

Microsoft-Graph SDK for .NET is not returning odata.nextLink after upgrading from v1.19 to latest

When I make a request to Graph API I get in the response the "#odata.nextLink", if I use .NET SDK v1.19 I still get the value in the AdditionalData and there is also a NextPageRequest property which has an instance to make a request to the next page. But after upgrading to the latest version 4.53, using the same query I get the response with the NextPageRequest instance but not the "#odata.nextLink" in the AdditionalData.
I took a look at the release notes and there is nothing mentioning this change.
I could take the info from the NextPageRequest but I would like to understand what is actually happening here.
I found the official document to recommend using PageIterator for Paging, so I checked the source code for different version and I think this can answer your question in some terms. I mean this is recommended by Microsoft and the way it used to get next page value should explain something....
And this is what I found in SDK version
dynamic page = _currentPage;
// There are more pages ready to be paged.
if (page.NextPageRequest != null)
{
Nextlink = page.NextPageRequest.GetHttpRequestMessage().RequestUri.AbsoluteUri;
return true;
}
And this is what I see in V1.19
if (_currentPage.AdditionalData.TryGetValue("#odata.nextLink", out var value))
{
Nextlink = value as string;
return true;
}
I found the reason for this issue, commit: https://github.com/microsoftgraph/msgraph-sdk-dotnet/commit/834549348081869cc4b97d9ead6dbbf12a516264?diff=split class GraphServiceUsersCollectionResponse
[JsonPropertyName("#odata.nextLink")]
public string NextLink { get; set; }
The addition of the above property removed the odata.nextLink from the AdditionalData dictionary. This is an old commit, I'm not sure why this has not been reported as a breaking change.
I'm going to open a ticket directly to the project.

Async Function Fails when called as part of a Constructor

I'm rather new to Blazor, but I am currently trying to get access to some classes from within a class library that I've created and deployed as a Nuget package. As background, the Nuget package is an Api library, which allows me to talk to a webservice (I don't know if this is relevant or not). However, every time I go to the page where I'm testing, the page never loads and instead I left looking at the browser loading circle until I navigate away or close the application. During my testing here, it seems like it's the #inject call of my interface into the Blazor component which is causing the issue as when I remove it and try to load the page normally, the page does so.
So to demonstrate what I have setup, here is where I've added the Singletons to the DI:
builder.Services.AddSingleton<IApiConfigHelper, ApiConfigHelper>();
builder.Services.AddSingleton<IApiHelper, ApiHelper>();
builder.Services.AddSingleton<ISystemEndpoint, SystemEndpoint>();
Then on the blazor page, I have the following declarations at the top of my page:
#using Library.Endpoints
#using Library.Models
#page "/"
#inject ISystemEndpoint _systemEndpoint
Now I am leaning towards is this something to do with the Nuget package and using it with DI. I have tested the library away from this project (In a console application) and can confirm it's working as it should.
I have also created a local class library as a test to, to see if I could inject a data access class into the page and I can confirm that this works without an issue, which suggests to me that DI is working, just not with my Nuget package.
I did have a look into CORS, given that the Nuget package is accessing an external domain, and setup the following simple CORS policy in the app:
builder.Services.AddCors(policy =>
{
policy.AddPolicy("OpenCorsPolicy", opt =>
opt.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
Which is added to the app after the AddRouting call like so:
app.UseCors("OpenCorsPolicy");
However again, this wasn't the solution so if anyone is able to point me in the right direction with where I may be going wrong with this or offer any advice, I would be most grateful.
EDIT 1 - Provides details #mason queried
Regarding SystemEndpoint, the constructor is being injected with 2 things, as below:
public SystemEndpoint(IApiHelper apiHelper, IOptions<UriConfigModel> uriOptions)
{
_apiHelper = apiHelper;
_uriOptions = uriOptions.Value;
}
My Nuget Library is dependant on the following:
Azure.Identity
Azure.Security.KeyVault.Secrets
Microsoft.AspNet.WebApi.Client
Microsoft.Extensisons.Options.ConfigurationExtensions
EDIT 2 - Doing some further testing with this I have added a simple Endpoint class to my Nuget library, which returns a string with a basic message, as well as returning the values of the 2 UriConfig properties as below. I added this test to 1) sanity check that my DI was working correctly, and 2) check the values that are being assigned from appsettings to my UriConfig Object.
public class TestEndpoint : ITestEndpoint
{
private readonly IOptions<UriConfigModel> _uriConfig;
public TestEndpoint(IOptions<UriConfigModel> uriConfig)
{
_uriConfig = uriConfig;
}
public string TestMethod()
{
return $"You have successfully called the test method\n\n{_uriConfig.Value.Release} / {_uriConfig.Value.Version}";
}
}
However when adding in the dependency of IApiHelper into the Ctor, the method then breaks and fails to load the page. Looking into ApiHeloer, the Ctor has a dependency being injected into it of IApiConfigHelper. Looking at the implementation, the Ctor of ApiConfigHelper is setting up the values and parameters of the HttpClient that should make the REST calls to the external Api.
Now I believe what is breaking the code at this point is a call I'm making to Azure Key Vault, via REST, to pull out the secret values to connect to the Api. The call to KeyVault is being orchestrated via the following method, making use of the Azure.Security.KeyVault.Secrets Nuget Package, however I assume that at the heart of it, it's making a REST call to Azure on my behalf:
private async Task<KeyVaultSecret> GetKeyVaultValue(string secretName = "")
{
try
{
if (_secretClient is not null)
{
var result = await _secretClient.GetSecretAsync(secretName);
return result.Value;
}
}
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
}
catch (Azure.RequestFailedException rfe)
{
Console.WriteLine(rfe.Message);
}
return new(secretName, "");
}
So that's where I stand with this at the moment. I still believe it could be down to CORS, as it seems to be falling over when making a call to an external service / domain, but I still can say 100%. As a closing thought, could it be something as simple as when I call call the above method, it's not being awaited????
So after persisting with this it seems like the reason it was failing was down to "awaiting" the call to Azure KeyVault, which was happening indirectly via the constructor of ApiConfigHelper. The resulting method for getting KeyVault value is now:
private KeyVaultSecret GetKeyVaultValue(string secretName = "")
{
try
{
if (_secretClient is not null)
{
var result = _secretClient.GetSecret(secretName);
if (result is not null)
{
return result.Value;
}
}
}
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
}
catch (Azure.RequestFailedException rfe)
{
Console.WriteLine(rfe.Message);
}
return new(secretName, "");
}
I am now able to successfully make calls to my library and return values from the Api it interacts with.
I can also confirm that this IS NOT a CORS issue. Once I saw that removing the await was working, I then removed the CORS policy declarations from the service and the app in my Blazor's start-up code and everything continued to work without an issue.
As a final note, I must stress that this is only seems an issue when using the library with Blazor (possibly webApi projects) as I am able to use the library, awaiting the Azure call just fine in a console application.

Sustainsys SAML2 Sample for ASP.NET Core WebAPI without Identity

Does anyone have a working sample for Sustainsys Saml2 library for ASP.NET Core WebAPI only project (no Mvc) and what's more important without ASP Identity? The sample provided on github strongly relies on MVC and SignInManager which I do not need nor want to use.
I added Saml2 authentication and at first it worked fine with my IdP (I also checked the StubIdP provided by Sustainsys) for first few steps so:
IdP metadata get properly loaded
My API properly redirects to sign-in page
Sign-in page redirects to /Saml2/Acs page, and I see in the logs that it parses the result successfully
However I don't know how to move forward from there and extract user login and additional claims (my IdP provided also an e-mail, and it is included in SAML response which I confirmed in the logs).
Following some samples found on the web and modyfing a little bit the MVC Sample from GitHub I did the following:
In Startup.cs:
...
.AddSaml2(Saml2Defaults.Scheme,
options =>
{
options.SPOptions.EntityId = new EntityId("...");
options.SPOptions.ServiceCertificates.Add(...));
options.SPOptions.Logger = new SerilogSaml2Adapter();
options.SPOptions.ReturnUrl = new Uri(Culture.Invariant($"https://localhost:44364/Account/Callback?returnUrl=%2F"));
var idp =
new IdentityProvider(new EntityId("..."), options.SPOptions)
{
LoadMetadata = true,
AllowUnsolicitedAuthnResponse = true, // At first /Saml2/Acs page throwed an exception that response was unsolicited so I set it to true
MetadataLocation = "...",
SingleSignOnServiceUrl = new Uri("...") // I need to set it explicitly because my IdP returns different url in the metadata
};
options.IdentityProviders.Add(idp);
});
In AccountContoller.cs (I tried to follow a somewhat similar situation described at how to implement google login in .net core without an entityframework provider):
[Route("[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
private readonly ILog _log;
public AccountController(ILog log)
{
_log = log;
}
[HttpGet("Login")]
[AllowAnonymous]
public IActionResult Login(string returnUrl)
{
return new ChallengeResult(
Saml2Defaults.Scheme,
new AuthenticationProperties
{
// It looks like this parameter is ignored, so I set ReturnUrl in Startup.cs
RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
});
}
[HttpGet("Callback")]
[AllowAnonymous]
public async Task<IActionResult> LoginCallback(string returnUrl)
{
var authenticateResult = await HttpContext.AuthenticateAsync(Constants.Auth.Schema.External);
_log.Information("Authenticate result: {#authenticateResult}", authenticateResult);
// I get false here and no information on claims etc.
if (!authenticateResult.Succeeded)
{
return Unauthorized();
}
// HttpContext.User does not contain any data either
// code below is not executed
var claimsIdentity = new ClaimsIdentity(Constants.Auth.Schema.Application);
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
_log.Information("Logged in user with following claims: {#Claims}", authenticateResult.Principal.Claims);
await HttpContext.SignInAsync(Constants.Auth.Schema.Application, new ClaimsPrincipal(claimsIdentity));
return LocalRedirect(returnUrl);
}
TLDR: Configuration for SAML in my ASP.NET Core WebApi project looks fine, and I get success response with proper claims which I checked in the logs. I do not know how to extract this data (either return url is wrong or my callback method should work differently). Also, it is puzzling why successfuly redirect from SSO Sign-In page is treated as "unsolicited", maybe this is the problem?
Thanks for any assistance
For anyone who still needs assistance on this issue, I pushed a full working example to github which uses a .Net Core WebAPI for backend and an Angular client using the WebAPI. you can find the example from here:
https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample
As it turned out, the various errors I've been getting were due to my solution being hosted inside docker container. This caused a little malfunction in internal aspnet keychain. More details can be found here (docker is mentioned almost at the end of the article):
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?tabs=aspnetcore2x&view=aspnetcore-2.2
Long story short, for the code to be working I had to add only these lines:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("/some/volume/outside/docker")); // it needs to be outside container, even better if it's in redis or other common resource
It fixed everything, which includes:
Sign-in action to external cookie
Unsolicited SSO calls
Exceptions with data protection key chain
So it was very difficult to find, since exceptions thrown by the code didn't point out what's going on (and the unsolicited SSO calls made me think that the SSO provider was wrongly configured). It was only when I disassembled the Saml2 package and tried various code pieces one by one I finally encoutered proper exception (about the key chain) which in turned led me to an article about aspnet data protection.
I provide this answer so that maybe it will help someone, and I added docker tag for proper audience.

MVC: A public method on my controller is called OK in my development server, but not the testing server

I am completely baffled by this.
I have a public method on my controller which works on my development machine. But when I deploy the app I get an error message saying the method is not found;
[HttpGet]
[Authorize(Roles = "Administrator, AdminIT, ManagerIT")]
public ActionResult ListExistingIT(GridSortOptions sort, int? page)
{
if (Request.QueryString["lastPersonMessage"] == null)
ViewData["LastPersonMessage"] = string.Empty;
else
ViewData["LastPersonMessage"] = Request.QueryString["lastPersonMessage"];
EmployeeListViewModel elvm = new EmployeeListViewModel();
elvm.EmployeeList = EmployeeExtended.GetITEmployees();
if (sort.Column != null)
{
elvm.EmployeeList = elvm.EmployeeList.OrderBy(sort.Column, sort.Direction);
}
elvm.EmployeeList = elvm.EmployeeList.AsPagination(page ?? 1, Utility.GetPageLength());
ViewData["sort"] = sort;
return View(elvm);
}
The error message is; System.Web.HttpException: A public action method 'ListExistingIT' was not found on controller 'SHP.Controllers.EmployeeController'.
Now you might think that IIS is not picking up the latest deployment. However I make a change elsewhere and deploy it, and that works. I also restart IIS as well.
I cannot imagine how this happens, or how to detect where the error could be.
There is plenty of discussion on a similar (the same?) issue here on SO:Intermittent asp.net mvc exception: “A public action method ABC could not be found on controller XYZ.”
I can think of 3 different possibilities:
A conflict with the HttpVerb
causing the method not to be found.
A conflict with the filters applied
causing the method to be avoided.
A routing issue, but this one is
probably the last possibility. You
may want to try testing with the
RouteDebugger and see what that
shows you.

On Redirect - Failed to generate a user instance of SQL Server

Hello (this is a long post sorry),
I am writing a application in ASP.NET MVC 2 and I have reached a point where I am receiving this error when I connect remotely to my Server.
Failed to generate a user instance of SQL Server due to failure in retrieving the user's local application data path. Please make sure the user has a local user profile on the computer. The connection will be closed.
I thought I had worked around this problem locally, as I was getting this error in debug when site was redirected to a baseUrl if a subdomain was invalid using this code:
protected override void Initialize(RequestContext requestContext)
{
string[] host = requestContext.HttpContext.Request.Headers["Host"].Split(':');
_siteProvider.Initialise(host, LiveMeet.Properties.Settings.Default["baseUrl"].ToString());
base.Initialize(requestContext);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Site == null)
{
string[] host = filterContext.HttpContext.Request.Headers["Host"].Split(':');
string newUrl;
if (host.Length == 2)
newUrl = "http://sample.local:" + host[1];
else
newUrl = "http://sample.local";
Response.Redirect(newUrl, true);
}
ViewData["Site"] = Site;
base.OnActionExecuting(filterContext);
}
public Site Site
{
get
{
return _siteProvider.GetCurrentSite();
}
}
The Site object is returned from a Provider named siteProvider, this does two checks, once against a database containing a list of all available subdomains, then if that fails to find a valid subdomain, or valid domain name, searches a memory cache of reserved domains, if that doesn't hit then returns a baseUrl where all invalid domains are redirected.
locally this worked when I added the true to Response.Redirect, assuming a halting of the current execution and restarting the execution on the browser redirect.
What I have found in the stack trace is that the error is thrown on the second attempt to access the database.
#region ISiteProvider Members
public void Initialise(string[] host, string basehost)
{
if (host[0].Contains(basehost))
host = host[0].Split('.');
Site getSite = GetSites().WithDomain(host[0]);
if (getSite == null)
{
sites.TryGetValue(host[0], out getSite);
}
_site = getSite;
}
public Site GetCurrentSite()
{
return _site;
}
public IQueryable<Site> GetSites()
{
return from p in _repository.groupDomains
select new Site
{
Host = p.domainName,
GroupGuid = (Guid)p.groupGuid,
IsSubDomain = p.isSubdomain
};
}
#endregion
The Linq query ^^^ is hit first, with a filter of WithDomain, the error isn't thrown till the WithDomain filter is attempted.
In summary: The error is hit after the page is redirected, so the first iteration is executing as expected (so permissions on the database are correct, user profiles etc) shortly after the redirect when it filters the database query for the possible domain/subdomain of current redirected page, it errors out.
Make sure App Pool is set to NetworkService if under windows 7 and IIS 7.5
I searched long and hard for this one, it was my error all along. It seem that at sometime someone decided to change my applciationpool settings and changed the applicationpool for my application. But for some strange reason it didn't impact me until I tried to use the redirection code.
Before adding the code all databases connected without any issues and data was pulled without error, but afterwords it didn't, so after looking at my appool, I noticed it was not set to networkservice any longer, switched, fixed.
So maybe this question should be, why would database connections work before the new code, but not after while not under networdservice user?

Resources