How to extend C# DirectoryServices UserContext with msTSProfilePath, msTSHomeDirectory etc - c#

I need to read/write ActiveDirectory User object Terminal Services properties.
I tried this:
PrincipalContext context = new PrincipalContext(ContextType.Domain, "CA");
using (context)
{
UserPrincipal user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, "CA\\vlekovic");
if (user != null)
{
DirectoryEntry entry = (DirectoryEntry)user.GetUnderlyingObject();
entry.Properties["msTSProfilePath"].Value = "";
entry.Properties["msTSHomeDirectory"].Value = "";
entry.Properties["msTSHomeDrive"].Value = "";
entry.CommitChanges();
}
}
And I tried this:
PrincipalContext context = new PrincipalContext(ContextType.Domain, "CA");
using (context)
{
UserPrincipal user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, "CA\\vlekovic");
if (user != null)
{
DirectoryEntry entry = (DirectoryEntry)user.GetUnderlyingObject();
entry.InvokeSet("msTSProfilePath", "");
entry.InvokeSet("msTSHomeDirectory", "");
entry.InvokeSet("msTSHomeDrive", "");
entry.CommitChanges();
}
}
But nothing works.
I tried also with following property names:
TerminalServicesProfilePath
TerminalServicesHomeDirectory
TerminalServicesHomeDrive
But no luck. Any help would be appreciated!
Thanks,
Vojin

If you're on .NET 3.5 and up and using the System.DirectoryServices.AccountManagement (S.DS.AM) namespace, you can easily extend the existing UserPrincipal class to get at more advanced properties, like Manager etc.
Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you just define a derived class based on UserPrincipal, and then you define your additional properties you want:
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalEx : UserPrincipal
{
// Implement the constructor using the base class constructor.
public UserPrincipalEx(PrincipalContext context) : base(context)
{ }
// Implement the constructor with initialization parameters.
public UserPrincipalEx(PrincipalContext context,
string samAccountName,
string password,
bool enabled) : base(context, samAccountName, password, enabled)
{}
// Create the "TermSrvProfilePath" property.
[DirectoryProperty("msTSProfilePath")]
public string TermSrvProfilePath
{
get
{
if (ExtensionGet("msTSProfilePath").Length != 1)
return string.Empty;
return (string)ExtensionGet("msTSProfilePath")[0];
}
set { ExtensionSet("msTSProfilePath", value); }
}
}
Now, you can use the "extended" version of the UserPrincipalEx in your code:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// Search the directory for the new object.
UserPrincipalEx inetPerson = UserPrincipalEx.FindByIdentity(ctx, IdentityType.SamAccountName, "someuser");
// you can easily access the TermSrvProfilePath now
string path = inetPerson.TermSrvProfilePath;
}
The same way, you can also add the other properties you're looking for.

I did exactly the same way you told us.
I tried with an other fiel as
[DirectoryProperty("wWWHomePage")]
public string wWWHomePage
{
get
{
if (ExtensionGet("wWWHomePage").Length != 1)
return null;
return (string)ExtensionGet("wWWHomePage")[0];
}
set { this.ExtensionSet("wWWHomePage", value); }
}
But with the TermSrvProfilePath , it always return "empty" ...

Related

How do I create a windows service for authentication that custom web applications can use to authenticate AD users?

My company is moving to a more centralized model, and we want to have a service that checks the user logged into the system again groups in AD. The service should act like a plug-in, in that when we create or update an application, we should be able to add the service to the application with little to no configuration. I have some experience with Active Directory, but I just need to figure out the best way to start the project.
In your question, you have given a response that this is an MVC application. You can do something like this:
[Authorize(Role = "role 1, role2")]
public ActionResult Index()
{
//your code here
return //your return object
}
This is a class file I created that will do that checking for you and more and should do everything you need.
public class IsUserInRole
{
public bool IsInGroup(string groupName)
{
var myIdentity = GetUserIdWithDomain();
var myPrincipal = new WindowsPrincipal(myIdentity);
return myPrincipal.IsInRole(groupName);
}
public WindowsIdentity GetUserIdWithDomain()
{
var myIdentity = WindowsIdentity.GetCurrent();
return myIdentity;
}
public string GetUserId()
{
return GetUserInformation().Name;
}
public string GetUserDisplayName()
{
return GetUserInformation().DisplayName;
}
public UserPrincipal GetUserInformation()
{
var id = GetUserIdWithDomain().Name.Split('\\');
var dc = new PrincipalContext(ContextType.Domain, id[0]);
return UserPrincipal.FindByIdentity(dc, id[1]);
}
public UserPrincipal GetUserInformation(string domain, string lanId)
{
var dc = new PrincipalContext(ContextType.Domain, domain);
return UserPrincipal.FindByIdentity(dc, lanId);
}
}

How to get HttpContext.Current.User as custom principal in mvc c#

I am trying to use the HttpContext.Current.User with my own class that inherits from IPrincipal. I am not exactly sure why..
var lIdentity = new UserIdentity(lFormsTicket);
var lRoles = lAuthUser.Roles.Select(x => x.Role.ToString()).ToArray();
var lPrincipal = new GenericPrincipal(lIdentity, lRoles);
System.Web.HttpContext.Current.User = lIdentity;
Right after I am setting this it works. But if I do another request on the site, it says that it cannot cast it. Am I missing something here?
You are setting User as IIdentity when it should be IPrincipal
var lIdentity = new UserIdentity(lFormsTicket);
var lRoles = lAuthUser.Roles.Select(x => x.Role.ToString()).ToArray();
var lPrincipal = new GenericPrincipal(lIdentity, lRoles);
System.Web.HttpContext.Current.User = lPrincipal;
You will probably need to use your own custom IPrincipal
public class UserPrincipal : IPrincipal {
string[] roles;
public UserPrincipal (IIdentity identity, param string[] roles) {
this.Identity = identity;
this.roles = roles ?? new string[]{ };
}
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) {
return roles.Any(s => s == role);
}
}
I'm not sure if the application will complain as it may be expecting a ClaimsPrincipal derived type.

PrincipalContextSearcher not returning correct results on extended principal type

I'm having an issue where if I try extend the UserPrincipal class the PrincipalContextSearcher does not return the correct results when using the extended class as the query filter.
So, for example, If I create the following minimal extension of UserPrincipal
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("user")]
public class UserPrincipalExtended : UserPrincipal
{
public UserPrincipalExtended(PrincipalContext context) : base(context) { }
public UserPrincipalExtended(PrincipalContext context, string samAccountName, string password, bool enabled) : base(context, samAccountName, password, enabled) { }
}
If I search using a (non-extended) UserPrincipal, like follows:
using (var searchCritera = new UserPrincipal(context))
{
searchCritera.SamAccountName = searchTerm;
using (var searcher = new PrincipalSearcher(searchCritera))
{
foreach (var principal in searcher.FindAll())
{
... do stuff
}
}
}
It will correctly return only user accounts. But if I use UserPrincipalExtended instead of the UserPrincipal, it returns matches to computers and all kinds of other things which is not the behavior I want. All I want to do is be able to add a few additional properties to retrieve in the UserPrincipal but simply extending the class before adding anything into it seems to change the filtering behavior.
What am I missing and how do I get PrincipalSearcher using an extended UserPrincipal ?
Digging a bit as to what was going on I looked at the DirectorySearcher used under the covers of PrincipalSearcher to see what the difference was.
((System.DirectoryServices.DirectorySearcher)(principalSearcher.GetUnderlyingSearcher())).Filter
when using UserPrincipal as the queryfilter the filter looks like:
Filter: "(&(objectCategory=user)(objectClass=user)(sAMAccountName=*searchTermWithWildcards*))" string
when using UserPrincipalExtended as the queryfilter the filter looks like:
Filter: "(&(objectClass=user)(sAMAccountName=*searchTermWithWildcards*))" string
It's missing the objectCategory
I resorted to the following deplorable hack to get it working but I would prefer a better way to specify the objectCategory
[DirectoryRdnPrefix("CN")]
#region *UGLY CODE WARNING* Don't look in here....
// When building the underlying DirectorySearcher filter the DirectoryObjectClass value is inserted in as: (objectClass={value})
// The following injection will result in : (objectClass=user)(objectCategory=user)
[DirectoryObjectClass("user)(objectCategory=user")]
#endregion
public class UserPrincipalExtended : UserPrincipal
{
public UserPrincipalExtended(PrincipalContext context) : base(context) { }
public UserPrincipalExtended(PrincipalContext context, string samAccountName, string password, bool enabled)
: base(context, samAccountName, password, enabled) { }
}
It's possible to replace the AdvancedSearchFilter property (using new not override, because the original property is read-only) and add your very own CustomAdvancedFilter with any custom filter you want using AdvancedFilterSet.
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("user")]
public class UserPrincipalExtended : UserPrincipal
{
public UserPrincipalExtended(PrincipalContext context) : base(context) {
AdvancedSearchFilter = new CustomAdvancedFilter(this);
}
public UserPrincipalExtended(PrincipalContext context, string samAccountName, string password, bool enabled) : base(context, samAccountName, password, enabled)
{
AdvancedSearchFilter = new CustomAdvancedFilter(this);
}
public new AdvancedFilters AdvancedSearchFilter { get; set; }
}
public class CustomAdvancedFilter : AdvancedFilters
{
public CustomAdvancedFilter(Principal principal) : base(principal)
{
AdvancedFilterSet("objectCategory", "user", typeof(string), MatchType.Equals);
}
}

ServiceStack Authentication with Existing Database

I've been looking at ServiceStack and I'm trying to understand how to use BasicAuthentication on a service with an existing database. I would like to generate a public key (username) and secret key (password) and put that in an existing user record. The user would then pass that to the ServiceStack endpoint along with their request.
What do I need to implement in the ServiceStack stack to get this working?
I have looked at both IUserAuthRepository and CredentialsAuthProvider base class and it looks like I should just implement IUserAuthRepository on top of my existing database tables.
I am also trying to figure out what is the bare minimum I should implement to get authentication working. I will not be using the service to Add or Update user access to the Service, but instead using a separate web application.
Any help and past experiences are greatly appreciated.
Example of authenticating against an existing database (in this case via Umbraco/ASP.NET membership system). 1) Create your AuthProvider (forgive the verbose code, and note you don't have to override TryAuthenticate too, this is done here to check if the user is a member of specific Umbraco application aliases):
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Security;
using ServiceStack.Configuration;
using ServiceStack.Logging;
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;
using ServiceStack.WebHost.Endpoints;
using umbraco.BusinessLogic;
using umbraco.providers;
public class UmbracoAuthProvider : CredentialsAuthProvider
{
public UmbracoAuthProvider(IResourceManager appSettings)
{
this.Provider = "umbraco";
}
private UmbracoAuthConfig AuthConfig
{
get
{
return EndpointHost.AppHost.TryResolve<UmbracoAuthConfig>();
}
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
ILog log = LogManager.GetLogger(this.GetType());
var membershipProvider = (UsersMembershipProvider)Membership.Providers["UsersMembershipProvider"];
if (membershipProvider == null)
{
log.Error("UmbracoAuthProvider.OnAuthenticated - NullReferenceException - UsersMembershipProvider");
session.IsAuthenticated = false;
return;
}
MembershipUser user = membershipProvider.GetUser(session.UserAuthName, false);
if (user == null)
{
log.ErrorFormat(
"UmbracoAuthProvider.OnAuthenticated - GetMembershipUser failed - {0}", session.UserAuthName);
session.IsAuthenticated = false;
return;
}
if (user.ProviderUserKey == null)
{
log.ErrorFormat(
"UmbracoAuthProvider.OnAuthenticated - ProviderUserKey failed - {0}", session.UserAuthName);
session.IsAuthenticated = false;
return;
}
User umbracoUser = User.GetUser((int)user.ProviderUserKey);
if (umbracoUser == null || umbracoUser.Disabled)
{
log.WarnFormat(
"UmbracoAuthProvider.OnAuthenticated - GetUmbracoUser failed - {0}", session.UserAuthName);
session.IsAuthenticated = false;
return;
}
session.UserAuthId = umbracoUser.Id.ToString(CultureInfo.InvariantCulture);
session.Email = umbracoUser.Email;
session.DisplayName = umbracoUser.Name;
session.IsAuthenticated = true;
session.Roles = new List<string>();
if (umbracoUser.UserType.Name == "Administrators")
{
session.Roles.Add(RoleNames.Admin);
}
authService.SaveSession(session);
base.OnAuthenticated(authService, session, tokens, authInfo);
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
ILog log = LogManager.GetLogger(this.GetType());
var membershipProvider = (UsersMembershipProvider)Membership.Providers["UsersMembershipProvider"];
if (membershipProvider == null)
{
log.Error("UmbracoAuthProvider.TryAuthenticate - NullReferenceException - UsersMembershipProvider");
return false;
}
if (!membershipProvider.ValidateUser(userName, password))
{
log.WarnFormat("UmbracoAuthProvider.TryAuthenticate - ValidateUser failed - {0}", userName);
return false;
}
MembershipUser user = membershipProvider.GetUser(userName, false);
if (user == null)
{
log.ErrorFormat("UmbracoAuthProvider.TryAuthenticate - GetMembershipUser failed - {0}", userName);
return false;
}
if (user.ProviderUserKey == null)
{
log.ErrorFormat("UmbracoAuthProvider.TryAuthenticate - ProviderUserKey failed - {0}", userName);
return false;
}
User umbracoUser = User.GetUser((int)user.ProviderUserKey);
if (umbracoUser == null || umbracoUser.Disabled)
{
log.WarnFormat("UmbracoAuthProvider.TryAuthenticate - GetUmbracoUser failed - {0}", userName);
return false;
}
if (umbracoUser.UserType.Name == "Administrators"
|| umbracoUser.GetApplications()
.Any(app => this.AuthConfig.AllowedApplicationAliases.Any(s => s == app.alias)))
{
return true;
}
log.WarnFormat("UmbracoAuthProvider.TryAuthenticate - AllowedApplicationAliases failed - {0}", userName);
return false;
}
}
public class UmbracoAuthConfig
{
public UmbracoAuthConfig(IResourceManager appSettings)
{
this.AllowedApplicationAliases = appSettings.GetList("UmbracoAuthConfig.AllowedApplicationAliases").ToList();
}
public List<string> AllowedApplicationAliases { get; private set; }
}
2) Register provider via usual AppHost Configure method:
public override void Configure(Container container)
{
// .... some config code omitted....
var appSettings = new AppSettings();
AppConfig = new AppConfig(appSettings);
container.Register(AppConfig);
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));
this.Plugins.Add(
new AuthFeature(
// using a custom AuthUserSession here as other checks performed here, e.g. validating Google Apps domain if oAuth enabled/plugged in.
() => new CustomAuthSession(),
new IAuthProvider[] { new UmbracoAuthProvider(appSettings)
}) {
HtmlRedirect = "/api/login"
});
}
3) Can now authenticate against existing Umbraco database # yourapidomain/auth/umbraco, using Umbraco to manage users/access to API. No need to implement extra user keys/secrets or BasicAuthentication, unless you really want to....
I'm just starting with ServiceStack and I needed exactly the same thing - and I managed to get it to work today.
The absolute bare minimum for logging in users via Basic Auth is this:
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;
public class CustomBasicAuthProvider : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
// here, you can get the user data from your database instead
if (userName == "MyUser" && password == "123")
{
return true;
}
return false;
}
}
...and register it in the AppHost:
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomBasicAuthProvider()
}) { HtmlRedirect = null });
That's all!
Another possible solution would be to use the default BasicAuthProvider and provide an own implementation of IUserAuthRepository instead.
I can show you an example of this as well, if you're interested.
EDIT:
Here's the bare minimum IUserAuthRepository - just inherit from InMemoryAuthRepository and override TryAuthenticate:
using ServiceStack.ServiceInterface.Auth;
public class CustomAuthRepository : InMemoryAuthRepository
{
public override bool TryAuthenticate(string userName, string password, out UserAuth userAuth)
{
userAuth = null;
if (userName == "MyUser" && password == "123")
{
userAuth = new UserAuth();
return true;
}
return false;
}
}
...and register it in the AppHost:
container.Register<IUserAuthRepository>(r => new CustomAuthRepository());
Of course, you need to register one of the default AuthProviders (Basic, Credentials, whatever) as well.

How can I authenticate against Active Directory in Nancy?

It's an outdated article, but http://msdn.microsoft.com/en-us/library/ff650308.aspx#paght000026_step3 illustrates what I want to do. I've chosen Nancy as my web framework because of it's simplicity and low-ceremony approach. So, I need a way to authenticate against Active Directory using Nancy.
In ASP.NET, it looks like you can just switch between a db-based membership provider and Active Directory just by some settings in your web.config file. I don't need that specifically, but the ability to switch between dev and production would be amazing.
How can this be done?
Really the solution is much simpler than it may seem. Just think of Active Directory as a repository for your users (just like a database). All you need to do is query AD to verify that the username and password entered are valid. SO, just use Nancy's Forms Validation and handle the connetion to AD in your implementation of IUserMapper. Here's what I came up with for my user mapper:
public class ActiveDirectoryUserMapper : IUserMapper, IUserLoginManager
{
static readonly Dictionary<Guid, long> LoggedInUserIds = new Dictionary<Guid, long>();
readonly IAdminUserValidator _adminUserValidator;
readonly IAdminUserFetcher _adminUserFetcher;
readonly ISessionContainer _sessionContainer;
public ActiveDirectoryUserMapper(IAdminUserValidator adminUserValidator, IAdminUserFetcher adminUserFetcher, ISessionContainer sessionContainer)
{
_adminUserValidator = adminUserValidator;
_adminUserFetcher = adminUserFetcher;
_sessionContainer = sessionContainer;
}
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
{
_sessionContainer.OpenSession();
var adminUserId = LoggedInUserIds.First(x => x.Key == identifier).Value;
var adminUser = _adminUserFetcher.GetAdminUser(adminUserId);
return new ApiUserIdentity(adminUser);
}
public Guid Login(string username, string clearTextPassword, string domain)
{
var adminUser = _adminUserValidator.ValidateAndReturnAdminUser(username, clearTextPassword, domain);
var identifier = Guid.NewGuid();
LoggedInUserIds.Add(identifier, adminUser.Id);
return identifier;
}
}
I'm keeping a record in my database to handle roles, so this class handles verifying with AD and fetching the user from the database:
public class AdminUserValidator : IAdminUserValidator
{
readonly IActiveDirectoryUserValidator _activeDirectoryUserValidator;
readonly IAdminUserFetcher _adminUserFetcher;
public AdminUserValidator(IAdminUserFetcher adminUserFetcher,
IActiveDirectoryUserValidator activeDirectoryUserValidator)
{
_adminUserFetcher = adminUserFetcher;
_activeDirectoryUserValidator = activeDirectoryUserValidator;
}
#region IAdminUserValidator Members
public AdminUser ValidateAndReturnAdminUser(string username, string clearTextPassword, string domain)
{
_activeDirectoryUserValidator.Validate(username, clearTextPassword, domain);
return _adminUserFetcher.GetAdminUser(1);
}
#endregion
}
And this class actually verifies that the username/password combination exist in Active Directory:
public class ActiveDirectoryUserValidator : IActiveDirectoryUserValidator
{
public void Validate(string username, string clearTextPassword, string domain)
{
using (var principalContext = new PrincipalContext(ContextType.Domain, domain))
{
// validate the credentials
bool isValid = principalContext.ValidateCredentials(username, clearTextPassword);
if (!isValid)
throw new Exception("Invalid username or password.");
}
}
}

Categories