github.com/cs3org/reva/v2@v2.27.7/pkg/password/password_policies.go (about) 1 package password 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" 7 "strings" 8 "unicode/utf8" 9 ) 10 11 // https://owasp.org/www-community/password-special-characters 12 var _defaultSpecialCharacters = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" 13 14 // Validator describes the interface providing a password Validate method 15 type Validator interface { 16 Validate(str string) error 17 } 18 19 // Policies represents a password validation rules 20 type Policies struct { 21 minCharacters int 22 minLowerCaseCharacters int 23 minUpperCaseCharacters int 24 minDigits int 25 minSpecialCharacters int 26 bannedPasswordsList map[string]struct{} 27 digitsRegexp *regexp.Regexp 28 specialCharactersRegexp *regexp.Regexp 29 } 30 31 // NewPasswordPolicy returns a new NewPasswordPolicy instance 32 func NewPasswordPolicy(minCharacters, minLowerCaseCharacters, minUpperCaseCharacters, minDigits, minSpecialCharacters int, bannedPasswordsList map[string]struct{}) Validator { 33 p := &Policies{ 34 minCharacters: minCharacters, 35 minLowerCaseCharacters: minLowerCaseCharacters, 36 minUpperCaseCharacters: minUpperCaseCharacters, 37 minDigits: minDigits, 38 minSpecialCharacters: minSpecialCharacters, 39 bannedPasswordsList: bannedPasswordsList, 40 } 41 42 p.digitsRegexp = regexp.MustCompile("[0-9]") 43 p.specialCharactersRegexp = regexp.MustCompile(specialCharactersExp(_defaultSpecialCharacters)) 44 return p 45 } 46 47 // Validate implements a password validation regarding the policy 48 func (s Policies) Validate(str string) error { 49 var allErr error 50 if !utf8.ValidString(str) { 51 return fmt.Errorf("the password contains invalid characters") 52 } 53 err := s.validateBannedList(str) 54 if err != nil { 55 return err 56 } 57 err = s.validateCharacters(str) 58 if err != nil { 59 allErr = errors.Join(allErr, err) 60 } 61 err = s.validateLowerCase(str) 62 if err != nil { 63 allErr = errors.Join(allErr, err) 64 } 65 err = s.validateUpperCase(str) 66 if err != nil { 67 allErr = errors.Join(allErr, err) 68 } 69 err = s.validateDigits(str) 70 if err != nil { 71 allErr = errors.Join(allErr, err) 72 } 73 err = s.validateSpecialCharacters(str) 74 if err != nil { 75 allErr = errors.Join(allErr, err) 76 } 77 if allErr != nil { 78 return allErr 79 } 80 return nil 81 } 82 83 func (s Policies) validateBannedList(str string) error { 84 if len(s.bannedPasswordsList) == 0 { 85 return nil 86 } 87 if _, ok := s.bannedPasswordsList[str]; ok { 88 return fmt.Errorf("unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety") 89 } 90 return nil 91 } 92 93 func (s Policies) validateCharacters(str string) error { 94 if s.count(str) < s.minCharacters { 95 return fmt.Errorf("at least %d characters are required", s.minCharacters) 96 } 97 return nil 98 } 99 100 func (s Policies) validateLowerCase(str string) error { 101 if s.countLowerCaseCharacters(str) < s.minLowerCaseCharacters { 102 return fmt.Errorf("at least %d lowercase letters are required", s.minLowerCaseCharacters) 103 } 104 return nil 105 } 106 107 func (s Policies) validateUpperCase(str string) error { 108 if s.countUpperCaseCharacters(str) < s.minUpperCaseCharacters { 109 return fmt.Errorf("at least %d uppercase letters are required", s.minUpperCaseCharacters) 110 } 111 return nil 112 } 113 114 func (s Policies) validateDigits(str string) error { 115 if s.countDigits(str) < s.minDigits { 116 return fmt.Errorf("at least %d numbers are required", s.minDigits) 117 } 118 return nil 119 } 120 121 func (s Policies) validateSpecialCharacters(str string) error { 122 if s.countSpecialCharacters(str) < s.minSpecialCharacters { 123 return fmt.Errorf("at least %d special characters are required %s", s.minSpecialCharacters, _defaultSpecialCharacters) 124 } 125 return nil 126 } 127 128 func (s Policies) count(str string) int { 129 return utf8.RuneCount([]byte(str)) 130 } 131 132 func (s Policies) countLowerCaseCharacters(str string) int { 133 var count int 134 for _, c := range str { 135 if strings.ToLower(string(c)) == string(c) && strings.ToUpper(string(c)) != string(c) { 136 count++ 137 } 138 } 139 return count 140 } 141 142 func (s Policies) countUpperCaseCharacters(str string) int { 143 var count int 144 for _, c := range str { 145 if strings.ToUpper(string(c)) == string(c) && strings.ToLower(string(c)) != string(c) { 146 count++ 147 } 148 } 149 return count 150 } 151 152 func (s Policies) countDigits(str string) int { 153 return len(s.digitsRegexp.FindAllStringIndex(str, -1)) 154 } 155 156 func (s Policies) countSpecialCharacters(str string) int { 157 if s.specialCharactersRegexp == nil { 158 return 0 159 } 160 res := s.specialCharactersRegexp.FindAllStringIndex(str, -1) 161 return len(res) 162 } 163 164 func specialCharactersExp(str string) string { 165 // escape the '-' character because it is a not meta-characters but, they are special inside of [] 166 return "[" + strings.ReplaceAll(regexp.QuoteMeta(str), "-", `\-`) + "]" 167 }