Browse Source

enforce group-level password strength for users and shares

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 1 week ago
parent
commit
5ce9688780

+ 13 - 3
internal/dataprovider/dataprovider.go

@@ -2144,9 +2144,6 @@ func UpdateUserPassword(username, plainPwd, executor, ipAddress, role string) er
 		return err
 		return err
 	}
 	}
 	userCopy := user.getACopy()
 	userCopy := user.getACopy()
-	if err := userCopy.LoadAndApplyGroupSettings(); err != nil {
-		return err
-	}
 	userCopy.Password = plainPwd
 	userCopy.Password = plainPwd
 	if err := createUserPasswordHash(&userCopy); err != nil {
 	if err := createUserPasswordHash(&userCopy); err != nil {
 		return err
 		return err
@@ -3336,6 +3333,19 @@ func hashPlainPassword(plainPwd string) (string, error) {
 
 
 func createUserPasswordHash(user *User) error {
 func createUserPasswordHash(user *User) error {
 	if user.Password != "" && !user.IsPasswordHashed() {
 	if user.Password != "" && !user.IsPasswordHashed() {
+		for _, g := range user.Groups {
+			if g.Type == sdk.GroupTypePrimary {
+				group, err := GroupExists(g.Name)
+				if err != nil {
+					return errors.New("unable to load group password policies")
+				}
+				if minEntropy := group.UserSettings.Filters.PasswordStrength; minEntropy > 0 {
+					if err := passwordvalidator.Validate(user.Password, float64(minEntropy)); err != nil {
+						return util.NewI18nError(util.NewValidationError(err.Error()), util.I18nErrorPasswordComplexity)
+					}
+				}
+			}
+		}
 		if minEntropy := user.getMinPasswordEntropy(); minEntropy > 0 {
 		if minEntropy := user.getMinPasswordEntropy(); minEntropy > 0 {
 			if err := passwordvalidator.Validate(user.Password, minEntropy); err != nil {
 			if err := passwordvalidator.Validate(user.Password, minEntropy); err != nil {
 				return util.NewI18nError(util.NewValidationError(err.Error()), util.I18nErrorPasswordComplexity)
 				return util.NewI18nError(util.NewValidationError(err.Error()), util.I18nErrorPasswordComplexity)

+ 1 - 1
internal/dataprovider/share.go

@@ -146,7 +146,7 @@ func (s *Share) HasRedactedPassword() bool {
 
 
 func (s *Share) hashPassword() error {
 func (s *Share) hashPassword() error {
 	if s.Password != "" && !util.IsStringPrefixInSlice(s.Password, internalHashPwdPrefixes) {
 	if s.Password != "" && !util.IsStringPrefixInSlice(s.Password, internalHashPwdPrefixes) {
-		user, err := UserExists(s.Username, "")
+		user, err := GetUserWithGroupSettings(s.Username, "")
 		if err != nil {
 		if err != nil {
 			return util.NewGenericError(fmt.Sprintf("unable to validate user: %v", err))
 			return util.NewGenericError(fmt.Sprintf("unable to validate user: %v", err))
 		}
 		}

+ 54 - 0
internal/httpd/httpd_test.go

@@ -1842,6 +1842,19 @@ func TestGroupSettingsOverride(t *testing.T) {
 		assert.Contains(t, user.Filters.WebClient, sdk.WebClientInfoChangeDisabled)
 		assert.Contains(t, user.Filters.WebClient, sdk.WebClientInfoChangeDisabled)
 		assert.Contains(t, user.Filters.WebClient, sdk.WebClientMFADisabled)
 		assert.Contains(t, user.Filters.WebClient, sdk.WebClientMFADisabled)
 	}
 	}
+	// Attempt to create a user with a weak password and group1 as the primary group: this should fail
+	u = getTestUser()
+	u.Username = rand.Text()
+	u.Password = defaultPassword
+	u.Groups = []sdk.GroupMapping{
+		{
+			Name: group1.Name,
+			Type: sdk.GroupTypePrimary,
+		},
+	}
+	_, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
+	assert.NoError(t, err)
+	assert.Contains(t, string(resp), "insecure password")
 
 
 	err = os.RemoveAll(user.GetHomeDir())
 	err = os.RemoveAll(user.GetHomeDir())
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -15109,6 +15122,47 @@ func TestShareUsage(t *testing.T) {
 	executeRequest(req)
 	executeRequest(req)
 }
 }
 
 
+func TestSharePasswordPolicy(t *testing.T) {
+	g := getTestGroup()
+	g.UserSettings.Filters.PasswordStrength = 70
+	group, _, err := httpdtest.AddGroup(g, http.StatusCreated)
+	assert.NoError(t, err)
+
+	u := getTestUser()
+	u.Groups = []sdk.GroupMapping{
+		{
+			Name: g.Name,
+			Type: sdk.GroupTypePrimary,
+		},
+	}
+	u.Password = rand.Text()
+	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
+	assert.NoError(t, err)
+
+	webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, u.Password)
+	assert.NoError(t, err)
+
+	share := dataprovider.Share{
+		Name:     util.GenerateUniqueID(),
+		Scope:    dataprovider.ShareScopeRead,
+		Paths:    []string{"/"},
+		Password: defaultPassword,
+	}
+	asJSON, err := json.Marshal(share)
+	assert.NoError(t, err)
+	req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
+	assert.NoError(t, err)
+	setBearerForReq(req, webAPIToken)
+	rr := executeRequest(req)
+	checkResponseCode(t, http.StatusBadRequest, rr)
+	assert.Contains(t, rr.Body.String(), "insecure password")
+
+	_, err = httpdtest.RemoveUser(user, http.StatusOK)
+	assert.NoError(t, err)
+	_, err = httpdtest.RemoveGroup(group, http.StatusOK)
+	assert.NoError(t, err)
+}
+
 func TestShareMaxExpiration(t *testing.T) {
 func TestShareMaxExpiration(t *testing.T) {
 	u := getTestUser()
 	u := getTestUser()
 	u.Filters.MaxSharesExpiration = 5
 	u.Filters.MaxSharesExpiration = 5