2019-10-14 18:24:26 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package password
import (
2019-11-20 01:44:58 +03:00
"bytes"
2020-09-09 01:06:39 +03:00
goContext "context"
2019-10-14 18:24:26 +03:00
"crypto/rand"
"math/big"
2019-10-16 06:09:58 +03:00
"strings"
2019-10-14 18:24:26 +03:00
"sync"
2019-11-20 01:44:58 +03:00
"code.gitea.io/gitea/modules/context"
2019-10-14 18:24:26 +03:00
"code.gitea.io/gitea/modules/setting"
)
2019-11-20 01:44:58 +03:00
// complexity contains information about a particular kind of password complexity
type complexity struct {
ValidChars string
TrNameOne string
}
2019-10-16 06:09:58 +03:00
var (
matchComplexityOnce sync . Once
validChars string
2019-11-20 01:44:58 +03:00
requiredList [ ] complexity
2019-10-16 06:09:58 +03:00
2019-11-20 01:44:58 +03:00
charComplexities = map [ string ] complexity {
"lower" : {
` abcdefghijklmnopqrstuvwxyz ` ,
"form.password_lowercase_one" ,
} ,
"upper" : {
` ABCDEFGHIJKLMNOPQRSTUVWXYZ ` ,
"form.password_uppercase_one" ,
} ,
"digit" : {
` 0123456789 ` ,
"form.password_digit_one" ,
} ,
"spec" : {
` !"#$%&'()*+,-./:;<=>?@[\]^_ { |}~ ` + "`" ,
"form.password_special_one" ,
} ,
2019-10-16 06:09:58 +03:00
}
)
2019-10-14 18:24:26 +03:00
// NewComplexity for preparation
func NewComplexity ( ) {
matchComplexityOnce . Do ( func ( ) {
2019-10-16 06:09:58 +03:00
setupComplexity ( setting . PasswordComplexity )
} )
}
func setupComplexity ( values [ ] string ) {
if len ( values ) != 1 || values [ 0 ] != "off" {
for _ , val := range values {
2019-11-20 01:44:58 +03:00
if complex , ok := charComplexities [ val ] ; ok {
validChars += complex . ValidChars
requiredList = append ( requiredList , complex )
2019-10-14 18:24:26 +03:00
}
2019-10-16 06:09:58 +03:00
}
2019-11-20 01:44:58 +03:00
if len ( requiredList ) == 0 {
2019-10-16 06:09:58 +03:00
// No valid character classes found; use all classes as default
2019-11-20 01:44:58 +03:00
for _ , complex := range charComplexities {
validChars += complex . ValidChars
requiredList = append ( requiredList , complex )
2019-10-14 18:24:26 +03:00
}
}
2019-10-16 06:09:58 +03:00
}
if validChars == "" {
// No complexities to check; provide a sensible default for password generation
2019-11-20 01:44:58 +03:00
validChars = charComplexities [ "lower" ] . ValidChars + charComplexities [ "upper" ] . ValidChars + charComplexities [ "digit" ] . ValidChars
2019-10-16 06:09:58 +03:00
}
2019-10-14 18:24:26 +03:00
}
2019-10-16 06:09:58 +03:00
// IsComplexEnough return True if password meets complexity settings
2019-10-14 18:24:26 +03:00
func IsComplexEnough ( pwd string ) bool {
2019-10-16 06:09:58 +03:00
NewComplexity ( )
if len ( validChars ) > 0 {
2019-11-20 01:44:58 +03:00
for _ , req := range requiredList {
if ! strings . ContainsAny ( req . ValidChars , pwd ) {
2019-10-14 18:24:26 +03:00
return false
}
}
}
return true
}
2020-09-09 01:06:39 +03:00
// Generate a random password
2019-10-14 18:24:26 +03:00
func Generate ( n int ) ( string , error ) {
NewComplexity ( )
buffer := make ( [ ] byte , n )
max := big . NewInt ( int64 ( len ( validChars ) ) )
for {
for j := 0 ; j < n ; j ++ {
rnd , err := rand . Int ( rand . Reader , max )
if err != nil {
return "" , err
}
buffer [ j ] = validChars [ rnd . Int64 ( ) ]
}
2020-09-09 01:06:39 +03:00
pwned , err := IsPwned ( goContext . Background ( ) , string ( buffer ) )
if err != nil {
return "" , err
}
if IsComplexEnough ( string ( buffer ) ) && ! pwned && string ( buffer [ 0 ] ) != " " && string ( buffer [ n - 1 ] ) != " " {
2019-10-14 18:24:26 +03:00
return string ( buffer ) , nil
}
}
}
2019-11-20 01:44:58 +03:00
// BuildComplexityError builds the error message when password complexity checks fail
func BuildComplexityError ( ctx * context . Context ) string {
var buffer bytes . Buffer
buffer . WriteString ( ctx . Tr ( "form.password_complexity" ) )
buffer . WriteString ( "<ul>" )
for _ , c := range requiredList {
buffer . WriteString ( "<li>" )
buffer . WriteString ( ctx . Tr ( c . TrNameOne ) )
buffer . WriteString ( "</li>" )
}
buffer . WriteString ( "</ul>" )
return buffer . String ( )
}