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 } }