Given the following code, why do I get a PrincipalExistsException when I know for a fact that the principal doesn't exist and isn't actually made?
public UserPrincipal Add(
string givenName,
string surname,
string domain) {
UserPrincipal principal = new UserPrincipal(context: base.Context) {
Enabled = true
};
if (!String.IsNullOrEmpty(givenName) && !String.IsNullOrEmpty(surname) && !String.IsNullOrEmpty(domain)) {
this.RenameInternal(principal: principal, givenName: givenName, surname: surname, domain: domain);
principal.Save();
};
return principal;
}
I can confirm that the RenameInternal() method works just fine because it get's called by a method named Rename(). So, there must be an issue with how the object is created and/or saved, but I don't know how to find out where the error is. This seems like simple enough code...
Looking through the domain controller (Windows Server 2008 R2) I can't find the "newly" create principal anywhere, so I'm assuming that it's not being created and that the exception is lying to me somehow.
I'd appreciate any help on this. Thanks in advance.
Ok, I figured it out. The SamAccountName was what was causing the exception to be thrown (thank you Microsoft for the ever helpful error messages). Anyway, it was the issue because it was trying to set it to a name that already exists. I modified it by tossing in a middle initial and it's working fine, or at-least until I get people with the same first initial, middle initial and last name.
The only thing I can think of off hand would be the following
Whatever ID your application is running under, needs to have "write" access to AD. Pretty much any ID can query AD but only ID's explicitly granted the privilege can write to it.
Related
I am calling the okta API from my .Net back-end. It works fine when creating the user (the first part of the snippet below), so I know the token is correct etc. and I can confirm the new user exists.
I also, subsequently, update a property I've added to the user called 'Site'. When attempting to assign this property a value it throws an exception on the 'UpdateAsync' line that the request body was not well formed
var user = await _oktaClient.Users.CreateUserAsync(new CreateUserWithPasswordOptions
{
Profile = new UserProfile
{
FirstName = value.FirstName,
LastName = value.Surname,
Email = value.Email,
Login = value.Email,
},
Password = value.Password,
Activate = true,
});
var newUser = await _oktaClient.Users.GetUserAsync(user.Id);
newUser["site"] = Site;
await newUser.UpdateAsync(); // This line throws!
Note: I wasn't always doing it this way, I was previously assigning to the user object and then calling UpdateAsync() once I'd updated 'site' on the user object. This is an alternative approach as I'm trying different things (i.e. getting the user from the API again). Neither approach works.
What's very strange is that this has been working, I am 100% certain of that. I'm looking through my okta users directory 'as we speak', and there are a load of users, all with their site property populated. There has been no problem previously. I believe something must've been updated okta's end that now doesn't like my request for some reason but in either case I need a work-around ideally.
I can confirm Site is neither null or empty, it's simply a string.
UPDATE
I just discovered a SetProperty extension on okta's IResource, calling this before attempting to update the user causes the same issue.
Any help on this greatly appreciated.
Looks like custom attributes can't be assigned to the User object itself anymore, but need to be in the users profile: newUser.Profile["site"] = Site;
I downloaded and setup the example,I uncommented the following in the code.
static public string[] SCOPES = { PlusService.Scope.PlusLogin, PlusService.Scope.UserinfoEmail };
It retrieves my name, friend etc but it does not retrieve my email address.
Is anyone able to assist? Possibly i'm looking in the incorrect place.
I used tested it using Try It. I tested it with all of the different scopes
https://www.googleapis.com/auth/plus.login Know your basic profile
info and list of people in your circles.
https://www.googleapis.com/auth/plus.me Know who you are on Google
https://www.googleapis.com/auth/userinfo.email View your email address
https://www.googleapis.com/auth/userinfo.profile View basic
information about your account
It doesn't appear to matter you get back the email in all of the scopes. But what does matter is that the Users email must be set to public in the Account. If its set to anything else, your circles, only you. its not listed. This appears to be true even when you are trying to see your own information. (Sending Me)
I need some help with examples how to use Credential of a current user running application.
So in windows 7 you can run application using user loged in by simply running application or you can use "Run as a different User" option and run it as another user.
In my Active Directory I have 2 account Domain User and one with Domain Admin rights. I'm login Windows as a Domain User and when I need I'm using "Run as a different User" to launch some task as a Domain Admin.
So the task is to get my Credential and use it to perform some task, lets say rename active directory user name.
Best way to do this as I can see is to ask user running application to enter Domain Admin credential on then start application and use them for various task. Of course I can easily run application with "Run as a different User" but I still need to get this credential and use them.
I've searched through the web and I can't find this, all i could find is using credential for a web auth.
If you can show me some examples how to:
1) Ask user for a Admin user credential ( i can leave without this )
2) Get and use credentials of a user running application
I don't want to know password I know I can't. Don't really want to add to a WPF form password box I prefer to use windows API to handle this i've already entered user name and password using "Run as a different User".
PS: I sorry if this topic exists :( I guess I'm bad at creating correct search requests.
ADDED: to be more clear what I need. In powershell it will look like this:
# This Asks user to enter credentials
$cred = Get-Credential;
# this checks if I have rights to use them.
Get-ADDomain “DOMAIN” –Server “Domain.com” –Credential $cred;
Of course it's simplified as hell though the point is that I can use credentials user entered when ever it's needed.
The equivalent C# to your Get-ADDomain is quite simple, it is just
public void PerformSomeActionAsAdmin(string adminUsername, string adminPassword)
{
//Null causes the constructor to connect to the current domain the machine is on.
// |
// V
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, null, adminUsername, adminPassword))
{
//do something here with ctx, the operations will be performed as whoever's username and password you passed in.
}
}
if you don't want to connect to the current domain and instead want to connect to Domain.com then replace the null with the appropriate string.
EDIT: if you want to use secure strings you can't use System.DirectoryServices.AccountManagement.PrincipalContext, you will need to go with the lower level calls in System.DirectoryServices.Protocols. Doing this process is quite complex, here is a link to the MSDN article "Introduction to System.DirectoryServices.Protocols (S.DS.P)" explaining how to use it. It is a big complex read and honestly I don't think it is worth it to be able to use encrypted strings.
public void PerformSomeActionAsAdmin(NetworkCredential adminCredential)
{
using(LdapConnection connection = new LdapConnection("fabrikam.com", adminCredential))
{
// MAGIC
}
}
Do you want to check if the current user is a doman admin? start by looking at his code, it should help you get started identifying what AD groups the current user is in. This will give you a list of strings that are each group's name the current user belongs to. Then you can check that list against whatever AD group you are trying to check for. Replace YourDomain with your domain name:
WindowsIdentity wi = WindowIdentity.GetCurrent();
List<string> result = new List<string>();
foreach (IdentityReference group in wi.Groups)
{
result.Add(group.Translate(typeof(NTAccount)).ToString().Replace("YourDomain\\", String.Empty));
}
Since i'm not quite sure what you're trying to do, this also might be helpful. You'd have to get the user name and password from a textobx, password box etc. This could be used for an "override" to use, for example, a manager's credentials etc. to do something the current user wasn't allowed to do because of AD group membership etc.
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YourDomain"))
{
if (UserName.Contains("YourDomain\\"))
{
UserName = UserName.Replace("YourDomain\\", String.Empty);
}
//validate the credentials
bool IsValid = pc.ValidateCredentials(UserName, Password);
}
History
We have an elaborate system which ties into DNN on multiple levels for custom user information. Our users are governed under a completely different database than DNN. Because of this, we have special requirements and have created our own membership provider to our database and registration form for our specific needs.
The Problem
In the past, we have used our membership provider and registration form with no problems. Recently, we have encountered a strange situation where the username is mangled after someone attempts to register a new account. It is mangled in such a way that the username becomes the user's Region followed by a hyphen, followed by the user's email address in full. As an example, if a user fills out the form with the following:
username: user
region: CA
email: me#example.com
Before UserController.CreateUser(user) is called, the userInfo.Username equals "user". However, after the call completes, it will be "CA-me#example.com" and the created record in the User table will reflect this change.
Using my custom membership provider (which calls the default AspNetMembershipProvider) verifies that the issue seems to be with AspNetMembershipProvider's CreateUser(ref userInfo); Before this call, the username is "user", but after this call completes it becomes "CA-me#example.com". The result of the call is Success and the user is created in the database. So it doesn't appear to be a failure that is causing the issue.
Settings
I'm not sure if there are settings on DNN which may cause this problem. In my development environment, this entire thing is a non-issue. It only occurs in the live and a secondary internal test environment. Since I'm not able to reproduce this bug in my debug environment, I've considered this might be a site configuration issue. The only option I've found on Site Settings is to make the username the same as the email address, but this is disabled.
I'm going to post my membeship provider code for updating the DNN record, and I'll post the code which creates the user in my register module. I don't know if anyone might need additional code, but I'm willing to provide more where necessary.
Membership Provider
/// <summary>
/// Updates the user in the DNN database.
/// </summary>
/// <param name="userInfo"></param>
private void UpdateDnn(UserInfo userInfo)
{
// _defaultProvider is an instance of AspNetMembershipProvider
if (_defaultProvider.GetUser(userInfo.PortalID, userInfo.UserID) != null)
_defaultProvider.UpdateUser(userInfo);
else
_defaultProvider.CreateUser(ref userInfo);
}
Register Module
public DotNetNuke.Security.Membership.UserCreateStatus Register(Inputs.RegistrationInput input)
{
var userInfo = new UserInfo();
input.Fill(portalId, userInfo); // This copies the user input into the userInfo object.
return UserController.CreateUser(ref userInfo);
}
RegistrationInput.Fill()
public void Fill(int portalId, UserInfo userInfo)
{
if (userInfo == null) throw new ArgumentNullException("userInfo");
userInfo.PortalID = portalId;
userInfo.FirstName = FirstName;
userInfo.LastName = LastName;
userInfo.Username = UserName;
userInfo.DisplayName = DisplayName;
userInfo.Email = Email;
userInfo.Username = UserName;
userInfo.Membership.Password = Password;
userInfo.Profile.Street = Street;
userInfo.Profile.Unit = SuiteApt;
userInfo.Profile.Region = State;
userInfo.Profile.City = City;
userInfo.Profile.PostalCode = Zip;
userInfo.Profile.Telephone = PrimaryPhone;
}
Update
I was digging around in DDN's core for a work around and stumbled upon this (AspNetMembershipProvider.cs:870):
// Check that the OAuth service currently being used for login is the same as was previously used (this should always be true if user authenticated to userid)
if (authUser == null || authUser.AuthenticationType.Equals(service, StringComparison.OrdinalIgnoreCase))
{
isOAuthUser = true;
//DNN-4133 Change username to email address to ensure multiple users with the same email prefix, but different email domains can authenticate
user.Username = service + "-" + user.Email;
}
else
{
createStatus = UserCreateStatus.DuplicateEmail;
}
The line user.Username = service + "-" + user.Email is in the exact format of the username that I am receiving after I call UserController.CreateUser(). The question now is: is this an error in my code or their code? I'm going to keep digging into this, and if nobody answers, I'll try to post an answer to this problem after I understand what is happening here. Furthermore, if I find this to be a bug in their code, I'll post a bug report on DNN's bug tracker and link back to the bug page here.
I finally found the problem. It turns out that the issue is with a line of code above the posted code in the membership provider in the same function:
string service = HttpContext.Current.Request.Params["state"];
The problem with this line is that I pass state via POST as the user's state of residency. DNN seems to consider it the service state. The solution to this is to change the POST input parameter name from state to something else - preferably region as DNN recognizes states as regions. An easy fix, all-in-all, but it took me quite a while to find it. I think DNN should have allowed this parameter to be changed via the state of the membership object or parameter rather than grabbing it directly from the request, but there's not much that can be done on our end in that regard (modifying the core source is strongly discouraged).
I have an MVC application that needs to login and verify a user against Active Directory. I am using the PrincipalContext.ValidateCredentials method but always get a authentication of false.
Connecting to the Server is fine. The problem seems to occur in the ValidateCredentials.
Here is my code:
public static bool IsAuthenticated(string domain, string username, string pwd) {
bool IsAuthenticated = false;
try {
PrincipalContext insPrincipalContext =
new PrincipalContext(ContextType.Domain, domain, "DC=c1w,DC=com");
username = "c1w\\" + username;
IsAuthenticated = insPrincipalContext.ValidateCredentials(username, pwd);
}
catch (Exception ex)
{
// Rethrow this exception
ExceptionPolicy.HandleException(ex, "Exception Policy");
}
return IsAuthenticated;
}
Anyone know why this would be happening?
Here's how ValidateCredentials(string, string) works: First, it tries to authenticate with the Negotiate, Signing, and Sealing context options. If this fails, it tries again with SimpleBind and SecureSocketLayer.
The problem is that the NT4 (AKA "legacy", AKA "down-level name") format (DOMAIN\UserName, or more correctly, NetBiosName\SamAccountName) doesn't work with Negotiate. But it does work with SimpleBind.
So what's probably happening when calling the 2-parameter ValidateCredentials() method, is that it first fails using Negotiate because it doesn't like the NT4 format, and then fails again when using simple bind.
During my own testing, I've found that the reason why it fails even after falling back to using simple bind is that it's not only using SimpleBind. It's using SimpleBind plus SecureSocketLayer. This means that it will still fail if the Active Directory server isn't set up correctly to use SSL (a common scenario for test environments).
As was mentioned in one of the comments, you NEVER, NEVER want to use SimpleBind by itself (without SecureSocketLayer), otherwise your passwords are sent over the network in plain text.
In the wild, I've seen that some Active Directory systems don't allow the use of simple binds at all, so you must make it work with Negotiate.
I've found 2 ways to deal with this problem:
1) If everything is happening on the same domain, you should be able to call ValidateCredentials with only the username (SAM account name), leaving out the "DOMAIN\" part. Then, it will work properly the first time with Negotiate.
2) If the domain part is important because there may be multiple domains involved (i.e. Domain1\UserA and Domain2\UserA are different people), then it gets a bit more complicated. In this case what I ended up doing was translating the NT4 name (DOMAIN\User) to "user principal name" format (e.g. LogonName#domain.com). There are a couple different ways to do this. The easiest is probably to use the 3-parameter overload of UserPrincipal.FindByIdentity(), and then grab the value of the UserPrincipalName property on the result. Another way would be to use a DirectorySearcher and query LDAP://domain for the userPrincipalName property of the user with the matching sAMAccountName value. Note: this solution will only work if all the domains involved are in the same forest.
I don't see where you initializes the "pwd" variable
Maybe you should use ContextOption in this method to specify exactly the reqired behaviour. Sorry for too broad response but there is no much details in your question
It seems you are validating the user with domain\userName format. You might want to parse the domain name from userName and user the ValidateCredential.