Authentication against AD (or other)

Oct 7, 2010 at 3:06 AM

We want to use Orchard as an Intranet site, and as such would like it to authenticate against AD and use custom roles defined elsewhere.

I initially tried switching the web.config to use Windows authentication, but this really isn't supported, and breaks many things.

For authentication I looked deeper at Orchard.Users and Orchard.Roles, and ultimately decided the best course of action would be to simply implement my own IMembershipService and put my AD authentication check in the ValidateUser() method. None of the other methods need to be supported for my needs. So my first question is:

1. How do I get Orchard.Users to use my IMembershipService implementation instead of it's own?

For the list of users and list of roles, I am building a new module that has an admin screen to refresh users/roles (basically add IUser and IRole content items for any that aren't currently there).

The admin screen would also be needed to periodically refresh the User<->Role joins, as I don't see a clean way of making that automatic. I looked at implementing my own IAuthorizationService, but it would need access to permission<->role mapping, and result in copying a great deal of the existing code in the RolesBasedAuthorizationService class in Orchard.Roles. What I really need is some way to hook into the reading of the Roles property on the current user.

2. Is there a way to replace the User<->Role join checking with my own code?

An issue I don't know how to solve is auto-login (single-sign-on), but I'm prepared for the fact that it probably isn't possible:

3. How do I (if possible) auto-login using Windows authentication?

 

Coordinator
Oct 7, 2010 at 3:11 AM

If you search on MSDN forums you should find another thread with the same question. If you implement your own IMembershipService, you would just have to take a dependency from the manifest to the feature that currently implements it. The outermost dependency (that's your module now) always takes precedence.

You should also be able to replace Orchard.roles altogether. I don't see why you couldn't use auto-login as you should be able to take over the whole authentication process.

Oct 7, 2010 at 3:33 AM

Thanks,

I did some searches for "Active Directory" on the forums but only found the post about the User Profile component being written, but upon your suggestion I searched again and found this post:

http://social.msdn.microsoft.com/Forums/en-US/orcharddiscussions/thread/be4b060e-7f20-4c38-a959-a0c360445696

(PS: anyone else find it crazy that searching for "AD" gives me every thread that has "ad" in it's GUID?)

Anyway, thought I would link it here in case anyone else is interested. I'll post back with my progress.

Oct 7, 2010 at 6:24 AM
bertrandleroy wrote:

If you implement your own IMembershipService, you would just have to take a dependency from the manifest to the feature that currently implements it. The outermost dependency (that's your module now) always takes precedence.

I tried the "Module.txt" file, changing the dependency there (tried both directions, in case I misunderstood you). This had no effect.

Concerned that it might be an issue with the fact that Orchard.Users is a core module with other dependencies I tried creating 3 test modules, an Interface defined in Test1, with an implementation in Test2 and Test3, both having a project and manifest reference to Test1.

Regardless of what Module.txt dependency or project references I setup, it seemed to just use the implementation from one module (Test3). I've used Castle, but not Autofac, so I'm not sure of the finer details on how to configure this, especially since the loading of modules is done deep inside Orchard where I doubt I have much control.

Did I understand your suggestion correctly? Do I perhaps have to use XML configuration for Autofac to force this? Could you perhaps provide a little more detail on how this works?

Coordinator
Oct 7, 2010 at 2:16 PM

You can override which implementation can be used for a specific service by defining the full class name inside ~/Config/Host.Config. There is a sample file inside this folder, and you can see how we do it in Azure for blog storage un Azure folder. By altering it you will be able to shortcut any other implementation. This is pure autofac configuration.

Oct 7, 2010 at 10:49 PM
Edited Oct 8, 2010 at 12:52 AM

Either this isn't working correctly, or there's a bug here, or perhaps I've missed something?

Here's my test scenario, please tell me if I've just done something silly or if this is some kind of issue that needs fixing:

Module: Test1
Test1\Services\ITestService.cs = interface with "string GetName()" method defined

Module: Test2
Test2\Controllers\AdminController.cs = constructor has injected ITest, saves reference and Index() method calls GetName() and copies it to ViewModel
Test2\Services\TestService.cs = class, GetName() returns "Test2.Services.TestService"
Test2\ViewModels\Test2IndexViewModel.cs = class to allow passing of name to View, "public string Name { get; set; }"
Test2\Views\Admin\Index.ascx = view, has static heading of "Test2 Admin/Index", as well as "<p>Name = <%: Model.Name %></p>"
Test2\AdminMenu.cs = same as other AdminMenu classes, just creates a "Tests" heading with a "Test 2" item that links to the Admin Index

Module: Test3
same as Test2, except replace Test2 with Test3 everywhere

At this point both show "Name = Test3.Services.TestService" for me, so I tried creating the ~Config/Host.config as directed.

Orchard.Web\Config\Host.config

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="autofac" type="Autofac.Configuration.SectionHandler, Autofac.Configuration"/>
  </configSections>
  <autofac defaultAssembly="Orchard.Framework">
    <components>
      <component instance-scope="single-instance"
                 type="Test2.Services.TestService, Test2"
                 service="Test1.Services.ITestService, Test1">
      </component>
    </components>
  </autofac>
</configuration>

Initially I left off the ", Test2" on type and ", Test1" on service and Autofac complained, so it is definitely reading the config. Unfortunately it's not changing the output, both still show "Name = Test3.Services.TestService" for me.

EDIT: I also tried making Test3 depend on Test2 (project and Module.txt dependency), and the reverse (Test2 depend on Test3), no change in behaviour. I also tried disabling Test3 and saw that Test2 now showed "Name = Test2.Services.TestServcie" as expected. Lastly I tried changing the order in which I endabled Test2 and Test3 to see if the order had any effect, it didn't.

Based on all this, I can only surmise that there's something broken either in Autofac (unlikely, but possible) or in the module registration into the container (my best guess).

If I can't get this basic test working, then there's no way I can get the IMembershipService replacement to work, which I need for my original request.

Oct 18, 2010 at 7:01 PM
TimothyWalters wrote:

We want to use Orchard as an Intranet site, and as such would like it to authenticate against AD and use custom roles defined elsewhere.

I initially tried switching the web.config to use Windows authentication, but this really isn't supported, and breaks many things.

For authentication I looked deeper at Orchard.Users and Orchard.Roles, and ultimately decided the best course of action would be to simply implement my own IMembershipService and put my AD authentication check in the ValidateUser() method. None of the other methods need to be supported for my needs. So my first question is:

1. How do I get Orchard.Users to use my IMembershipService implementation instead of it's own?

I am very interested in this answer.

Oct 18, 2010 at 11:34 PM

As an update to this, I created a new module with a Services directory and a class there that inherited from IMembershipService. I found that this time it was picked up and used. The only difference between this attempt that worked and my last attempt that failed as far as I can tell is that this time my module name started with P, last time it started with A. I have a theory that regardless of dependencies it's using an alphabetically sorted list and picking up the last one, but haven't had time to confirm this.

There does seem to be some issue in the way Autofac is used, perhaps it will be fixed in 0.8?

While my auto-login component does work now, the logins still timeout and require a manual process to log back in. I am hoping to be able to tie in to the IMembershipService to perform the auto-login there any time it checks for access. This is still a work-in-progress for me.

Jan 18, 2011 at 9:58 AM

Hello I am currently investigating how to create a module that will override IMembershipService and use AD to login to orchard in a similar fashion described in the first post of this blog.

Did you find out how to specify which implementation should be picked up by an interface?

Also I was wondering how far you got with your module and if you came across any more problems. Ideally if you have a solution would you share the module as it would save me some time