I'll explain what I'm trying to do first. I have a claims identifier being passed in, for example, 0e.t|saml provider|first.last#domain.local. I want to trim that to first.last#domain.local.
I am well aware that this can be done with simple string formatting, however, this is not very flexible, so if something gets passed in that I don't expect, the string formatting could fail. It's simply more prone to issues.
I want to do this dynamically instead. Here's what I've tried.
Attempting to EnsureUser with the claims identifier above, then calling SPUser.Name:
SPUser spUser = SPContext.Current.Web.EnsureUser(spUserName);
userName = spUser.Name;
I've tried the following strings as a parameter in EnsureUser, all result in an exception: The user with the specified logonName cannot be found in Active Directory.
0e.t|saml provider|first.last#domain.local
saml provider|first.last#domain.local
first.last#domain.local
Again, all of those fail.
Another approach I tried, using SPClaimProviderManager (pulled from this blog):
SPClaimProviderManager mgr = SPClaimProviderManager.Local;
if (mgr != null && SPClaimProviderManager.IsEncodedClaim(userName))
{
spUserName = mgr.DecodeClaim(SPContext.Current.Web.CurrentUser.LoginName).Value;
}
I want to ensure that I'm attempting to decode an actual claim, and not something else, so I call IsEncodedClaim. This, however, always returns false.
My questions:
1) What am I doing wrong here that results in both of these attempting failing? What do I need to do differently for them to function properly?
2) Is there a better method to get a friendly claims user name without string parsing?
sigh Somehow the i: at the beginning of the claims string was being lopped off.
Should have read i:0e.t|saml provider|first.last#domain.local. Once that was added back, everything started functioning properly.
Related
I am using .NET Framework 4.8 by necessity. I am running into an issue where:
UserPrincipal.FindByIdentity(context, username); is resulting in a System.DirectoryServices.AccountManagement.MultipleMatchesException exception for a specific username. I can retrieve other users just fine.
There are two accounts in my Active Directory that this user utilizes, however, both of them have different UPNs, sAMAccountNames, etc.
I took a look at the IdentityType enum and the FindByIdentity overload that allows for an exact IdentityType look-up. With this information, I used each possible IdentityType and tried the searches:
None of these lines of code result in a System.DirectoryServices.AccountManagement.MultipleMatchesException exception. The Sid and Guid look-ups fail because the username field isn't in the right format, and the other look-ups either pull the user or pull null.
So what am I missing here? Does the overload that leaves out a specific IdentityType do a different look-up than anything that's possible by supplying an IdentityType?
Edit #1: As Alex pointed out, I left out that I tried IdentityType.UserPrincipalName from my screenshot. This was just a cropping mistake. That call also does not throw an exception.
When you don't specify which identifier you're using, it's going to try them all at once. The source code is available now. The code that actually builds the query and executes it is here. Specifically it's line 617 where it starts building the filter. The source for IdentityClaimToFilter is here. Then it puts each condition in an OR.
So if we ignore Guid, Sid, and DistinguishedName, since we know those won't match, this is the relevant LDAP query (assuming the username you're using is "UserName"):
(|(sAMAccountName=UserName)(userPrincipalName=UserName)(name=UserName))
Try making that query in PowerShell and see if you get multiple matches (I'm assuming the computer you run this from is joined to the domain you want to search):
$search = [adsisearcher]"(|(sAMAccountName=UserName)(userPrincipalName=UserName)(name=UserName))"
$search.FindAll()
If the sAMAccountName of one account matches the name of another account, for example, you will get multiple matches.
I was just wondering how I could check (when a user is changing password after the password expires) how I could use (in WebMatrix) an if branch to ensure that the new password does not equal the previous password.
I don't think I want to check for any more password history beyond just the last password used, so as long as I can just check the previous password, I think that will be fine.
I could of course query the database and check, but as the password doesn't get stored in plain text, I know that this won't work, but I also checked on the WebSecurity methods here:
http://msdn.microsoft.com/en-us/library/webmatrix.webdata.websecurity(v=vs.111).aspx
and didn't find anything.
What is the best way to get this done?
Since the password is not stored in the database, there is no way for you to do this, unless you make a note of the password when the user first registers, and whenever they subsequently change it.
The irony here is that by storing the original password (even in an encrypted state), you actually reduce the security of your application.
For any who are interested, I did find a nice workaround to this question, that gets the job done just fine.
Keep in mind, though, that this will only work to check the very last password they had/have.
This is what I implemented:
First, of course, in the log-in page, among other code and after actual log-in, I have the obvious (to check if their password is over 6 months old and require change):
if(WebSecurity.GetPasswordChangedDate(username).AddMonths(6) < DateTime.UtcNow)
{
WebSecurity.Logout();
Session["gActionMessage"] = "Your password has expired. Please change your password by visiting \"Login\" then \"Change Password\"";
Session["gActionMessageDisplayed"] = "not";
Response.Redirect("~/");
}
Then, I came up with this on the "Change Password" page (actually the redirected page after email verification for password reset token, but you get the idea):
if(WebSecurity.Login(email, newPassword, false) && WebSecurity.UserExists(email) && WebSecurity.GetPasswordChangedDate(email).AddMonths(6) < DateTime.UtcNow)
{
WebSecurity.Logout();
errorMessage = "You cannot repeat your last expired password.";
}
The if branch here does three checks:
First, it effectively checks and logs them in if possible based off of what they typed in as their new password.
Secondly, it checks if the user exists (not really sure if I even need this, but whatever).
And lastly, checks to make sure that their password change date is over 6 months old (because the same page is used for "forgot password" stuff, so this just ensures that the right circumstances are met before erring in this way).
So, in short, if their new password is still sufficient to log them in (before it actually gets changed, of course), then it is a repeat and subsequently logs them out and throws the error message at them instead of changing the password. If it is not sufficient to log them in, then it can't be a repeated password, and (so long as it meets any other requirements) the password is then changed.
Hope this helps anyone, who may require a non-repeated password using WebMatrix upon password change, in the future!
Reference the System.Web.Helpers assembly,
Create a custom table called 'UserPasswordHistory' that contains stored hashed passwords in the 'Password' column with an incremented version number in the 'PasswordVersion' column, a row of which is inserted each time a user is registered or a password updated for a given user,
the following code works.
var userProfile = db.UserProfiles.First(x => x.UserId == userId);
var passwords = (userProfile.UserPasswordHistory
.OrderByDescending(x => x.PasswordVersion)
.Take(_configuration.PasswordCountBeforeReuseAllowed))
.Select(x => x.Password);
return passwords.Any(previousPassword => Crypto.VerifyHashedPassword(previousPassword, password));
To answer the question specifically asked _configuration.PasswordCountBeforeReuseAllowed would be set to 1
I'm trying to retrieve some obsure Active Directory Attributes:
msexchmailboxsecuritydescriptor, and
terminalservicesprofilepath (in userparameters)
I am having trouble getting to both of them.
For example, for msexchmailboxsecuritydescriptor, if I have code similar to the following:
DirectoryEntry deresult = result.GetDirectoryEntry();
byte[] bteMailACL =(byte[])deresult.Properties["msexchmailboxsecuritydescriptor"].Value;
It complains that I cannot cast System.__ComObject to System.Byte[], but I have seen several example that use code similar to the above.
How do I understand these blobs of information?
I think your problem is in .Value part of the statement. Not sure how the examples have been doing it but I've noticed that whenever I call an AD Property like that, I always get an array back of which I get index 0 in case of single result items.
just changing the last statment to:
byte[] btwMailACL = (byte[])deresult.Properties["msexchmailboxsecuritydescriptor"][0];
solves your problem.
Edit: for production code, please do remember that this can throw a NullReferenceException so do check if the property actually returned a value before calling on the index.
(Tested on my machine and working as above)
I might be overlooking something obvious here, but is it possible to return a Sitecore user with the username in the capitalisation they used when registering?
At the moment the username will be displayed as whatever the user typed when they logged in, but I'd like to be able to get the original string.
I'm using User user = User.FromName(domainUser, false);
UPDATE:
This is what I ended up with after Yan's excellent answer:
// get the MembershipUser object normally by name
var initialUser = Membership.GetUser(domainUser, false);
if (initialUser != null)
{
// get the same MembershipUser by Id - in this case it retuns username in correct case
initialUser = Membership.GetUser(initialUser.ProviderUserKey, false);
}
// get the Sitecore user from the username in correct case
Sitecore.Security.Accounts.User user = Sitecore.Security.Accounts.User.FromName(initialUser.UserName, false);
Thanks,
Annelie
That's an interesting question, and it was fun to dig into this deeper.
The line of code you mentioned doesn't actually go to the security database to get the user. It only creates a User object with the name you provided.
You can refer to the lower-level ASP.NET class System.Web.Security.Membership. It contains the method GetUser, which is overloaded a number of times. The trick is that its overloaded version which accepts username as a parameter behaves the same as User.FromName. I mean it doesn't return actual UserName from the underlying SQL table, but uses the username you passed in parameters to GetUser method. It still works correctly - all comparisons are done in lowercase.
So, if you DO need to get real case of the user name, you can use one of the methods which actually query the underlying SQL source for the UserName field. For instance, GetUser(object, bool), which gets the user by UserId:
// get the MembershipUser object normally by name
var user = Membership.GetUser(#"sitecore\admin", false);
if (user != null)
{
// get the same MembershipUser by Id - in this case it retuns username in correct case
user = Membership.GetUser(user.ProviderUserKey, false);
// use the user.UserName value as you wish
Response.Write(user.UserName);
}
As you can see, it is 2 calls to the database instead of 1 for Membership.GetUser(username) and instead of 0 for User.FromName(username).
Alternatively, if you know for sure the user has email, and you know this email, you can use Membership.GetUserNameByEmail(email) method.
Consider the following scenario:
http://www.yourdomain.com/Default.aspx?p=2
Now we ofcourse want to check if the querystring parameter p doesnt contain errors.
I now have this setup:
1) Check if p exists
2) Filter out html from p's value
3) htmlencode p's value
4) check if p is integer
5) check if p's integer exists in db
This is how I usual do it, though step 5 is ofcourse a performance hit.
Kind regards,
Mark
My view: Generally a querystring parameter of this kind isn't really "entered" by users but is submitted as a link. So over-complex slow validation isn't really necessary.
So I would just pass this through to the persistence / data layer and handle any errors that come back as a regular 404 Not Found or 500 Internal Server Error depending on the kind of system I'm working with.
If your intent is to use the parameter to retrieve something from the database, why filter out html or encode it? It's not like you're going to store it in the database, or display it on the front end. Just immediately throw it to the DAL if it exists. You're DAL should be smart enough to tell you if it failed to retrieve a record with that ID, or if the ID couldn't be parsed, etc..
If you are going to convert the input to an integer anyway, then steps 2 and 3 are not needed - just use int.TryParse to see what you have. I would encode and test the input for html only if you are expecting a string which you will use in a dynamic sql statement, or will be displaying on your site
What about:
int p = 0;
if(!Int32.TryParse(Request.QueryString["p"], out p))
throw new ArgumentOutOfRangeException("p");
Quite simple. For most data types (integers, decimals, doubles, dates and booleans) there is a very strict format. If the value does not parse under the strict format, it's an error.
Strings sometimes have a strict format, like an email address or a phone number. Those can be validated with a simple regexp. If it conforms, use it, otherwise it's an error.
Most of the time however strings will simply need to be persisted to the DB and later displayed again. In that case no processing is needed, aside from escaping when inserting into DB (unnecessary as well if you used parametrized queries)k, and HTML-encoding when rendering to the display.
This way any and all data is validated, and there is no risk of any injections whatsoever.
The rare exception of a loose format for a string is, well... rare. I can't think of any right now. For that you can afford some more extensive parsing and processing.
Added: Oh, yes, checking whether IDs (or other values) are valid in respect to a DB. You're doing it right, but think if you always need it. Quite often you can put the check into some other query that you have to do anyway. Like when you select data based on the ID, you don't need to explicitly check that it exists - just be ready that your query can return no data.
Sometimes you don't need to use the value at all, then you can simply ignore it.
But, of course, there are other times, like when inserting/updating data, that you indeed need to explicitly check whether the data exists and is valid in the current context.