mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-29 01:51:38 +08:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
312412ffe4 | ||
![]() |
295a69c5f7 | ||
![]() |
a8a8f39963 | ||
![]() |
90f8eba02d | ||
![]() |
2cca1c9136 | ||
![]() |
c2eebd61a1 | ||
![]() |
59566f61d7 | ||
![]() |
7e4c9c91cd | ||
![]() |
430ee616db | ||
![]() |
2e3a323528 |
@@ -451,7 +451,7 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
properties := map[string]string{}
|
||||
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
||||
properties["no"] = strconv.Itoa(object.GetUserCount(application.Organization, "", "") + 2)
|
||||
initScore, err := getInitScore(organization)
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||
|
@@ -21,14 +21,6 @@ import (
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type LdapServer struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Admin string `json:"admin"`
|
||||
Passwd string `json:"passwd"`
|
||||
BaseDn string `json:"baseDn"`
|
||||
}
|
||||
|
||||
type LdapResp struct {
|
||||
// Groups []LdapRespGroup `json:"groups"`
|
||||
Users []object.LdapRespUser `json:"users"`
|
||||
@@ -44,21 +36,17 @@ type LdapSyncResp struct {
|
||||
Failed []object.LdapRespUser `json:"failed"`
|
||||
}
|
||||
|
||||
// GetLdapUser
|
||||
// GetLdapUsers
|
||||
// @Tag Account API
|
||||
// @Title GetLdapser
|
||||
// @router /get-ldap-user [post]
|
||||
func (c *ApiController) GetLdapUser() {
|
||||
ldapServer := LdapServer{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
|
||||
if err != nil || util.IsStringsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
// @router /get-ldap-users [get]
|
||||
func (c *ApiController) GetLdapUsers() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var resp LdapResp
|
||||
_, ldapId := util.GetOwnerAndNameFromId(id)
|
||||
ldapServer := object.GetLdap(ldapId)
|
||||
|
||||
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
||||
conn, err := ldapServer.GetLdapConn()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -83,6 +71,8 @@ func (c *ApiController) GetLdapUser() {
|
||||
return
|
||||
}
|
||||
|
||||
var resp LdapResp
|
||||
uuids := make([]string, len(users))
|
||||
for _, user := range users {
|
||||
resp.Users = append(resp.Users, object.LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
@@ -95,9 +85,12 @@ func (c *ApiController) GetLdapUser() {
|
||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
||||
})
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
c.ResponseOk(resp)
|
||||
existUuids := object.CheckLdapUuidExist(ldapServer.Owner, uuids)
|
||||
|
||||
c.ResponseOk(resp, existUuids)
|
||||
}
|
||||
|
||||
// GetLdaps
|
||||
@@ -134,7 +127,7 @@ func (c *ApiController) AddLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -150,14 +143,14 @@ func (c *ApiController) AddLdap() {
|
||||
|
||||
affected := object.AddLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
resp.Data2 = ldap
|
||||
|
||||
if ldap.AutoSync != 0 {
|
||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||
}
|
||||
|
||||
c.ResponseOk(resp)
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateLdap
|
||||
@@ -174,17 +167,15 @@ func (c *ApiController) UpdateLdap() {
|
||||
|
||||
prevLdap := object.GetLdap(ldap.Id)
|
||||
affected := object.UpdateLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
|
||||
if ldap.AutoSync != 0 {
|
||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
|
||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||
}
|
||||
|
||||
c.ResponseOk(resp)
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteLdap
|
||||
@@ -199,8 +190,12 @@ func (c *ApiController) DeleteLdap() {
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.DeleteLdap(&ldap)
|
||||
|
||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||
c.ResponseOk(wrapActionResponse(object.DeleteLdap(&ldap)))
|
||||
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// SyncLdapUsers
|
||||
@@ -226,20 +221,3 @@ func (c *ApiController) SyncLdapUsers() {
|
||||
Failed: *failed,
|
||||
})
|
||||
}
|
||||
|
||||
// CheckLdapUsersExist
|
||||
// @Tag Account API
|
||||
// @Title CheckLdapUserExist
|
||||
// @router /check-ldap-users-exist [post]
|
||||
func (c *ApiController) CheckLdapUsersExist() {
|
||||
owner := c.Input().Get("owner")
|
||||
var uuids []string
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &uuids)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
exist := object.CheckLdapUuidExist(owner, uuids)
|
||||
c.ResponseOk(exist)
|
||||
}
|
||||
|
@@ -12,6 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !skipCi
|
||||
// +build !skipCi
|
||||
|
||||
package i18n
|
||||
|
||||
import (
|
||||
|
@@ -15,11 +15,9 @@
|
||||
package idp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -170,10 +168,18 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
||||
Email: dtUserInfo.Email,
|
||||
AvatarUrl: dtUserInfo.AvatarUrl,
|
||||
}
|
||||
isUserInOrg, err := idp.isUserInOrg(userInfo.UnionId)
|
||||
if !isUserInOrg {
|
||||
|
||||
corpAccessToken := idp.getInnerAppAccessToken()
|
||||
userId, err := idp.getUserId(userInfo.UnionId, corpAccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
corpEmail, err := idp.getUserCorpEmail(userId, corpAccessToken)
|
||||
if err == nil && corpEmail != "" {
|
||||
userInfo.Email = corpEmail
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
@@ -202,23 +208,14 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
|
||||
}
|
||||
|
||||
func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
|
||||
appKey := idp.Config.ClientID
|
||||
appSecret := idp.Config.ClientSecret
|
||||
body := make(map[string]string)
|
||||
body["appKey"] = appKey
|
||||
body["appSecret"] = appSecret
|
||||
bodyData, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
reader := bytes.NewReader(bodyData)
|
||||
request, err := http.NewRequest("POST", "https://api.dingtalk.com/v1.0/oauth2/accessToken", reader)
|
||||
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
||||
resp, err := idp.Client.Do(request)
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
body["appKey"] = idp.Config.ClientID
|
||||
body["appSecret"] = idp.Config.ClientSecret
|
||||
respBytes, err := idp.postWithBody(body, "https://api.dingtalk.com/v1.0/oauth2/accessToken")
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
|
||||
var data struct {
|
||||
ExpireIn int `json:"expireIn"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
@@ -230,34 +227,53 @@ func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
|
||||
return data.AccessToken
|
||||
}
|
||||
|
||||
func (idp *DingTalkIdProvider) isUserInOrg(unionId string) (bool, error) {
|
||||
func (idp *DingTalkIdProvider) getUserId(unionId string, accessToken string) (string, error) {
|
||||
body := make(map[string]string)
|
||||
body["unionid"] = unionId
|
||||
bodyData, err := json.Marshal(body)
|
||||
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token="+accessToken)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
reader := bytes.NewReader(bodyData)
|
||||
accessToken := idp.getInnerAppAccessToken()
|
||||
request, _ := http.NewRequest("POST", "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token="+accessToken, reader)
|
||||
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
||||
resp, err := idp.Client.Do(request)
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
var data struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMessage string `json:"errmsg"`
|
||||
Result struct {
|
||||
UserId string `json:"userid"`
|
||||
} `json:"result"`
|
||||
}
|
||||
err = json.Unmarshal(respBytes, &data)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
return "", err
|
||||
}
|
||||
if data.ErrCode == 60121 {
|
||||
return false, fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
|
||||
return "", fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
|
||||
} else if data.ErrCode != 0 {
|
||||
return false, fmt.Errorf(data.ErrMessage)
|
||||
return "", fmt.Errorf(data.ErrMessage)
|
||||
}
|
||||
return true, nil
|
||||
return data.Result.UserId, nil
|
||||
}
|
||||
|
||||
func (idp *DingTalkIdProvider) getUserCorpEmail(userId string, accessToken string) (string, error) {
|
||||
body := make(map[string]string)
|
||||
body["userid"] = userId
|
||||
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/v2/user/get?access_token="+accessToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var data struct {
|
||||
ErrMessage string `json:"errmsg"`
|
||||
Result struct {
|
||||
Email string `json:"email"`
|
||||
} `json:"result"`
|
||||
}
|
||||
err = json.Unmarshal(respBytes, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if data.ErrMessage != "ok" {
|
||||
return "", fmt.Errorf(data.ErrMessage)
|
||||
}
|
||||
return data.Result.Email, nil
|
||||
}
|
||||
|
2
main.go
2
main.go
@@ -55,7 +55,7 @@ func main() {
|
||||
beego.SetStaticPath("/swagger", "swagger")
|
||||
beego.SetStaticPath("/files", "files")
|
||||
// https://studygolang.com/articles/2303
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||
beego.InsertFilter("*", beego.BeforeStatic, routers.StaticFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
||||
|
@@ -196,7 +196,7 @@ func checkLdapUserPassword(user *User, password string, lang string) (*User, str
|
||||
ldaps := GetLdaps(user.Owner)
|
||||
ldapLoginSuccess := false
|
||||
for _, ldapServer := range ldaps {
|
||||
conn, err := GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
||||
conn, err := ldapServer.GetLdapConn()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
@@ -33,6 +33,7 @@ type Ldap struct {
|
||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
EnableSsl bool `xorm:"bool" json:"enableSsl"`
|
||||
Admin string `xorm:"varchar(100)" json:"admin"`
|
||||
Passwd string `xorm:"varchar(100)" json:"passwd"`
|
||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||
@@ -152,20 +153,26 @@ func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
return isMicrosoft, err
|
||||
}
|
||||
|
||||
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
|
||||
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||
func (ldap *Ldap) GetLdapConn() (c *ldapConn, err error) {
|
||||
var conn *goldap.Conn
|
||||
if ldap.EnableSsl {
|
||||
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), nil)
|
||||
} else {
|
||||
conn, err = goldap.Dial("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(adminUser, adminPasswd)
|
||||
err = conn.Bind(ldap.Admin, ldap.Passwd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isAD, err := isMicrosoftAD(conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to get Ldap server type [%s]", adminUser)
|
||||
return nil, err
|
||||
}
|
||||
return &ldapConn{Conn: conn, IsAD: isAD}, nil
|
||||
}
|
||||
@@ -352,7 +359,7 @@ func UpdateLdap(ldap *Ldap) bool {
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||
"port", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
|
||||
"port", "enable_ssl", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@@ -76,7 +76,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
||||
|
||||
UpdateLdapSyncTime(ldap.Id)
|
||||
// fetch all users
|
||||
conn, err := GetLdapConn(ldap.Host, ldap.Port, ldap.Admin, ldap.Passwd)
|
||||
conn, err := ldap.GetLdapConn()
|
||||
if err != nil {
|
||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||
continue
|
||||
|
@@ -44,7 +44,7 @@ func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
||||
|
||||
if provider.Type == sender.Aliyun {
|
||||
for i, number := range phoneNumbers {
|
||||
phoneNumbers[i] = strings.TrimPrefix(number, "+")
|
||||
phoneNumbers[i] = strings.TrimPrefix(number, "+86")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -118,13 +118,12 @@ func initAPI() {
|
||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||
|
||||
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
||||
beego.Router("/api/get-ldap-users", &controllers.ApiController{}, "GET:GetLdapUsers")
|
||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "GET:GetLdaps")
|
||||
beego.Router("/api/get-ldap", &controllers.ApiController{}, "GET:GetLdap")
|
||||
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
|
||||
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
|
||||
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
|
||||
beego.Router("/api/check-ldap-users-exist", &controllers.ApiController{}, "POST:CheckLdapUsersExist")
|
||||
beego.Router("/api/sync-ldap-users", &controllers.ApiController{}, "POST:SyncLdapUsers")
|
||||
|
||||
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
|
||||
|
@@ -636,14 +636,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/check-ldap-users-exist": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Account API"
|
||||
],
|
||||
"operationId": "ApiController.CheckLdapUserExist"
|
||||
}
|
||||
},
|
||||
"/api/check-user-password": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -1349,8 +1341,8 @@
|
||||
"operationId": "ApiController.GetLdap"
|
||||
}
|
||||
},
|
||||
"/api/get-ldap-user": {
|
||||
"post": {
|
||||
"/api/get-ldap-users": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Account API"
|
||||
],
|
||||
@@ -1835,20 +1827,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-release": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"System API"
|
||||
],
|
||||
"description": "get local github repo's latest release version info",
|
||||
"operationId": "ApiController.GitRepoVersion",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "{string} local latest version hash of casdoor"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-resource": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -2340,6 +2318,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-version-info": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"System API"
|
||||
],
|
||||
"description": "get local git repo's latest release version info",
|
||||
"operationId": "ApiController.GetVersionInfo",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "{string} local latest version hash of Casdoor"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-webhook": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -3635,11 +3627,11 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"2346.0xc000278ab0.false": {
|
||||
"2268.0xc000528cf0.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
"2381.0xc000278ae0.false": {
|
||||
"2302.0xc000528d20.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -3766,10 +3758,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/2346.0xc000278ab0.false"
|
||||
"$ref": "#/definitions/2268.0xc000528cf0.false"
|
||||
},
|
||||
"data2": {
|
||||
"$ref": "#/definitions/2381.0xc000278ae0.false"
|
||||
"$ref": "#/definitions/2302.0xc000528d20.false"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
|
@@ -412,11 +412,6 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/check-ldap-users-exist:
|
||||
post:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.CheckLdapUserExist
|
||||
/api/check-user-password:
|
||||
post:
|
||||
tags:
|
||||
@@ -875,8 +870,8 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.GetLdap
|
||||
/api/get-ldap-user:
|
||||
post:
|
||||
/api/get-ldap-users:
|
||||
get:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.GetLdapser
|
||||
@@ -1193,15 +1188,6 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Record'
|
||||
/api/get-release:
|
||||
get:
|
||||
tags:
|
||||
- System API
|
||||
description: get local github repo's latest release version info
|
||||
operationId: ApiController.GitRepoVersion
|
||||
responses:
|
||||
"200":
|
||||
description: '{string} local latest version hash of casdoor'
|
||||
/api/get-resource:
|
||||
get:
|
||||
tags:
|
||||
@@ -1525,6 +1511,15 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.User'
|
||||
/api/get-version-info:
|
||||
get:
|
||||
tags:
|
||||
- System API
|
||||
description: get local git repo's latest release version info
|
||||
operationId: ApiController.GetVersionInfo
|
||||
responses:
|
||||
"200":
|
||||
description: '{string} local latest version hash of Casdoor'
|
||||
/api/get-webhook:
|
||||
get:
|
||||
tags:
|
||||
@@ -2379,10 +2374,10 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
definitions:
|
||||
2346.0xc000278ab0.false:
|
||||
2268.0xc000528cf0.false:
|
||||
title: "false"
|
||||
type: object
|
||||
2381.0xc000278ae0.false:
|
||||
2302.0xc000528d20.false:
|
||||
title: "false"
|
||||
type: object
|
||||
Response:
|
||||
@@ -2469,9 +2464,9 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/2346.0xc000278ab0.false'
|
||||
$ref: '#/definitions/2268.0xc000528cf0.false'
|
||||
data2:
|
||||
$ref: '#/definitions/2381.0xc000278ae0.false'
|
||||
$ref: '#/definitions/2302.0xc000528d20.false'
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
|
17
sync/conf.go
17
sync/conf.go
@@ -1,17 +0,0 @@
|
||||
package sync
|
||||
|
||||
var (
|
||||
host1 = "127.0.0.1"
|
||||
port1 = 3306
|
||||
username1 = "root"
|
||||
password1 = "123456"
|
||||
database1 = "db"
|
||||
)
|
||||
|
||||
var (
|
||||
host2 = "127.0.0.1"
|
||||
port2 = 3306
|
||||
username2 = "root"
|
||||
password2 = "123456"
|
||||
database2 = "db"
|
||||
)
|
100
sync/database.go
Normal file
100
sync/database.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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 sync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-mysql-org/go-mysql/canal"
|
||||
"github.com/xorm-io/xorm"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
host string
|
||||
port int
|
||||
database string
|
||||
username string
|
||||
password string
|
||||
|
||||
engine *xorm.Engine
|
||||
serverId uint32
|
||||
serverUuid string
|
||||
Gtid string
|
||||
canal.DummyEventHandler
|
||||
}
|
||||
|
||||
func newDatabase(host string, port int, database string, username string, password string) *Database {
|
||||
db := &Database{
|
||||
host: host,
|
||||
port: port,
|
||||
database: database,
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
|
||||
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username, password, host, port, database)
|
||||
engine, err := createEngine(dataSourceName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
db.engine = engine
|
||||
|
||||
db.serverId, err = getServerId(engine)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
db.serverUuid, err = getServerUuid(engine)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func (db *Database) getCanalConfig() *canal.Config {
|
||||
// config canal
|
||||
cfg := canal.NewDefaultConfig()
|
||||
cfg.Addr = fmt.Sprintf("%s:%d", db.host, db.port)
|
||||
cfg.Password = db.password
|
||||
cfg.User = db.username
|
||||
// We only care table in database1
|
||||
cfg.Dump.TableDB = db.database
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (db *Database) startCanal(targetDb *Database) error {
|
||||
canalConfig := db.getCanalConfig()
|
||||
c, err := canal.NewCanal(canalConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gtidSet, err := c.GetMasterGTIDSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Register a handler to handle RowsEvent
|
||||
c.SetEventHandler(targetDb)
|
||||
|
||||
// Start replication
|
||||
err = c.StartFromGTID(gtidSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -17,84 +17,32 @@ package sync
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-mysql-org/go-mysql/canal"
|
||||
"github.com/go-mysql-org/go-mysql/mysql"
|
||||
"github.com/go-mysql-org/go-mysql/replication"
|
||||
"github.com/siddontang/go-log/log"
|
||||
"github.com/xorm-io/xorm"
|
||||
)
|
||||
|
||||
type MyEventHandler struct {
|
||||
dataSourceName string
|
||||
engine *xorm.Engine
|
||||
serverId uint32
|
||||
serverUUID string
|
||||
GTID string
|
||||
canal.DummyEventHandler
|
||||
}
|
||||
|
||||
func StartCanal(cfg *canal.Config, username string, password string, host string, port int, database string) error {
|
||||
c, err := canal.NewCanal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
GTIDSet, err := c.GetMasterGTIDSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eventHandler := GetMyEventHandler(username, password, host, port, database)
|
||||
// Register a handler to handle RowsEvent
|
||||
c.SetEventHandler(&eventHandler)
|
||||
|
||||
// Start replication
|
||||
err = c.StartFromGTID(GTIDSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartBinlogSync() error {
|
||||
var wg sync.WaitGroup
|
||||
// init config
|
||||
cfg1 := GetCanalConfig(username1, password1, host1, port1, database1)
|
||||
cfg2 := GetCanalConfig(username2, password2, host2, port2, database2)
|
||||
|
||||
// start canal1 replication
|
||||
go StartCanal(cfg1, username2, password2, host2, port2, database2)
|
||||
wg.Add(1)
|
||||
|
||||
// start canal2 replication
|
||||
go StartCanal(cfg2, username1, password1, host1, port1, database1)
|
||||
wg.Add(1)
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MyEventHandler) OnGTID(header *replication.EventHeader, gtid mysql.GTIDSet) error {
|
||||
func (db *Database) OnGTID(header *replication.EventHeader, gtid mysql.GTIDSet) error {
|
||||
log.Info("OnGTID: ", gtid.String())
|
||||
h.GTID = gtid.String()
|
||||
db.Gtid = gtid.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MyEventHandler) onDDL(header *replication.EventHeader, nextPos mysql.Position, queryEvent *replication.QueryEvent) error {
|
||||
func (db *Database) onDDL(header *replication.EventHeader, nextPos mysql.Position, queryEvent *replication.QueryEvent) error {
|
||||
log.Info("into DDL event")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
||||
func (db *Database) OnRow(e *canal.RowsEvent) error {
|
||||
log.Info("serverId: ", e.Header.ServerID)
|
||||
if strings.Contains(h.GTID, h.serverUUID) {
|
||||
if strings.Contains(db.Gtid, db.serverUuid) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the next gtid of the target library to the gtid of the current target library to avoid loopbacks
|
||||
h.engine.Exec(fmt.Sprintf("SET GTID_NEXT= '%s'", h.GTID))
|
||||
db.engine.Exec(fmt.Sprintf("SET GTID_NEXT= '%s'", db.Gtid))
|
||||
length := len(e.Table.Columns)
|
||||
columnNames := make([]string, length)
|
||||
oldColumnValue := make([]interface{}, length)
|
||||
@@ -110,11 +58,11 @@ func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
||||
}
|
||||
}
|
||||
// get pk column name
|
||||
pkColumnNames := GetPKColumnNames(columnNames, e.Table.PKColumns)
|
||||
pkColumnNames := getPkColumnNames(columnNames, e.Table.PKColumns)
|
||||
|
||||
switch e.Action {
|
||||
case canal.UpdateAction:
|
||||
h.engine.Exec("BEGIN")
|
||||
db.engine.Exec("BEGIN")
|
||||
for i, row := range e.Rows {
|
||||
for j, item := range row {
|
||||
if i%2 == 0 {
|
||||
@@ -136,23 +84,23 @@ func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
||||
}
|
||||
}
|
||||
if i%2 == 1 {
|
||||
pkColumnValue := GetPKColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||
updateSql, args, err := GetUpdateSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue, pkColumnNames, pkColumnValue)
|
||||
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||
updateSql, args, err := getUpdateSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue, pkColumnNames, pkColumnValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := h.engine.DB().Exec(updateSql, args...)
|
||||
res, err := db.engine.DB().Exec(updateSql, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(updateSql, args, res)
|
||||
}
|
||||
}
|
||||
h.engine.Exec("COMMIT")
|
||||
h.engine.Exec("SET GTID_NEXT='automatic'")
|
||||
db.engine.Exec("COMMIT")
|
||||
db.engine.Exec("SET GTID_NEXT='automatic'")
|
||||
case canal.DeleteAction:
|
||||
h.engine.Exec("BEGIN")
|
||||
db.engine.Exec("BEGIN")
|
||||
for _, row := range e.Rows {
|
||||
for j, item := range row {
|
||||
if isChar[j] == true {
|
||||
@@ -162,22 +110,22 @@ func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
||||
}
|
||||
}
|
||||
|
||||
pkColumnValue := GetPKColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||
deleteSql, args, err := GetDeleteSql(e.Table.Schema, e.Table.Name, pkColumnNames, pkColumnValue)
|
||||
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||
deleteSql, args, err := getDeleteSql(e.Table.Schema, e.Table.Name, pkColumnNames, pkColumnValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := h.engine.DB().Exec(deleteSql, args...)
|
||||
res, err := db.engine.DB().Exec(deleteSql, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(deleteSql, args, res)
|
||||
}
|
||||
h.engine.Exec("COMMIT")
|
||||
h.engine.Exec("SET GTID_NEXT='automatic'")
|
||||
db.engine.Exec("COMMIT")
|
||||
db.engine.Exec("SET GTID_NEXT='automatic'")
|
||||
case canal.InsertAction:
|
||||
h.engine.Exec("BEGIN")
|
||||
db.engine.Exec("BEGIN")
|
||||
for _, row := range e.Rows {
|
||||
for j, item := range row {
|
||||
if isChar[j] == true {
|
||||
@@ -191,25 +139,25 @@ func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
||||
}
|
||||
}
|
||||
|
||||
insertSql, args, err := GetInsertSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue)
|
||||
insertSql, args, err := getInsertSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := h.engine.DB().Exec(insertSql, args...)
|
||||
res, err := db.engine.DB().Exec(insertSql, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(insertSql, args, res)
|
||||
}
|
||||
h.engine.Exec("COMMIT")
|
||||
h.engine.Exec("SET GTID_NEXT='automatic'")
|
||||
db.engine.Exec("COMMIT")
|
||||
db.engine.Exec("SET GTID_NEXT='automatic'")
|
||||
default:
|
||||
log.Infof("%v", e.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MyEventHandler) String() string {
|
||||
return "MyEventHandler"
|
||||
func (db *Database) String() string {
|
||||
return "Database"
|
||||
}
|
32
sync/sync.go
Normal file
32
sync/sync.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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 sync
|
||||
|
||||
import "sync"
|
||||
|
||||
func startSyncJob(db1 *Database, db2 *Database) error {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// start canal1 replication
|
||||
go db1.startCanal(db2)
|
||||
wg.Add(1)
|
||||
|
||||
// start canal2 replication
|
||||
go db2.startCanal(db1)
|
||||
wg.Add(1)
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
30
sync/sync_test.go
Normal file
30
sync/sync_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
//go:build !skipCi
|
||||
// +build !skipCi
|
||||
|
||||
package sync
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func TestStartSyncJob(t *testing.T) {
|
||||
db1 := newDatabase("127.0.0.1", 3306, "casdoor", "root", "123456")
|
||||
db2 := newDatabase("127.0.0.1", 3306, "casdoor2", "root", "123456")
|
||||
startSyncJob(db1, db2)
|
||||
}
|
@@ -19,13 +19,11 @@ import (
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-mysql-org/go-mysql/canal"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/xorm-io/xorm"
|
||||
)
|
||||
|
||||
func GetUpdateSql(schemaName string, tableName string, columnNames []string, newColumnVal []interface{}, pkColumnNames []string, pkColumnValue []interface{}) (string, []interface{}, error) {
|
||||
func getUpdateSql(schemaName string, tableName string, columnNames []string, newColumnVal []interface{}, pkColumnNames []string, pkColumnValue []interface{}) (string, []interface{}, error) {
|
||||
updateSql := squirrel.Update(schemaName + "." + tableName)
|
||||
for i, columnName := range columnNames {
|
||||
updateSql = updateSql.Set(columnName, newColumnVal[i])
|
||||
@@ -43,13 +41,13 @@ func GetUpdateSql(schemaName string, tableName string, columnNames []string, new
|
||||
return sql, args, nil
|
||||
}
|
||||
|
||||
func GetInsertSql(schemaName string, tableName string, columnNames []string, columnValue []interface{}) (string, []interface{}, error) {
|
||||
func getInsertSql(schemaName string, tableName string, columnNames []string, columnValue []interface{}) (string, []interface{}, error) {
|
||||
insertSql := squirrel.Insert(schemaName + "." + tableName).Columns(columnNames...).Values(columnValue...)
|
||||
|
||||
return insertSql.ToSql()
|
||||
}
|
||||
|
||||
func GetDeleteSql(schemaName string, tableName string, pkColumnNames []string, pkColumnValue []interface{}) (string, []interface{}, error) {
|
||||
func getDeleteSql(schemaName string, tableName string, pkColumnNames []string, pkColumnValue []interface{}) (string, []interface{}, error) {
|
||||
deleteSql := squirrel.Delete(schemaName + "." + tableName)
|
||||
|
||||
for i, columnName := range pkColumnNames {
|
||||
@@ -59,7 +57,7 @@ func GetDeleteSql(schemaName string, tableName string, pkColumnNames []string, p
|
||||
return deleteSql.ToSql()
|
||||
}
|
||||
|
||||
func CreateEngine(dataSourceName string) (*xorm.Engine, error) {
|
||||
func createEngine(dataSourceName string) (*xorm.Engine, error) {
|
||||
engine, err := xorm.NewEngine("mysql", dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -75,7 +73,7 @@ func CreateEngine(dataSourceName string) (*xorm.Engine, error) {
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
func GetServerId(engin *xorm.Engine) (uint32, error) {
|
||||
func getServerId(engin *xorm.Engine) (uint32, error) {
|
||||
res, err := engin.QueryInterface("SELECT @@server_id")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -84,16 +82,16 @@ func GetServerId(engin *xorm.Engine) (uint32, error) {
|
||||
return uint32(serverId), nil
|
||||
}
|
||||
|
||||
func GetServerUUID(engin *xorm.Engine) (string, error) {
|
||||
func getServerUuid(engin *xorm.Engine) (string, error) {
|
||||
res, err := engin.QueryString("show variables like 'server_uuid'")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
serverUUID := fmt.Sprintf("%s", res[0]["Value"])
|
||||
return serverUUID, err
|
||||
serverUuid := fmt.Sprintf("%s", res[0]["Value"])
|
||||
return serverUuid, err
|
||||
}
|
||||
|
||||
func GetPKColumnNames(columnNames []string, PKColumns []int) []string {
|
||||
func getPkColumnNames(columnNames []string, PKColumns []int) []string {
|
||||
pkColumnNames := make([]string, len(PKColumns))
|
||||
for i, index := range PKColumns {
|
||||
pkColumnNames[i] = columnNames[index]
|
||||
@@ -101,30 +99,10 @@ func GetPKColumnNames(columnNames []string, PKColumns []int) []string {
|
||||
return pkColumnNames
|
||||
}
|
||||
|
||||
func GetPKColumnValues(columnValues []interface{}, PKColumns []int) []interface{} {
|
||||
func getPkColumnValues(columnValues []interface{}, PKColumns []int) []interface{} {
|
||||
pkColumnNames := make([]interface{}, len(PKColumns))
|
||||
for i, index := range PKColumns {
|
||||
pkColumnNames[i] = columnValues[index]
|
||||
}
|
||||
return pkColumnNames
|
||||
}
|
||||
|
||||
func GetCanalConfig(username string, password string, host string, port int, database string) *canal.Config {
|
||||
// config canal
|
||||
cfg := canal.NewDefaultConfig()
|
||||
cfg.Addr = fmt.Sprintf("%s:%d", host, port)
|
||||
cfg.Password = password
|
||||
cfg.User = username
|
||||
// We only care table in database1
|
||||
cfg.Dump.TableDB = database
|
||||
return cfg
|
||||
}
|
||||
|
||||
func GetMyEventHandler(username string, password string, host string, port int, database string) MyEventHandler {
|
||||
var eventHandler MyEventHandler
|
||||
eventHandler.dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username, password, host, port, database)
|
||||
eventHandler.engine, _ = CreateEngine(eventHandler.dataSourceName)
|
||||
eventHandler.serverId, _ = GetServerId(eventHandler.engine)
|
||||
eventHandler.serverUUID, _ = GetServerUUID(eventHandler.engine)
|
||||
return eventHandler
|
||||
}
|
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||
import {EyeInvisibleOutlined, EyeTwoTone} from "@ant-design/icons";
|
||||
import * as LddpBackend from "./backend/LdapBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
@@ -83,7 +83,12 @@ class LdapEditPage extends React.Component {
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{i18next.t("ldap:Edit LDAP")}
|
||||
<Button type="primary" onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
|
||||
<Button onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitLdapEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
<Button style={{marginLeft: "20px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/sync/${this.state.organizationName}/${this.state.ldapId}`)}>
|
||||
{i18next.t("ldap:Sync")} LDAP
|
||||
</Button>
|
||||
</div>
|
||||
} style={{marginLeft: "5px"}} type="inner">
|
||||
<Row style={{marginTop: "10px"}}>
|
||||
@@ -141,6 +146,16 @@ class LdapEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Enable SSL"), i18next.t("ldap:Enable SSL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Switch checked={this.state.ldap.enableSsl} onChange={checked => {
|
||||
this.updateLdapField("enableSsl", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Base DN"), i18next.t("ldap:Base DN - Tooltip"))} :
|
||||
@@ -190,14 +205,18 @@ class LdapEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitLdapEdit() {
|
||||
submitLdapEdit(willExist) {
|
||||
LddpBackend.updateLdap(this.state.ldap)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", "Update LDAP server success");
|
||||
this.setState((prevState) => {
|
||||
prevState.ldap = res.data2;
|
||||
this.setState({
|
||||
organizationName: this.state.ldap.owner,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
this.props.history.push(`/organizations/${this.state.organizationName}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
@@ -210,25 +229,13 @@ class LdapEditPage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{width: "100%"}}>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
{
|
||||
this.state.ldap !== null ? this.renderLdap() : null
|
||||
}
|
||||
</Col>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{margin: 10}}>
|
||||
<Col span={2}>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Button type="primary" size="large"
|
||||
onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.ldap !== null ? this.renderLdap() : null
|
||||
}
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button size="large" onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitLdapEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -81,44 +81,28 @@ class LdapSyncPage extends React.Component {
|
||||
prevState.ldap = res.data;
|
||||
return prevState;
|
||||
});
|
||||
this.getLdapUser(res.data);
|
||||
this.getLdapUser();
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getLdapUser(ldap) {
|
||||
LdapBackend.getLdapUser(ldap)
|
||||
getLdapUser() {
|
||||
LdapBackend.getLdapUser(this.state.organizationName, this.state.ldapId)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState((prevState) => {
|
||||
prevState.users = res.data.users;
|
||||
prevState.existUuids = res.data2?.length > 0 ? res.data2 : [];
|
||||
return prevState;
|
||||
});
|
||||
this.getExistUsers(ldap.owner, res.data.users);
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getExistUsers(owner, users) {
|
||||
const uuidArray = [];
|
||||
users.forEach(elem => {
|
||||
uuidArray.push(elem.uuid);
|
||||
});
|
||||
LdapBackend.checkLdapUsersExist(owner, uuidArray)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState(prevState => {
|
||||
prevState.existUuids = res.data?.length > 0 ? res.data : [];
|
||||
return prevState;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
buildValArray(data, key) {
|
||||
const valTypesArray = [];
|
||||
|
||||
@@ -220,9 +204,14 @@ class LdapSyncPage extends React.Component {
|
||||
title={"Please confirm to sync selected users"}
|
||||
onConfirm={() => this.syncUsers()}
|
||||
>
|
||||
<Button type="primary" size="small"
|
||||
style={{marginLeft: "10px"}}>{i18next.t("ldap:Sync")}</Button>
|
||||
<Button type="primary" style={{marginLeft: "10px"}}>
|
||||
{i18next.t("ldap:Sync")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<Button style={{marginLeft: "20px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
|
||||
{i18next.t("general:Edit")} LDAP
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={users === null}
|
||||
@@ -234,17 +223,20 @@ class LdapSyncPage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{width: "100%"}}>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
<Row style={{width: "100%", justifyContent: "center"}}>
|
||||
<Col span={22}>
|
||||
{
|
||||
this.renderTable(this.state.users)
|
||||
}
|
||||
</Col>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
</Row>
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => {
|
||||
this.props.history.push(`/organizations/${this.state.organizationName}`);
|
||||
}}>
|
||||
{i18next.t("general:Save & Exit")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -58,14 +58,14 @@ class LdapTable extends React.Component {
|
||||
LdapBackend.addLdap(newLdap)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", "Add LDAP server success");
|
||||
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||
if (table === undefined) {
|
||||
table = [];
|
||||
}
|
||||
table = Setting.addRow(table, res.data2);
|
||||
this.updateTable(table);
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -78,14 +78,13 @@ class LdapTable extends React.Component {
|
||||
LdapBackend.deleteLdap(table[i])
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", "Delete LDAP server success");
|
||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||
table = Setting.deleteRow(table, i);
|
||||
this.updateTable(table);
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Delete LDAP server failed: ${error}`);
|
||||
});
|
||||
@@ -153,11 +152,14 @@ class LdapTable extends React.Component {
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary"
|
||||
onClick={() => Setting.goToLink(`/ldap/sync/${record.owner}/${record.id}`)}>
|
||||
{i18next.t("ldap:Sync")}
|
||||
</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||
type="primary"
|
||||
onClick={() => Setting.goToLink(`/ldap/sync/${record.owner}/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/${record.owner}/${record.id}`)}>{i18next.t("general:Edit")}</Button>
|
||||
onClick={() => Setting.goToLink(`/ldap/${record.owner}/${record.id}`)}>
|
||||
{i18next.t("general:Edit")}
|
||||
</Button>
|
||||
<PopconfirmModal
|
||||
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
|
||||
onConfirm={() => this.deleteRow(table, index)}
|
||||
|
@@ -384,7 +384,7 @@ class UserListPage extends BaseListPage {
|
||||
const field = params.searchedColumn, value = params.searchText;
|
||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
this.setState({loading: true});
|
||||
if (this.state.organizationName === undefined) {
|
||||
if (this.props.match.params.organizationName === undefined) {
|
||||
(Setting.isAdminUser(this.props.account) ? UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) : UserBackend.getUsers(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
@@ -413,7 +413,7 @@ class UserListPage extends BaseListPage {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
UserBackend.getUsers(this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
UserBackend.getUsers(this.props.match.params.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
|
@@ -46,7 +46,6 @@ class LoginPage extends React.Component {
|
||||
username: null,
|
||||
validEmailOrPhone: false,
|
||||
validEmail: false,
|
||||
validPhone: false,
|
||||
loginMethod: "password",
|
||||
enableCaptchaModal: false,
|
||||
openCaptchaModal: false,
|
||||
@@ -427,16 +426,15 @@ class LoginPage extends React.Component {
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (this.state.loginMethod === "verificationCode") {
|
||||
if (!Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
|
||||
if (!Setting.isValidEmail(value) && !Setting.isValidPhone(value)) {
|
||||
this.setState({validEmailOrPhone: false});
|
||||
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
|
||||
}
|
||||
|
||||
if (Setting.isValidPhone(this.state.username)) {
|
||||
this.setState({validPhone: true});
|
||||
}
|
||||
if (Setting.isValidEmail(this.state.username)) {
|
||||
if (Setting.isValidEmail(value)) {
|
||||
this.setState({validEmail: true});
|
||||
} else {
|
||||
this.setState({validEmail: false});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -67,11 +67,10 @@ export function updateLdap(body) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getLdapUser(body) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-ldap-user`, {
|
||||
method: "POST",
|
||||
export function getLdapUser(owner, name) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-ldap-users?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
@@ -88,14 +87,3 @@ export function syncUsers(owner, ldapId, body) {
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function checkLdapUsersExist(owner, body) {
|
||||
return fetch(`${Setting.ServerUrl}/api/check-ldap-users-exist?owner=${owner}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ export const SendCodeInput = (props) => {
|
||||
onChange={e => onChange(e.target.value)}
|
||||
enterButton={
|
||||
<Button style={{fontSize: 14}} type={"primary"} disabled={disabled || buttonLeftTime > 0} loading={buttonLoading}>
|
||||
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending Code") : i18next.t("code:Send Code")}
|
||||
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending") : i18next.t("code:Send Code")}
|
||||
</Button>
|
||||
}
|
||||
onSearch={() => setVisible(true)}
|
||||
|
@@ -121,7 +121,7 @@
|
||||
"Please input your phone verification code!": "Bitte geben Sie Ihren Telefon-Verifizierungscode ein!",
|
||||
"Please input your verification code!": "Bitte geben Sie Ihren Bestätigungscode ein!",
|
||||
"Send Code": "Code senden",
|
||||
"Sending Code": "Code wird gesendet",
|
||||
"Sending": "Code wird gesendet",
|
||||
"Submit and complete": "Absenden und abschließen"
|
||||
},
|
||||
"forget": {
|
||||
@@ -288,6 +288,8 @@
|
||||
"CN": "KN",
|
||||
"Edit LDAP": "LDAP bearbeiten",
|
||||
"Email": "E-Mail",
|
||||
"Enable SSL": "Enable SSL",
|
||||
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||
"Group Id": "Gruppen Id",
|
||||
"ID": "ID",
|
||||
"Last Sync": "Letzter Sync",
|
||||
|
@@ -121,7 +121,7 @@
|
||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Send Code": "Send Code",
|
||||
"Sending Code": "Sending Code",
|
||||
"Sending": "Sending",
|
||||
"Submit and complete": "Submit and complete"
|
||||
},
|
||||
"forget": {
|
||||
@@ -288,6 +288,8 @@
|
||||
"CN": "CN",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Email": "Email",
|
||||
"Enable SSL": "Enable SSL",
|
||||
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||
"Group Id": "Group Id",
|
||||
"ID": "ID",
|
||||
"Last Sync": "Last Sync",
|
||||
|
@@ -121,7 +121,7 @@
|
||||
"Please input your phone verification code!": "Ingrese su código de verificación teléfonico!",
|
||||
"Please input your verification code!": "Ingrese su código de verificación!",
|
||||
"Send Code": "Enviar código",
|
||||
"Sending Code": "Enviando código",
|
||||
"Sending": "Enviando código",
|
||||
"Submit and complete": "Enviar y completar"
|
||||
},
|
||||
"forget": {
|
||||
@@ -288,6 +288,8 @@
|
||||
"CN": "CN",
|
||||
"Edit LDAP": "Editar LDAP",
|
||||
"Email": "Email",
|
||||
"Enable SSL": "Enable SSL",
|
||||
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||
"Group Id": "Group Id",
|
||||
"ID": "ID",
|
||||
"Last Sync": "Última Sincronización",
|
||||
|
@@ -121,7 +121,7 @@
|
||||
"Please input your phone verification code!": "Veuillez entrer le code de vérification de votre téléphone !",
|
||||
"Please input your verification code!": "Veuillez entrer votre code de vérification !",
|
||||
"Send Code": "Envoyer le code",
|
||||
"Sending Code": "Code d'envoi",
|
||||
"Sending": "Code d'envoi",
|
||||
"Submit and complete": "Soumettre et compléter"
|
||||
},
|
||||
"forget": {
|
||||
@@ -288,6 +288,8 @@
|
||||
"CN": "CN",
|
||||
"Edit LDAP": "Modifier LDAP",
|
||||
"Email": "Courriel",
|
||||
"Enable SSL": "Enable SSL",
|
||||
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||
"Group Id": "Identifiant du groupe",
|
||||
"ID": "ID",
|
||||
"Last Sync": "Dernière synchronisation",
|
||||
|
@@ -121,7 +121,7 @@
|
||||
"Please input your phone verification code!": "電話番号を入力してください!",
|
||||
"Please input your verification code!": "認証コードを入力してください!",
|
||||
"Send Code": "コードを送信",
|
||||
"Sending Code": "コードを送信中",
|
||||
"Sending": "コードを送信中",
|
||||
"Submit and complete": "提出して完了"
|
||||
},
|
||||
"forget": {
|
||||
@@ -288,6 +288,8 @@
|
||||
"CN": "CN",
|
||||
"Edit LDAP": "LDAP を編集",
|
||||
"Email": "Eメールアドレス",
|
||||
"Enable SSL": "Enable SSL",
|
||||
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||
"Group Id": "グループ ID",
|
||||
"ID": "ID",
|
||||
"Last Sync": "前回の同期",
|
||||
|
@@ -121,7 +121,7 @@
|
||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Send Code": "Send Code",
|
||||
"Sending Code": "Sending Code",
|
||||
"Sending": "Sending",
|
||||
"Submit and complete": "Submit and complete"
|
||||
},
|
||||
"forget": {
|
||||
@@ -288,6 +288,8 @@
|
||||
"CN": "CN",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Email": "Email",
|
||||
"Enable SSL": "Enable SSL",
|
||||
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||
"Group Id": "Group Id",
|
||||
"ID": "ID",
|
||||
"Last Sync": "Last Sync",
|
||||
|
@@ -121,7 +121,7 @@
|
||||
"Please input your phone verification code!": "Пожалуйста, введите код подтверждения!",
|
||||
"Please input your verification code!": "Пожалуйста, введите код подтверждения!",
|
||||
"Send Code": "Отправить код",
|
||||
"Sending Code": "Отправка кода",
|
||||
"Sending": "Отправка кода",
|
||||
"Submit and complete": "Отправить и завершить"
|
||||
},
|
||||
"forget": {
|
||||
@@ -288,6 +288,8 @@
|
||||
"CN": "КНР",
|
||||
"Edit LDAP": "Редактировать LDAP",
|
||||
"Email": "Почта",
|
||||
"Enable SSL": "Enable SSL",
|
||||
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||
"Group Id": "ID группы",
|
||||
"ID": "ID",
|
||||
"Last Sync": "Последняя синхронизация",
|
||||
|
@@ -121,7 +121,7 @@
|
||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Send Code": "Send Code",
|
||||
"Sending Code": "Sending Code",
|
||||
"Sending": "Sending",
|
||||
"Submit and complete": "Submit and complete"
|
||||
},
|
||||
"forget": {
|
||||
@@ -288,6 +288,8 @@
|
||||
"CN": "CN",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Email": "Email",
|
||||
"Enable SSL": "Enable SSL",
|
||||
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||
"Group Id": "Group Id",
|
||||
"ID": "ID",
|
||||
"Last Sync": "Last Sync",
|
||||
|
@@ -121,7 +121,7 @@
|
||||
"Please input your phone verification code!": "请输入您的手机验证码!",
|
||||
"Please input your verification code!": "请输入您的验证码!",
|
||||
"Send Code": "发送验证码",
|
||||
"Sending Code": "发送中",
|
||||
"Sending": "发送中",
|
||||
"Submit and complete": "完成提交"
|
||||
},
|
||||
"forget": {
|
||||
@@ -288,6 +288,8 @@
|
||||
"CN": "CN",
|
||||
"Edit LDAP": "编辑LDAP",
|
||||
"Email": "电子邮件",
|
||||
"Enable SSL": "启用 SSL",
|
||||
"Enable SSL - Tooltip": "启用 SSL",
|
||||
"Group Id": "组ID",
|
||||
"ID": "ID",
|
||||
"Last Sync": "最近同步",
|
||||
|
Reference in New Issue
Block a user