bmcdavid / IdentityCollection

MIT License
0 stars 2 forks source link

Trouble getting project working #3

Open markrullo opened 3 years ago

markrullo commented 3 years ago

Hi Brad,

This project was recommended to us by our Episerver solution architect, but we're having some trouble getting it to work.

If you have time, we have some questions about how this works as well. We're trying to use AAD for our auth, and keep role management within Episerver for our business users to be able to manage roles. When we implement this correctly, does this plug into the existing role management within Episerver, or do we have to write an interface to manage that?

Before implementing this, we were able to get the AAD authentication working.

This is the issue we're having with implementation and what we've tried: Our project is running EF 6.x and I was having trouble with getting the migrations to work, so I ran your project's migrations locally and merged those changes into our Episerver DB via script. (potential issue for us #1)

This is the stack trace (below) we're getting and it's a bit overwhelming. I admittedly haven't worked with StructureMap, so I'm not familiar with the errors coming from it.

When running the project it gets through -ExtendedRoleConfigurationModule.ConfigureContainer -ClaimsCriteriaConfigurationModule.ConfigureContainer and sets up the DI (from what I gather).

It looks like it's setting up the ExtendedRoleDbContextFactory properly and injecting the IDbContextSettings (I've manually set the RunMigrations to false as I integrated the DB changes already (potential issue 2?).

This ExtendedUserProvider constructor is getting a ExtendedRoleDbContextFactory injected properly. Then that's when I get the error.

If there's anything you can point us to, or if you have other documentation for this, we'd really appreciate it.

Cheers

[InvalidOperationException: No owin.Environment item was found in the context.] System.Web.HttpContextExtensions.GetOwinContext(HttpContext context) +78 EPiServer.Cms.UI.AspNetIdentity.<>c.<ConfigureContainer>b__0_1(IServiceLocator s) +31 EPiServer.ServiceLocation.Internal.TypedFactory1.b__2_0(IServiceLocator s) +13 lambda_method(Closure , IBuildSession , IContext ) +181

[StructureMapBuildException: Failure while building 'Lambda: Invoke(value(EPiServer.ServiceLocation.Internal.StructureMapConfiguredType1+<>c__DisplayClass11_01[EPiServer.Shell.Security.UIRoleProvider,EPiServer.Shell.Security.UIRoleProvider]).instanceAccessor, value(EPiServer.ServiceLocation.Internal.StructureMapConfiguredType1[EPiServer.Shell.Security.UIRoleProvider])._serviceLocator)', check the inner exception for details 1.) Lambda: Invoke(value(EPiServer.ServiceLocation.Internal.StructureMapConfiguredType1+<>cDisplayClass11_01[EPiServer.Shell.Security.UIRoleProvider,EPiServer.Shell.Security.UIRoleProvider]).instanceAccessor, value(EPiServer.ServiceLocation.Internal.StructureMapConfiguredType1[EPiServer.Shell.Security.UIRoleProvider])._serviceLocator) 2.) Instance of EPiServer.Shell.Security.UIRoleProvider 3.) new ExtendedSecurityProvider(Default of SynchronizingRolesSecurityEntityProvider, Default of UIRoleProvider, Default of UIUserProvider) 4.) bmcdavid.Episerver.SynchronizedProviderExtensions.ExtendedSecurityProvider 5.) Instance of EPiServer.Security.SecurityEntityProvider (bmcdavid.Episerver.SynchronizedProviderExtensions.ExtendedSecurityProvider) 6.) new DefaultApprovalRepository(Default of ServiceAccessor, Default of ILanguageBranchRepository, Default of IValidationService, Default of IApprovalTypeRegistry, Default of SecurityEntityProvider) 7.) EPiServer.Approvals.Internal.DefaultApprovalRepository 8.) Instance of EPiServer.Approvals.IApprovalRepository (EPiServer.Approvals.Internal.DefaultApprovalRepository) 9.) new ApprovalService(Default of IApprovalDefinitionRepository, Default of IApprovalDefinitionVersionRepository, Default of QueryableNotificationUserService, Default of ContentLoaderService, Default of IApprovalRepository, Default of IApprovalEngine, Default of ServiceAccessor, Default of SecurityEntityProvider) 10.) EPiServer.Cms.Shell.UI.Rest.Approvals.ApprovalService 11.) Instance of EPiServer.Cms.Shell.UI.Rest.Approvals.ApprovalService 12.) new ContentService(Default of IContentRepository, Default of IContentVersionRepository, Default of ILanguageBranchRepository, Default of IContentProviderManager, Default of AncestorReferencesLoader, Default of LanguageSelectorFactory, Default of ISiteDefinitionRepository, Default of ProjectLoaderService, Default of ContentEvents, Default of Settings, Default of ISiteConfigurationRepository, Default of IStatusTransitionEvaluator, Default of ApprovalService, Default of SaveActionRuleEngine) 13.) EPiServer.Cms.Shell.Service.Internal.ContentService 14.) Instance of EPiServer.Cms.Shell.Service.Internal.ContentService 15.) new ProjectService(Default of ProjectRepository, Default of ProjectPublisher, Default of ContentService, Default of IContentChangeManager, Default of LanguageSelectorFactory, Default of CurrentProject, Default of ISiteConfigurationRepository, Default of IConfigurationSource, Default of ApprovalService, Default of LocalizationService) 16.) EPiServer.Cms.Shell.UI.Rest.Projects.Internal.ProjectService 17.) Instance of EPiServer.Cms.Shell.UI.Rest.Projects.IProjectService (EPiServer.Cms.Shell.UI.Rest.Projects.Internal.ProjectService) 18.) Container.GetInstance(EPiServer.Cms.Shell.UI.Rest.Projects.IProjectService) ] lambda_method(Closure , IBuildSession , IContext ) +1040 StructureMap.Building.BuildPlan.Build(IBuildSession session, IContext context) +92 StructureMap.BuildSession.BuildNewInSession(Type pluginType, Instance instance) +92 StructureMap.BuildSession.BuildNewInOriginalContext(Type pluginType, Instance instance) +73 StructureMap.Pipeline.LifecycleObjectCache.buildWithSession(Type pluginType, Instance instance, IBuildSession session) +14 StructureMap.Pipeline.<>c__DisplayClass5_0.b0(Int32 _) +27 System.Collections.Concurrent.ConcurrentDictionary2.GetOrAdd(TKey key, Func2 valueFactory) +65 StructureMap.Pipeline.LifecycleObjectCache.Get(Type pluginType, Instance instance, IBuildSession session) +117 StructureMap.BuildSession.ResolveFromLifecycle(Type pluginType, Instance instance) +47 StructureMap.SessionCache.GetObject(Type pluginType, Instance instance, ILifecycle lifecycle) +128 StructureMap.SessionCache.GetDefault(Type pluginType, IPipelineGraph pipelineGraph) +100 StructureMap.Container.GetInstance(Type pluginType) +178 EPiServer.ServiceLocation.StructureMapServiceLocator.DoGetInstance(Type serviceType, String key) +40 EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key) +42

[ActivationException: Activation error occurred while trying to get instance of type IProjectService, key ""] EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key) +101 EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance() +60 EPiServer.Cms.Shell.CmsModule..ctor(String name, String routeBasePath, String resourceBasePath) +432

[TargetInvocationException: Exception has been thrown by the target of an invocation.] System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0 System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +183 System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark) +1173 System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) +130 System.Activator.CreateInstance(Type type, Object[] args) +22 EPiServer.Shell.Modules.ModuleFinder.GetModuleInDirectory(String routeBasePath, String moduleResourcePath, IEnumerable1 configuredAssemblyNames, AutoDiscoveryLevel discoveryMode, String configuredName) +486 EPiServer.Shell.Modules.<>c__DisplayClass7_0.<GetConfiguredModules>b__0(ModuleDetails i) +89 System.Linq.WhereSelectListIterator2.MoveNext() +97 System.Linq.WhereEnumerableIterator1.MoveNext() +171 System.Collections.Generic.List1..ctor(IEnumerable1 collection) +246 System.Linq.Enumerable.ToList(IEnumerable1 source) +54 EPiServer.Shell.Modules.ConfigModuleProvider.GetConfiguredModules(ModuleOptionsBase moduleInfo) +226 EPiServer.Shell.Modules.ConfigModuleProvider.GetModules() +17 EPiServer.Shell.<>c.b16_0(IModuleProvider p) +10 System.Linq.d172.MoveNext() +170 System.Collections.Generic.List1..ctor(IEnumerable1 collection) +186 System.Linq.Enumerable.ToList(IEnumerable1 source) +54 EPiServer.Shell.Modules.ShellModule.MergeDuplicateModules(IEnumerable`1 modules) +34 EPiServer.Shell.ShellInitialization.GetConfiguredModules(IServiceLocator locator) +108 EPiServer.Shell.ShellInitialization.Initialize(InitializationEngine context) +160 EPiServer.Framework.Initialization.Internal.<>cDisplayClass2_0.b0() +19 EPiServer.Framework.Initialization.Internal.ModuleNode.Execute(Action a, String key) +52 EPiServer.Framework.Initialization.Internal.ModuleNode.Initialize(InitializationEngine context) +80 EPiServer.Framework.Initialization.InitializationEngine.InitializeModules() +179

[InitializationException: Initialize action failed for Initialize on class EPiServer.Shell.ShellInitialization, EPiServer.Shell, Version=11.23.8.0, Culture=neutral, PublicKeyToken=8fe83dea738b45b7] EPiServer.Framework.Initialization.InitializationEngine.InitializeModules() +482 EPiServer.Framework.Initialization.InitializationEngine.ExecuteTransition(Boolean continueTransitions) +153 EPiServer.Framework.Initialization.InitializationEngine.Initialize() +40 EPiServer.Framework.Initialization.<>c.b__7_0(InitializationEngine e) +9 EPiServer.Framework.Initialization.InitializationModule.EngineExecute(HostType hostType, Action`1 engineAction) +462 EPiServer.Framework.Initialization.InitializationModule.FrameworkInitialization(HostType hostType) +170 EPiServer.Global..ctor() +44 AlloyDemo.EPiServerApplication..ctor() +29 ASP.global_asax..ctor() in c:\Users\Mark\AppData\Local\Temp\Temporary ASP.NET Files\vs\7050e84d\d5c45277\App_global.asax.knja3v7f.0.cs:0

[TargetInvocationException: Exception has been thrown by the target of an invocation.] System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0 System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +122 System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +239 System.Activator.CreateInstance(Type type, Boolean nonPublic) +85 System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark) +1173 System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) +130 System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture) +21 System.Web.HttpRuntime.CreateNonPublicInstance(Type type, Object[] args) +59 System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) +148 System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext) +303

[HttpException (0x80004005): Exception has been thrown by the target of an invocation.] System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +659 System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +89 System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +189`

bmcdavid commented 3 years ago

@markrullo Can you try adding this as a startup module somewhere in your web solution? I think in the latest versions of Episerver the singleton lifetimes I used are the issue.

Only the namespace should need to be updated.

using bmcdavid.Episerver.SynchronizedProviderExtensions;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using EPiServer.Shell.Security;

namespace AlloyWeb
{
  [ModuleDependency(typeof(ServiceContainerInitialization))]
  [ModuleDependency(typeof(ExtendedRoleConfigurationModule))]
  public class OverrideSynchModuleDefaults : IConfigurableModule
  {
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
      // replaces lifetimes set in ExtendedRoleConfigurationModule
      context.Services.RemoveAll<UIUserProvider>();
      context.Services.RemoveAll<UIRoleProvider>();
      context.Services
          .AddScoped<EPiServer.Notification.IQueryableNotificationUsers, ExtendedUserProvider>()
          .AddScoped<UIUserProvider, ExtendedUserProvider>()
          .AddScoped<UIRoleProvider, ExtendedRoleProvider>()
          .AddTransient<SecurityEntityProvider, ExtendedSecurityProvider>();
    }

    public void Initialize(InitializationEngine context) { }

    public void Uninitialize(InitializationEngine context) { }
  }
}
bmcdavid commented 3 years ago

And a few other notes

Other than that the manual groups with AD users should behave as expected with native Episerver group management.

markrullo commented 3 years ago

Brad, thank you so much for getting back to us! I had to update a bunch of packages with our proof of concept solution, but it worked! Also, the admin tools seem to work really well too!

Now that this works, should we update ExtendedRoleConfigurationModule to have this code instead?

context.Services
   .AddScoped<EPiServer.Notification.IQueryableNotificationUsers, ExtendedUserProvider>()
   .AddScoped<UIUserProvider, ExtendedUserProvider>()
   .AddScoped<UIRoleProvider, ExtendedRoleProvider>()
   .AddTransient<SecurityEntityProvider, ExtendedSecurityProvider>();

Do we still need the singletons for:

context.Services
     .AddSingleton<IDbContextSettings, ExtendedRoleDbContextSettings>()
     .AddSingleton<ExtendedRoleDbContextFactory>()
     .AddSingleton<IExtendedUserTools, ExtendedUserTools>();

If we write something to insert a user into tblSynchedUser, would this enable us to assign roles to the user before the first login?

bmcdavid commented 3 years ago

Great news!

If you have the full source code then yes change those from the example to scoped.

You will need the others singletons as well.

I think that will work to make them available in the UI before login but honestly never tried.

Best of luck and let me know if other issues occur.

markrullo commented 3 years ago

Thanks for all the help Brad. The below is an FYI if you'd like to know.

I tried to re-do the implementation on my side so that it would run the migrations, and I'd use the Nuget package. First I updated all of the dependencies in my project to the latest version, then I added your library. When I added the library, and the above fix for DI, it gave me the error below.

Method not found: 'Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder`1.HasIndex(System.Linq.Expressions.Expression`1<System.Func`2<!0,System.Object>>)'.

From what I gather it's because the dependencies for your library were on EntityFrameworkCore 2.x and my project had 3.1.7 (after all the updates).

I ditched the Nuget package and made references to your source again, updated the packages to 3.1.7 and it worked beautifully.

I've done a test where I added a user manually to the tblSynchedUser, and I can modify their roles before they login. I think I'll try to make a little interface to add users by querying Microsoft Graph.

markrullo commented 3 years ago

I spoke too soon. I'm sorry to keep bugging on this. It's a critical piece for our implementation. This could be a symptom of my lack of experience with the Epi virtualRoles, but now that we tried to lock the admin interface back down, it's denying us even though we're in the appropriate role. I'm trying to figure out where I can put a breakpoint in the code to see where the ExtendedSecurityProvider is being called to be able to troubleshoot this with no luck.

When we change the web.config virtual roles from the two lines after the commented out, to the commented out ones (CmsAdmins and CmsEditors), we get denied. The user I'm trying is in 2 roles, WebAdmins, and CmsAdmins. I put both on just in case. It's still being denied though.

If you're able to point us in the right direction, or at least where to start debugging, I'd really appreciate it.

<add name="Administrators" type="EPiServer.Security.WindowsAdministratorsRole, EPiServer.Framework" />
<add name="Everyone" type="EPiServer.Security.EveryoneRole, EPiServer.Framework" />
<add name="Authenticated" type="EPiServer.Security.AuthenticatedRole, EPiServer.Framework" />
<add name="Anonymous" type="EPiServer.Security.AnonymousRole, EPiServer.Framework" />
<!--<add name="CmsAdmins" type="EPiServer.Security.MappedRole, EPiServer.Framework" roles="WebAdmins, Administrators, AccessToAdminView" mode="Any" />-->
<!--<add name="CmsEditors" type="EPiServer.Security.MappedRole, EPiServer.Framework" roles="AccessToEditView" mode="Any" />-->
<add name="CmsAdmins" type="EPiServer.Security.MappedRole, EPiServer.Framework" roles="Authenticated, WebAdmins" mode="Any" />
<add name="CmsEditors" type="EPiServer.Security.MappedRole, EPiServer.Framework" roles="Authenticated" mode="Any" />
<add name="Creator" type="EPiServer.Security.CreatorRole, EPiServer" />
<add name="VisitorGroupAdmins" type="EPiServer.Security.MappedRole,EPiServer.Framework" roles="Personalizers" mode="Any" />
<add name="EPiBetaUsers" type="EPiServer.Security.MappedRole,EPiServer.Framework" roles="Developers" mode="Any" />

One additional thing I did try was to put the ExtendedSecurityProvider in the list of providers. I wasn't sure if this was necessary as it was being injected via code:

        <securityEntity>
            <providers>
                <!--<add name="SynchronizingProvider" type="EPiServer.Security.SynchronizingRolesSecurityEntityProvider, EPiServer" />-->
                <add name="ExtendedSecurityProvider" type="bmcdavid.Episerver.SynchronizedProviderExtensions.ExtendedSecurityProvider, bmcdavid.Episerver.SynchronizedProviderExtensions" />
            </providers>
        </securityEntity>
bmcdavid commented 3 years ago

I’ve got a sample here but honestly can not recall if I tested virtual roles: https://github.com/bmcdavidepi/single-sign-on

Virtual roles do have an add claims attribute for the web.config which should be true.

You can see how the web.config is set as well as the Owin startup class.

Hope it helps.

markrullo commented 3 years ago

Thank you for getting back to me so quickly, much appreciated. I'll go over that sample but I think I found what my issue was.

I had this:

                TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = false,
                    RoleClaimType = ClaimTypes.Email                  
                }

I changed it to this:

                TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = false,
                    RoleClaimType = ClaimTypes.Role
                }

I originally misunderstood what the RoleClaimType was. That did the trick and that's with virtual roles (I think).