SSRS Reporting Server 2019 CustomSecurity extension issue - ssrs-2012

In Microsoft SQL 2016 SSRS, in a CustomSecurity extension, this GetUserInfo method is invoked and HttpContext.Current.User contains data:
public void GetUserInfo(out IIdentity userIdentity, out IntPtr userId)
{
// If the current user identity is not null,
// set the userIdentity parameter to that of the current user
if (HttpContext.Current != null
&& HttpContext.Current.User != null)
{
userIdentity = HttpContext.Current.User.Identity;
}
else
userIdentity = null;
// initialize a pointer to the current user id to zero
userId = IntPtr.Zero;
}
HttpContext.current.User Details for 2016 server
But in Microsoft SQL 2019 SSRS, in the same CustomSecurity extension, another GetUserInfo method (IAuthenticationExtension2) is invoked (by the same method in the client application that sends requests to this CustomSecurity extension) and HttpContext.Current and requestContext.User don’t contain data:
public void GetUserInfo(IRSRequestContext requestContext, out IIdentity userIdentity, out IntPtr userId)
{
userIdentity = null;
if (requestContext.User != null)
{
userIdentity = requestContext.User;
}
// initialize a pointer to the current user id to zero
userId = IntPtr.Zero;
}
IRSRequestContext requestconrext user,cookies and headers going null
Why are HttpContext.Current and requestContext.User null in Microsoft SQL 2019 SSRS CustomSecurity extension? And how can we get userIdentity there in this case?
Expecting sqlAuthCookie generation for user info details, since user authentication is not happening , exception is occurring by the source code as custom security exception is not enabled

Related

ABP.IO - MultiTenancy - Setting Tenant from External IDP

I am trying to configure Auth0 as an external login provider in my ABP.IO application (MVC with integrated identity server). I've got it working so that I can log in fine, but what I can't figure out is how to set the tenant in the ABP side.
What I came up with is a rule on the Auth0 side to populate the TenantId as a claim in the id token, so I can parse that in my custom SingInManager in the GetExternalLoginInfoAsync method, like so:
string tenantId = auth.Principal.FindFirstValue("https://example.com/tenantId");
I'm just having a hard time figuring out what to do with it from there. The assumption is that users will be configured to authenticate via Auth0 and the users will get created locally on first login (which, again, is working EXCEPT for the Tenant part).
Alright, here is the workaround I have in place, and it SHOULD be transferable to any external login system that you are depending on. I'm not sure if this is the correct way of doing this, so if anybody else wants to chip in with a more efficient system I am all ears.
Anyway, my workflow assumes that you have, like I did, created a mechanism for the TenantId to be sent from the external IDP. For this, I used the Organizations feature in Auth0 and added the TenantId as metadata, then I created an Action in Auth0 to attach that metadata as a claim to be used on the ABP side.
In ABP, I followed this article to override the SignInManager: https://community.abp.io/articles/how-to-customize-the-signin-manager-3e858753
As in the article, I overrode the GetExternalLoginInfoAsync method of the sign in manager and added the following lines to pull the TenantId out of the Auth0 claims and add it back in using the pre-defined AbpClaimTypes.TenantId value.
EDIT: I also had to override the ExternalLoginSignInAsync method to account for multi-tenancy (otherwise it kept trying to recreate the users and throwing duplicate email errors). I'll post the full class below with my added stuff in comments:
public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>
{
private const string LoginProviderKey = "LoginProvider";
private const string XsrfKey = "XsrfId";
private readonly IDataFilter _dataFilter;
public CustomSignInManager(
IDataFilter dataFilter,
Microsoft.AspNetCore.Identity.UserManager<Volo.Abp.Identity.IdentityUser> userManager,
Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor,
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser> claimsFactory,
Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor,
Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>> logger,
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes,
Microsoft.AspNetCore.Identity.IUserConfirmation<Volo.Abp.Identity.IdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
_dataFilter = dataFilter;
}
/// <summary>
/// Gets the external login information for the current login, as an asynchronous operation.
/// </summary>
/// <param name="expectedXsrf">Flag indication whether a Cross Site Request Forgery token was expected in the current request.</param>
/// <returns>The task object representing the asynchronous operation containing the <see name="ExternalLoginInfo"/>
/// for the sign-in attempt.</returns>
public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await Context.AuthenticateAsync(IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey(XsrfKey))
{
return null;
}
var userId = items[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = items[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName
?? provider;
/* Begin tenantId claim search */
string tenantId = auth.Principal.FindFirstValue("https://example.com/tenantId"); //pull the tenantId claim if it exists
if(!string.IsNullOrEmpty(tenantId))
{
auth.Principal.Identities.FirstOrDefault().AddClaim(new Claim(AbpClaimTypes.TenantId, tenantId)); //if there is a tenantId, add the AbpClaimTypes.TenantId claim back into the principal
}
/* End tenantId claim search */
var eli = new ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
{
AuthenticationTokens = auth.Properties.GetTokens(),
AuthenticationProperties = auth.Properties
};
return eli;
}
/// <summary>
/// Signs in a user via a previously registered third party login, as an asynchronous operation.
/// </summary>
/// <param name="loginProvider">The login provider to use.</param>
/// <param name="providerKey">The unique provider identifier for the user.</param>
/// <param name="isPersistent">Flag indicating whether the sign-in cookie should persist after the browser is closed.</param>
/// <param name="bypassTwoFactor">Flag indicating whether to bypass two factor authentication.</param>
/// <returns>The task object representing the asynchronous operation containing the <see name="SignInResult"/>
/// for the sign-in attempt.</returns>
public override async Task<SignInResult> ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, bool bypassTwoFactor)
{
Volo.Abp.Identity.IdentityUser user = null; //stage the user variable as null
using (_dataFilter.Disable<IMultiTenant>()) //disable the tenantid filters so we can search all logins for the expected key
{
user = await UserManager.FindByLoginAsync(loginProvider, providerKey); //search logins for the expected key
}
if (user == null)
{
return SignInResult.Failed;
}
var error = await PreSignInCheck(user);
if (error != null)
{
return error;
}
return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor);
}
}
Once that was done, I tracked down where the GetExternalLoginInfoAsync was being utilized and figured out I had to override the CreateExternalUserAsync method inside of the LoginModel for the Login page. To that end, I followed the directions in this article for creating a CustomLoginModel.cs and Login.cshtml : https://community.abp.io/articles/hide-the-tenant-switch-of-the-login-page-4foaup7p
So, my Auth0LoginModel class looks like this:
public class Auth0LoginModel : LoginModel
{
public Auth0LoginModel(IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IOptions<IdentityOptions> identityOptions) : base(schemeProvider, accountOptions, identityOptions)
{
}
protected override async Task<IdentityUser> CreateExternalUserAsync(ExternalLoginInfo info)
{
await IdentityOptions.SetAsync();
var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email);
/* Begin TenantId claim check */
var tenantId = info.Principal.FindFirstValue(AbpClaimTypes.TenantId);
if (!string.IsNullOrEmpty(tenantId))
{
try
{
CurrentTenant.Change(Guid.Parse(tenantId));
}
catch
{
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = "Unable to parse TenantId: " + tenantId
}) ;
}
}
/* End TenantId claim check */
var user = new IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id);
CheckIdentityErrors(await UserManager.CreateAsync(user));
CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress));
CheckIdentityErrors(await UserManager.AddLoginAsync(user, info));
CheckIdentityErrors(await UserManager.AddDefaultRolesAsync(user));
return user;
}
}
The code added is between the comments, the rest of the method was pulled from the source. So I look for the AbpClaimTypes.TenantId claim being present, and if it does I attempt to use the CurrentTenant.Change method to change the tenant prior to the call to create the new IdentityUser.
Once that is done, the user gets created in the correct tenant and everything flows like expected.

invalid_grant of OAuthAuthorizationServerProvider

I'm working on writing fully customized ASP.NET Identity for my WebAPi.
I have rewritten my own derived OAuthAuthorizationServerProvider in this way:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Check User availability ...
//ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
// if i couldn't found user in my DataBase ...
//if (user == null)
//{
//context.SetError("invalid_grant", "The user name or password is incorrect.");
// return;
//}
context.Validated();
}
}
GrantResourceOwnerCredentials just returns an invalid_grant error for each calls. i want to handle it but, i don't know how.
ValidateClientAuthentication is where you would do your authentication checks and this is where you throw errors if anything doesn't match.
move your code there and do the checks before you call context.Validated(). You only call the Validate method once you make sure everything is validated correctly.
here is an example of such an implementation I did a while back:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
//first try to get the client details from the Authorization Basic header
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
//no details in the Authorization Header so try to find matching post values
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("client_not_authorized", "invalid client details");
return Task.FromResult<object>(null);
}
var dataLayer = new RepoManager(new DataLayerDapper()).DataLayer;
var audienceDto = dataLayer.GetAudience(clientId);
if (audienceDto == null || !clientSecret.Equals(audienceDto.Secret))
{
context.SetError("unauthorized_client", "unauthorized client");
return Task.FromResult<object>(null);
}
context.Validated();
return Task.FromResult<object>(null);
}
Notice how the checks happen in order and certain errors are raised with some appropriate errors.
This code takes a client id and client secret from an authorization header but you can easily drop all that and replace it with your own checks and database calls.
The important part is that this is where you deal with stuff like this and this is where you set the errors so your clients know what's going on.
GrantResourceOwnerCredentials this is where you get once the call is properly authenticated, at which point you can start creating tokens, adding claims and creating the authentication ticket. This method does not get hit if the previous one fails to authenticate the request.
Here is a working example:
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim("clientID", context.ClientId));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"audience", context.ClientId
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
return Task.FromResult<object>(null);
}
Now, if you get an invalid grant error that usually happens because you either didn't set up the grant_type in your initial call or you set up the wrong value.
in my case I had to setup this:
"grant_type", "password"

Generate Access Token In Web Api action method using OWIN and IIS host

I'm trying to generate a token inside Web Api action method based on the code below:
private JObject GeneratePaymentTokenResponse(string email, bool rememberMe)
{
//var tokenExpiration = rememberMe ? TimeSpan.FromDays(14) : TimeSpan.FromMinutes(30);
var tokenExpiration = rememberMe ? TimeSpan.FromMinutes(30) : TimeSpan.FromMinutes(5);
ClaimsIdentity identity = new ClaimsIdentity("CustomType", ClaimTypes.Email, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.Email, email));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration)
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("email", email),
new JProperty("customToken", accessToken),
new JProperty("expiresIn", tokenExpiration.TotalSeconds),
new JProperty("issuedUtc", ticket.Properties.IssuedUtc),
new JProperty("expiresUtc", ticket.Properties.ExpiresUtc)
);
return tokenResponse;
}
The OAuthBeaerOptions object is coming from the Startup class as the below:
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
// Token Consumption (Resource Server)
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
Now when I try to pass a valid access token but has been expired and call AccessTokenFormat.Unprotect as the code below
Microsoft.Owin.Security.AuthenticationTicket ticket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(paymentToken);
if ((ticket == null) || (!ticket.Identity.IsAuthenticated))
{
actionContext.Response = CreateForbiddenResponse(actionContext);
return Task.FromResult<object>(null);
}
I'm receiving a valid ticket and the value of ticket.Identity.IsAuthenticated is true even that token is expired.
Currently I'm using the latest version (3.0.1) of Microsoft.Owin.Security assembly
I would appreciate any clue on how to set the expiry date for this token?
I'm receiving a valid ticket and the value of ticket.Identity.IsAuthenticated is true even that token is expired.
That's totally intended: Unprotect will return a ticket with a valid ClaimsIdentity even if it is expired. Since ClaimsIdentity.IsAuthenticated only ensures the ClaimsIdentity.AuthenticationType property is not null, it's not a reliable way to ensure the ticket is not expired.
Actually, it's up to you to determine whether the ticket is still valid and return an error if necessary (which is exactly what the bearer middleware does internally when receiving an access token: https://github.com/jchannon/katanaproject/blob/master/src/Microsoft.Owin.Security.OAuth/OAuthBearerAuthenticationHandler.cs#L68-L73)
if (ticket.Properties.ExpiresUtc.HasValue &&
ticket.Properties.ExpiresUtc.Value < DateTimeOffset.Now)
{
return Task.FromResult<object>(null);
}

.net web executable to .net web api 401 error using BASIC authentication

I am not allowed to install fiddler at work so I am kind of flying blind.
I am running the web api and the web executable on local host through two separate instances of visual studio
I am fairly certain my Web API is working ok I type the URL manually into a web browser it asks me for user Id and password and then returns my JSON.
The web executable that calls the web api also works fine until I attempted to add BASIC authentication to the controller method now I am receiving a 401 error.
here is my code from the executable.
Public Function get_vsmric_webApi(ByRef sErrorDescription As String) As Boolean
Try
Using proxy As New WebClient()
Dim myurl As String = ConfigurationManager.AppSettings("WEBAPI_URL") & "vsmric"
Dim userName As String = "QBERT"
Dim passWord As String = "Qb3RT!"
Dim credentials As String = Convert.ToBase64String(Encoding.ASCII.GetBytes(userName + ":" + passWord))
proxy.Headers(HttpRequestHeader.Authorization) = "BASIC" + credentials
Dim json As String = proxy.DownloadString(myurl)
Dim rics As List(Of DB2VSMRIC) = JsonConvert.DeserializeObject(Of List(Of DB2VSMRIC))(json)
Dim list As List(Of DB2VSMRIC) = rics.Where(Function(p) HasData(p.Cage)).ToList
If list.Count < 1 Then
sErrorDescription = "No VSMRIC w/Cage records found."
Else
dictShipFrom = New Dictionary(Of String, String)
dictShipFrom = list.ToDictionary(Function(p) p.Ric, Function(p) p.Dodaac)
dictCage = New Dictionary(Of String, String)
dictCage = list.ToDictionary(Function(p) p.Ric, Function(p) p.Cage)
End If
End Using
Catch ex As Exception
sErrorDescription = "Exception in get_vsmric_webApi(), " & ex.Message
Return False
Finally
End Try
Return True
End Function
here is the controller method on the web api
[CustomAuthentication]
[CustomAuthorization("qbert")]
public class VSMRICController : ApiController
{
/// <summary>
/// Returns all records in the DB2 VSM RIC table
/// </summary>
/// <param name="id">The ID of the data.</param>
public IEnumerable<DB2VSMRIC> Get()
{
return DB2VSMRICRepository.getAll();
}
here is the filter (for authentication)
public class CustomAuthenticationAttribute : Attribute, IAuthenticationFilter
{
// the job of the AuthenticateAsync method is to examine the request to see whether it contains
// the information that is required to identify a user. Information about the request is provided
// through an instance of the HttpAuthenticationContext class.
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
context.Principal = null;
AuthenticationHeaderValue authentication = context.Request.Headers.Authorization;
if (authentication != null && authentication.Scheme == "Basic")
{
string[] authData = Encoding.ASCII.GetString(Convert.FromBase64String(
authentication.Parameter)).Split(':');
context.Principal
= ApiManager.AuthenticateUser(authData[0], authData[1]);
}
if (context.Principal == null)
{
context.ErrorResult
= new UnauthorizedResult(new AuthenticationHeaderValue[]{
new AuthenticationHeaderValue("Basic")}, context.Request);
}
return Task.FromResult<object>(null);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
return Task.FromResult<object>(null);
}
public bool AllowMultiple
{
get { return false; }
}
}
Again I'm fairly confident the Web API is working fine as I can get to JSON by directly navigating to the url and providing credentials in any web browser. I'm thinking I am doing something wrong when I set up the header in the executable. any thoughts? (I am running everything locally via 2 instance of visual studio)
The problem is on the line where you set the basic authentication. It should be
... = "Basic " + credentials
instead of
... = "BASIC" + credentials
Case sensitive and a space.
Happy coding.

How to get user context during Web Api calls?

I have an web front end calling an ASP Web Api 2 backend. Authentication is managed with ASP Identity. For some of the controllers I'm creating I need to know the user making the call. I don't want to have to create some weird model to pass in including the user's identity (which I don't even store in the client).
All calls to the API are authorized using a bearer token, my thought is the controller should be able to determine the user context based on this but I do not know how to implement. I have searched but I don't know what I'm searching for exactly and haven't found anything relevant. I'm going for something like...
public async Task<IHttpActionResult> Post(ApplicationIdentity identity, WalkthroughModel data)
Update
I found the below which looked very promising... but the value is always null! My controller inherits from ApiController and has an Authorize header.
var userid = User.Identity.GetUserId();
Update 2
I have also tried all of the solutions in Get the current user, within an ApiController action, without passing the userID as a parameter but none work. No matter what I am getting an Identity that is valid and auth'd, but has a null UserID
Update 3
Here's where I'm at now.
[Authorize]
[Route("Email")]
public async Task<IHttpActionResult> Get()
{
var testa = User.Identity.GetType();
var testb = User.Identity.GetUserId();
var testc = User.Identity.AuthenticationType;
var testd = User.Identity.IsAuthenticated;
return Ok();
}
testa = Name: ClaimsIdentity,
testb = null,
testc = Bearer,
testd = true
The user is obviously authenticated but I am unable to retrieve their userID.
Update 4
I found an answer, but I'm really unhappy with it...
ClaimsIdentity identity = (ClaimsIdentity)User.Identity;
string username = identity.Claims.First().Value;
That gets me the username without any db calls but it seems very janky and a pain to support in the future. Would love if anyone had a better answer.
What if I need to change what claims are issued down the road? Plus any time I actually need the user's id I have to make a db call to convert username to ID
A common approach is to create a base class for your ApiControllers and take advantage of the ApplicationUserManager to retrieve the information you need. With this approach, you can keep the logic for accessing the user's information in one location and reuse it across your controllers.
public class BaseApiController : ApiController
{
private ApplicationUser _member;
public ApplicationUserManager UserManager
{
get { return HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
}
public string UserIdentityId
{
get
{
var user = UserManager.FindByName(User.Identity.Name);
return user.Id;
}
}
public ApplicationUser UserRecord
{
get
{
if (_member != null)
{
return _member ;
}
_member = UserManager.FindByEmail(Thread.CurrentPrincipal.Identity.Name);
return _member ;
}
set { _member = value; }
}
}
I use a custom user authentication (I dont use AspIdentity because my existing user table fields was far different from IdentityUser properties) and create ClaimsIdentity passing my table UserID and UserName to validate my bearer token on API calls.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
User user;
try
{
var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
_service = scope.Resolve<IUserService>();
user = await _service.FindUserAsync(context.UserName);
if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
catch (Exception ex)
{
context.SetError("invalid_grant", ex.Message);
return;
}
var properties = new Dictionary<string, string>()
{
{ ClaimTypes.NameIdentifier, user.UserID.ToString() },
{ ClaimTypes.Name, context.UserName }
};
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
properties.ToList().ForEach(c => identity.AddClaim(new Claim(c.Key, c.Value)));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties(properties));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(identity);
}
And how I use the ClaimsIdentity to retrieve my User table details on User ApiController Details call.
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("Details")]
public async Task<IHttpActionResult> Details()
{
var user = await _service.GetAsync(RequestContext.Principal.Identity.GetUserId<int>());
var basicDetails = Mapper.Map<User, BasicUserModel>(user);
return Ok(basicDetails);
}
Notice the
ClaimTypes.NameIdentifier = GetUserId() and ClaimTypes.Name = GetUserName()

Resources