namespace System.Web.Security {
using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Util;
///
/// Validates whether a password field meets the current Membership Provider's password requirements.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "This attribute is designed to be a base class for other attributes which further want to customize password validation.")]
public class MembershipPasswordAttribute : ValidationAttribute {
#region Fields
private int? _minRequiredPasswordLength;
private int? _minRequiredNonAlphanumericCharacters;
private string _passwordStrengthRegularExpression;
private Type _resourceType;
private LocalizableString _minPasswordLengthError = new LocalizableString("MinPasswordLengthError");
private LocalizableString _minNonAlphanumericCharactersError = new LocalizableString("MinNonAlphanumericCharactersError");
private LocalizableString _passwordStrengthError = new LocalizableString("PasswordStrengthError");
#endregion
#region Properties
///
/// Minimum required password length this attribute uses for validation.
/// If not explicitly set, defaults to .
///
public int MinRequiredPasswordLength {
get {
return _minRequiredPasswordLength != null ? (int)_minRequiredPasswordLength : Membership.Provider.MinRequiredPasswordLength;
}
set {
_minRequiredPasswordLength = value;
}
}
///
/// Minimum required non-alpha numeric characters this attribute uses for validation.
/// If not explicitly set, defaults to .
///
public int MinRequiredNonAlphanumericCharacters {
get {
return _minRequiredNonAlphanumericCharacters != null ? (int)_minRequiredNonAlphanumericCharacters : Membership.Provider.MinRequiredNonAlphanumericCharacters;
}
set {
_minRequiredNonAlphanumericCharacters = value;
}
}
///
/// Regular expression string representing the password strength this attribute uses for validation.
/// If not explicitly set, defaults to .
///
public string PasswordStrengthRegularExpression {
get {
return _passwordStrengthRegularExpression ?? Membership.Provider.PasswordStrengthRegularExpression;
}
set {
_passwordStrengthRegularExpression = value;
}
}
///
/// Gets or sets the that contains the resources for ,
/// , and .
///
public Type ResourceType {
get {
return this._resourceType;
}
set {
if (this._resourceType != value) {
this._resourceType = value;
this._minPasswordLengthError.ResourceType = value;
this._minNonAlphanumericCharactersError.ResourceType = value;
this._passwordStrengthError.ResourceType = value;
}
}
}
///
/// Gets or sets the MinPasswordLengthError attribute property, which may be a resource key string.
///
///
/// The property contains either the literal, non-localized string or the resource key
/// to be used in conjunction with to configure the localized
/// error message displayed when the provided password is shorter than .
///
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "The property and method are a matched pair")]
public string MinPasswordLengthError {
get {
return this._minPasswordLengthError.Value;
}
set {
if (this._minPasswordLengthError.Value != value) {
this._minPasswordLengthError.Value = value;
}
}
}
///
/// Gets or sets the MinNonAlphanumericCharactersError attribute property, which may be a resource key string.
///
///
/// The property contains either the literal, non-localized string or the resource key
/// to be used in conjunction with to configure the localized
/// error message displayed when the provided password contains less number of non-alphanumeric characters than
///
///
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "The property and method are a matched pair")]
public string MinNonAlphanumericCharactersError {
get {
return this._minNonAlphanumericCharactersError.Value;
}
set {
if (this._minNonAlphanumericCharactersError.Value != value) {
this._minNonAlphanumericCharactersError.Value = value;
}
}
}
///
/// Gets or sets the PasswordStrengthError attribute property, which may be a resource key string.
///
///
/// The property contains either the literal, non-localized string or the resource key
/// to be used in conjunction with to configure the localized
/// error message displayed when the provided password is shorter than .
///
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "The property and method are a matched pair")]
public string PasswordStrengthError {
get {
return this._passwordStrengthError.Value;
}
set {
if (this._passwordStrengthError.Value != value) {
this._passwordStrengthError.Value = value;
}
}
}
// The timeout for the regex we use to check password strength
public int? PasswordStrengthRegexTimeout { get; set; }
#endregion
#region Overriden Methods
///
/// Overrider of .
///
///
/// Checks if the given value meets the password requirements such as minimum length, minimum number of non-alpha numeric characters
/// and password strength regular expression set in current
///
/// The value to validate.
/// A instance that provides
/// context about the validation operation, such as the object and member being validated.
///
/// When validation is valid, .
///
/// When validation is invalid, an instance of .
///
///
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
string valueAsString = value as string;
string name = (validationContext != null) ? validationContext.DisplayName : String.Empty;
string[] memberNames = (validationContext != null) ? new[] { validationContext.MemberName } : null;
string errorMessage;
if (String.IsNullOrEmpty(valueAsString)) {
return ValidationResult.Success;
}
if (valueAsString.Length < MinRequiredPasswordLength) {
errorMessage = GetMinPasswordLengthError();
return new ValidationResult(FormatErrorMessage(errorMessage, name, MinRequiredPasswordLength), memberNames);
}
int nonAlphanumericCharacters = valueAsString.Count(c => !Char.IsLetterOrDigit(c));
if (nonAlphanumericCharacters < MinRequiredNonAlphanumericCharacters) {
errorMessage = GetMinNonAlphanumericCharactersError();
return new ValidationResult(FormatErrorMessage(errorMessage, name, MinRequiredNonAlphanumericCharacters), memberNames);
}
string passwordStrengthRegularExpression = PasswordStrengthRegularExpression;
if (passwordStrengthRegularExpression != null) {
Regex passwordStrengthRegex;
try {
// Adding timeout for Regex in case of malicious string causing DoS
passwordStrengthRegex = RegexUtil.CreateRegex(passwordStrengthRegularExpression, RegexOptions.None, PasswordStrengthRegexTimeout);
}
catch (ArgumentException ex) {
throw new InvalidOperationException(SR.GetString(SR.MembershipPasswordAttribute_InvalidRegularExpression), ex);
}
if (!passwordStrengthRegex.IsMatch(valueAsString)) {
errorMessage = GetPasswordStrengthError();
return new ValidationResult(FormatErrorMessage(errorMessage, name, additionalArgument: String.Empty), memberNames);
}
}
return ValidationResult.Success;
}
public override string FormatErrorMessage(string name) {
return FormatErrorMessage(errorMessageString: ErrorMessageString, name: name, additionalArgument: String.Empty);
}
#endregion
#region Private Methods
///
/// Gets the error message string shown when the provided password is shorter than .
///
/// This can be either a literal, non-localized string provided to or the
/// localized string found when has been specified and
/// represents a resource key within that resource type.
///
///
///
/// When has not been specified, the value of
/// will be returned.
///
/// When has been specified and
/// represents a resource key within that resource type, then the localized value will be returned.
///
///
/// When has not been specified, a default error message will be returned.
///
///
///
/// After setting both the property and the property,
/// but a public static property with a name matching the value couldn't be found
/// on the .
///
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method does work using a property of the same name")]
private string GetMinPasswordLengthError() {
return this._minPasswordLengthError.GetLocalizableValue() ?? SR.GetString(SR.MembershipPasswordAttribute_InvalidPasswordLength);
}
///
/// Gets the error message string shown when the provided password contains less number of non-alphanumeric characters than
///
///
/// This can be either a literal, non-localized string provided to or the
/// localized string found when has been specified and
/// represents a resource key within that resource type.
///
///
///
/// When has not been specified, the value of
/// will be returned.
///
/// When has been specified and
/// represents a resource key within that resource type, then the localized value will be returned.
///
///
/// When has not been specified, a default error message will be returned.
///
///
///
/// After setting both the property and the property,
/// but a public static property with a name matching the value couldn't be found
/// on the .
///
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method does work using a property of the same name")]
private string GetMinNonAlphanumericCharactersError() {
return this._minNonAlphanumericCharactersError.GetLocalizableValue() ?? SR.GetString(SR.MembershipPasswordAttribute_InvalidPasswordNonAlphanumericCharacters);
}
///
/// Gets the error message string shown when the provided password is shorter than .
///
/// This can be either a literal, non-localized string provided to or the
/// localized string found when has been specified and
/// represents a resource key within that resource type.
///
///
///
/// When has not been specified, the value of
/// will be returned.
///
/// When has been specified and
/// represents a resource key within that resource type, then the localized value will be returned.
///
///
/// When has not been specified, a default error message will be returned.
///
///
///
/// After setting both the property and the property,
/// but a public static property with a name matching the value couldn't be found
/// on the .
///
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method does work using a property of the same name")]
private string GetPasswordStrengthError() {
return this._passwordStrengthError.GetLocalizableValue() ?? SR.GetString(SR.MembershipPasswordAttribute_InvalidPasswordStrength);
}
private string FormatErrorMessage(string errorMessageString, string name, object additionalArgument) {
return String.Format(CultureInfo.CurrentCulture, errorMessageString, name, additionalArgument);
}
#endregion
}
}