From 95f4f4cb6d4607edd939f823efff1b6ea5da9c0f Mon Sep 17 00:00:00 2001 From: Yaodong Yu <2814461814@qq.com> Date: Tue, 25 Apr 2023 23:05:53 +0800 Subject: [PATCH] feat: refactor out form package and optimize verification code module (#1787) * refactor: add forms package and optimize verification code module * chore: add license * chore: fix lint * chore: fix lint * chore: fix lint * chore: swagger --- controllers/account.go | 91 +++----- controllers/auth.go | 85 ++++---- controllers/base.go | 31 ++- controllers/user.go | 8 +- controllers/user_util.go | 138 ------------ controllers/verification.go | 204 ++++++++---------- controllers/webauthn.go | 7 +- form/auth.go | 53 +++++ form/verification.go | 67 ++++++ object/check.go | 47 ++-- object/organization.go | 4 +- object/user.go | 4 +- object/user_util.go | 119 ++++++++++ swagger/swagger.json | 135 +++--------- swagger/swagger.yml | 99 +++------ web/src/AdapterListPage.js | 2 +- web/src/ApplicationListPage.js | 2 +- web/src/CertListPage.js | 2 +- web/src/ChatListPage.js | 2 +- web/src/MessageListPage.js | 2 +- web/src/ModelListPage.js | 2 +- web/src/OrganizationListPage.js | 2 +- web/src/PaymentListPage.js | 2 +- web/src/PermissionListPage.js | 2 +- web/src/ProductListPage.js | 2 +- web/src/ProviderListPage.js | 2 +- web/src/ResourceListPage.js | 2 +- web/src/RoleListPage.js | 2 +- web/src/SessionListPage.js | 2 +- web/src/SyncerListPage.js | 2 +- web/src/TokenListPage.js | 2 +- web/src/UserListPage.js | 2 +- web/src/WebhookListPage.js | 2 +- web/src/backend/UserBackend.js | 4 +- web/src/{ => common/modal}/PopconfirmModal.js | 0 web/src/table/LdapTable.js | 2 +- 36 files changed, 538 insertions(+), 596 deletions(-) delete mode 100644 controllers/user_util.go create mode 100644 form/auth.go create mode 100644 form/verification.go rename web/src/{ => common/modal}/PopconfirmModal.js (100%) diff --git a/controllers/account.go b/controllers/account.go index cd33bb87..4d66588b 100644 --- a/controllers/account.go +++ b/controllers/account.go @@ -21,6 +21,7 @@ import ( "strconv" "strings" + "github.com/casdoor/casdoor/form" "github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/util" ) @@ -34,44 +35,6 @@ const ( ResponseTypeCas = "cas" ) -type RequestForm struct { - Type string `json:"type"` - - Organization string `json:"organization"` - Username string `json:"username"` - Password string `json:"password"` - Name string `json:"name"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Email string `json:"email"` - Phone string `json:"phone"` - Affiliation string `json:"affiliation"` - IdCard string `json:"idCard"` - Region string `json:"region"` - - Application string `json:"application"` - ClientId string `json:"clientId"` - Provider string `json:"provider"` - Code string `json:"code"` - State string `json:"state"` - RedirectUri string `json:"redirectUri"` - Method string `json:"method"` - - EmailCode string `json:"emailCode"` - PhoneCode string `json:"phoneCode"` - CountryCode string `json:"countryCode"` - - AutoSignin bool `json:"autoSignin"` - - RelayState string `json:"relayState"` - SamlRequest string `json:"samlRequest"` - SamlResponse string `json:"samlResponse"` - - CaptchaType string `json:"captchaType"` - CaptchaToken string `json:"captchaToken"` - ClientSecret string `json:"clientSecret"` -} - type Response struct { Status string `json:"status"` Msg string `json:"msg"` @@ -108,28 +71,28 @@ func (c *ApiController) Signup() { return } - var form RequestForm - err := json.Unmarshal(c.Ctx.Input.RequestBody, &form) + var authForm form.AuthForm + err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm) if err != nil { c.ResponseError(err.Error()) return } - application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) + application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) if !application.EnableSignUp { c.ResponseError(c.T("account:The application does not allow to sign up new account")) return } - organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization)) - msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.CountryCode, form.Affiliation, c.GetAcceptLanguage()) + organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", authForm.Organization)) + msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage()) if msg != "" { c.ResponseError(msg) return } - if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" { - checkResult := object.CheckVerificationCode(form.Email, form.EmailCode, c.GetAcceptLanguage()) + if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && authForm.Email != "" { + checkResult := object.CheckVerificationCode(authForm.Email, authForm.EmailCode, c.GetAcceptLanguage()) if checkResult.Code != object.VerificationSuccess { c.ResponseError(checkResult.Msg) return @@ -137,9 +100,9 @@ func (c *ApiController) Signup() { } var checkPhone string - if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && form.Phone != "" { - checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode) - checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage()) + if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && authForm.Phone != "" { + checkPhone, _ = util.GetE164Number(authForm.Phone, authForm.CountryCode) + checkResult := object.CheckVerificationCode(checkPhone, authForm.PhoneCode, c.GetAcceptLanguage()) if checkResult.Code != object.VerificationSuccess { c.ResponseError(checkResult.Msg) return @@ -148,7 +111,7 @@ func (c *ApiController) Signup() { id := util.GenerateId() if application.GetSignupItemRule("ID") == "Incremental" { - lastUser := object.GetLastUser(form.Organization) + lastUser := object.GetLastUser(authForm.Organization) lastIdInt := -1 if lastUser != nil { @@ -158,7 +121,7 @@ func (c *ApiController) Signup() { id = strconv.Itoa(lastIdInt + 1) } - username := form.Username + username := authForm.Username if !application.IsSignupItemVisible("Username") { username = id } @@ -170,21 +133,21 @@ func (c *ApiController) Signup() { } user := &object.User{ - Owner: form.Organization, + Owner: authForm.Organization, Name: username, CreatedTime: util.GetCurrentTime(), Id: id, Type: "normal-user", - Password: form.Password, - DisplayName: form.Name, + Password: authForm.Password, + DisplayName: authForm.Name, Avatar: organization.DefaultAvatar, - Email: form.Email, - Phone: form.Phone, - CountryCode: form.CountryCode, + Email: authForm.Email, + Phone: authForm.Phone, + CountryCode: authForm.CountryCode, Address: []string{}, - Affiliation: form.Affiliation, - IdCard: form.IdCard, - Region: form.Region, + Affiliation: authForm.Affiliation, + IdCard: authForm.IdCard, + Region: authForm.Region, Score: initScore, IsAdmin: false, IsGlobalAdmin: false, @@ -203,10 +166,10 @@ func (c *ApiController) Signup() { } if application.GetSignupItemRule("Display name") == "First, last" { - if form.FirstName != "" || form.LastName != "" { - user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName) - user.FirstName = form.FirstName - user.LastName = form.LastName + if authForm.FirstName != "" || authForm.LastName != "" { + user.DisplayName = fmt.Sprintf("%s %s", authForm.FirstName, authForm.LastName) + user.FirstName = authForm.FirstName + user.LastName = authForm.LastName } } @@ -223,7 +186,7 @@ func (c *ApiController) Signup() { c.SetSessionUsername(user.GetId()) } - object.DisableVerificationCode(form.Email) + object.DisableVerificationCode(authForm.Email) object.DisableVerificationCode(checkPhone) record := object.NewRecord(c.Ctx) diff --git a/controllers/auth.go b/controllers/auth.go index 07591b15..7d3105d3 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -28,6 +28,7 @@ import ( "github.com/casdoor/casdoor/captcha" "github.com/casdoor/casdoor/conf" + "github.com/casdoor/casdoor/form" "github.com/casdoor/casdoor/idp" "github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/proxy" @@ -56,7 +57,7 @@ func tokenToResponse(token *object.Token) *Response { } // HandleLoggedIn ... -func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) { +func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) { userId := user.GetId() allowed, err := object.CheckAccessPermission(userId, application) @@ -221,21 +222,21 @@ func isProxyProviderType(providerType string) bool { // @Param nonce query string false nonce // @Param code_challenge_method query string false code_challenge_method // @Param code_challenge query string false code_challenge -// @Param form body controllers.RequestForm true "Login information" +// @Param form body controllers.AuthForm true "Login information" // @Success 200 {object} Response The Response object // @router /login [post] func (c *ApiController) Login() { resp := &Response{} - var form RequestForm - err := json.Unmarshal(c.Ctx.Input.RequestBody, &form) + var authForm form.AuthForm + err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm) if err != nil { c.ResponseError(err.Error()) return } - if form.Username != "" { - if form.Type == ResponseTypeLogin { + if authForm.Username != "" { + if authForm.Type == ResponseTypeLogin { if c.GetSessionUsername() != "" { c.ResponseError(c.T("account:Please sign out first"), c.GetSessionUsername()) return @@ -245,25 +246,25 @@ func (c *ApiController) Login() { var user *object.User var msg string - if form.Password == "" { - if user = object.GetUserByFields(form.Organization, form.Username); user == nil { - c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username))) + if authForm.Password == "" { + if user = object.GetUserByFields(authForm.Organization, authForm.Username); user == nil { + c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username))) return } - verificationCodeType := object.GetVerifyType(form.Username) + verificationCodeType := object.GetVerifyType(authForm.Username) var checkDest string if verificationCodeType == object.VerifyTypePhone { - form.CountryCode = user.GetCountryCode(form.CountryCode) + authForm.CountryCode = user.GetCountryCode(authForm.CountryCode) var ok bool - if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok { - c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), form.CountryCode)) + if checkDest, ok = util.GetE164Number(authForm.Username, authForm.CountryCode); !ok { + c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode)) return } } // check result through Email or Phone - checkResult := object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage()) + checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage()) if len(checkResult) != 0 { c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult)) return @@ -272,9 +273,9 @@ func (c *ApiController) Login() { // disable the verification code object.DisableVerificationCode(checkDest) } else { - application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) + application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) if application == nil { - c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application)) + c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application)) return } if !application.EnablePassword { @@ -282,8 +283,8 @@ func (c *ApiController) Login() { return } var enableCaptcha bool - if enableCaptcha = object.CheckToEnableCaptcha(application, form.Organization, form.Username); enableCaptcha { - isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret) + if enableCaptcha = object.CheckToEnableCaptcha(application, authForm.Organization, authForm.Username); enableCaptcha { + isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret) if err != nil { c.ResponseError(err.Error()) return @@ -295,42 +296,42 @@ func (c *ApiController) Login() { } } - password := form.Password - user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage(), enableCaptcha) + password := authForm.Password + user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha) } if msg != "" { resp = &Response{Status: "error", Msg: msg} } else { - application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) + application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) if application == nil { - c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application)) + c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application)) return } - resp = c.HandleLoggedIn(application, user, &form) + resp = c.HandleLoggedIn(application, user, &authForm) record := object.NewRecord(c.Ctx) record.Organization = application.Organization record.User = user.Name util.SafeGoroutine(func() { object.AddRecord(record) }) } - } else if form.Provider != "" { + } else if authForm.Provider != "" { var application *object.Application - if form.ClientId != "" { - application = object.GetApplicationByClientId(form.ClientId) + if authForm.ClientId != "" { + application = object.GetApplicationByClientId(authForm.ClientId) } else { - application = object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) + application = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) } if application == nil { - c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application)) + c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application)) return } organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization)) - provider := object.GetProvider(util.GetId("admin", form.Provider)) + provider := object.GetProvider(util.GetId("admin", authForm.Provider)) providerItem := application.GetProviderItem(provider.Name) if !providerItem.IsProviderVisible() { c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name)) @@ -340,7 +341,7 @@ func (c *ApiController) Login() { userInfo := &idp.UserInfo{} if provider.Category == "SAML" { // SAML - userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider, c.Ctx.Request.Host) + userInfo.Id, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host) if err != nil { c.ResponseError(err.Error()) return @@ -355,7 +356,7 @@ func (c *ApiController) Login() { clientSecret = provider.ClientSecret2 } - idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl) + idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, authForm.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl) if idProvider == nil { c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type)) return @@ -363,13 +364,13 @@ func (c *ApiController) Login() { setHttpClient(idProvider, provider.Type) - if form.State != conf.GetConfigString("authState") && form.State != application.Name { - c.ResponseError(fmt.Sprintf(c.T("auth:State expected: %s, but got: %s"), conf.GetConfigString("authState"), form.State)) + if authForm.State != conf.GetConfigString("authState") && authForm.State != application.Name { + c.ResponseError(fmt.Sprintf(c.T("auth:State expected: %s, but got: %s"), conf.GetConfigString("authState"), authForm.State)) return } // https://github.com/golang/oauth2/issues/123#issuecomment-103715338 - token, err := idProvider.GetToken(form.Code) + token, err := idProvider.GetToken(authForm.Code) if err != nil { c.ResponseError(err.Error()) return @@ -387,7 +388,7 @@ func (c *ApiController) Login() { } } - if form.Method == "signup" { + if authForm.Method == "signup" { user := &object.User{} if provider.Category == "SAML" { user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id)) @@ -402,7 +403,7 @@ func (c *ApiController) Login() { c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator")) } - resp = c.HandleLoggedIn(application, user, &form) + resp = c.HandleLoggedIn(application, user, &authForm) record := object.NewRecord(c.Ctx) record.Organization = application.Organization @@ -477,7 +478,7 @@ func (c *ApiController) Login() { object.SetUserOAuthProperties(organization, user, provider.Type, userInfo) object.LinkUserAccount(user, provider.Type, userInfo.Id) - resp = c.HandleLoggedIn(application, user, &form) + resp = c.HandleLoggedIn(application, user, &authForm) record := object.NewRecord(c.Ctx) record.Organization = application.Organization @@ -493,7 +494,7 @@ func (c *ApiController) Login() { resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))} } // resp = &Response{Status: "ok", Msg: "", Data: res} - } else { // form.Method != "signup" + } else { // authForm.Method != "signup" userId := c.GetSessionUsername() if userId == "" { c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id)), userInfo) @@ -521,21 +522,21 @@ func (c *ApiController) Login() { } else { if c.GetSessionUsername() != "" { // user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in - application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) + application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) if application == nil { - c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application)) + c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application)) return } user := c.getCurrentUser() - resp = c.HandleLoggedIn(application, user, &form) + resp = c.HandleLoggedIn(application, user, &authForm) record := object.NewRecord(c.Ctx) record.Organization = application.Organization record.User = user.Name util.SafeGoroutine(func() { object.AddRecord(record) }) } else { - c.ResponseError(fmt.Sprintf(c.T("auth:Unknown authentication type (not password or provider), form = %s"), util.StructToJson(form))) + c.ResponseError(fmt.Sprintf(c.T("auth:Unknown authentication type (not password or provider), authForm = %s"), util.StructToJson(authForm))) return } } diff --git a/controllers/base.go b/controllers/base.go index 0fca671f..f5d914c9 100644 --- a/controllers/base.go +++ b/controllers/base.go @@ -41,18 +41,41 @@ type SessionData struct { } func (c *ApiController) IsGlobalAdmin() bool { + isGlobalAdmin, _ := c.isGlobalAdmin() + + return isGlobalAdmin +} + +func (c *ApiController) IsAdmin() bool { + isGlobalAdmin, user := c.isGlobalAdmin() + + return isGlobalAdmin || user.IsAdmin +} + +func (c *ApiController) isGlobalAdmin() (bool, *object.User) { username := c.GetSessionUsername() if strings.HasPrefix(username, "app/") { // e.g., "app/app-casnode" - return true + return true, nil } - user := object.GetUser(username) + user := c.getCurrentUser() if user == nil { - return false + return false, nil } - return user.Owner == "built-in" || user.IsGlobalAdmin + return user.Owner == "built-in" || user.IsGlobalAdmin, user +} + +func (c *ApiController) getCurrentUser() *object.User { + var user *object.User + userId := c.GetSessionUsername() + if userId == "" { + user = nil + } else { + user = object.GetUser(userId) + } + return user } // GetSessionUsername ... diff --git a/controllers/user.go b/controllers/user.go index 7dbae07e..92cc4431 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -162,7 +162,9 @@ func (c *ApiController) UpdateUser() { c.ResponseError(msg) return } - if pass, err := checkPermissionForUpdateUser(oldUser, &user, c); !pass { + + isAdmin := c.IsAdmin() + if pass, err := object.CheckPermissionForUpdateUser(oldUser, &user, isAdmin, c.GetAcceptLanguage()); !pass { c.ResponseError(err) return } @@ -172,9 +174,7 @@ func (c *ApiController) UpdateUser() { columns = strings.Split(columnsStr, ",") } - isGlobalAdmin := c.IsGlobalAdmin() - - affected := object.UpdateUser(id, &user, columns, isGlobalAdmin) + affected := object.UpdateUser(id, &user, columns, isAdmin) if affected { object.UpdateUserToOriginalDatabase(&user) } diff --git a/controllers/user_util.go b/controllers/user_util.go deleted file mode 100644 index 5972db39..00000000 --- a/controllers/user_util.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2023 The Casdoor Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controllers - -import ( - "encoding/json" - - "github.com/casdoor/casdoor/object" -) - -func checkPermissionForUpdateUser(oldUser, newUser *object.User, c *ApiController) (bool, string) { - organization := object.GetOrganizationByUser(oldUser) - var itemsChanged []*object.AccountItem - - if oldUser.Owner != newUser.Owner { - item := object.GetAccountItemByName("Organization", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Name != newUser.Name { - item := object.GetAccountItemByName("Name", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Id != newUser.Id { - item := object.GetAccountItemByName("ID", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.DisplayName != newUser.DisplayName { - item := object.GetAccountItemByName("Display name", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Avatar != newUser.Avatar { - item := object.GetAccountItemByName("Avatar", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Type != newUser.Type { - item := object.GetAccountItemByName("User type", organization) - itemsChanged = append(itemsChanged, item) - } - // The password is *** when not modified - if oldUser.Password != newUser.Password && newUser.Password != "***" { - item := object.GetAccountItemByName("Password", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Email != newUser.Email { - item := object.GetAccountItemByName("Email", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Phone != newUser.Phone { - item := object.GetAccountItemByName("Phone", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.CountryCode != newUser.CountryCode { - item := object.GetAccountItemByName("Country code", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Region != newUser.Region { - item := object.GetAccountItemByName("Country/Region", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Location != newUser.Location { - item := object.GetAccountItemByName("Location", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Affiliation != newUser.Affiliation { - item := object.GetAccountItemByName("Affiliation", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Title != newUser.Title { - item := object.GetAccountItemByName("Title", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Homepage != newUser.Homepage { - item := object.GetAccountItemByName("Homepage", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Bio != newUser.Bio { - item := object.GetAccountItemByName("Bio", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.Tag != newUser.Tag { - item := object.GetAccountItemByName("Tag", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.SignupApplication != newUser.SignupApplication { - item := object.GetAccountItemByName("Signup application", organization) - itemsChanged = append(itemsChanged, item) - } - - oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties) - newUserPropertiesJson, _ := json.Marshal(newUser.Properties) - if string(oldUserPropertiesJson) != string(newUserPropertiesJson) { - item := object.GetAccountItemByName("Properties", organization) - itemsChanged = append(itemsChanged, item) - } - - if oldUser.IsAdmin != newUser.IsAdmin { - item := object.GetAccountItemByName("Is admin", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin { - item := object.GetAccountItemByName("Is global admin", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.IsForbidden != newUser.IsForbidden { - item := object.GetAccountItemByName("Is forbidden", organization) - itemsChanged = append(itemsChanged, item) - } - if oldUser.IsDeleted != newUser.IsDeleted { - item := object.GetAccountItemByName("Is deleted", organization) - itemsChanged = append(itemsChanged, item) - } - - currentUser := c.getCurrentUser() - if currentUser == nil && c.IsGlobalAdmin() { - currentUser = &object.User{ - IsGlobalAdmin: true, - } - } - - for i := range itemsChanged { - if pass, err := object.CheckAccountItemModifyRule(itemsChanged[i], currentUser, c.GetAcceptLanguage()); !pass { - return pass, err - } - } - return true, "" -} diff --git a/controllers/verification.go b/controllers/verification.go index e04b6332..7f66776b 100644 --- a/controllers/verification.go +++ b/controllers/verification.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/casdoor/casdoor/captcha" + "github.com/casdoor/casdoor/form" "github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/util" ) @@ -32,60 +33,29 @@ const ( ForgetVerification = "forget" ) -func (c *ApiController) getCurrentUser() *object.User { - var user *object.User - userId := c.GetSessionUsername() - if userId == "" { - user = nil - } else { - user = object.GetUser(userId) - } - return user -} - // SendVerificationCode ... // @Title SendVerificationCode // @Tag Verification API // @router /send-verification-code [post] func (c *ApiController) SendVerificationCode() { - destType := c.Ctx.Request.Form.Get("type") - dest := c.Ctx.Request.Form.Get("dest") - countryCode := c.Ctx.Request.Form.Get("countryCode") - checkType := c.Ctx.Request.Form.Get("checkType") - clientSecret := c.Ctx.Request.Form.Get("clientSecret") - captchaToken := c.Ctx.Request.Form.Get("captchaToken") - applicationId := c.Ctx.Request.Form.Get("applicationId") - method := c.Ctx.Request.Form.Get("method") - checkUser := c.Ctx.Request.Form.Get("checkUser") + var vform form.VerificationForm + err := c.ParseForm(&vform) + if err != nil { + c.ResponseError(err.Error()) + return + } remoteAddr := util.GetIPFromRequest(c.Ctx.Request) - if dest == "" { - c.ResponseError(c.T("general:Missing parameter") + ": dest.") - return - } - if applicationId == "" { - c.ResponseError(c.T("general:Missing parameter") + ": applicationId.") - return - } - if checkType == "" { - c.ResponseError(c.T("general:Missing parameter") + ": checkType.") - return - } - if !strings.Contains(applicationId, "/") { - c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.") + if msg := vform.CheckParameter(form.SendVerifyCode, c.GetAcceptLanguage()); msg != "" { + c.ResponseError(msg) return } - if checkType != "none" { - if captchaToken == "" { - c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.") + if vform.CaptchaType != "none" { + if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil { + c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType) return - } - - if captchaProvider := captcha.GetCaptchaProvider(checkType); captchaProvider == nil { - c.ResponseError(c.T("general:don't support captchaProvider: ") + checkType) - return - } else if isHuman, err := captchaProvider.VerifyCaptcha(captchaToken, clientSecret); err != nil { + } else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil { c.ResponseError(err.Error()) return } else if !isHuman { @@ -94,7 +64,7 @@ func (c *ApiController) SendVerificationCode() { } } - application := object.GetApplication(applicationId) + application := object.GetApplication(vform.ApplicationId) organization := object.GetOrganization(util.GetId(application.Owner, application.Organization)) if organization == nil { c.ResponseError(c.T("check:Organization does not exist")) @@ -103,57 +73,57 @@ func (c *ApiController) SendVerificationCode() { var user *object.User // checkUser != "", means method is ForgetVerification - if checkUser != "" { + if vform.CheckUser != "" { owner := application.Organization - user = object.GetUser(util.GetId(owner, checkUser)) + user = object.GetUser(util.GetId(owner, vform.CheckUser)) } sendResp := errors.New("invalid dest type") - switch destType { + switch vform.Type { case object.VerifyTypeEmail: - if !util.IsEmailValid(dest) { + if !util.IsEmailValid(vform.Dest) { c.ResponseError(c.T("check:Email is invalid")) return } - if method == LoginVerification || method == ForgetVerification { - if user != nil && util.GetMaskedEmail(user.Email) == dest { - dest = user.Email + if vform.Method == LoginVerification || vform.Method == ForgetVerification { + if user != nil && util.GetMaskedEmail(user.Email) == vform.Dest { + vform.Dest = user.Email } - user = object.GetUserByEmail(organization.Name, dest) + user = object.GetUserByEmail(organization.Name, vform.Dest) if user == nil { c.ResponseError(c.T("verification:the user does not exist, please sign up first")) return } - } else if method == ResetVerification { + } else if vform.Method == ResetVerification { user = c.getCurrentUser() } provider := application.GetEmailProvider() - sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest) + sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest) case object.VerifyTypePhone: - if method == LoginVerification || method == ForgetVerification { - if user != nil && util.GetMaskedPhone(user.Phone) == dest { - dest = user.Phone + if vform.Method == LoginVerification || vform.Method == ForgetVerification { + if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest { + vform.Dest = user.Phone } - if user = object.GetUserByPhone(organization.Name, dest); user == nil { + if user = object.GetUserByPhone(organization.Name, vform.Dest); user == nil { c.ResponseError(c.T("verification:the user does not exist, please sign up first")) return } - countryCode = user.GetCountryCode(countryCode) - } else if method == ResetVerification { + vform.CountryCode = user.GetCountryCode(vform.CountryCode) + } else if vform.Method == ResetVerification { if user = c.getCurrentUser(); user != nil { - countryCode = user.GetCountryCode(countryCode) + vform.CountryCode = user.GetCountryCode(vform.CountryCode) } } provider := application.GetSmsProvider() - if phone, ok := util.GetE164Number(dest, countryCode); !ok { - c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), countryCode)) + if phone, ok := util.GetE164Number(vform.Dest, vform.CountryCode); !ok { + c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode)) return } else { sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, phone) @@ -167,6 +137,38 @@ func (c *ApiController) SendVerificationCode() { } } +// VerifyCaptcha ... +// @Title VerifyCaptcha +// @Tag Verification API +// @router /verify-captcha [post] +func (c *ApiController) VerifyCaptcha() { + var vform form.VerificationForm + err := c.ParseForm(&vform) + if err != nil { + c.ResponseError(err.Error()) + return + } + + if msg := vform.CheckParameter(form.VerifyCaptcha, c.GetAcceptLanguage()); msg != "" { + c.ResponseError(msg) + return + } + + provider := captcha.GetCaptchaProvider(vform.CaptchaType) + if provider == nil { + c.ResponseError(c.T("verification:Invalid captcha provider.")) + return + } + + isValid, err := provider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret) + if err != nil { + c.ResponseError(err.Error()) + return + } + + c.ResponseOk(isValid) +} + // ResetEmailOrPhone ... // @Tag Account API // @Title ResetEmailOrPhone @@ -200,7 +202,7 @@ func (c *ApiController) ResetEmailOrPhone() { return } - if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user, c.GetAcceptLanguage()); !pass { + if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass { c.ResponseError(errMsg) return } @@ -220,11 +222,12 @@ func (c *ApiController) ResetEmailOrPhone() { return } - if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user, c.GetAcceptLanguage()); !pass { + if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass { c.ResponseError(errMsg) return } } + if result := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess { c.ResponseError(result.Msg) return @@ -247,88 +250,55 @@ func (c *ApiController) ResetEmailOrPhone() { } // VerifyCode -// @Tag Account API +// @Tag Verification API // @Title VerifyCode // @router /api/verify-code [post] func (c *ApiController) VerifyCode() { - var form RequestForm - err := json.Unmarshal(c.Ctx.Input.RequestBody, &form) + var authForm form.AuthForm + err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm) if err != nil { c.ResponseError(err.Error()) return } var user *object.User - if form.Name != "" { - user = object.GetUserByFields(form.Organization, form.Name) + if authForm.Name != "" { + user = object.GetUserByFields(authForm.Organization, authForm.Name) } var checkDest string - if strings.Contains(form.Username, "@") { - if user != nil && util.GetMaskedEmail(user.Email) == form.Username { - form.Username = user.Email + if strings.Contains(authForm.Username, "@") { + if user != nil && util.GetMaskedEmail(user.Email) == authForm.Username { + authForm.Username = user.Email } - checkDest = form.Username + checkDest = authForm.Username } else { - if user != nil && util.GetMaskedPhone(user.Phone) == form.Username { - form.Username = user.Phone + if user != nil && util.GetMaskedPhone(user.Phone) == authForm.Username { + authForm.Username = user.Phone } } - if user = object.GetUserByFields(form.Organization, form.Username); user == nil { - c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username))) + if user = object.GetUserByFields(authForm.Organization, authForm.Username); user == nil { + c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username))) return } - verificationCodeType := object.GetVerifyType(form.Username) + verificationCodeType := object.GetVerifyType(authForm.Username) if verificationCodeType == object.VerifyTypePhone { - form.CountryCode = user.GetCountryCode(form.CountryCode) + authForm.CountryCode = user.GetCountryCode(authForm.CountryCode) var ok bool - if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok { - c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), form.CountryCode)) + if checkDest, ok = util.GetE164Number(authForm.Username, authForm.CountryCode); !ok { + c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode)) return } } - if result := object.CheckVerificationCode(checkDest, form.Code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess { + if result := object.CheckVerificationCode(checkDest, authForm.Code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess { c.ResponseError(result.Msg) return } object.DisableVerificationCode(checkDest) - c.SetSession("verifiedCode", form.Code) + c.SetSession("verifiedCode", authForm.Code) c.ResponseOk() } - -// VerifyCaptcha ... -// @Title VerifyCaptcha -// @Tag Verification API -// @router /verify-captcha [post] -func (c *ApiController) VerifyCaptcha() { - captchaType := c.Ctx.Request.Form.Get("captchaType") - - captchaToken := c.Ctx.Request.Form.Get("captchaToken") - clientSecret := c.Ctx.Request.Form.Get("clientSecret") - if captchaToken == "" { - c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.") - return - } - if clientSecret == "" { - c.ResponseError(c.T("general:Missing parameter") + ": clientSecret.") - return - } - - provider := captcha.GetCaptchaProvider(captchaType) - if provider == nil { - c.ResponseError(c.T("verification:Invalid captcha provider.")) - return - } - - isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret) - if err != nil { - c.ResponseError(err.Error()) - return - } - - c.ResponseOk(isValid) -} diff --git a/controllers/webauthn.go b/controllers/webauthn.go index f0d2bf63..1bc67beb 100644 --- a/controllers/webauthn.go +++ b/controllers/webauthn.go @@ -19,6 +19,7 @@ import ( "fmt" "io" + "github.com/casdoor/casdoor/form" "github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/util" "github.com/go-webauthn/webauthn/protocol" @@ -147,9 +148,9 @@ func (c *ApiController) WebAuthnSigninFinish() { util.LogInfo(c.Ctx, "API: [%s] signed in", userId) application := object.GetApplicationByUser(user) - var form RequestForm - form.Type = responseType - resp := c.HandleLoggedIn(application, user, &form) + var authForm form.AuthForm + authForm.Type = responseType + resp := c.HandleLoggedIn(application, user, &authForm) c.Data["json"] = resp c.ServeJSON() } diff --git a/form/auth.go b/form/auth.go new file mode 100644 index 00000000..5db76a16 --- /dev/null +++ b/form/auth.go @@ -0,0 +1,53 @@ +// Copyright 2023 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package form + +type AuthForm struct { + Type string `json:"type"` + + Organization string `json:"organization"` + Username string `json:"username"` + Password string `json:"password"` + Name string `json:"name"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Email string `json:"email"` + Phone string `json:"phone"` + Affiliation string `json:"affiliation"` + IdCard string `json:"idCard"` + Region string `json:"region"` + + Application string `json:"application"` + ClientId string `json:"clientId"` + Provider string `json:"provider"` + Code string `json:"code"` + State string `json:"state"` + RedirectUri string `json:"redirectUri"` + Method string `json:"method"` + + EmailCode string `json:"emailCode"` + PhoneCode string `json:"phoneCode"` + CountryCode string `json:"countryCode"` + + AutoSignin bool `json:"autoSignin"` + + RelayState string `json:"relayState"` + SamlRequest string `json:"samlRequest"` + SamlResponse string `json:"samlResponse"` + + CaptchaType string `json:"captchaType"` + CaptchaToken string `json:"captchaToken"` + ClientSecret string `json:"clientSecret"` +} diff --git a/form/verification.go b/form/verification.go new file mode 100644 index 00000000..e2a3b530 --- /dev/null +++ b/form/verification.go @@ -0,0 +1,67 @@ +// Copyright 2023 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package form + +import ( + "strings" + + "github.com/casdoor/casdoor/i18n" +) + +type VerificationForm struct { + Dest string `form:"dest"` + Type string `form:"type"` + CountryCode string `form:"countryCode"` + ApplicationId string `form:"applicationId"` + Method string `form:"method"` + CheckUser string `form:"checkUser"` + + CaptchaType string `form:"captchaType"` + ClientSecret string `form:"clientSecret"` + CaptchaToken string `form:"captchaToken"` +} + +const ( + SendVerifyCode = 0 + VerifyCaptcha = 1 +) + +func (form *VerificationForm) CheckParameter(checkType int, lang string) string { + if checkType == SendVerifyCode { + if form.Type == "" { + return i18n.Translate(lang, "general:Missing parameter") + ": type." + } + if form.Dest == "" { + return i18n.Translate(lang, "general:Missing parameter") + ": dest." + } + if form.CaptchaType == "" { + return i18n.Translate(lang, "general:Missing parameter") + ": checkType." + } + if !strings.Contains(form.ApplicationId, "/") { + return i18n.Translate(lang, "verification:Wrong parameter") + ": applicationId." + } + } + + if form.CaptchaType != "none" { + if form.CaptchaToken == "" { + return i18n.Translate(lang, "general:Missing parameter") + ": captchaToken." + } + if form.ClientSecret == "" { + return i18n.Translate(lang, "general:Missing parameter") + ": clientSecret." + } + } + + return "" +} diff --git a/object/check.go b/object/check.go index 4a121a91..315b98e2 100644 --- a/object/check.go +++ b/object/check.go @@ -22,6 +22,7 @@ import ( "unicode" "github.com/casdoor/casdoor/cred" + "github.com/casdoor/casdoor/form" "github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/util" goldap "github.com/go-ldap/ldap/v3" @@ -42,86 +43,86 @@ 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, countryCode string, affiliation string, lang string) string { +func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string { if organization == nil { return i18n.Translate(lang, "check:Organization does not exist") } if application.IsSignupItemVisible("Username") { - if len(username) <= 1 { + if len(form.Username) <= 1 { return i18n.Translate(lang, "check:Username must have at least 2 characters") } - if unicode.IsDigit(rune(username[0])) { + if unicode.IsDigit(rune(form.Username[0])) { return i18n.Translate(lang, "check:Username cannot start with a digit") } - if util.IsEmailValid(username) { + if util.IsEmailValid(form.Username) { return i18n.Translate(lang, "check:Username cannot be an email address") } - if reWhiteSpace.MatchString(username) { + if reWhiteSpace.MatchString(form.Username) { return i18n.Translate(lang, "check:Username cannot contain white spaces") } - if msg := CheckUsername(username, lang); msg != "" { + if msg := CheckUsername(form.Username, lang); msg != "" { return msg } - if HasUserByField(organization.Name, "name", username) { + if HasUserByField(organization.Name, "name", form.Username) { return i18n.Translate(lang, "check:Username already exists") } - if HasUserByField(organization.Name, "email", email) { + if HasUserByField(organization.Name, "email", form.Email) { return i18n.Translate(lang, "check:Email already exists") } - if HasUserByField(organization.Name, "phone", phone) { + if HasUserByField(organization.Name, "phone", form.Phone) { return i18n.Translate(lang, "check:Phone already exists") } } - if len(password) <= 5 { + if len(form.Password) <= 5 { return i18n.Translate(lang, "check:Password must have at least 6 characters") } if application.IsSignupItemVisible("Email") { - if email == "" { + if form.Email == "" { if application.IsSignupItemRequired("Email") { return i18n.Translate(lang, "check:Email cannot be empty") } } else { - if HasUserByField(organization.Name, "email", email) { + if HasUserByField(organization.Name, "email", form.Email) { return i18n.Translate(lang, "check:Email already exists") - } else if !util.IsEmailValid(email) { + } else if !util.IsEmailValid(form.Email) { return i18n.Translate(lang, "check:Email is invalid") } } } if application.IsSignupItemVisible("Phone") { - if phone == "" { + if form.Phone == "" { if application.IsSignupItemRequired("Phone") { return i18n.Translate(lang, "check:Phone cannot be empty") } } else { - if HasUserByField(organization.Name, "phone", phone) { + if HasUserByField(organization.Name, "phone", form.Phone) { return i18n.Translate(lang, "check:Phone already exists") - } else if !util.IsPhoneAllowInRegin(countryCode, organization.CountryCodes) { + } else if !util.IsPhoneAllowInRegin(form.CountryCode, organization.CountryCodes) { return i18n.Translate(lang, "check:Your region is not allow to signup by phone") - } else if !util.IsPhoneValid(phone, countryCode) { + } else if !util.IsPhoneValid(form.Phone, form.CountryCode) { return i18n.Translate(lang, "check:Phone number is invalid") } } } if application.IsSignupItemVisible("Display name") { - if application.GetSignupItemRule("Display name") == "First, last" && (firstName != "" || lastName != "") { - if firstName == "" { + if application.GetSignupItemRule("Display name") == "First, last" && (form.FirstName != "" || form.LastName != "") { + if form.FirstName == "" { return i18n.Translate(lang, "check:FirstName cannot be blank") - } else if lastName == "" { + } else if form.LastName == "" { return i18n.Translate(lang, "check:LastName cannot be blank") } } else { - if displayName == "" { + if form.Name == "" { return i18n.Translate(lang, "check:DisplayName cannot be blank") } else if application.GetSignupItemRule("Display name") == "Real name" { - if !isValidRealName(displayName) { + if !isValidRealName(form.Name) { return i18n.Translate(lang, "check:DisplayName is not valid real name") } } @@ -129,7 +130,7 @@ func CheckUserSignup(application *Application, organization *Organization, usern } if application.IsSignupItemVisible("Affiliation") { - if affiliation == "" { + if form.Affiliation == "" { return i18n.Translate(lang, "check:Affiliation cannot be blank") } } diff --git a/object/organization.go b/object/organization.go index a367336b..2350fd8f 100644 --- a/object/organization.go +++ b/object/organization.go @@ -209,14 +209,14 @@ func GetAccountItemByName(name string, organization *Organization) *AccountItem return nil } -func CheckAccountItemModifyRule(accountItem *AccountItem, user *User, lang string) (bool, string) { +func CheckAccountItemModifyRule(accountItem *AccountItem, isAdmin bool, lang string) (bool, string) { if accountItem == nil { return true, "" } switch accountItem.ModifyRule { case "Admin": - if user == nil || !user.IsAdmin && !user.IsGlobalAdmin { + if isAdmin { return false, fmt.Sprintf(i18n.Translate(lang, "organization:Only admin can modify the %s."), accountItem.Name) } case "Immutable": diff --git a/object/user.go b/object/user.go index f63933fb..66cf1296 100644 --- a/object/user.go +++ b/object/user.go @@ -425,7 +425,7 @@ func GetLastUser(owner string) *User { return nil } -func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) bool { +func UpdateUser(id string, user *User, columns []string, isAdmin bool) bool { owner, name := util.GetOwnerAndNameFromIdNoCheck(id) oldUser := getUser(owner, name) if oldUser == nil { @@ -456,7 +456,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo "signin_wrong_times", "last_signin_wrong_time", } } - if isGlobalAdmin { + if isAdmin { columns = append(columns, "name", "email", "phone", "country_code") } diff --git a/object/user_util.go b/object/user_util.go index 3621c7b3..8fdbd133 100644 --- a/object/user_util.go +++ b/object/user_util.go @@ -15,6 +15,7 @@ package object import ( + "encoding/json" "fmt" "reflect" "strings" @@ -179,6 +180,116 @@ func ClearUserOAuthProperties(user *User, providerType string) bool { return affected != 0 } +func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang string) (bool, string) { + organization := GetOrganizationByUser(oldUser) + var itemsChanged []*AccountItem + + if oldUser.Owner != newUser.Owner { + item := GetAccountItemByName("Organization", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Name != newUser.Name { + item := GetAccountItemByName("Name", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Id != newUser.Id { + item := GetAccountItemByName("ID", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.DisplayName != newUser.DisplayName { + item := GetAccountItemByName("Display name", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Avatar != newUser.Avatar { + item := GetAccountItemByName("Avatar", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Type != newUser.Type { + item := GetAccountItemByName("User type", organization) + itemsChanged = append(itemsChanged, item) + } + // The password is *** when not modified + if oldUser.Password != newUser.Password && newUser.Password != "***" { + item := GetAccountItemByName("Password", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Email != newUser.Email { + item := GetAccountItemByName("Email", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Phone != newUser.Phone { + item := GetAccountItemByName("Phone", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.CountryCode != newUser.CountryCode { + item := GetAccountItemByName("Country code", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Region != newUser.Region { + item := GetAccountItemByName("Country/Region", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Location != newUser.Location { + item := GetAccountItemByName("Location", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Affiliation != newUser.Affiliation { + item := GetAccountItemByName("Affiliation", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Title != newUser.Title { + item := GetAccountItemByName("Title", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Homepage != newUser.Homepage { + item := GetAccountItemByName("Homepage", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Bio != newUser.Bio { + item := GetAccountItemByName("Bio", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.Tag != newUser.Tag { + item := GetAccountItemByName("Tag", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.SignupApplication != newUser.SignupApplication { + item := GetAccountItemByName("Signup application", organization) + itemsChanged = append(itemsChanged, item) + } + + oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties) + newUserPropertiesJson, _ := json.Marshal(newUser.Properties) + if string(oldUserPropertiesJson) != string(newUserPropertiesJson) { + item := GetAccountItemByName("Properties", organization) + itemsChanged = append(itemsChanged, item) + } + + if oldUser.IsAdmin != newUser.IsAdmin { + item := GetAccountItemByName("Is admin", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin { + item := GetAccountItemByName("Is global admin", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.IsForbidden != newUser.IsForbidden { + item := GetAccountItemByName("Is forbidden", organization) + itemsChanged = append(itemsChanged, item) + } + if oldUser.IsDeleted != newUser.IsDeleted { + item := GetAccountItemByName("Is deleted", organization) + itemsChanged = append(itemsChanged, item) + } + + for i := range itemsChanged { + if pass, err := CheckAccountItemModifyRule(itemsChanged[i], isAdmin, lang); !pass { + return pass, err + } + } + return true, "" +} + func (user *User) GetCountryCode(countryCode string) string { if countryCode != "" { return countryCode @@ -193,3 +304,11 @@ func (user *User) GetCountryCode(countryCode string) string { } return "" } + +func (user *User) IsAdminUser() bool { + if user == nil { + return false + } + + return user.IsAdmin || user.IsGlobalAdmin +} diff --git a/swagger/swagger.json b/swagger/swagger.json index 435b06b6..38ae93b3 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -551,15 +551,33 @@ "operationId": "ApiController.GetCaptcha" } }, - "/api/api/get-webhook-event": { + "/api/api/get-captcha-status": { "get": { "tags": [ - "GetCaptchaStatus API" + "Token API" ], - "operationId": "ApiController.GetCaptchaStatus" + "description": "Get Login Error Counts", + "operationId": "ApiController.GetCaptchaStatus", + "parameters": [ + { + "in": "query", + "name": "id", + "description": "The id ( owner/name ) of user", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "The Response object", + "schema": { + "$ref": "#/definitions/controllers.Response" + } + } + } } }, - "/api/api/get-captcha-status": { + "/api/api/get-webhook-event": { "get": { "tags": [ "GetWebhookEventType API" @@ -662,7 +680,7 @@ "/api/api/verify-code": { "post": { "tags": [ - "Account API" + "Verification API" ], "operationId": "ApiController.VerifyCode" } @@ -2752,7 +2770,7 @@ "description": "Login information", "required": true, "schema": { - "$ref": "#/definitions/controllers.RequestForm" + "$ref": "#/definitions/controllers.AuthForm" } } ], @@ -3891,11 +3909,11 @@ } }, "definitions": { - "2306.0xc0004a1410.false": { + "1183.0xc000455050.false": { "title": "false", "type": "object" }, - "2340.0xc0004a1440.false": { + "1217.0xc000455080.false": { "title": "false", "type": "object" }, @@ -3907,6 +3925,10 @@ "title": "Response", "type": "object" }, + "controllers.AuthForm": { + "title": "AuthForm", + "type": "object" + }, "controllers.EmailForm": { "title": "EmailForm", "type": "object", @@ -3931,108 +3953,15 @@ } } }, - "controllers.RequestForm": { - "title": "RequestForm", - "type": "object", - "properties": { - "affiliation": { - "type": "string" - }, - "application": { - "type": "string" - }, - "autoSignin": { - "type": "boolean" - }, - "captchaToken": { - "type": "string" - }, - "captchaType": { - "type": "string" - }, - "clientId": { - "type": "string" - }, - "clientSecret": { - "type": "string" - }, - "code": { - "type": "string" - }, - "countryCode": { - "type": "string" - }, - "email": { - "type": "string" - }, - "emailCode": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "idCard": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "method": { - "type": "string" - }, - "name": { - "type": "string" - }, - "organization": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "phoneCode": { - "type": "string" - }, - "provider": { - "type": "string" - }, - "redirectUri": { - "type": "string" - }, - "region": { - "type": "string" - }, - "relayState": { - "type": "string" - }, - "samlRequest": { - "type": "string" - }, - "samlResponse": { - "type": "string" - }, - "state": { - "type": "string" - }, - "type": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, "controllers.Response": { "title": "Response", "type": "object", "properties": { "data": { - "$ref": "#/definitions/2306.0xc0004a1410.false" + "$ref": "#/definitions/1183.0xc000455050.false" }, "data2": { - "$ref": "#/definitions/2340.0xc0004a1440.false" + "$ref": "#/definitions/1217.0xc000455080.false" }, "msg": { "type": "string" diff --git a/swagger/swagger.yml b/swagger/swagger.yml index 6ab2847b..bdf90465 100644 --- a/swagger/swagger.yml +++ b/swagger/swagger.yml @@ -355,16 +355,28 @@ paths: tags: - Login API operationId: ApiController.GetCaptcha + /api/api/get-captcha-status: + get: + tags: + - Token API + description: Get Login Error Counts + operationId: ApiController.GetCaptchaStatus + parameters: + - in: query + name: id + description: The id ( owner/name ) of user + required: true + type: string + responses: + "200": + description: The Response object + schema: + $ref: '#/definitions/controllers.Response' /api/api/get-webhook-event: get: tags: - GetWebhookEventType API operationId: ApiController.GetWebhookEventType - /api/api/get-captcha-status: - get: - tags: - - GetCaptchaStatus API - operationId: ApiController.GetCaptchaStatus /api/api/reset-email-or-phone: post: tags: @@ -429,7 +441,7 @@ paths: /api/api/verify-code: post: tags: - - Account API + - Verification API operationId: ApiController.VerifyCode /api/api/webhook: post: @@ -1798,7 +1810,7 @@ paths: description: Login information required: true schema: - $ref: '#/definitions/controllers.RequestForm' + $ref: '#/definitions/controllers.AuthForm' responses: "200": description: The Response object @@ -2543,10 +2555,10 @@ paths: schema: $ref: '#/definitions/Response' definitions: - 2306.0xc0004a1410.false: + 1183.0xc000455050.false: title: "false" type: object - 2340.0xc0004a1440.false: + 1217.0xc000455080.false: title: "false" type: object LaravelResponse: @@ -2555,6 +2567,9 @@ definitions: Response: title: Response type: object + controllers.AuthForm: + title: AuthForm + type: object controllers.EmailForm: title: EmailForm type: object @@ -2571,76 +2586,14 @@ definitions: type: string title: type: string - controllers.RequestForm: - title: RequestForm - type: object - properties: - affiliation: - type: string - application: - type: string - autoSignin: - type: boolean - captchaToken: - type: string - captchaType: - type: string - clientId: - type: string - clientSecret: - type: string - code: - type: string - countryCode: - type: string - email: - type: string - emailCode: - type: string - firstName: - type: string - idCard: - type: string - lastName: - type: string - method: - type: string - name: - type: string - organization: - type: string - password: - type: string - phone: - type: string - phoneCode: - type: string - provider: - type: string - redirectUri: - type: string - region: - type: string - relayState: - type: string - samlRequest: - type: string - samlResponse: - type: string - state: - type: string - type: - type: string - username: - type: string controllers.Response: title: Response type: object properties: data: - $ref: '#/definitions/2306.0xc0004a1410.false' + $ref: '#/definitions/1183.0xc000455050.false' data2: - $ref: '#/definitions/2340.0xc0004a1440.false' + $ref: '#/definitions/1217.0xc000455080.false' msg: type: string name: diff --git a/web/src/AdapterListPage.js b/web/src/AdapterListPage.js index 523f258c..79d363a8 100644 --- a/web/src/AdapterListPage.js +++ b/web/src/AdapterListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as AdapterBackend from "./backend/AdapterBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class AdapterListPage extends BaseListPage { newAdapter() { diff --git a/web/src/ApplicationListPage.js b/web/src/ApplicationListPage.js index 47e8de9c..aea1cabb 100644 --- a/web/src/ApplicationListPage.js +++ b/web/src/ApplicationListPage.js @@ -21,7 +21,7 @@ import * as Setting from "./Setting"; import * as ApplicationBackend from "./backend/ApplicationBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class ApplicationListPage extends BaseListPage { constructor(props) { diff --git a/web/src/CertListPage.js b/web/src/CertListPage.js index 4fd3e18f..2593274e 100644 --- a/web/src/CertListPage.js +++ b/web/src/CertListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as CertBackend from "./backend/CertBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class CertListPage extends BaseListPage { newCert() { diff --git a/web/src/ChatListPage.js b/web/src/ChatListPage.js index a35872a4..d19e9d6d 100644 --- a/web/src/ChatListPage.js +++ b/web/src/ChatListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as ChatBackend from "./backend/ChatBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class ChatListPage extends BaseListPage { newChat() { diff --git a/web/src/MessageListPage.js b/web/src/MessageListPage.js index 50d17184..4a482fe4 100644 --- a/web/src/MessageListPage.js +++ b/web/src/MessageListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as MessageBackend from "./backend/MessageBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class MessageListPage extends BaseListPage { newMessage() { diff --git a/web/src/ModelListPage.js b/web/src/ModelListPage.js index 051a8395..bdf2ee33 100644 --- a/web/src/ModelListPage.js +++ b/web/src/ModelListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as ModelBackend from "./backend/ModelBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class ModelListPage extends BaseListPage { newModel() { diff --git a/web/src/OrganizationListPage.js b/web/src/OrganizationListPage.js index e4370368..4f39418d 100644 --- a/web/src/OrganizationListPage.js +++ b/web/src/OrganizationListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as OrganizationBackend from "./backend/OrganizationBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class OrganizationListPage extends BaseListPage { newOrganization() { diff --git a/web/src/PaymentListPage.js b/web/src/PaymentListPage.js index 6f1f2d94..64d1f3d7 100644 --- a/web/src/PaymentListPage.js +++ b/web/src/PaymentListPage.js @@ -21,7 +21,7 @@ import * as PaymentBackend from "./backend/PaymentBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; import * as Provider from "./auth/Provider"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class PaymentListPage extends BaseListPage { newPayment() { diff --git a/web/src/PermissionListPage.js b/web/src/PermissionListPage.js index d3c71a0c..fb817ac9 100644 --- a/web/src/PermissionListPage.js +++ b/web/src/PermissionListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as PermissionBackend from "./backend/PermissionBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class PermissionListPage extends BaseListPage { newPermission() { diff --git a/web/src/ProductListPage.js b/web/src/ProductListPage.js index beb02f4e..80ac17e5 100644 --- a/web/src/ProductListPage.js +++ b/web/src/ProductListPage.js @@ -21,7 +21,7 @@ import * as ProductBackend from "./backend/ProductBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; import {EditOutlined} from "@ant-design/icons"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class ProductListPage extends BaseListPage { newProduct() { diff --git a/web/src/ProviderListPage.js b/web/src/ProviderListPage.js index 207a0249..ae71abe4 100644 --- a/web/src/ProviderListPage.js +++ b/web/src/ProviderListPage.js @@ -21,7 +21,7 @@ import * as ProviderBackend from "./backend/ProviderBackend"; import * as Provider from "./auth/Provider"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class ProviderListPage extends BaseListPage { constructor(props) { diff --git a/web/src/ResourceListPage.js b/web/src/ResourceListPage.js index ab8189cc..72d9cfe4 100644 --- a/web/src/ResourceListPage.js +++ b/web/src/ResourceListPage.js @@ -21,7 +21,7 @@ import * as ResourceBackend from "./backend/ResourceBackend"; import i18next from "i18next"; import {Link} from "react-router-dom"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class ResourceListPage extends BaseListPage { constructor(props) { diff --git a/web/src/RoleListPage.js b/web/src/RoleListPage.js index 416c803a..57f9e1be 100644 --- a/web/src/RoleListPage.js +++ b/web/src/RoleListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as RoleBackend from "./backend/RoleBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class RoleListPage extends BaseListPage { newRole() { diff --git a/web/src/SessionListPage.js b/web/src/SessionListPage.js index 64705e5f..59439124 100644 --- a/web/src/SessionListPage.js +++ b/web/src/SessionListPage.js @@ -19,7 +19,7 @@ import {Link} from "react-router-dom"; import {Table, Tag} from "antd"; import React from "react"; import * as SessionBackend from "./backend/SessionBackend"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class SessionListPage extends BaseListPage { diff --git a/web/src/SyncerListPage.js b/web/src/SyncerListPage.js index 0744e780..446157d6 100644 --- a/web/src/SyncerListPage.js +++ b/web/src/SyncerListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as SyncerBackend from "./backend/SyncerBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class SyncerListPage extends BaseListPage { newSyncer() { diff --git a/web/src/TokenListPage.js b/web/src/TokenListPage.js index ba51a104..687a8f15 100644 --- a/web/src/TokenListPage.js +++ b/web/src/TokenListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as TokenBackend from "./backend/TokenBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class TokenListPage extends BaseListPage { newToken() { diff --git a/web/src/UserListPage.js b/web/src/UserListPage.js index d686f9ec..aee2e407 100644 --- a/web/src/UserListPage.js +++ b/web/src/UserListPage.js @@ -22,7 +22,7 @@ import * as Setting from "./Setting"; import * as UserBackend from "./backend/UserBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class UserListPage extends BaseListPage { constructor(props) { diff --git a/web/src/WebhookListPage.js b/web/src/WebhookListPage.js index 1d54f1aa..9bc935d9 100644 --- a/web/src/WebhookListPage.js +++ b/web/src/WebhookListPage.js @@ -20,7 +20,7 @@ import * as Setting from "./Setting"; import * as WebhookBackend from "./backend/WebhookBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; -import PopconfirmModal from "./PopconfirmModal"; +import PopconfirmModal from "./common/modal/PopconfirmModal"; class WebhookListPage extends BaseListPage { newWebhook() { diff --git a/web/src/backend/UserBackend.js b/web/src/backend/UserBackend.js index 53cef1c6..8a5ec1f5 100644 --- a/web/src/backend/UserBackend.js +++ b/web/src/backend/UserBackend.js @@ -113,9 +113,9 @@ export function setPassword(userOwner, userName, oldPassword, newPassword, code }).then(res => res.json()); } -export function sendCode(checkType, captchaToken, clientSecret, method, countryCode = "", dest, type, applicationId, checkUser = "") { +export function sendCode(captchaType, captchaToken, clientSecret, method, countryCode = "", dest, type, applicationId, checkUser = "") { const formData = new FormData(); - formData.append("checkType", checkType); + formData.append("captchaType", captchaType); formData.append("captchaToken", captchaToken); formData.append("clientSecret", clientSecret); formData.append("method", method); diff --git a/web/src/PopconfirmModal.js b/web/src/common/modal/PopconfirmModal.js similarity index 100% rename from web/src/PopconfirmModal.js rename to web/src/common/modal/PopconfirmModal.js diff --git a/web/src/table/LdapTable.js b/web/src/table/LdapTable.js index f19c0423..5836deaf 100644 --- a/web/src/table/LdapTable.js +++ b/web/src/table/LdapTable.js @@ -18,7 +18,7 @@ import * as Setting from "../Setting"; import i18next from "i18next"; import * as LdapBackend from "../backend/LdapBackend"; import {Link} from "react-router-dom"; -import PopconfirmModal from "../PopconfirmModal"; +import PopconfirmModal from "../common/modal/PopconfirmModal"; class LdapTable extends React.Component { constructor(props) {