new WebAdmin: add test cases

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2024-02-03 12:42:05 +01:00
parent 7ad6d99bd7
commit 71e01ab26d
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
7 changed files with 535 additions and 459 deletions

2
go.mod
View file

@ -41,7 +41,7 @@ require (
github.com/klauspost/compress v1.17.5
github.com/lestrrat-go/jwx/v2 v2.0.19
github.com/lithammer/shortuuid/v3 v3.0.7
github.com/mattn/go-sqlite3 v1.14.21
github.com/mattn/go-sqlite3 v1.14.22
github.com/mhale/smtpd v0.8.2
github.com/minio/sio v0.3.1
github.com/otiai10/copy v1.14.0

4
go.sum
View file

@ -286,8 +286,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.21 h1:IXocQLOykluc3xPE0Lvy8FtggMz1G+U3mEjg+0zGizc=
github.com/mattn/go-sqlite3 v1.14.21/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mhale/smtpd v0.8.2 h1:rHKOMHeFoDvcq8Na9ErCbNcjlWTSyGtznOmJpWsOzuc=
github.com/mhale/smtpd v0.8.2/go.mod h1:MQl+y2hwIEQCXtNhe5+55n0GZOjSmeqORDIXbqUL3x4=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=

File diff suppressed because it is too large Load diff

View file

@ -573,28 +573,38 @@ func TestInvalidToken(t *testing.T) {
rr = httptest.NewRecorder()
server.handleWebRestore(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebAddUserPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebUpdateUserPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebTemplateFolderPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebTemplateUserPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
getAllAdmins(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
getAllUsers(rr, req)
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
addFolder(rr, req)
@ -618,23 +628,23 @@ func TestInvalidToken(t *testing.T) {
rr = httptest.NewRecorder()
server.handleWebAddFolderPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebUpdateFolderPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebGetConnections(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebConfigsPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
addAdmin(rr, req)
@ -813,63 +823,63 @@ func TestInvalidToken(t *testing.T) {
rr = httptest.NewRecorder()
server.handleGetWebUsers(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebUpdateUserGet(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebUpdateRolePost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebAddRolePost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebAddAdminPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebAddGroupPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebUpdateGroupPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebAddEventActionPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebUpdateEventActionPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebAddEventRulePost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebUpdateEventRulePost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebUpdateIPListEntryPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
rr = httptest.NewRecorder()
server.handleWebClientTwoFactorRecoveryPost(rr, req)
@ -889,8 +899,8 @@ func TestInvalidToken(t *testing.T) {
rr = httptest.NewRecorder()
server.handleWebUpdateIPListEntryPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
form := make(url.Values)
req, _ = http.NewRequest(http.MethodPost, webIPListPath+"/1", bytes.NewBuffer([]byte(form.Encode())))
@ -900,8 +910,8 @@ func TestInvalidToken(t *testing.T) {
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
rr = httptest.NewRecorder()
server.handleWebAddIPListEntryPost(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "invalid token claims")
assert.Equal(t, http.StatusForbidden, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
}
func TestUpdateWebAdminInvalidClaims(t *testing.T) {
@ -933,7 +943,7 @@ func TestUpdateWebAdminInvalidClaims(t *testing.T) {
req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"]))
server.handleWebUpdateAdminPost(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
assert.Contains(t, rr.Body.String(), "Invalid token claims")
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidToken)
}
func TestRetentionInvalidTokenClaims(t *testing.T) {
@ -1023,7 +1033,7 @@ func TestOAuth2Redirect(t *testing.T) {
assert.NoError(t, err)
server.handleOAuth2TokenRedirect(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "token is unauthorized")
assert.Contains(t, rr.Body.String(), util.I18nOAuth2ErrorTitle)
ip := "127.1.1.4"
tokenString := createOAuth2Token(xid.New().String(), ip)
@ -1033,7 +1043,7 @@ func TestOAuth2Redirect(t *testing.T) {
req.RemoteAddr = ip
server.handleOAuth2TokenRedirect(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code)
assert.Contains(t, rr.Body.String(), "no auth request found for the specified state")
assert.Contains(t, rr.Body.String(), util.I18nOAuth2ErrorValidateState)
}
func TestOAuth2Token(t *testing.T) {
@ -1281,7 +1291,7 @@ func TestCreateTokenError(t *testing.T) {
rr = httptest.NewRecorder()
server.handleWebAdminChangePwdPost(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "invalid URL escape")
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidForm)
req, _ = http.NewRequest(http.MethodGet, webAdminLoginPath+"?a=a%C3%A2%G3", nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@ -1339,14 +1349,14 @@ func TestCreateTokenError(t *testing.T) {
rr = httptest.NewRecorder()
server.handleWebAdminTwoFactorPost(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "invalid URL escape")
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidForm)
req, _ = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath+"?a=a%C3%AO%GD", bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rr = httptest.NewRecorder()
server.handleWebAdminTwoFactorRecoveryPost(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "invalid URL escape")
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidForm)
req, _ = http.NewRequest(http.MethodPost, webClientTwoFactorPath+"?a=a%C3%AO%GC", bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@ -1367,7 +1377,7 @@ func TestCreateTokenError(t *testing.T) {
rr = httptest.NewRecorder()
server.handleWebAdminForgotPwdPost(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "invalid URL escape")
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidForm)
req, _ = http.NewRequest(http.MethodPost, webClientForgotPwdPath+"?a=a%C2%A1%GD", bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@ -1381,14 +1391,14 @@ func TestCreateTokenError(t *testing.T) {
rr = httptest.NewRecorder()
server.handleWebAdminPasswordResetPost(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "invalid URL escape")
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidForm)
req, _ = http.NewRequest(http.MethodPost, webAdminRolePath+"?a=a%C3%AO%JE", bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rr = httptest.NewRecorder()
server.handleWebAddRolePost(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "invalid URL escape")
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidForm)
req, _ = http.NewRequest(http.MethodPost, webClientResetPwdPath+"?a=a%C3%AO%JD", bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@ -2105,7 +2115,7 @@ func TestProxyHeaders(t *testing.T) {
rr = httptest.NewRecorder()
testServer.Config.Handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "login from IP 10.29.1.9 not allowed")
assert.Contains(t, rr.Body.String(), util.I18nErrorInvalidCredentials)
form.Set(csrfFormToken, createCSRFToken(validForwardedFor))
req, err = http.NewRequest(http.MethodPost, webAdminLoginPath, bytes.NewBuffer([]byte(form.Encode())))
@ -3067,7 +3077,7 @@ func TestWebAdminSetupWithInstallCode(t *testing.T) {
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
server.router.ServeHTTP(rr, r)
assert.Equal(t, http.StatusOK, rr.Code)
assert.Contains(t, rr.Body.String(), "Installation code mismatch")
assert.Contains(t, rr.Body.String(), util.I18nErrorSetupInstallCode)
_, err = dataprovider.AdminExists(defaultAdminUsername)
assert.Error(t, err)
@ -3123,7 +3133,7 @@ func TestWebAdminSetupWithInstallCode(t *testing.T) {
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
server.router.ServeHTTP(rr, r)
assert.Equal(t, http.StatusOK, rr.Code)
assert.Contains(t, rr.Body.String(), "Installation code mismatch")
assert.Contains(t, rr.Body.String(), util.I18nErrorSetupInstallCode)
_, err = dataprovider.AdminExists(defaultAdminUsername)
assert.Error(t, err)

View file

@ -2200,29 +2200,28 @@ func getHTTPPartsFromPostFields(r *http.Request) []dataprovider.HTTPPart {
for idx, partName := range names {
if partName != "" {
order, err := strconv.Atoi(orders[idx])
if err != nil {
continue
}
filePath := files[idx]
body := bodies[idx]
concatHeaders := getSliceFromDelimitedValues(headers[idx], "\n")
var headers []dataprovider.KeyValue
for _, h := range concatHeaders {
values := strings.SplitN(h, ":", 2)
if len(values) > 1 {
headers = append(headers, dataprovider.KeyValue{
Key: strings.TrimSpace(values[0]),
Value: strings.TrimSpace(values[1]),
})
if err == nil {
filePath := files[idx]
body := bodies[idx]
concatHeaders := getSliceFromDelimitedValues(headers[idx], "\n")
var headers []dataprovider.KeyValue
for _, h := range concatHeaders {
values := strings.SplitN(h, ":", 2)
if len(values) > 1 {
headers = append(headers, dataprovider.KeyValue{
Key: strings.TrimSpace(values[0]),
Value: strings.TrimSpace(values[1]),
})
}
}
result = append(result, dataprovider.HTTPPart{
Name: partName,
Filepath: filePath,
Headers: headers,
Body: body,
Order: order,
})
}
result = append(result, dataprovider.HTTPPart{
Name: partName,
Filepath: filePath,
Headers: headers,
Body: body,
Order: order,
})
}
}
@ -2495,7 +2494,7 @@ func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventCo
return conditions, nil
}
func getEventRuleActionsFromPostFields(r *http.Request) ([]dataprovider.EventAction, error) {
func getEventRuleActionsFromPostFields(r *http.Request) []dataprovider.EventAction {
var actions []dataprovider.EventAction
names := r.Form["action_name"]
@ -2504,25 +2503,24 @@ func getEventRuleActionsFromPostFields(r *http.Request) ([]dataprovider.EventAct
for idx, name := range names {
if name != "" {
order, err := strconv.Atoi(orders[idx])
if err != nil {
return actions, fmt.Errorf("invalid order: %w", err)
if err == nil {
options := r.Form["action_options"+strconv.Itoa(idx)]
actions = append(actions, dataprovider.EventAction{
BaseEventAction: dataprovider.BaseEventAction{
Name: name,
},
Order: order + 1,
Options: dataprovider.EventActionOptions{
IsFailureAction: util.Contains(options, "1"),
StopOnFailure: util.Contains(options, "2"),
ExecuteSync: util.Contains(options, "3"),
},
})
}
options := r.Form["action_options"+strconv.Itoa(idx)]
actions = append(actions, dataprovider.EventAction{
BaseEventAction: dataprovider.BaseEventAction{
Name: name,
},
Order: order + 1,
Options: dataprovider.EventActionOptions{
IsFailureAction: util.Contains(options, "1"),
StopOnFailure: util.Contains(options, "2"),
ExecuteSync: util.Contains(options, "3"),
},
})
}
}
return actions, nil
return actions
}
func updateRepeaterFormRuleFields(r *http.Request) {
@ -2589,17 +2587,13 @@ func getEventRuleFromPostFields(r *http.Request) (dataprovider.EventRule, error)
if err != nil {
return dataprovider.EventRule{}, err
}
actions, err := getEventRuleActionsFromPostFields(r)
if err != nil {
return dataprovider.EventRule{}, err
}
rule := dataprovider.EventRule{
Name: strings.TrimSpace(r.Form.Get("name")),
Status: status,
Description: r.Form.Get("description"),
Trigger: trigger,
Conditions: conditions,
Actions: actions,
Actions: getEventRuleActionsFromPostFields(r),
}
return rule, nil
}
@ -2881,7 +2875,7 @@ func getAllAdmins(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" {
sendAPIResponse(w, r, nil, util.I18nErrorDirList403, http.StatusForbidden)
sendAPIResponse(w, r, nil, util.I18nErrorInvalidToken, http.StatusForbidden)
return
}
admins := make([]dataprovider.Admin, 0, 50)
@ -3046,7 +3040,7 @@ func getAllUsers(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" {
sendAPIResponse(w, r, nil, util.I18nErrorDirList403, http.StatusForbidden)
sendAPIResponse(w, r, nil, util.I18nErrorInvalidToken, http.StatusForbidden)
return
}
users := make([]dataprovider.User, 0, 100)

View file

@ -115,7 +115,7 @@ const (
I18nErrorReservedUsername = "user.username_reserved"
I18nErrorInvalidEmail = "general.email_invalid"
I18nErrorInvalidUser = "user.username_invalid"
I18nErrorInvalidName = "user.name_invalid"
I18nErrorInvalidName = "general.name_invalid"
I18nErrorHomeRequired = "user.home_required"
I18nErrorHomeInvalid = "user.home_invalid"
I18nErrorPubKeyInvalid = "user.pub_key_invalid"

View file

@ -224,7 +224,7 @@
"zero_no_limit_help": "0 means no limit",
"global_settings": "Global settings",
"mandatory_encryption": "Mandatory encryption",
"name_invalid": "The specified username is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
"name_invalid": "The specified name is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
"associations": "Associations",
"template_placeholders": "The following placeholders are supported",
"duplicated_username": "The specified username already exists",