Adding users to AD using LDAP - vb.net

I'm writing an application that will add users to Active Directory. I'm trying to use this code to connect to the "Users" shared folder in AD
LDAP://celtestdomdc1.celtestdom.local/CN=Users,DC=celtestdom,DC=local
However it adds the user in with the shared folders, instead of within the "Users" shared folder. Shouldn't CN=Users mean it will add it to the "Users" folder?
Thanks

If you're creating a user, you need to
bind to the container you want to create the user in
create the new user account as a child of that container
Just by setting the LDAP path, you are not defining where the user will go!
Try something like this (C# sample - should be trivial to convert to VB.NET):
DirectoryEntry cnUsers = new DirectoryEntry("LDAP://CN=Users,DC=celtestdom,DC=local");
// create a user directory entry in the container
DirectoryEntry newUser = container.Children.Add("cn=NewUserAccount", "user");
// add the samAccountName mandatory attribute
newUser.Properties["sAMAccountName"].Value = "NewUser";
// add any optional attributes
newUser.Properties["givenName"].Value = "User";
newUser.Properties["sn"].Value = "One";
// save to the directory
newUser.CommitChanges();
// set a password for the user account
// using Invoke method and IadsUser.SetPassword
newUser.Invoke("SetPassword", new object[] { "pAssw0rdO1" });
// require that the password must be changed on next logon
newUser.Properties["pwdLastSet"].Value = 0;
// save to the directory
newUser.CommitChanges();
Or if you're using .NET 3.5 or newer, you could also use the new System.DirectoryServices.AccountManagement namespace that makes lots of things easier.
Then the code looks a bit simpler:
// create a context for a domain and define "base" container to use
PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
"celtestdom", "CN=Users,DC=celtestdom,DC=local");
// create a user principal object
UserPrincipal user = new UserPrincipal(ctx, "NewUser", "pass#1w0rd01", true);
// assign some properties to the user principal
user.GivenName = "User";
user.Surname = "One";
// force the user to change password at next logon
user.ExpirePasswordNow();
// save the user to the directory
user.Save();
Check out more about the System.DirectoryServices.AccountManagement (S.DS.AM) namespace here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement

Related

Overriding ASP.NET Core Windows Authentication Identity

I was looking to use Windows Authentication for a Blazor Server app, but hit a small issue with the default Identity Name.
i.e. when you used
<AuthorizeView>
Hi #context.User.Identity.Name
</AuthorizeView>
I got back 'Hi DOMAIN\A123456', which might be the AD object name, but its not what users would say their name was. I also noticed during debugging that the Identity had pulled back all of my AD groups, but not things like Given Name.
How can I override/amend/alter the processing to 'fix' this, ideally put a proper name in the Name claim and move the id into the NameIdentifier claim.
This is what I came up with using IClaimsTransformation, but not sure if its the right approach at all, esp given that with just the out-of-the-box Blazor project this thing is called 7 times! If I added any db type logic to get the roles or name then this is going tank performance...
public class RoleClaimsTransformer : IClaimsTransformation
{
private readonly ILogger<RoleClaimsTransformer> _logger;
public RoleClaimsTransformer(ILogger<RoleClaimsTransformer> logger)
{
_logger = logger;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
_logger.LogDebug($"Role Transform for {principal.Identity.Name} Auth: {principal.Identity.IsAuthenticated}");
//get the original name claim
var ci = (ClaimsIdentity)principal.Identity;
Claim nameClaim = principal.FindFirst(ci.NameClaimType);
//create a new principal
ClaimsPrincipal newCP = new ClaimsPrincipal();
//and a new identity, using the original authtype (just in case it matters down the line)
var newId = new GenericIdentity("Joe Bloggs", principal.Identity.AuthenticationType);
//add the original name as a NameId
newId.AddClaim(new Claim(ClaimTypes.NameIdentifier, nameClaim.Value));
//add roles etc
newId.AddClaim(new Claim(ClaimTypes.Role, "admin"));
newCP.AddIdentity(newId);
return Task.FromResult(newCP);
}
}
Hopefully its reasonably clear what I've done, but basically ignore the principal from the built in Windows Auth and create your own. Also note that the GenericIdentity does want a ClaimTypes.Role for roles (for use in the AuthorizeView components), and not whatever type the WindowsIdentity needed.
I've subsequently realised that WindowsAuthentication isn't not going to work for my app, and I'll go back to custom auth that just uses AD to check their passwords via a standard pair of login boxes.

First login to web site after Azure AD authentication

I have an Asp.Net core 3.1 MVC web application. I use EF and Identity
I'm in the process of migrating the authentication process to Azure AD (organizational directory)
I need to find a way to know when the user is login to my site for the first time.
I want to add user-information such as prefered-color, name etc. and to store it in my local DB
My goal is to know when the user logs-in for the first time and redirect him to the profile page for the extra details
Basically, I do it with OnTicketReceived event.
In this event I get the current user after the login process completed, I get the user details from the token and check if the user already in my local DB.
Now I need to redirect it to his profile page if the user record was not found.
I cannot change the redirect URL in OnTicketReceived event, I decided to create a middleware as follows:
In OnTicketReceived I check if the user in my local DB and add "NewUser" key to the session
Added NewUserMiddleware that runs and checks the session key. If "NewUser" key exits, I generate a new record in my local DB and redirect to the profile page, then I remove the key
It's working but I'm sure that there is an easier way to do it...
I don't want to create a register button - the site is internal to the organization and I don't need to create an anonymous register page
Some code stuff:
in OnTicketReceived:
var db = context.HttpContext.RequestServices.GetService<MyDbContext>();
var user = db.Users.SingleOrDefault(u => u.Email == email);
if (user == null)
{
context.HttpContext.Session.SetInt32("NewUser", 1);
}
NewUserMiddleware:
public async Task InvokeAsync(HttpContext context)
{
int? isNewUser = context.Session.GetInt32("NewUser");
if(isNewUser.HasValue && isNewUser.Value == 1)
{
MyDbContext _dbContext = context.RequestServices.GetService(typeof(MyDbContext)) as MyDbContext;
var identity = context.User.Identity as ClaimsIdentity;
string email = identity.FindFirst(c => c.Type == "preferred_username")?.Value;
string name = identity.FindFirst(c => c.Type == "name")?.Value;
User user = new User()
{
Email = email,
UserName = name,
Color = GenerateColor()
};
_dbContext.Users.Add(user);
_dbContext.SaveChanges();
context.Session.Remove("NewUser");
await context.Session.CommitAsync();
context.Response.Redirect("account/profile", true);
}
await _next(context);
}

Get AD Guid from HttpContext.Current.User

I have tried many, many different ways, to get this data. But I can't get it to work.
I have a MVC4 application, hooked up with Active Directory. But I need the users AD GUID.
I tried:
(Guid)Membership.GetUser(User.Identity.Name).ProviderUserKey;
WebSecurity.CurrentUserId;
But none of them work.
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, User.Identity.Name);
if(user != null)
{
Guid userGuid = user.Guid ?? Guid.Empty;
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
I managed to solve it (Not pretty...):
string login = HttpContext.Current.User.Identity.Name;
string domain = login.Substring(0, login.IndexOf('\\'));
string userName = login.Substring(login.IndexOf('\\') + 1);
DirectoryEntry domainEntry = new DirectoryEntry("LDAP://" + domain);
DirectorySearcher searcher = new DirectorySearcher(domainEntry);
searcher.Filter = string.Format("(&(objectCategory=person)(objectClass=user)(sAMAccountName={0}))",userName);
SearchResult searchResult = searcher.FindOne();
DirectoryEntry entry = searchResult.GetDirectoryEntry();
Guid objectGuid = entry.Guid;
The original code used : entry.NativeGuid, but I changed because of Little / Big endian "problems"
entry.Guid has the same "format" as in AD.

ASP.NET Active Directory Search

I'm trying to create an intranet Website on ASP.NET MVC 4 using Windows Login. I have successfully done the windows login. The only thing I am stuck up with is searching the active directory with partial username. I tried searching the web and stackoverflow website but still couldn't find the answer.
DirectoryEntry directory = new DirectoryEntry("LDAP://DC=NUAXIS");
string filter = "(&(cn=jinal*))";
string[] strCats = { "cn" };
List<string> items = new List<string>();
DirectorySearcher dirComp = new DirectorySearcher(directory, filter, strCats, SearchScope.Subtree);
SearchResultCollection results = dirComp.FindAll();
You can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// define a "query-by-example" principal - here, we search for a UserPrincipal
// and with the first name (GivenName) of "Jinal*"
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.GivenName = "Jinal*";
// create your principal searcher passing in the QBE principal
using (PrincipalSearcher srch = new PrincipalSearcher(qbeUser))
{
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here - "found" is of type "Principal" -
// it could be user, group, computer.....
}
}
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement. Or see the MSDN documentation on the System.DirectoryServices.AccountManagement namespace.
Of course, depending on your need, you might want to specify other properties on that "query-by-example" user principal you create:
DisplayName (typically: first name + space + last name)
SAM Account Name - your Windows/AD account name
User Principal Name - your "username#yourcompany.com" style name
You can specify any of the properties on the UserPrincipal and use those as "query-by-example" for your PrincipalSearcher.
Your current code is on the right track.
I think you had your wildcard backwards.
Consider this:
search.Filter = string.Format("(&(sn={0}*)(givenName={1}*)(objectSid=*))", lastName, firstName);

Simple login for multi-domain intranet?

I have an intranet server on a Windows domain (server is Windows 2003, IIS6, NTFS permissions). It is on the domain Domain01. I have users from two domains in the same forest that access this intranet: Domain01 and Domain02 (DCs also running Windows 2003). Currently, the users are required to login by entering either:
Domain01\username or username#Domain01
My users are completely and thoroughly confused by having to enter the domain each time they log in.
Is there any way to simply allow them to log in by entering just their username and password WITHOUT the domain? For example, have the server try Domain01 by default, and if the login fails to try Domain02?
NOTE: I would like to do this via IIS or server settings if possible, rather than programmatically (for reference, I am using ASP.NET 2.0).
Yes. Usually what I do is do a global catalog search using the supplied user name as the sAMAccountName. Doing this with a PrincipalSearcher requires getting the underlying DirectorySearcher and replacing it's SearchRoot. Once I find the corresponding user object I extract the domain from the user object's path and use that as the domain for the authentication step. How you do the authentication varies depending on what you need it to do. If you don't need impersonation you can use PrincipalContext.ValidateCredentials to make sure that the username/password match using a PrincipalContext that matches the domain of the user account that you previously found. If you need impersonation check out this reference.
// NOTE: implement IDisposable and dispose of this if not null when done.
private DirectoryEntry userSearchRoot = null;
private UserPrincipal FindUserInGlobalContext( string userName )
{
using (PrincipalSearcher userSearcher = new PrincipalSearcher())
{
using (PrincipalContext context
= new PrincipalContext( ContextType.Domain ))
{
userSearcher.QueryFilter = new UserPrincipal( context );
DirectorySearcher searcher
= (DirectorySearcher)userSearcher.GetUnderlyingSearcher();
// I usually set the GC path from the existing search root
// by doing some string manipulation based on our domain
// Your code would be different.
string GCPath = ...set GC path..
// lazy loading of the search root entry.
if (userSearchRoot == null)
{
userSearchRoot = new DirectoryEntry( GCPath );
}
searcher.SearchRoot = userSearchRoot;
using (PrincipalContext gcContext =
new PrincipalContext( ContextType.Domain,
null,
GCPath.Replace("GC://",""))
{
UserPrincipal userFilter = new UserPrincipal( gcContext );
userFilter.SamAccountName = userName;
userSearcher.QueryFilter = userFilter;
return userSearcher.FindOne() as UserPrincipal;
}
}
}
}