Base solution for your next web application

Activities of "hra"

A supplement to @ismcagdas's workaround - he's also supplied some additional code - thanks:

protected virtual void FillClaimsFromJObject(ExternalAuthUserInfo userInfo, JObject payload) { var dict = new Dictionary<string, string>(); foreach (var item in payload) { dict.Add(item.Key,item.Value?.ToString()); } userInfo.Claims = dict; }

You can use the class MicrosoftAuthProviderApi in the TokenAuthController's (for angular project) ExternalAuthenticate method. Just check for (input.AuthProvider == "Microsoft") and if it is true, create a new instance of MicrosoftAuthProviderApi and get the user info from your custom class.

If you are using the MVC version of AspNet Zero, you can do it in AccountController.

Prerequisites

  • AspNetZero 10

We have enabled Microsoft authentication, and have had users report that they get an error while attempting to associate their Microsoft account with our product.

We tracked it back to the fact that some of the users do not have a "Last Name" set in their Microsoft accounts.

This is the error in AspNetZero. You can clearly see that AspNetZero demands a last name, which is inherently incompatible with Microsoft accounts.

RROR 2021-10-19 03:01:25,045 [6    ] Mvc.ExceptionHandling.AbpExceptionFilter - An error occurred while updating the entries. See the inner exception for details.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
 ---> Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert the value NULL into column 'Surname', table 'cloudrisk-staging.dbo.AbpUsers'; column does not allow nulls. INSERT fails.
The statement has been terminated.
   at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__169_0(Task`1 result)
   at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
   at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
ClientConnectionId:a272394f-be75-4a1d-ab5b-4f6838ec0bdd
Error Number:515,State:2,Class:16
ClientConnectionId before routing:e3fa7b20-81ee-4edd-9aa2-c99dfb447e78
Routing Destination:b8b26ffad02d.tr1.australiacentral1-a.worker.database.windows.net,11020
   --- End of inner exception stack trace ---
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(DbContext _, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Abp.EntityFrameworkCore.AbpDbContext.SaveChangesAsync(CancellationToken cancellationToken)
   at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext`3.SaveChangesAsync(CancellationToken cancellationToken)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContextAsync(DbContext dbContext)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesAsync()
   at Abp.Authorization.Users.AbpUserStore`2.CreateAsync(TUser user, CancellationToken cancellationToken)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation)
   at Microsoft.AspNetCore.Identity.UserManager`1.CreateAsync(TUser user)
   at Abp.Authorization.Users.AbpUserManager`2.CreateAsync(TUser user)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation)
   at Microsoft.AspNetCore.Identity.UserManager`1.CreateAsync(TUser user, String password)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation)
   at HRA.Portal.Authorization.Users.UserRegistrationManager.RegisterAsync(String name, String surname, String emailAddress, String userName, String plainPassword, Boolean isEmailConfirmed, String emailActivationLink) in /home/vsts/work/1/s/aspnet-core/src/HRA.Portal.Core/Authorization/Users/UserRegistrationManager.cs:line 87
   at HRA.Portal.Web.Controllers.TokenAuthController.RegisterExternalUserAsync(ExternalAuthUserInfo externalLoginInfo) in /home/vsts/work/1/s/aspnet-core/src/HRA.Portal.Web.Core/Controllers/TokenAuthController.cs:line 572
   at HRA.Portal.Web.Controllers.TokenAuthController.ExternalAuthenticate(ExternalAuthenticateModel model) in /home/vsts/work/1/s/aspnet-core/src/HRA.Portal.Web.Core/Controllers/TokenAuthController.cs:line 490
   at lambda_method2047(Closure , Object )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeActionMethodAsync&gt;g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

Hi @ismcagdas,

I'm on version 10.0 of Core/Angular.

Whats the justification for using OpenID Conect vs Microsoft?

@ismcagdas - I'm pretty sure this is contributing to our issue here: https://support.aspnetzero.com/QA/Questions/10620/Setting-up-Microsoft-Azure-AD-Login

Where did this end up?

I'm setting up Azure A/D authentication integation to our AspNetZero Angular/Core application.

After reading this document: https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant

I found that all I allegedly need to do is enable Microsoft (social) logins - which AspNetZero supports. https://docs.aspnetzero.com/en/aspnet-core-angular/latest/Features-Angular-Social-Logins

I did so, and the "Microsoft" button appears on the tenant login page. Great. However, after using that button to log into my Microsoft account, the AspNetZero page shows an error popup "Could not validate external user login".

I'm guesing there is some step I am missing? But I don't know what it is. Something method I'm supposed to override in the .Net Core code?

The following documentation explains what should happen if an external account is and isn't identified for EXTERNAL authentication - but I'm not sure if that should apply to Microsoft auth as your documentation identifies that as "Social" authentication. https://aspnetboilerplate.com/Pages/Documents/Zero/User-Management

Anyway, the missing knowledge is... I ASSUME that one should only be able to "log in" with a Microsoft account that was used to first CREATE an AspNetZero user account... however, when I visit the tenant "create account" page, there is no "Create with Microsoft" button...

What am I missing?

Thanks,

--- UPDATE --- I've found something in the TokenAuthController ProviderKey comparison which looks likely to be a bug... confirm? Note how the provider key in the UserInfo object differs from the provider key in the Model by only the hypens being stripped (see watch window in screenshot). Bug?

--- UPDATE 2 --- Using the inspector, I updated the ProviderKey to match such that the comparison passes. The next issue is that by the time "UserManager.FindAsync" is called, no tenant id is provided by the code - which means the the query against UserLogin doesnt find the entry that is mapped to a specific tenant. I'm guessing this should have been populated somehow by now? How is the query to find the UserLogin supposed to know which tenant to search under?

--- UPDATE 3 ---

Clearly there are issues here. Status? https://github.com/aspnetzero/aspnet-zero-core/issues/3046

I guess these are the same issues. Broken for almost 2 years? I can get accounts to be created if I tweak the ProviderKey comparison to strip hyphens - however the activate user cannot log in (error "User name XXX is already taken") because the search for the user account is performed with a null tenancyName.

Hi,

I need to include a custom '.well-known' folder in the root of my angular build output. My project looks like this (see screenshot) - note how I have a '.well-known' folder in my src folder.

I need this folder to be addressable via the url https://myangularsite/.well-known/

What script do I edit, and how do I edit it, to include it in the build output?

While this is not strictly a bug, its an intersection of functionalities where the expected behavior has not been explicitly designed in a desireable way.

We have a set of permissions, some of which are dependent on specific features.

When disabling the feature for a tenant, all users in the tenant lose the permissions which depend on that feaure (this is expected). When enabling the feature for a tenant, none of the users have those permissions restored - this is the bit which is not clearly defined.

From a users perspective, if the permissions were granted through the assignment of a role (rather than explicit per-user grants) - one would expect the permissions to be restored.

Has the AspNetZero team considered and defined this behavior? If not, can we look at clearly defining this? What would be the best way to implement the behavior I defined above (where permission assignments received through roles are reassessed when necessary)?

Root cause is that when editing permissions for a user, hidden permissions (such as ones that the user cannot see because a missing feature is hiding them) will actually create a "isGranted = 0" record for those hidden permissions. Then, when switching ON the feature that will make that permission possible, it is initially explicitly disabled because of that "isGranted = 0" record.

Hi @ismcagdas,

Thank you for your response.

I hear your point, however I would like to respectfully appeal that you reassess your position for the sake of the AspNetZero/Boilerplate platform. All things being equal, if both are strange behaviors, it would be far safer to err on the side of not leaking potentially sensitive data to unintended users/organisations.

At best, data could be moved from tenant to host, causing the effect I had, a customer noticing missing data. At worse, data could be moved to another tenant, causing leakage of potentially sensitive data outside of the organisation, exposing your customers to potential legal ramifications.

Should the user have intended the data to be moved to another tenant, this specific failure would be observed in feature testing - as it's an explicit feature rather than an edge case. We could mitigate this being overlooked by throwing an exception should the tenant change without the "block tenant changes guard/filter" being disabled - this would clearly highlight the violation in testing.

There is no impact on my organisation given I have caught this flaw and mitigated it, so I promote discussion on this topic for the benefit of my peers in this community. I feel this also reflects on AspNetZero's internal security strategy whether or not to err on the side of caution.

Thank you and best Regards,

I have found that while IMustHaveTenant ensures that newly inserted entities are stored against the active tenant, the same behavior is not applied to modified entities.

While I accept that it's possible to have a scenario where by one would want to move an entity from one tenant to another, I don't believe it's wise for this to be the default behavior.

Currently, if you instantiate an entity (IMustHaveTenant), and store it - it will receive the active tenant ID. If you then load that entity, change its tenant id (to that of a valid tenant), it will be moved to that tenant.

I feel there should be a filter in place, to mark the TenantId property as "unmodified" such that the EF context never updates it. It should then be the developer's responsibility to disable this filter if they want to move an entity between tenants.

This is the safer default behavior.

I discovered this, because I had a bad AutoMapper configuration, which reset the TenantId of an entity to 0 during an update, and it moved the entity to the host tenant.

The customer though he had lost data, fortunately it was possible to just move it back to the correct tenant, however it highlighted this security concern.

In the interim, my guard is implemented on my AbpDbContext as follows:

protected override void ApplyAbpConceptsForModifiedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport)
{
    // prevent changes to TenantId
    if (entry.Entity is IMustHaveTenant || entry.Entity is IMayHaveTenant)
    {
        var tenantProperty = entry.Property(nameof(IMustHaveTenant.TenantId));
        if (tenantProperty.IsModified)
        {
            tenantProperty.IsModified = false;
            object[] keyParts = entry.Metadata.FindPrimaryKey()
             .Properties
             .Select(p => entry.Property(p.Name).CurrentValue)
             .ToArray();
            var keyAsString = String.Join(", ", Array.ConvertAll(keyParts, x => $"'{x}'"));
            this.Logger.Warn($"Blocked attempted change to {entry.Metadata.ClrType.Name}.{tenantProperty.Metadata.Name} for Key {keyAsString}, from {tenantProperty.OriginalValue} to {tenantProperty.CurrentValue}");
        }
    }

    base.ApplyAbpConceptsForModifiedEntity(entry, userId, changeReport);
}

Hi @payoff,

Thank you for your suggestion. This is exactly the kind of best-practice I was looking for.

Regards,

Showing 41 to 50 of 59 entries