in cli/azd/pkg/password/generator.go [69:179]
func Generate(config GenerateConfig) (string, error) {
var minLower uint
if config.MinLower != nil {
minLower = *config.MinLower
}
var minUpper uint
if config.MinUpper != nil {
minUpper = *config.MinUpper
}
var minNumeric uint
if config.MinNumeric != nil {
minNumeric = *config.MinNumeric
}
var minSpecial uint
if config.MinSpecial != nil {
minSpecial = *config.MinSpecial
}
// a cluster is a group of characters that are required to be present in the password
clustersLength := minLower + minUpper + minNumeric + minSpecial
totalLength := config.Length
if totalLength == 0 {
totalLength = clustersLength
}
if clustersLength > totalLength {
return "",
fmt.Errorf("the sum of MinLower, MinUpper, MinNumeric, and MinSpecial must be less than or equal to the length")
}
if totalLength == 0 {
return "", fmt.Errorf(
"either Length or the sum of MinLower, MinUpper, MinNumeric, and MinSpecial must be greater than 0")
}
unassignedClusterSize := totalLength - clustersLength
var generated string
genCluster := func(minClusterSize uint, disallowedCluster *bool, alphabet string, appendTo *string) error {
if disallowedCluster != nil && *disallowedCluster {
if minClusterSize > 0 {
return fmt.Errorf("cluster size is greater than 0 but the condition is false")
}
return nil
}
if minClusterSize > 0 {
gen, err := FromAlphabet(alphabet, minClusterSize)
if err != nil {
return fmt.Errorf("generating fixed size cluster: %w", err)
}
*appendTo += gen
}
return nil
}
if err := genCluster(minLower, config.NoLower, LowercaseLetters, &generated); err != nil {
return "", err
}
if err := genCluster(minUpper, config.NoUpper, UppercaseLetters, &generated); err != nil {
return "", err
}
if err := genCluster(minNumeric, config.NoNumeric, Digits, &generated); err != nil {
return "", err
}
if err := genCluster(minSpecial, config.NoSpecial, Symbols, &generated); err != nil {
return "", err
}
// Strategy for generating remaining characters:
// 1. If all characters are disallowed, return an error
// 2. For each character that needs to be generated, generate a charset picking one random char for each allowed cluster
// 3. Use the generated charset to pick a random character for the char
// This strategy gives the same changes for each character to be picked from any cluster, regardless of the cluster size
// For example, picking a char from lowercase letters and numbers should not give more changes to get a lowercase letter
// just because there are more lower case letters than numbers.
for unassignedClusterSize > 0 {
var combinedAlphabet string
var noDisallow bool
if config.NoLower == nil || (config.NoLower != nil && !*config.NoLower) {
if err := genCluster(1, &noDisallow, LowercaseLetters, &combinedAlphabet); err != nil {
return "", err
}
}
if config.NoUpper == nil || (config.NoUpper != nil && !*config.NoUpper) {
if err := genCluster(1, &noDisallow, UppercaseLetters, &combinedAlphabet); err != nil {
return "", err
}
}
if config.NoNumeric == nil || (config.NoNumeric != nil && !*config.NoNumeric) {
if err := genCluster(1, &noDisallow, Digits, &combinedAlphabet); err != nil {
return "", err
}
}
if config.NoSpecial == nil || (config.NoSpecial != nil && !*config.NoSpecial) {
if err := genCluster(1, &noDisallow, Symbols, &combinedAlphabet); err != nil {
return "", err
}
}
if combinedAlphabet == "" {
return "", fmt.Errorf("can't generate if all characters are disallowed (noLower, noUpper, noNumeric, noSpecial)")
}
if err := genCluster(1, &noDisallow, combinedAlphabet, &generated); err != nil {
return "", err
}
unassignedClusterSize--
}
fixedSizeClustersStringChars := strings.Split(generated, "")
if err := Shuffle(fixedSizeClustersStringChars); err != nil {
return "", fmt.Errorf("shuffling fixed size cluster: %w", err)
}
return strings.Join(fixedSizeClustersStringChars, ""), nil
}