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).
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;
We have a mvc application that is using Active Directory to authenticate our users. We are leveraging System.DirectoryServices and using the PricipalContext to authenticate:
_principalContext.ValidateCredentials(userName, pass, ContextOptions.SimpleBind);
However this method only returns a bool and we want to return better messages or even redirect the user to a password reset screen for instances like:
The user is locked out of their account.
The users password is expired.
The user needs to change their password at next login.
So if the user fails to login we call NetValidatePasswordPolicy to see why the user was not able to log in. This seemed to work well but we realized that this method was only returning NET_API_STATUS.NERR_PasswordMustChange no matter what the state of the Active Directory user was.
The only example I have found with this same problem comes from a Sublime Speech plugin here. The code I am using is as follows:
var outputPointer = IntPtr.Zero;
var inputArgs = new NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG { PasswordMatched = false, UserAccountName = username };
inputArgs.ClearPassword = Marshal.StringToBSTR(password);
var inputPointer = IntPtr.Zero;
inputPointer = Marshal.AllocHGlobal(Marshal.SizeOf(inputArgs));
Marshal.StructureToPtr(inputArgs, inputPointer, false);
using (new ComImpersonator(adImpersonatingUserName, adImpersonatingDomainName, adImpersonatingPassword))
{
var status = NetValidatePasswordPolicy(serverName, IntPtr.Zero, NET_VALIDATE_PASSWORD_TYPE.NetValidateAuthentication, inputPointer, ref outputPointer);
if (status == NET_API_STATUS.NERR_Success)
{
var outputArgs = (NET_VALIDATE_OUTPUT_ARG)Marshal.PtrToStructure(outputPointer, typeof(NET_VALIDATE_OUTPUT_ARG));
return outputArgs.ValidationStatus;
}
else
{
//fail
}
}
The code always succeeds so why is the value of outputArgs.ValidationStatus the same result every time regardless of the state of the Active Directory user?
I will break the answer to this question into three different sections:
The Current Problem With Your Methodology
The Issues With Recommended Solutions both Online, and in this Thread
The Solution
The current problem with your methodology.
NetValidatePasswordPolicy requires its InputArgs parameter to take in a pointer to a structure, and the structure you pass in depend on the ValidationType your're passing in. In this case, you are passing NET_VALIDATE_PASSWORD_TYPE.NetValidateAuthentication, which requires an InputArgs of NET_VALIDATE_AUTHENTICATION_INPUT_ARG but you're passing in a pointer to NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG.
Furthermore, you are attempting to assign a "currentPassword' type of value to the NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG structure.
However, there's a bigger fundamental proble to the use of NetValidatePasswordPolicy and that is that you are trying to use this function to validate passwords in Active Directory, but this is not what it is used for. NetValidatePasswordPolicy is used to allow applications to validate against a authentication database provided by the application.
There's more information about NetValidatePasswordPolicy here.
The issues with recommended solutions both online, and in this thread
Various articles online recommend using the LogonUser function found in AdvApi32.dll but this implementation carries its own set of issues:
The first is that LogonUser validates against a local cache, and that means that you will not get immediate accurate information about the account, unless you use the "Network" mode.
The second is that using LogonUser on a Web application, in my opinion is a bit hacky, as it is designed for desktop applications running on client machines. However, considering the limitations provided Microsoft if LogonUser gives desired results, I don't see why it shouldn't be used - barring the caching issues.
Another issue with LogonUser is that how well it works for your use case depends on how your server is configured, for example: There are some particular permissions that need to be enabled on the domain you're authenticating against that need to be in place for 'Network' logon type to work.
More information about LogonUser here.
Also, GetLastError() should not be used, GetLastWin32Error() should be used instead, as it is not safe to use GetLastError().
More information about GetLastWin32Error() here.
The solution.
In order to get an accurate error code from Active Directory, without any caching issues and straight from directory services, this is what needs to be done: rely on COMException coming back from AD when there's an issue with the account, because ultimately, errors is what you're looking for.
First, here's how you trigger an error from Active Directory on authentication of a current user name and a password:
public LdapBindAuthenticationErrors AuthenticateUser(string domain, string username, string password, string ouString)
{
// The path (ouString) should not include the user in the directory, otherwise this will always return true
DirectoryEntry entry = new DirectoryEntry(ouString, username, password);
try
{
// Bind to the native object, this forces authentication.
var obj = entry.NativeObject;
var search = new DirectorySearcher(entry) { Filter = string.Format("({0}={1})", ActiveDirectoryStringConstants.SamAccountName, username) };
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (result != null)
{
return LdapBindAuthenticationErrors.OK;
}
}
catch (DirectoryServicesCOMException c)
{
LdapBindAuthenticationErrors ldapBindAuthenticationError = -1;
// These LDAP bind error codes are found in the "data" piece (string) of the extended error message we are evaluating, so we use regex to pull that string
if (Regex.Match(c.ExtendedErrorMessage, #" data (?<ldapBindAuthenticationError>[a-f0-9]+),").Success)
{
string errorHexadecimal = match.Groups["ldapBindAuthenticationError"].Value;
ldapBindAuthenticationError = (LdapBindAuthenticationErrors)Convert.ToInt32(errorHexadecimal , 16);
return ldapBindAuthenticationError;
}
catch (Exception e)
{
throw;
}
}
return LdapBindAuthenticationErrors.ERROR_LOGON_FAILURE;
}
And these are your "LdapBindAuthenticationErrors", you can find more in MSDN, here.
internal enum LdapBindAuthenticationErrors
{
OK = 0
ERROR_INVALID_PASSWORD = 0x56,
ERROR_PASSWORD_RESTRICTION = 0x52D,
ERROR_LOGON_FAILURE = 0x52e,
ERROR_ACCOUNT_RESTRICTION = 0x52f,
ERROR_INVALID_LOGON_HOURS = 0x530,
ERROR_PASSWORD_EXPIRED = 0x532,
ERROR_ACCOUNT_DISABLED = 0x533,
ERROR_ACCOUNT_EXPIRED = 0x701,
ERROR_PASSWORD_MUST_CHANGE = 0x773,
ERROR_ACCOUNT_LOCKED_OUT = 0x775
}
Then you can use the return type of this Enum and do what you need with it in your controller. The important thing to note, is that you're looking for the "data" piece of the string in the "Extended Error Message" of your COMException because this contains the almighty error code you are hunting for.
Good luck, and I hope this helps. I tested it, and it works great for me.
For the life of me I can no figure out where to get the SessionID out of the service so that I can use it in a future call to recreate the same session.
I am currently using the 2013_1_0 version of the wsdl in C#
When referencing the older documents SuiteTalkWebServicesPlatformGuide_2011.1.pdf on page 78 (Reusing Session IDs During Login) they talk about this.
Any Suggestions?
Here is my sample code for logging into NetSuite without using a previous SessionID...
// invoke the login operation
Passport passport = new Passport();
passport.account = _sNetSuiteAccount;
passport.email = _sNetSuiteUserName;
RecordRef role = new RecordRef();
role.externalId = _sNetSuiteRole;
passport.role = role;
passport.password = _sNetSuitePassword;
status = service.login(passport).status;
// Confirm login was a success
// Process response
if (status.isSuccess == true)
{
_bIsNetSuiteAuthenticated = true;
}
Additionally I have yet to find sample code for using the SessionID to re-create the service. So any suggestion there would also be welcomed.
Thank you!
In talking with NetSuite what I desire to do is impossible and the SessionID is stored as a private variable and handled by the .NET code.
I am currently seeking an alternative to a persistent session so when code base is called multiple times that the user will not need to be prompted for each iteration to enter their NetSuite cridentials. I will update this answer as more details come available on this.
Update
Thanks to a comment by #IvanL, it turns out that the problem is Google specific. I have since tried other providers and for those everything works as expected. Google just doesn't seem to send claims information. Haven't yet been able to figure out why or what I need to differently to get Google to send it.
A wild stab in the dark says it may be related to the realm being defaulted to http://:/ as I have seen an answer by Andrew Arnott that Google changes the claimed identifier for the same account based on the realm passed with the authentication request.
Another possibly important tidbit of information: unlike many of the examples that can be found around the web for using dotnetopenauth, I am not using a "simple" textbox and composing the openIdIdentifier myself, but I am using the openID selector and that is providing the openIdIdentifier passed to the ValidateAtOpenIdProvider. (As per the Adding OpenID authentication to your ASP.NET MVC 4 application article.)
Question is: why is IAuthenticationResponse.GetExtension() always returning null when using Google as the openId provider, when otherwise all relevant gotcha's with regard to Google (Email requested as required, AXFetchAsSregTransform, etc) have been addressed?
Original
I am struggling with getting DotNetOpenAuth to parse the response returned from the provider. Followed the instructions of Adding OpenID authentication to your ASP.NET MVC 4 application up to the point where the login should be working and a login result in a return to the home page with the user's name (nick name) displayed at the top right. (That is up to "The user should at this point see the following:" just over half way down the article).
I am using Visual Studio Web Developer 2010 Express with C#. DotNetOpenAuth version is 4.0.3.12153 (according to the packages.config, 4.0.3.12163 according to Windows Explorer).
My web.config was modified following the instructions in Activating AXFetchAsSregTransform which was the solution for DotNetOpenId - Open Id get some data
Unfortunately it wasn't enough to get it working for me.
The openid-selector is working fine and resulting in a correct selection of the openid provider. The authentication request is created as follows:
public IAuthenticationRequest ValidateAtOpenIdProvider(string openIdIdentifier)
{
IAuthenticationRequest openIdRequest = openId.CreateRequest(Identifier.Parse(openIdIdentifier));
var fields = new ClaimsRequest()
{
Email = DemandLevel.Require,
FullName = DemandLevel.Require,
Nickname = DemandLevel.Require
};
openIdRequest.AddExtension(fields);
return openIdRequest;
}
This all works. I can login and authorize the page to receive my information, which then results in a call to GetUser:
public OpenIdUser GetUser()
{
OpenIdUser user = null;
IAuthenticationResponse openIdResponse = openId.GetResponse();
if (openIdResponse.IsSuccessful())
{
user = ResponseIntoUser(openIdResponse);
}
return user;
}
openIdResponse.IsSuccessful is implemented as an extension method (see linked article):
return response != null && response.Status == AuthenticationStatus.Authenticated;
and always is successful as the ResponseIntoUser method is entered:
private OpenIdUser ResponseIntoUser(IAuthenticationResponse response)
{
OpenIdUser user = null;
var claimResponseUntrusted = response.GetUntrustedExtension<ClaimsResponse>();
var claimResponse = response.GetExtension<ClaimsResponse>();
// For this to work with the newer/est version of DotNetOpenAuth, make sure web.config
// file contains required settings. See link for more details.
// http://www.dotnetopenauth.net/developers/help/the-axfetchassregtransform-behavior/
if (claimResponse != null)
{
user = new OpenIdUser(claimResponse, response.ClaimedIdentifier);
}
else if (claimResponseUntrusted != null)
{
user = new OpenIdUser(claimResponseUntrusted, response.ClaimedIdentifier);
}
else
{
user = new OpenIdUser("ikke#gmail.com;ikke van ikkenstein;ikke nick;ikkeclaimedid");
}
return user;
}
My version above only differs from the code in the linked article by my addition of the final else block to ensure that I always get the home page with a user name and a logoff link displayed (which helps when trying to do this several times in succession).
I have tried both Google and Yahoo. Both authenticate fine, both return an identity assertion as logged by the WebDev server. However, GetUntrustedExtenstion and GetExtension always return null. I always get to see "ikke nick" from the last else, never the name I actually used to authenticate.
I am at a loss on how to continue to try and get this to work. It probably is some oversight on my part (I am an experienced developer but just started dipping my toes in C# and web front-end development), and I can't see it.
Any and all suggestions on how to proceed / debug this are very much welcome.
Are you using Google as OpenId provider to test your solution against? Because Google has/had the habit of including the Claims only the first time you authenticate the application. So perhaps try using a fresh google account and see if that works?
Sorry for the slow response, doing a big migration at a client this week :-) Glad that this little comment resolved your issue.
I am editing a c# WinForm solution and I do not understand the code that gets the user account name. The code is shown below.
The application shows a customized form for each user account and the user account name is needed to get user-specific configuration values from an SQL database.
What happens, to the best I can tell, is the returned user name is correct for the first user account accessed, but after switching to a different user account, the returned user account name is not updated and the initial user account name continues to be returned.
#region "Function to retrieve LoggedIn user"
/// <summary>
/// "Function to retrieve LoggedIn user"
/// </summary>
/// <returns></returns>
private string GetLoggedInUserName()
{
ManagementClass objManClass = new ManagementClass("Win32_Process");
ManagementObjectCollection arrManObjects = objManClass.GetInstances();
foreach (ManagementObject objMan in arrManObjects)
{
if (objMan["Name"].ToString().Trim().ToLower() == "explorer.exe")
{
string[] arrArgs = { "", "" };
try
{
objMan.InvokeMethod("GetOwner", arrArgs);
sUserName = arrArgs[0];
break;
}
catch (Exception lExp)
{
BusinessObject.Logger.Logger.Log(lExp);
}
}
}
return sUserName;
}
#endregion
This application is to run on XP, Vista and 7.
My instinct is to just use something like...
string sUserName = Environment.UserName;
...but my knowledge of the Windows OS is poor and the people who wrote the original code are much smarter than me.
So my two questions are:
(1) Why does this code appear to not update to the new user name when I change user accounts?
(2) why use the 'explore.exe' method instead of simply using 'Environment.UserName'?
Also, two projects in my solution have a GetLoggedInUserName()method. One project runs in the background with a timer that calls the other project, and that project generates the user-customized form.
I have another related question about why the form fails to appear for all user accounts except the admin account that I will post as a separate question once I figure out this question.
If you want the currently logged in user, use can use the WindowsIdentity object:
string currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
The Explorer process is always running when you log onto a Windows box, so it will always be found. If you open Task Manager and view the processes you will see it, and the account that started it. It looks like a throw back to VBScript, although I'm sure that there is an easier way to it with that too.
There is no good reason to use WMI to get the current user account on a local machine over other simpler methods.
For the user name bit try ...
string username = System.Security.Principal.WindowsIdentity.GetCurrent().Name;