feat: support i18n in backend err messages (#1232)

* feat: support i18n in backend err messages

* use gofumpt to fmt code

* fix review problems

* support auto generate err message

* delete beego/i18n moudle

* fix Github action test problems

* fix review problems

* use gofumpt to format code

* use gofumpt to fmt code
This commit is contained in:
Mr Forest
2022-10-23 15:16:24 +08:00
committed by GitHub
parent 7c77519069
commit d86f3c88c7
64 changed files with 1838 additions and 194 deletions

View File

@ -22,7 +22,6 @@ import (
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v3"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)

View File

@ -22,6 +22,7 @@ import (
"unicode"
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
)
@ -41,89 +42,89 @@ func init() {
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
}
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, affiliation string) string {
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, affiliation string, lang string) string {
if organization == nil {
return "organization does not exist"
return i18n.Translate(lang, "OrgErr.DoNotExist")
}
if application.IsSignupItemVisible("Username") {
if len(username) <= 1 {
return "username must have at least 2 characters"
return i18n.Translate(lang, "UserErr.NameLessThanTwoCharacters")
}
if unicode.IsDigit(rune(username[0])) {
return "username cannot start with a digit"
return i18n.Translate(lang, "UserErr.NameStartWithADigitErr")
}
if util.IsEmailValid(username) {
return "username cannot be an email address"
return i18n.Translate(lang, "UserErr.NameIsEmailErr")
}
if reWhiteSpace.MatchString(username) {
return "username cannot contain white spaces"
return i18n.Translate(lang, "UserErr.NameCantainWhitSpaceErr")
}
msg := CheckUsername(username)
msg := CheckUsername(username, lang)
if msg != "" {
return msg
}
if HasUserByField(organization.Name, "name", username) {
return "username already exists"
return i18n.Translate(lang, "UserErr.NameExistedErr")
}
if HasUserByField(organization.Name, "email", email) {
return "email already exists"
return i18n.Translate(lang, "EmailErr.ExistedErr")
}
if HasUserByField(organization.Name, "phone", phone) {
return "phone already exists"
return i18n.Translate(lang, "PhoneErr.ExistedErr")
}
}
if len(password) <= 5 {
return "password must have at least 6 characters"
return i18n.Translate(lang, "UserErr.PasswordLessThanSixCharacters")
}
if application.IsSignupItemVisible("Email") {
if email == "" {
if application.IsSignupItemRequired("Email") {
return "email cannot be empty"
return i18n.Translate(lang, "EmailErr.EmptyErr")
} else {
return ""
}
}
if HasUserByField(organization.Name, "email", email) {
return "email already exists"
return i18n.Translate(lang, "EmailErr.ExistedErr")
} else if !util.IsEmailValid(email) {
return "email is invalid"
return i18n.Translate(lang, "EmailErr.EmailInvalid")
}
}
if application.IsSignupItemVisible("Phone") {
if phone == "" {
if application.IsSignupItemRequired("Phone") {
return "phone cannot be empty"
return i18n.Translate(lang, "PhoneErr.EmptyErr")
} else {
return ""
}
}
if HasUserByField(organization.Name, "phone", phone) {
return "phone already exists"
return i18n.Translate(lang, "PhoneErr.ExistedErr")
} else if organization.PhonePrefix == "86" && !util.IsPhoneCnValid(phone) {
return "phone number is invalid"
return i18n.Translate(lang, "PhoneErr.NumberInvalid")
}
}
if application.IsSignupItemVisible("Display name") {
if application.GetSignupItemRule("Display name") == "First, last" && (firstName != "" || lastName != "") {
if firstName == "" {
return "firstName cannot be blank"
return i18n.Translate(lang, "UserErr.FirstNameBlankErr")
} else if lastName == "" {
return "lastName cannot be blank"
return i18n.Translate(lang, "UserErr.LastNameBlankErr")
}
} else {
if displayName == "" {
return "displayName cannot be blank"
return i18n.Translate(lang, "UserErr.DisplayNameBlankErr")
} else if application.GetSignupItemRule("Display name") == "Real name" {
if !isValidRealName(displayName) {
return "displayName is not valid real name"
return i18n.Translate(lang, "UserErr.DisplayNameInvalid")
}
}
}
@ -131,14 +132,14 @@ func CheckUserSignup(application *Application, organization *Organization, usern
if application.IsSignupItemVisible("Affiliation") {
if affiliation == "" {
return "affiliation cannot be blank"
return i18n.Translate(lang, "UserErr.AffiliationBlankErr")
}
}
return ""
}
func checkSigninErrorTimes(user *User) string {
func checkSigninErrorTimes(user *User, lang string) string {
if user.SigninWrongTimes >= SigninWrongTimesLimit {
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
@ -146,7 +147,7 @@ func checkSigninErrorTimes(user *User) string {
// deny the login if the error times is greater than the limit and the last login time is less than the duration
if seconds > 0 {
return fmt.Sprintf("You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", seconds/60, seconds%60)
return fmt.Sprintf(i18n.Translate(lang, "AuthErr.WrongPasswordManyTimes"), seconds/60, seconds%60)
}
// reset the error times
@ -158,15 +159,15 @@ func checkSigninErrorTimes(user *User) string {
return ""
}
func CheckPassword(user *User, password string) string {
func CheckPassword(user *User, password string, lang string) string {
// check the login error times
if msg := checkSigninErrorTimes(user); msg != "" {
if msg := checkSigninErrorTimes(user, lang); msg != "" {
return msg
}
organization := GetOrganizationByUser(user)
if organization == nil {
return "organization does not exist"
return i18n.Translate(lang, "OrgErr.DoNotExist")
}
credManager := cred.GetCredManager(organization.PasswordType)
@ -185,11 +186,11 @@ func CheckPassword(user *User, password string) string {
return recordSigninErrorInfo(user)
} else {
return fmt.Sprintf("unsupported password type: %s", organization.PasswordType)
return fmt.Sprintf(i18n.Translate(lang, "LoginErr.UnsupportedPasswordType"), organization.PasswordType)
}
}
func checkLdapUserPassword(user *User, password string) (*User, string) {
func checkLdapUserPassword(user *User, password string, lang string) (*User, string) {
ldaps := GetLdaps(user.Owner)
ldapLoginSuccess := false
for _, ldapServer := range ldaps {
@ -209,7 +210,7 @@ func checkLdapUserPassword(user *User, password string) (*User, string) {
if len(searchResult.Entries) == 0 {
continue
} else if len(searchResult.Entries) > 1 {
return nil, "Error: multiple accounts with same uid, please check your ldap server"
return nil, i18n.Translate(lang, "LdapErr.MultipleAccounts")
}
dn := searchResult.Entries[0].DN
@ -220,26 +221,26 @@ func checkLdapUserPassword(user *User, password string) (*User, string) {
}
if !ldapLoginSuccess {
return nil, "ldap user name or password incorrect"
return nil, i18n.Translate(lang, "LdapErr.PasswordWrong")
}
return user, ""
}
func CheckUserPassword(organization string, username string, password string) (*User, string) {
func CheckUserPassword(organization string, username string, password string, lang string) (*User, string) {
user := GetUserByFields(organization, username)
if user == nil || user.IsDeleted == true {
return nil, "the user does not exist, please sign up first"
return nil, i18n.Translate(lang, "UserErr.DoNotExistSignUp")
}
if user.IsForbidden {
return nil, "the user is forbidden to sign in, please contact the administrator"
return nil, i18n.Translate(lang, "LoginErr.UserIsForbidden")
}
if user.Ldap != "" {
// ONLY for ldap users
return checkLdapUserPassword(user, password)
return checkLdapUserPassword(user, password, lang)
} else {
msg := CheckPassword(user, password)
msg := CheckPassword(user, password, lang)
if msg != "" {
return nil, msg
}
@ -251,15 +252,15 @@ func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
}
func CheckUserPermission(requestUserId, userId, userOwner string, strict bool) (bool, error) {
func CheckUserPermission(requestUserId, userId, userOwner string, strict bool, lang string) (bool, error) {
if requestUserId == "" {
return false, fmt.Errorf("please login first")
return false, fmt.Errorf(i18n.Translate(lang, "LoginErr.LoginFirst"))
}
if userId != "" {
targetUser := GetUser(userId)
if targetUser == nil {
return false, fmt.Errorf("the user: %s doesn't exist", userId)
return false, fmt.Errorf(i18n.Translate(lang, "UserErr.DoNotExist"), userId)
}
userOwner = targetUser.Owner
@ -271,7 +272,7 @@ func CheckUserPermission(requestUserId, userId, userOwner string, strict bool) (
} else {
requestUser := GetUser(requestUserId)
if requestUser == nil {
return false, fmt.Errorf("session outdated, please login again")
return false, fmt.Errorf(i18n.Translate(lang, "LoginErr.SessionOutdated"))
}
if requestUser.IsGlobalAdmin {
hasPermission = true
@ -286,7 +287,7 @@ func CheckUserPermission(requestUserId, userId, userOwner string, strict bool) (
}
}
return hasPermission, fmt.Errorf("you don't have the permission to do this")
return hasPermission, fmt.Errorf(i18n.Translate(lang, "LoginErr.NoPermission"))
}
func CheckAccessPermission(userId string, application *Application) (bool, error) {
@ -319,11 +320,11 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
return allowed, err
}
func CheckUsername(username string) string {
func CheckUsername(username string, lang string) string {
if username == "" {
return "Empty username."
return i18n.Translate(lang, "UserErr.NameEmptyErr")
} else if len(username) > 39 {
return "Username is too long (maximum is 39 characters)."
return i18n.Translate(lang, "UserErr.NameTooLang")
}
exclude, _ := regexp.Compile("^[\u0021-\u007E]+$")
@ -334,7 +335,7 @@ func CheckUsername(username string) string {
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
re, _ := regexp.Compile("^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$")
if !re.MatchString(username) {
return "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline."
return i18n.Translate(lang, "UserErr.NameFormatErr")
}
return ""

View File

@ -62,7 +62,7 @@ func GetFilteredUsers(m *ldapserver.Message, name, org string) ([]*User, int) {
return nil, ldapserver.LDAPResultInsufficientAccessRights
}
} else {
hasPermission, err := CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), org, true)
hasPermission, err := CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), org, true, "en")
if !hasPermission {
log.Printf("ErrMsg = %v", err.Error())
return nil, ldapserver.LDAPResultInsufficientAccessRights

View File

@ -18,6 +18,7 @@ import (
"fmt"
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -202,18 +203,18 @@ func GetAccountItemByName(name string, organization *Organization) *AccountItem
return nil
}
func CheckAccountItemModifyRule(accountItem *AccountItem, user *User) (bool, string) {
func CheckAccountItemModifyRule(accountItem *AccountItem, user *User, lang string) (bool, string) {
switch accountItem.ModifyRule {
case "Admin":
if !(user.IsAdmin || user.IsGlobalAdmin) {
return false, fmt.Sprintf("Only admin can modify the %s.", accountItem.Name)
return false, fmt.Sprintf(i18n.Translate(lang, "OrgErr.OnlyAdmin"), accountItem.Name)
}
case "Immutable":
return false, fmt.Sprintf("The %s is immutable.", accountItem.Name)
return false, fmt.Sprintf(i18n.Translate(lang, "OrgErr.Immutable"), accountItem.Name)
case "Self":
break
default:
return false, fmt.Sprintf("Unknown modify rule %s.", accountItem.ModifyRule)
return false, fmt.Sprintf(i18n.Translate(lang, "OrgErr.UnknownModifyRule"), accountItem.ModifyRule)
}
return true, ""
}

View File

@ -17,6 +17,7 @@ package object
import (
"fmt"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
@ -228,7 +229,7 @@ func (p *Provider) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}
func GetCaptchaProviderByOwnerName(applicationId string) (*Provider, error) {
func GetCaptchaProviderByOwnerName(applicationId, lang string) (*Provider, error) {
owner, name := util.GetOwnerAndNameFromId(applicationId)
provider := Provider{Owner: owner, Name: name, Category: "Captcha"}
existed, err := adapter.Engine.Get(&provider)
@ -237,26 +238,26 @@ func GetCaptchaProviderByOwnerName(applicationId string) (*Provider, error) {
}
if !existed {
return nil, fmt.Errorf("the provider: %s does not exist", applicationId)
return nil, fmt.Errorf(i18n.Translate(lang, "ProviderErr.DoNotExist"), applicationId)
}
return &provider, nil
}
func GetCaptchaProviderByApplication(applicationId, isCurrentProvider string) (*Provider, error) {
func GetCaptchaProviderByApplication(applicationId, isCurrentProvider, lang string) (*Provider, error) {
if isCurrentProvider == "true" {
return GetCaptchaProviderByOwnerName(applicationId)
return GetCaptchaProviderByOwnerName(applicationId, lang)
}
application := GetApplication(applicationId)
if application == nil || len(application.Providers) == 0 {
return nil, fmt.Errorf("invalid application id")
return nil, fmt.Errorf(i18n.Translate(lang, "ApplicationErr.InvalidID"))
}
for _, provider := range application.Providers {
if provider.Provider == nil {
continue
}
if provider.Provider.Category == "Captcha" {
return GetCaptchaProviderByOwnerName(fmt.Sprintf("%s/%s", provider.Provider.Owner, provider.Provider.Name))
return GetCaptchaProviderByOwnerName(fmt.Sprintf("%s/%s", provider.Provider.Owner, provider.Provider.Name), lang)
}
}
return nil, nil

View File

@ -24,6 +24,7 @@ import (
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n"
saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig"
)
@ -41,10 +42,10 @@ func ParseSamlResponse(samlResponse string, providerType string) (string, error)
return assertionInfo.NameID, nil
}
func GenerateSamlLoginUrl(id, relayState string) (string, string, error) {
func GenerateSamlLoginUrl(id, relayState, lang string) (string, string, error) {
provider := GetProvider(id)
if provider.Category != "SAML" {
return "", "", fmt.Errorf("provider %s's category is not SAML", provider.Name)
return "", "", fmt.Errorf(i18n.Translate(lang, "ProviderErr.CategoryNotSAML"), provider.Name)
}
sp, err := buildSp(provider, "")
if err != nil {

View File

@ -21,6 +21,7 @@ import (
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/storage"
"github.com/casdoor/casdoor/util"
)
@ -126,16 +127,16 @@ func UploadFileSafe(provider *Provider, fullFilePath string, fileBuffer *bytes.B
return fileUrl, objectKey, nil
}
func DeleteFile(provider *Provider, objectKey string) error {
func DeleteFile(provider *Provider, objectKey string, lang string) error {
// check fullFilePath is there security issue
if strings.Contains(objectKey, "..") {
return fmt.Errorf("the objectKey: %s is not allowed", objectKey)
return fmt.Errorf(i18n.Translate(lang, "StorageErr.ObjectKeyNotAllowed"), objectKey)
}
endpoint := getProviderEndpoint(provider)
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
if storageProvider == nil {
return fmt.Errorf("the provider type: %s is not supported", provider.Type)
return fmt.Errorf(i18n.Translate(lang, "ProviderErr.ProviderNotSupported"), provider.Type)
}
if provider.Domain == "" {

View File

@ -21,6 +21,7 @@ import (
"strings"
"time"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
@ -238,14 +239,14 @@ func GetTokenByTokenAndApplication(token string, application string) *Token {
return &tokenResult
}
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string, lang string) (string, *Application) {
if responseType != "code" && responseType != "token" && responseType != "id_token" {
return fmt.Sprintf("error: grant_type: %s is not supported in this application", responseType), nil
return fmt.Sprintf(i18n.Translate(lang, "ApplicationErr.GrantTypeNotSupport"), responseType), nil
}
application := GetApplicationByClientId(clientId)
if application == nil {
return "Invalid client_id", nil
return i18n.Translate(lang, "TokenErr.InvalidClientId"), nil
}
validUri := false
@ -256,7 +257,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
}
}
if !validUri {
return fmt.Sprintf("Redirect URI: \"%s\" doesn't exist in the allowed Redirect URI list", redirectUri), application
return fmt.Sprintf(i18n.Translate(lang, "TokenErr.RedirectURIDoNotExist"), redirectUri), application
}
// Mask application for /api/get-app-login
@ -264,7 +265,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
return "", application
}
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string) *Code {
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) *Code {
user := GetUser(userId)
if user == nil {
return &Code{
@ -279,7 +280,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
}
}
msg, application := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
msg, application := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state, lang)
if msg != "" {
return &Code{
Message: msg,
@ -322,7 +323,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
}
}
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) interface{} {
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string, lang string) interface{} {
application := GetApplicationByClientId(clientId)
if application == nil {
return &TokenError{
@ -353,7 +354,7 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
if tag == "wechat_miniprogram" {
// Wechat Mini Program
token, tokenError = GetWechatMiniProgramToken(application, code, host, username, avatar)
token, tokenError = GetWechatMiniProgramToken(application, code, host, username, avatar, lang)
}
if tokenError != nil {
@ -559,7 +560,7 @@ func GetPasswordToken(application *Application, username string, password string
ErrorDescription: "the user does not exist",
}
}
msg := CheckPassword(user, password)
msg := CheckPassword(user, password, "en")
if msg != "" {
return nil, &TokenError{
Error: InvalidGrant,
@ -669,7 +670,7 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
// GetWechatMiniProgramToken
// Wechat Mini Program flow
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, *TokenError) {
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string, lang string) (*Token, *TokenError) {
mpProvider := GetWechatMiniProgramProvider(application)
if mpProvider == nil {
return nil, &TokenError{
@ -703,7 +704,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
}
// Add new user
var name string
if CheckUsername(username) == "" {
if CheckUsername(username, lang) == "" {
name = username
} else {
name = fmt.Sprintf("wechat-%s", openId)

View File

@ -21,6 +21,7 @@ import (
"time"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -122,11 +123,11 @@ func getVerificationRecord(dest string) *VerificationRecord {
return &record
}
func CheckVerificationCode(dest, code string) string {
func CheckVerificationCode(dest, code, lang string) string {
record := getVerificationRecord(dest)
if record == nil {
return "Code has not been sent yet!"
return i18n.Translate(lang, "PhoneErr.CodeNotSent")
}
timeout, err := conf.GetConfigInt64("verificationCodeTimeout")
@ -136,7 +137,7 @@ func CheckVerificationCode(dest, code string) string {
now := time.Now().Unix()
if now-record.Time > timeout*60 {
return fmt.Sprintf("You should verify your code in %d min!", timeout)
return fmt.Sprintf(i18n.Translate(lang, "PhoneErr.CodeTimeOut"), timeout)
}
if record.Code != code {