mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-08 00:50:28 +08:00
Compare commits
40 Commits
v1.952.0
...
translatio
Author | SHA1 | Date | |
---|---|---|---|
eeda5e5629 | |||
bce606896f | |||
4ff4961312 | |||
b2dd8f4fa3 | |||
ae9ebd2de1 | |||
441d69f4ac | |||
1550956c8e | |||
0967217778 | |||
3820a0185c | |||
3905df8546 | |||
e16ff7f4a9 | |||
bbec117fd6 | |||
8ae4e30620 | |||
814ab9c11f | |||
78c5757d85 | |||
ee92a9b7b4 | |||
21eb1e8037 | |||
2297251dd7 | |||
532dc75033 | |||
e7de0e4132 | |||
7e6af1e858 | |||
cca6a635c3 | |||
a355798a79 | |||
14445e7c3b | |||
aae09648e9 | |||
a95b168a54 | |||
834693a0a2 | |||
7b1386764c | |||
b7077c61be | |||
4c27ae68fb | |||
1576c01d8f | |||
968eccf193 | |||
8eb5a7b163 | |||
4a6ec33b9c | |||
f011dc06d8 | |||
d409de6591 | |||
c9b6bc79a2 | |||
dd4f197454 | |||
576d0f12dd | |||
c37e5d044a |
1
.github/workflows/sync.yml
vendored
1
.github/workflows/sync.yml
vendored
@ -35,3 +35,4 @@ jobs:
|
||||
with:
|
||||
commit_message: Sync from crowdin
|
||||
file_pattern: web/src/locales/*
|
||||
commit_author: ${{ secrets.CROWDIN_BOT }}
|
12
README.md
12
README.md
@ -47,6 +47,18 @@ Backend | RESTful API backend for Casdoor | Golang + Beego + MySQL | https://git
|
||||
git clone https://github.com/casbin/casdoor
|
||||
```
|
||||
|
||||
## Run through Docker
|
||||
- Install Docker and Docker-compose,you see [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/)
|
||||
- vi casdoor/conf/app.conf
|
||||
- Modify dataSourceName = root:123@tcp(localhost:3306)/ to dataSourceName = root:123@tcp(db:3306)/
|
||||
- Execute the following command
|
||||
```shell
|
||||
docker-compose up
|
||||
```
|
||||
- Open browser:
|
||||
|
||||
http://localhost:8000/
|
||||
|
||||
## Run (Dev Environment)
|
||||
|
||||
- Run backend (in port 8000):
|
||||
|
@ -82,7 +82,7 @@ p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-users, *, *
|
||||
p, *, *, GET, /api/get-user, *, *
|
||||
p, *, *, GET, /api/get-organizations, *, *
|
||||
p, *, *, GET, /api/get-default-application, *, *
|
||||
p, *, *, GET, /api/get-user-application, *, *
|
||||
p, *, *, GET, /api/get-default-providers, *, *
|
||||
p, *, *, POST, /api/upload-avatar, *, *
|
||||
p, *, *, POST, /api/unlink, *, *
|
||||
|
@ -52,6 +52,8 @@ type RequestForm struct {
|
||||
EmailCode string `json:"emailCode"`
|
||||
PhoneCode string `json:"phoneCode"`
|
||||
PhonePrefix string `json:"phonePrefix"`
|
||||
|
||||
AutoSignin bool `json:"autoSignin"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
@ -78,8 +80,8 @@ type HumanCheck struct {
|
||||
func (c *ApiController) Signup() {
|
||||
var resp Response
|
||||
|
||||
if c.GetSessionUser() != "" {
|
||||
c.ResponseErrorWithData("Please sign out first before signing up", c.GetSessionUser())
|
||||
if c.GetSessionUsername() != "" {
|
||||
c.ResponseErrorWithData("Please sign out first before signing up", c.GetSessionUsername())
|
||||
return
|
||||
}
|
||||
|
||||
@ -161,7 +163,7 @@ func (c *ApiController) Signup() {
|
||||
|
||||
if application.HasPromptPage() {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUser(user.GetId())
|
||||
c.SetSessionUsername(user.GetId())
|
||||
}
|
||||
|
||||
object.DisableVerificationCode(form.Email)
|
||||
@ -181,10 +183,11 @@ func (c *ApiController) Signup() {
|
||||
func (c *ApiController) Logout() {
|
||||
var resp Response
|
||||
|
||||
user := c.GetSessionUser()
|
||||
user := c.GetSessionUsername()
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
c.SetSessionUser("")
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
|
||||
resp = Response{Status: "ok", Msg: "", Data: user}
|
||||
|
||||
|
@ -44,23 +44,19 @@ func (c *ApiController) GetApplication() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetDefaultApplication
|
||||
// @Description get the detail of the default application
|
||||
// @Param owner query string true "The owner of the application."
|
||||
// @Title GetUserApplication
|
||||
// @Description get the detail of the user's application
|
||||
// @Param id query string true "The id of the user"
|
||||
// @Success 200 {object} object.Application The Response object
|
||||
// @router /get-default-application [get]
|
||||
func (c *ApiController) GetDefaultApplication() {
|
||||
//owner := c.Input().Get("owner")
|
||||
|
||||
if c.GetSessionUser() == "" {
|
||||
c.Data["json"] = nil
|
||||
c.ServeJSON()
|
||||
// @router /get-user-application [get]
|
||||
func (c *ApiController) GetUserApplication() {
|
||||
id := c.Input().Get("id")
|
||||
user := object.GetUser(id)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
username := c.GetSessionUser()
|
||||
user := object.GetUser(username)
|
||||
|
||||
c.Data["json"] = object.GetApplicationByUser(user)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
@ -38,7 +39,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
userId := user.GetId()
|
||||
resp := &Response{}
|
||||
if form.Type == ResponseTypeLogin {
|
||||
c.SetSessionUser(userId)
|
||||
c.SetSessionUsername(userId)
|
||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||
resp = &Response{Status: "ok", Msg: "", Data: userId}
|
||||
} else if form.Type == ResponseTypeCode {
|
||||
@ -53,11 +54,21 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
|
||||
if application.HasPromptPage() {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUser(userId)
|
||||
c.SetSessionUsername(userId)
|
||||
}
|
||||
} else {
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
|
||||
}
|
||||
|
||||
// if user did not check auto signin
|
||||
if resp.Status == "ok" && !form.AutoSignin {
|
||||
timestamp := time.Now().Unix()
|
||||
timestamp += 3600 * 24
|
||||
c.SetSessionData(&SessionData{
|
||||
ExpireTime: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
@ -108,8 +119,8 @@ func (c *ApiController) Login() {
|
||||
|
||||
if form.Username != "" {
|
||||
if form.Type == ResponseTypeLogin {
|
||||
if c.GetSessionUser() != "" {
|
||||
resp = &Response{Status: "error", Msg: "Please log out first before signing in", Data: c.GetSessionUser()}
|
||||
if c.GetSessionUsername() != "" {
|
||||
resp = &Response{Status: "error", Msg: "Please log out first before signing in", Data: c.GetSessionUsername()}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
@ -181,6 +192,12 @@ func (c *ApiController) Login() {
|
||||
} else {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
|
||||
record := util.Records(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.Username = user.Name
|
||||
|
||||
object.AddRecord(record)
|
||||
}
|
||||
} else if form.Provider != "" {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
@ -252,6 +269,12 @@ func (c *ApiController) Login() {
|
||||
//}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
|
||||
record := util.Records(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.Username = user.Name
|
||||
|
||||
object.AddRecord(record)
|
||||
} else {
|
||||
// Sign up via OAuth
|
||||
if !application.EnableSignUp {
|
||||
@ -294,10 +317,16 @@ func (c *ApiController) Login() {
|
||||
object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
|
||||
record := util.Records(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.Username = user.Name
|
||||
|
||||
object.AddRecord(record)
|
||||
}
|
||||
//resp = &Response{Status: "ok", Msg: "", Data: res}
|
||||
} else { // form.Method != "signup"
|
||||
userId := c.GetSessionUser()
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
resp = &Response{Status: "error", Msg: "The account does not exist", Data: userInfo}
|
||||
c.Data["json"] = resp
|
||||
|
@ -14,13 +14,32 @@
|
||||
|
||||
package controllers
|
||||
|
||||
import "github.com/astaxie/beego"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type ApiController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionUser() string {
|
||||
type SessionData struct {
|
||||
ExpireTime int64
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionUsername() string {
|
||||
// check if user session expired
|
||||
sessionData := c.GetSessionData()
|
||||
if sessionData != nil &&
|
||||
sessionData.ExpireTime != 0 &&
|
||||
sessionData.ExpireTime < time.Now().Unix() {
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
return ""
|
||||
}
|
||||
|
||||
user := c.GetSession("username")
|
||||
if user == nil {
|
||||
return ""
|
||||
@ -29,10 +48,34 @@ func (c *ApiController) GetSessionUser() string {
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
func (c *ApiController) SetSessionUser(user string) {
|
||||
func (c *ApiController) SetSessionUsername(user string) {
|
||||
c.SetSession("username", user)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionData() *SessionData {
|
||||
session := c.GetSession("SessionData")
|
||||
if session == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sessionData := &SessionData{}
|
||||
err := util.JsonToStruct(session.(string), sessionData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sessionData
|
||||
}
|
||||
|
||||
func (c *ApiController) SetSessionData(s *SessionData) {
|
||||
if s == nil {
|
||||
c.DelSession("SessionData")
|
||||
return
|
||||
}
|
||||
|
||||
c.SetSession("SessionData", util.StructToJson(s))
|
||||
}
|
||||
|
||||
func wrapActionResponse(affected bool) *Response {
|
||||
if affected {
|
||||
return &Response{Status: "ok", Msg: "", Data: "Affected"}
|
||||
|
209
controllers/ldap.go
Normal file
209
controllers/ldap.go
Normal file
@ -0,0 +1,209 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
"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"`
|
||||
}
|
||||
|
||||
//type LdapRespGroup struct {
|
||||
// GroupId string
|
||||
// GroupName string
|
||||
//}
|
||||
|
||||
type LdapSyncResp struct {
|
||||
Exist []object.LdapRespUser `json:"exist"`
|
||||
Failed []object.LdapRespUser `json:"failed"`
|
||||
}
|
||||
|
||||
func (c *ApiController) GetLdapUser() {
|
||||
ldapServer := LdapServer{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
|
||||
if err != nil || util.IsStrsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
var resp LdapResp
|
||||
|
||||
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
||||
if err != nil {
|
||||
c.Data["json"] = Response{Status: "error", Msg: err.Error()}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||
//if err != nil {
|
||||
// c.Data["json"] = Response{Status: "error", Msg: err.Error()}
|
||||
// c.ServeJSON()
|
||||
// return
|
||||
//}
|
||||
|
||||
//for _, group := range groupsMap {
|
||||
// resp.Groups = append(resp.Groups, LdapRespGroup{
|
||||
// GroupId: group.GidNumber,
|
||||
// GroupName: group.Cn,
|
||||
// })
|
||||
//}
|
||||
|
||||
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
|
||||
if err != nil {
|
||||
c.Data["json"] = Response{Status: "error", Msg: err.Error()}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
resp.Users = append(resp.Users, object.LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
//GroupName: groupsMap[user.GidNumber].Cn,
|
||||
Uuid: user.Uuid,
|
||||
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
||||
})
|
||||
}
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Data: resp}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ApiController) GetLdaps() {
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdaps(owner)}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetLdap() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
if util.IsStrsEmpty(id) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdap(id)}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) AddLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if object.CheckLdapExist(&ldap) {
|
||||
c.ResponseError("Ldap server exist")
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.AddLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) UpdateLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil || util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.UpdateLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) DeleteLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteLdap(&ldap))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) SyncLdapUsers() {
|
||||
owner := c.Input().Get("owner")
|
||||
ldapId := c.Input().Get("ldapId")
|
||||
var users []object.LdapRespUser
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
object.UpdateLdapSyncTime(ldapId)
|
||||
|
||||
exist, failed := object.SyncLdapUsers(owner, users)
|
||||
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
||||
Exist: *exist,
|
||||
Failed: *failed,
|
||||
}}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) CheckLdapUsersExist() {
|
||||
owner := c.Input().Get("owner")
|
||||
var uuids []string
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &uuids)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exist := object.CheckLdapUuidExist(owner, uuids)
|
||||
c.Data["json"] = &Response{Status: "ok", Data: exist}
|
||||
c.ServeJSON()
|
||||
}
|
46
controllers/record.go
Normal file
46
controllers/record.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
)
|
||||
|
||||
// @Title GetRecords
|
||||
// @Description get all records
|
||||
// @Success 200 {array} object.Records The Response object
|
||||
// @router /get-records [get]
|
||||
func (c *ApiController) GetRecords() {
|
||||
c.Data["json"] = object.GetRecords()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetRecordsByFilter
|
||||
// @Description get records by filter
|
||||
// @Param body body object.Records true "filter Record message"
|
||||
// @Success 200 {array} object.Records The Response object
|
||||
// @router /get-records-filter [post]
|
||||
func (c *ApiController) GetRecordsByFilter() {
|
||||
var record object.Records
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetRecordsByField(&record)
|
||||
c.ServeJSON()
|
||||
}
|
@ -170,7 +170,7 @@ func (c *ApiController) SetPassword() {
|
||||
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||
|
||||
requestUserId := c.GetSessionUser()
|
||||
requestUserId := c.GetSessionUsername()
|
||||
if requestUserId == "" {
|
||||
c.ResponseError("Please login first.")
|
||||
return
|
||||
@ -223,7 +223,7 @@ func (c *ApiController) SetPassword() {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetSessionUser("")
|
||||
c.SetSessionUsername("")
|
||||
|
||||
targetUser.Password = newPassword
|
||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||
|
@ -60,7 +60,7 @@ func (c *ApiController) ResponseErrorWithData(error string, data interface{}) {
|
||||
}
|
||||
|
||||
func (c *ApiController) RequireSignedIn() (string, bool) {
|
||||
userId := c.GetSessionUser()
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
resp := Response{Status: "error", Msg: "Please sign in first"}
|
||||
c.Data["json"] = resp
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
userId := c.GetSessionUser()
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
user = nil
|
||||
} else {
|
||||
|
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
@ -0,0 +1,19 @@
|
||||
version: '3.1'
|
||||
services:
|
||||
casdoor:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: go-dockerfile
|
||||
ports:
|
||||
- 8000:8000
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
restart: always
|
||||
image: mysql:8.0.25
|
||||
ports:
|
||||
- 3306:3306
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 123
|
||||
volumes:
|
||||
- /usr/local/docker/mysql:/var/lib/mysql
|
25
go-dockerfile
Normal file
25
go-dockerfile
Normal file
@ -0,0 +1,25 @@
|
||||
FROM golang:1.17-rc-buster
|
||||
WORKDIR /casdoor
|
||||
COPY ./ /casdoor
|
||||
RUN go env -w CGO_ENABLED=0 GOPROXY=https://goproxy.io,direct GOOS=linux GOARCH=amd64 \
|
||||
&& apt update && apt install sudo \
|
||||
&& wget https://nodejs.org/dist/v12.22.0/node-v12.22.0-linux-x64.tar.gz \
|
||||
&& sudo tar xf node-v12.22.0-linux-x64.tar.gz \
|
||||
&& sudo apt install wait-for-it
|
||||
ENV PATH=$PATH:/casdoor/node-v12.22.0-linux-x64/bin
|
||||
RUN npm install -g yarn \
|
||||
&& cd web \
|
||||
&& yarn install \
|
||||
&& yarn run build \
|
||||
&& rm -rf node_modules \
|
||||
&& cd /casdoor \
|
||||
&& go build main.go
|
||||
FROM alpine:3.7
|
||||
COPY --from=0 /casdoor /
|
||||
COPY --from=0 /usr/bin/wait-for-it /
|
||||
RUN set -eux \
|
||||
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
|
||||
&& apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add bash
|
||||
CMD ./wait-for-it db:3306 && ./main
|
1
go.mod
1
go.mod
@ -13,6 +13,7 @@ require (
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/jinzhu/configor v1.2.1 // indirect
|
||||
|
7
go.sum
7
go.sum
@ -1,6 +1,8 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
@ -63,10 +65,14 @@ github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
@ -220,6 +226,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
331
idp/linkedin.go
Normal file
331
idp/linkedin.go
Normal file
@ -0,0 +1,331 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type LinkedInIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewLinkedInIdProvider(clientId string, clientSecret string, redirectUrl string) *LinkedInIdProvider {
|
||||
idp := &LinkedInIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *LinkedInIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *LinkedInIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://www.linkedIn.com/oauth/v2/accessToken",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"email,public_profile"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type LinkedInAccessToken struct {
|
||||
AccessToken string `json:"access_token"` //Interface call credentials
|
||||
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://docs.microsoft.com/en-us/linkedIn/shared/authentication/authorization-code-flow?context=linkedIn%2Fcontext&tabs=HTTPS
|
||||
func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
|
||||
bs, _ := json.Marshal(params.Encode())
|
||||
req, _ := http.NewRequest("POST", accessTokenUrl, strings.NewReader(string(bs)))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rbs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenResp := LinkedInAccessToken{}
|
||||
if err = json.Unmarshal(rbs, &tokenResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: tokenResp.AccessToken,
|
||||
TokenType: "Bearer",
|
||||
Expiry: time.Unix(time.Now().Unix()+tokenResp.ExpiresIn, 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"firstName": {
|
||||
"localized": {
|
||||
"zh_CN": "继坤"
|
||||
},
|
||||
"preferredLocale": {
|
||||
"country": "CN",
|
||||
"language": "zh"
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"localized": {
|
||||
"zh_CN": "刘"
|
||||
},
|
||||
"preferredLocale": {
|
||||
"country": "CN",
|
||||
"language": "zh"
|
||||
}
|
||||
},
|
||||
"profilePicture": {
|
||||
"displayImage": "urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg",
|
||||
"displayImage~": {
|
||||
"paging": {
|
||||
"count": 10,
|
||||
"start": 0,
|
||||
"links": []
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"artifact": "urn:li:digitalmediaMediaArtifact:(urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg,urn:li:digitalmediaMediaArtifactClass:profile-displayphoto-shrink_100_100)",
|
||||
"authorizationMethod": "PUBLIC",
|
||||
"data": {
|
||||
"com.linkedin.digitalmedia.mediaartifact.StillImage": {
|
||||
"mediaType": "image/jpeg",
|
||||
"rawCodecSpec": {
|
||||
"name": "jpeg",
|
||||
"type": "image"
|
||||
},
|
||||
"displaySize": {
|
||||
"width": 100.0,
|
||||
"uom": "PX",
|
||||
"height": 100.0
|
||||
},
|
||||
"storageSize": {
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"storageAspectRatio": {
|
||||
"widthAspect": 1.0,
|
||||
"heightAspect": 1.0,
|
||||
"formatted": "1.00:1.00"
|
||||
},
|
||||
"displayAspectRatio": {
|
||||
"widthAspect": 1.0,
|
||||
"heightAspect": 1.0,
|
||||
"formatted": "1.00:1.00"
|
||||
}
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"identifier": "https://media.licdn.cn/dms/image/C5603AQHbdR8RkG62yg/profile-displayphoto-shrink_100_100/0/1625279434135?e=1630540800&v=beta&t=Z-bQKf_jFv8L1uwr6X5AJLoTQRWZrueT7qrITDSvxWM",
|
||||
"index": 0,
|
||||
"mediaType": "image/jpeg",
|
||||
"file": "urn:li:digitalmediaFile:(urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg,urn:li:digitalmediaMediaArtifactClass:profile-displayphoto-shrink_100_100,0)",
|
||||
"identifierType": "EXTERNAL_URL",
|
||||
"identifierExpiresInSeconds": 1630540800
|
||||
}
|
||||
]
|
||||
},
|
||||
// ...
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "vvMfLsLIRs"
|
||||
}
|
||||
*/
|
||||
|
||||
type LinkedInUserInfo struct {
|
||||
FirstName struct {
|
||||
Localized map[string]string `json:"localized"`
|
||||
PreferredLocale struct {
|
||||
Country string `json:"country"`
|
||||
Language string `json:"language"`
|
||||
} `json:"preferredLocale"`
|
||||
} `json:"firstName"`
|
||||
LastName struct {
|
||||
Localized map[string]string `json:"localized"`
|
||||
PreferredLocale struct {
|
||||
Country string `json:"country"`
|
||||
Language string `json:"language"`
|
||||
} `json:"preferredLocale"`
|
||||
} `json:"lastName"`
|
||||
ProfilePicture struct {
|
||||
DisplayImage string `json:"displayImage"`
|
||||
DisplayImage1 struct {
|
||||
Paging struct {
|
||||
Count int `json:"count"`
|
||||
Start int `json:"start"`
|
||||
Links []interface{} `json:"links"`
|
||||
} `json:"paging"`
|
||||
Elements []struct {
|
||||
Artifact string `json:"artifact"`
|
||||
AuthorizationMethod string `json:"authorizationMethod"`
|
||||
Data struct {
|
||||
ComLinkedinDigitalmediaMediaartifactStillImage struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
RawCodecSpec struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
} `json:"rawCodecSpec"`
|
||||
DisplaySize struct {
|
||||
Width float64 `json:"width"`
|
||||
Uom string `json:"uom"`
|
||||
Height float64 `json:"height"`
|
||||
} `json:"displaySize"`
|
||||
StorageSize struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
} `json:"storageSize"`
|
||||
StorageAspectRatio struct {
|
||||
WidthAspect float64 `json:"widthAspect"`
|
||||
HeightAspect float64 `json:"heightAspect"`
|
||||
Formatted string `json:"formatted"`
|
||||
} `json:"storageAspectRatio"`
|
||||
DisplayAspectRatio struct {
|
||||
WidthAspect float64 `json:"widthAspect"`
|
||||
HeightAspect float64 `json:"heightAspect"`
|
||||
Formatted string `json:"formatted"`
|
||||
} `json:"displayAspectRatio"`
|
||||
} `json:"com.linkedin.digitalmedia.mediaartifact.StillImage"`
|
||||
} `json:"data"`
|
||||
Identifiers []struct {
|
||||
Identifier string `json:"identifier"`
|
||||
Index int `json:"index"`
|
||||
MediaType string `json:"mediaType"`
|
||||
File string `json:"file"`
|
||||
IdentifierType string `json:"identifierType"`
|
||||
IdentifierExpiresInSeconds int `json:"identifierExpiresInSeconds"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"elements"`
|
||||
} `json:"displayImage~"`
|
||||
} `json:"profilePicture"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"handle": "urn:li:emailAddress:3775708763",
|
||||
"handle~": {
|
||||
"emailAddress": "hsimpson@linkedin.com"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
type LinkedInUserEmail struct {
|
||||
Elements []struct {
|
||||
Handle struct {
|
||||
EmailAddress string `json:"emailAddress"`
|
||||
} `json:"handle~"`
|
||||
Handle1 string `json:"handle"`
|
||||
} `json:"elements"`
|
||||
}
|
||||
|
||||
// GetUserInfo use LinkedInAccessToken gotten before return LinkedInUserInfo
|
||||
// get more detail via: https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context
|
||||
func (idp *LinkedInIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var linkedInUserInfo LinkedInUserInfo
|
||||
bs, err := idp.GetUrlRespWithAuthorization("https://api.linkedIn.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))", token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(bs, &linkedInUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var linkedInUserEmail LinkedInUserEmail
|
||||
bs, err = idp.GetUrlRespWithAuthorization("https://api.linkedIn.com/v2/emailAddress?q=members&projection=(elements*(handle~))", token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(bs, &linkedInUserEmail); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
username := ""
|
||||
for _, name := range linkedInUserInfo.FirstName.Localized {
|
||||
username += name
|
||||
}
|
||||
for _, name := range linkedInUserInfo.LastName.Localized {
|
||||
username += name
|
||||
}
|
||||
userInfo := UserInfo{
|
||||
Id: linkedInUserInfo.Id,
|
||||
DisplayName: username,
|
||||
Username: username,
|
||||
Email: linkedInUserEmail.Elements[0].Handle.EmailAddress,
|
||||
AvatarUrl: linkedInUserInfo.ProfilePicture.DisplayImage1.Elements[0].Identifiers[0].Identifier,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([]byte, error) {
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bs, nil
|
||||
}
|
@ -51,6 +51,8 @@ func GetIdProvider(providerType string, clientId string, clientSecret string, re
|
||||
return NewWeiBoIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "Gitee" {
|
||||
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "LinkedIn" {
|
||||
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
1
main.go
1
main.go
@ -48,6 +48,7 @@ func main() {
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoLoginFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||
|
||||
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
||||
beego.BConfig.WebConfig.Session.SessionProvider = "file"
|
||||
|
@ -133,4 +133,13 @@ func (a *Adapter) createTable() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = a.Engine.Sync2(new(Records))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Ldap))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,11 @@ func GetApplicationByOrganizationName(organization string) *Application {
|
||||
}
|
||||
|
||||
func GetApplicationByUser(user *User) *Application {
|
||||
return GetApplicationByOrganizationName(user.Owner)
|
||||
if user.SignupApplication != "" {
|
||||
return getApplication("admin", user.SignupApplication)
|
||||
} else {
|
||||
return GetApplicationByOrganizationName(user.Owner)
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByClientId(clientId string) *Application {
|
||||
|
@ -1,3 +1,17 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
|
@ -1,3 +1,17 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import "github.com/casdoor/casdoor/util"
|
||||
@ -6,6 +20,7 @@ func InitDb() {
|
||||
initBuiltInOrganization()
|
||||
initBuiltInUser()
|
||||
initBuiltInApplication()
|
||||
initBuiltInLdap()
|
||||
}
|
||||
|
||||
func initBuiltInOrganization() {
|
||||
@ -15,12 +30,15 @@ func initBuiltInOrganization() {
|
||||
}
|
||||
|
||||
organization = &Organization{
|
||||
Owner: "admin",
|
||||
Name: "built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Built-in Organization",
|
||||
WebsiteUrl: "https://example.com",
|
||||
PasswordType: "plain",
|
||||
Owner: "admin",
|
||||
Name: "built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Built-in Organization",
|
||||
WebsiteUrl: "https://example.com",
|
||||
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
||||
PhonePrefix: "86",
|
||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||
PasswordType: "plain",
|
||||
}
|
||||
AddOrganization(organization)
|
||||
}
|
||||
@ -36,11 +54,13 @@ func initBuiltInUser() {
|
||||
Name: "admin",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Type: "normal-user",
|
||||
Password: "123",
|
||||
DisplayName: "Admin",
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: "admin@example.com",
|
||||
Phone: "1-12345678",
|
||||
Phone: "12345678910",
|
||||
Address: []string{},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
IsAdmin: true,
|
||||
@ -74,3 +94,24 @@ func initBuiltInApplication() {
|
||||
}
|
||||
AddApplication(application)
|
||||
}
|
||||
|
||||
func initBuiltInLdap() {
|
||||
ldap := GetLdap("ldap-built-in")
|
||||
if ldap != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ldap = &Ldap{
|
||||
Id: "ldap-built-in",
|
||||
Owner: "built-in",
|
||||
ServerName: "BuildIn LDAP Server",
|
||||
Host: "example.com",
|
||||
Port: 389,
|
||||
Admin: "cn=buildin,dc=example,dc=com",
|
||||
Passwd: "123",
|
||||
BaseDn: "ou=BuildIn,dc=example,dc=com",
|
||||
AutoSync: 0,
|
||||
LastSync: "",
|
||||
}
|
||||
AddLdap(ldap)
|
||||
}
|
||||
|
381
object/ldap.go
Normal file
381
object/ldap.go
Normal file
@ -0,0 +1,381 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Ldap struct {
|
||||
Id string `xorm:"varchar(100) notnull pk" json:"id"`
|
||||
Owner string `xorm:"varchar(100)" json:"owner"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
Admin string `xorm:"varchar(100)" json:"admin"`
|
||||
Passwd string `xorm:"varchar(100)" json:"passwd"`
|
||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||
|
||||
AutoSync int `json:"autoSync"`
|
||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
||||
}
|
||||
|
||||
type ldapConn struct {
|
||||
Conn *goldap.Conn
|
||||
}
|
||||
|
||||
//type ldapGroup struct {
|
||||
// GidNumber string
|
||||
// Cn string
|
||||
//}
|
||||
|
||||
type ldapUser struct {
|
||||
UidNumber string
|
||||
Uid string
|
||||
Cn string
|
||||
GidNumber string
|
||||
//Gcn string
|
||||
Uuid string
|
||||
Mail string
|
||||
Email string
|
||||
EmailAddress string
|
||||
TelephoneNumber string
|
||||
Mobile string
|
||||
MobileTelephoneNumber string
|
||||
RegisteredAddress string
|
||||
PostalAddress string
|
||||
}
|
||||
|
||||
type LdapRespUser struct {
|
||||
UidNumber string `json:"uidNumber"`
|
||||
Uid string `json:"uid"`
|
||||
Cn string `json:"cn"`
|
||||
GroupId string `json:"groupId"`
|
||||
//GroupName string `json:"groupName"`
|
||||
Uuid string `json:"uuid"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
|
||||
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(adminUser, adminPasswd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
|
||||
}
|
||||
|
||||
return &ldapConn{Conn: conn}, nil
|
||||
}
|
||||
|
||||
//FIXME: The Base DN does not necessarily contain the Group
|
||||
//func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
|
||||
// SearchFilter := "(objectClass=posixGroup)"
|
||||
// SearchAttributes := []string{"cn", "gidNumber"}
|
||||
// groupMap := make(map[string]ldapGroup)
|
||||
//
|
||||
// searchReq := goldap.NewSearchRequest(baseDn,
|
||||
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
// SearchFilter, SearchAttributes, nil)
|
||||
// searchResult, err := l.Conn.Search(searchReq)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if len(searchResult.Entries) == 0 {
|
||||
// return nil, errors.New("no result")
|
||||
// }
|
||||
//
|
||||
// for _, entry := range searchResult.Entries {
|
||||
// var ldapGroupItem ldapGroup
|
||||
// for _, attribute := range entry.Attributes {
|
||||
// switch attribute.Name {
|
||||
// case "gidNumber":
|
||||
// ldapGroupItem.GidNumber = attribute.Values[0]
|
||||
// break
|
||||
// case "cn":
|
||||
// ldapGroupItem.Cn = attribute.Values[0]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
|
||||
// }
|
||||
//
|
||||
// return groupMap, nil
|
||||
//}
|
||||
|
||||
func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
||||
SearchFilter := "(objectClass=posixAccount)"
|
||||
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
||||
|
||||
searchReq := goldap.NewSearchRequest(baseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
searchResult, err := l.Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return nil, errors.New("no result")
|
||||
}
|
||||
|
||||
var ldapUsers []ldapUser
|
||||
|
||||
for _, entry := range searchResult.Entries {
|
||||
var ldapUserItem ldapUser
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "uidNumber":
|
||||
ldapUserItem.UidNumber = attribute.Values[0]
|
||||
break
|
||||
case "uid":
|
||||
ldapUserItem.Uid = attribute.Values[0]
|
||||
break
|
||||
case "cn":
|
||||
ldapUserItem.Cn = attribute.Values[0]
|
||||
break
|
||||
case "gidNumber":
|
||||
ldapUserItem.GidNumber = attribute.Values[0]
|
||||
break
|
||||
case "entryUUID":
|
||||
ldapUserItem.Uuid = attribute.Values[0]
|
||||
break
|
||||
case "mail":
|
||||
ldapUserItem.Mail = attribute.Values[0]
|
||||
break
|
||||
case "email":
|
||||
ldapUserItem.Email = attribute.Values[0]
|
||||
break
|
||||
case "emailAddress":
|
||||
ldapUserItem.EmailAddress = attribute.Values[0]
|
||||
break
|
||||
case "telephoneNumber":
|
||||
ldapUserItem.TelephoneNumber = attribute.Values[0]
|
||||
break
|
||||
case "mobile":
|
||||
ldapUserItem.Mobile = attribute.Values[0]
|
||||
break
|
||||
case "mobileTelephoneNumber":
|
||||
ldapUserItem.MobileTelephoneNumber = attribute.Values[0]
|
||||
break
|
||||
case "registeredAddress":
|
||||
ldapUserItem.RegisteredAddress = attribute.Values[0]
|
||||
break
|
||||
case "postalAddress":
|
||||
ldapUserItem.PostalAddress = attribute.Values[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
ldapUsers = append(ldapUsers, ldapUserItem)
|
||||
}
|
||||
|
||||
return ldapUsers, nil
|
||||
}
|
||||
|
||||
func AddLdap(ldap *Ldap) bool {
|
||||
if len(ldap.Id) == 0 {
|
||||
ldap.Id = util.GenerateId()
|
||||
}
|
||||
|
||||
if len(ldap.CreatedTime) == 0 {
|
||||
ldap.CreatedTime = util.GetCurrentTime()
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func CheckLdapExist(ldap *Ldap) bool {
|
||||
var result []*Ldap
|
||||
err := adapter.Engine.Find(&result, &Ldap{
|
||||
Owner: ldap.Owner,
|
||||
Host: ldap.Host,
|
||||
Port: ldap.Port,
|
||||
Admin: ldap.Admin,
|
||||
Passwd: ldap.Passwd,
|
||||
BaseDn: ldap.BaseDn,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(result) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func GetLdaps(owner string) []*Ldap {
|
||||
var ldaps []*Ldap
|
||||
err := adapter.Engine.Desc("created_time").Find(&ldaps, &Ldap{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ldaps
|
||||
}
|
||||
|
||||
func GetLdap(id string) *Ldap {
|
||||
if util.IsStrsEmpty(id) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ldap := Ldap{Id: id}
|
||||
existed, err := adapter.Engine.Get(&ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &ldap
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateLdap(ldap *Ldap) bool {
|
||||
if GetLdap(ldap.Id) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||
"port", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteLdap(ldap *Ldap) bool {
|
||||
affected, err := adapter.Engine.ID(ldap.Id).Delete(&Ldap{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]LdapRespUser) {
|
||||
var existUsers []LdapRespUser
|
||||
var failedUsers []LdapRespUser
|
||||
var uuids []string
|
||||
|
||||
for _, user := range users {
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
existUuids := CheckLdapUuidExist(owner, uuids)
|
||||
|
||||
for _, user := range users {
|
||||
if len(existUuids) > 0 {
|
||||
for index, existUuid := range existUuids {
|
||||
if user.Uuid == existUuid {
|
||||
existUsers = append(existUsers, user)
|
||||
existUuids = append(existUuids[:index], existUuids[index+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !AddUser(&User{
|
||||
Owner: owner,
|
||||
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Password: "123",
|
||||
DisplayName: user.Cn,
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Address: []string{user.Address},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Ldap: user.Uuid,
|
||||
}) {
|
||||
failedUsers = append(failedUsers, user)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return &existUsers, &failedUsers
|
||||
}
|
||||
|
||||
func UpdateLdapSyncTime(ldapId string) {
|
||||
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckLdapUuidExist(owner string, uuids []string) []string {
|
||||
var results []User
|
||||
var existUuids []string
|
||||
|
||||
//whereStr := ""
|
||||
//for i, uuid := range uuids {
|
||||
// if i == 0 {
|
||||
// whereStr = fmt.Sprintf("'%s'", uuid)
|
||||
// } else {
|
||||
// whereStr = fmt.Sprintf(",'%s'", uuid)
|
||||
// }
|
||||
//}
|
||||
|
||||
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'" + strings.Join(uuids, "','") + "'"), owner).Find(&results)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(results) > 0 {
|
||||
for _, result := range results {
|
||||
existUuids = append(existUuids, result.Ldap)
|
||||
}
|
||||
}
|
||||
return existUuids
|
||||
}
|
||||
|
||||
func buildLdapUserName(uid, uidNum string) string {
|
||||
var result User
|
||||
uidWithNumber := fmt.Sprintf("%s_%s", uid, uidNum)
|
||||
|
||||
has, err := adapter.Engine.Where("name = ? or name = ?", uid, uidWithNumber).Get(&result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
if result.Name == uid {
|
||||
return uidWithNumber
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
|
||||
}
|
||||
|
||||
return uid
|
||||
}
|
65
object/record.go
Normal file
65
object/record.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type Records struct {
|
||||
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||
Record util.Record `xorm:"extends"`
|
||||
}
|
||||
|
||||
func AddRecord(record *util.Record) bool {
|
||||
records := new(Records)
|
||||
records.Record = *record
|
||||
|
||||
affected, err := adapter.Engine.Insert(records)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func GetRecordCount() int {
|
||||
count, err := adapter.Engine.Count(&Records{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetRecords() []*Records {
|
||||
records := []*Records{}
|
||||
err := adapter.Engine.Desc("id").Find(&records)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
|
||||
func GetRecordsByField(record *Records) []*Records {
|
||||
records := []*Records{}
|
||||
err := adapter.Engine.Find(&records, record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
@ -54,7 +54,9 @@ type User struct {
|
||||
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||
|
||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
}
|
||||
|
||||
@ -141,7 +143,9 @@ func UpdateUser(id string, user *User) bool {
|
||||
|
||||
user.UpdateUserHash()
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols("owner", "display_name", "avatar", "address","language", "affiliation", "score", "tag", "is_admin", "is_global_admin", "is_forbidden", "hash", "properties").Update(user)
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols("owner", "display_name", "avatar",
|
||||
"address", "language", "affiliation", "score", "tag", "is_admin", "is_global_admin", "is_forbidden",
|
||||
"hash", "properties").Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -1,3 +1,17 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
|
70
routers/record.go
Normal file
70
routers/record.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2021 The casbin 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 routers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func getUser(ctx *context.Context) (username string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
username = getUserByClientIdSecret(ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
username = ctx.Input.Session("username").(string)
|
||||
|
||||
if username == "" {
|
||||
username = getUserByClientIdSecret(ctx)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getUserByClientIdSecret(ctx *context.Context) string {
|
||||
requestUri := ctx.Request.RequestURI
|
||||
clientId := parseQuery(requestUri, "clientId")
|
||||
clientSecret := parseQuery(requestUri, "clientSecret")
|
||||
if len(clientId) == 0 || len(clientSecret) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
app := object.GetApplicationByClientId(clientId)
|
||||
if app == nil || app.ClientSecret != clientSecret {
|
||||
return ""
|
||||
}
|
||||
return app.Organization+"/"+app.Name
|
||||
}
|
||||
|
||||
func RecordMessage(ctx *context.Context) {
|
||||
if ctx.Request.URL.Path != "/api/login" {
|
||||
user := getUser(ctx)
|
||||
userinfo := strings.Split(user,"/")
|
||||
if user == "" {
|
||||
userinfo = append(userinfo,"")
|
||||
}
|
||||
record := util.Records(ctx)
|
||||
record.Organization = userinfo[0]
|
||||
record.Username = userinfo[1]
|
||||
|
||||
object.AddRecord(record)
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,14 @@ func initAPI() {
|
||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||
beego.Router("/api/get-human-check", &controllers.ApiController{}, "GET:GetHumanCheck")
|
||||
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
||||
beego.Router("/api/get-ldap", &controllers.ApiController{}, "POST: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")
|
||||
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
|
||||
@ -73,7 +81,7 @@ func initAPI() {
|
||||
|
||||
beego.Router("/api/get-applications", &controllers.ApiController{}, "GET:GetApplications")
|
||||
beego.Router("/api/get-application", &controllers.ApiController{}, "GET:GetApplication")
|
||||
beego.Router("/api/get-default-application", &controllers.ApiController{}, "GET:GetDefaultApplication")
|
||||
beego.Router("/api/get-user-application", &controllers.ApiController{}, "GET:GetUserApplication")
|
||||
beego.Router("/api/update-application", &controllers.ApiController{}, "POST:UpdateApplication")
|
||||
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
|
||||
beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication")
|
||||
@ -84,4 +92,8 @@ func initAPI() {
|
||||
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
|
||||
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
||||
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
||||
|
||||
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
|
||||
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
||||
}
|
||||
|
||||
|
@ -362,6 +362,65 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-default-application": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"api"
|
||||
],
|
||||
"description": "get the detail of the default application",
|
||||
"operationId": "ApiController.GetDefaultApplication",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "owner",
|
||||
"description": "The owner of the application.",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Application"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-email-and-phone": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"api"
|
||||
],
|
||||
"description": "get email and phone by username",
|
||||
"operationId": "ApiController.GetEmailAndPhone",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "username",
|
||||
"description": "The username of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "organization",
|
||||
"description": "The organization of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-global-users": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -492,6 +551,57 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-records": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"api"
|
||||
],
|
||||
"description": "get all records",
|
||||
"operationId": "ApiController.GetRecords",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Records"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-records-filter": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"api"
|
||||
],
|
||||
"description": "get records by filter",
|
||||
"operationId": "ApiController.GetRecordsByFilter",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "filter Record message",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Records"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Records"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-token": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -700,18 +810,65 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/register": {
|
||||
"/api/set-password": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"api"
|
||||
],
|
||||
"description": "register a new user",
|
||||
"operationId": "ApiController.Register",
|
||||
"description": "set password",
|
||||
"operationId": "ApiController.SetPassword",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "userOwner",
|
||||
"description": "The owner of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "userName",
|
||||
"description": "The name of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "oldPassword",
|
||||
"description": "The old password of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "newPassword",
|
||||
"description": "The new password of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/signup": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"api"
|
||||
],
|
||||
"description": "sign up a new user",
|
||||
"operationId": "ApiController.Signup",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "username",
|
||||
"description": "The username to register",
|
||||
"description": "The username to sign up",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
@ -965,7 +1122,7 @@
|
||||
"tags": [
|
||||
"api"
|
||||
],
|
||||
"description": "register a new user",
|
||||
"description": "upload avatar",
|
||||
"operationId": "ApiController.UploadAvatar",
|
||||
"parameters": [
|
||||
{
|
||||
@ -995,7 +1152,11 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"1471.0xc0003bd890.false": {
|
||||
"1671.0xc00044ab10.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
"1705.0xc00044ab40.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
@ -1008,7 +1169,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/1471.0xc0003bd890.false"
|
||||
"$ref": "#/definitions/1671.0xc00044ab10.false"
|
||||
},
|
||||
"data2": {
|
||||
"$ref": "#/definitions/1705.0xc00044ab40.false"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
@ -1023,7 +1187,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/1471.0xc0003bd890.false"
|
||||
"$ref": "#/definitions/1671.0xc00044ab10.false"
|
||||
},
|
||||
"data2": {
|
||||
"$ref": "#/definitions/1705.0xc00044ab40.false"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
@ -1037,6 +1204,9 @@
|
||||
"title": "Application",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"affiliationUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -1062,6 +1232,9 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"forgetUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"homepageUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -1074,19 +1247,16 @@
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
"organizationObj": {
|
||||
"$ref": "#/definitions/object.Organization"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerObjs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Provider"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/object.ProviderItem"
|
||||
}
|
||||
},
|
||||
"redirectUris": {
|
||||
@ -1094,6 +1264,18 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"signinUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"signupItems": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.SignupItem"
|
||||
}
|
||||
},
|
||||
"signupUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1104,15 +1286,30 @@
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"defaultAvatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"favicon": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"passwordSalt": {
|
||||
"type": "string"
|
||||
},
|
||||
"passwordType": {
|
||||
"type": "string"
|
||||
},
|
||||
"phonePrefix": {
|
||||
"type": "string"
|
||||
},
|
||||
"websiteUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
@ -1122,32 +1319,121 @@
|
||||
"title": "Provider",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"providerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"regionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"signName": {
|
||||
"type": "string"
|
||||
},
|
||||
"templateCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.ProviderItem": {
|
||||
"title": "ProviderItem",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertType": {
|
||||
"type": "string"
|
||||
},
|
||||
"canSignIn": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canSignUp": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canUnlink": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"provider": {
|
||||
"$ref": "#/definitions/object.Provider"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Records": {
|
||||
"title": "Records",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Record": {
|
||||
"$ref": "#/definitions/util.Record"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.SignupItem": {
|
||||
"title": "SignupItem",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"rule": {
|
||||
"type": "string"
|
||||
},
|
||||
"visible": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Token": {
|
||||
"title": "Token",
|
||||
"type": "object",
|
||||
@ -1171,6 +1457,9 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -1179,6 +1468,9 @@
|
||||
},
|
||||
"tokenType": {
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1205,6 +1497,12 @@
|
||||
"title": "User",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"affiliation": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -1214,27 +1512,45 @@
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"dingtalk": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"facebook": {
|
||||
"type": "string"
|
||||
},
|
||||
"gitee": {
|
||||
"type": "string"
|
||||
},
|
||||
"github": {
|
||||
"type": "string"
|
||||
},
|
||||
"google": {
|
||||
"type": "string"
|
||||
},
|
||||
"hash": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"isAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isForbidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isGlobalAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -1244,17 +1560,65 @@
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"passwordType": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
},
|
||||
"preHash": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"qq": {
|
||||
"type": "string"
|
||||
},
|
||||
"score": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"signupApplication": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"wechat": {
|
||||
"type": "string"
|
||||
},
|
||||
"weibo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"util.Record": {
|
||||
"title": "Record",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ip": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestUri": {
|
||||
"type": "string"
|
||||
},
|
||||
"urlpath": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -234,6 +234,45 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Application'
|
||||
/api/get-default-application:
|
||||
get:
|
||||
tags:
|
||||
- api
|
||||
description: get the detail of the default application
|
||||
operationId: ApiController.GetDefaultApplication
|
||||
parameters:
|
||||
- in: query
|
||||
name: owner
|
||||
description: The owner of the application.
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Application'
|
||||
/api/get-email-and-phone:
|
||||
post:
|
||||
tags:
|
||||
- api
|
||||
description: get email and phone by username
|
||||
operationId: ApiController.GetEmailAndPhone
|
||||
parameters:
|
||||
- in: formData
|
||||
name: username
|
||||
description: The username of the user
|
||||
required: true
|
||||
type: string
|
||||
- in: formData
|
||||
name: organization
|
||||
description: The organization of the user
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/get-global-users:
|
||||
get:
|
||||
tags:
|
||||
@ -319,6 +358,39 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Provider'
|
||||
/api/get-records:
|
||||
get:
|
||||
tags:
|
||||
- api
|
||||
description: get all records
|
||||
operationId: ApiController.GetRecords
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Records'
|
||||
/api/get-records-filter:
|
||||
post:
|
||||
tags:
|
||||
- api
|
||||
description: get records by filter
|
||||
operationId: ApiController.GetRecordsByFilter
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: filter Record message
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Records'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Records'
|
||||
/api/get-token:
|
||||
get:
|
||||
tags:
|
||||
@ -456,16 +528,48 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/register:
|
||||
/api/set-password:
|
||||
post:
|
||||
tags:
|
||||
- api
|
||||
description: register a new user
|
||||
operationId: ApiController.Register
|
||||
description: set password
|
||||
operationId: ApiController.SetPassword
|
||||
parameters:
|
||||
- in: formData
|
||||
name: userOwner
|
||||
description: The owner of the user
|
||||
required: true
|
||||
type: string
|
||||
- in: formData
|
||||
name: userName
|
||||
description: The name of the user
|
||||
required: true
|
||||
type: string
|
||||
- in: formData
|
||||
name: oldPassword
|
||||
description: The old password of the user
|
||||
required: true
|
||||
type: string
|
||||
- in: formData
|
||||
name: newPassword
|
||||
description: The new password of the user
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/signup:
|
||||
post:
|
||||
tags:
|
||||
- api
|
||||
description: sign up a new user
|
||||
operationId: ApiController.Signup
|
||||
parameters:
|
||||
- in: formData
|
||||
name: username
|
||||
description: The username to register
|
||||
description: The username to sign up
|
||||
required: true
|
||||
type: string
|
||||
- in: formData
|
||||
@ -633,7 +737,7 @@ paths:
|
||||
post:
|
||||
tags:
|
||||
- api
|
||||
description: register a new user
|
||||
description: upload avatar
|
||||
operationId: ApiController.UploadAvatar
|
||||
parameters:
|
||||
- in: formData
|
||||
@ -652,7 +756,10 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
definitions:
|
||||
1471.0xc0003bd890.false:
|
||||
1671.0xc00044ab10.false:
|
||||
title: "false"
|
||||
type: object
|
||||
1705.0xc00044ab40.false:
|
||||
title: "false"
|
||||
type: object
|
||||
RequestForm:
|
||||
@ -663,7 +770,9 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/1471.0xc0003bd890.false'
|
||||
$ref: '#/definitions/1671.0xc00044ab10.false'
|
||||
data2:
|
||||
$ref: '#/definitions/1705.0xc00044ab40.false'
|
||||
msg:
|
||||
type: string
|
||||
status:
|
||||
@ -673,7 +782,9 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/1471.0xc0003bd890.false'
|
||||
$ref: '#/definitions/1671.0xc00044ab10.false'
|
||||
data2:
|
||||
$ref: '#/definitions/1705.0xc00044ab40.false'
|
||||
msg:
|
||||
type: string
|
||||
status:
|
||||
@ -682,6 +793,8 @@ definitions:
|
||||
title: Application
|
||||
type: object
|
||||
properties:
|
||||
affiliationUrl:
|
||||
type: string
|
||||
clientId:
|
||||
type: string
|
||||
clientSecret:
|
||||
@ -699,6 +812,8 @@ definitions:
|
||||
expireInHours:
|
||||
type: integer
|
||||
format: int64
|
||||
forgetUrl:
|
||||
type: string
|
||||
homepageUrl:
|
||||
type: string
|
||||
logo:
|
||||
@ -707,54 +822,130 @@ definitions:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
organizationObj:
|
||||
$ref: '#/definitions/object.Organization'
|
||||
owner:
|
||||
type: string
|
||||
providerObjs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Provider'
|
||||
providers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
$ref: '#/definitions/object.ProviderItem'
|
||||
redirectUris:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
signinUrl:
|
||||
type: string
|
||||
signupItems:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.SignupItem'
|
||||
signupUrl:
|
||||
type: string
|
||||
object.Organization:
|
||||
title: Organization
|
||||
type: object
|
||||
properties:
|
||||
createdTime:
|
||||
type: string
|
||||
defaultAvatar:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
favicon:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
passwordSalt:
|
||||
type: string
|
||||
passwordType:
|
||||
type: string
|
||||
phonePrefix:
|
||||
type: string
|
||||
websiteUrl:
|
||||
type: string
|
||||
object.Provider:
|
||||
title: Provider
|
||||
type: object
|
||||
properties:
|
||||
appId:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
clientId:
|
||||
type: string
|
||||
clientSecret:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
host:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
format: int64
|
||||
providerUrl:
|
||||
type: string
|
||||
regionId:
|
||||
type: string
|
||||
signName:
|
||||
type: string
|
||||
templateCode:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
object.ProviderItem:
|
||||
title: ProviderItem
|
||||
type: object
|
||||
properties:
|
||||
alertType:
|
||||
type: string
|
||||
canSignIn:
|
||||
type: boolean
|
||||
canSignUp:
|
||||
type: boolean
|
||||
canUnlink:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
prompted:
|
||||
type: boolean
|
||||
provider:
|
||||
$ref: '#/definitions/object.Provider'
|
||||
object.Records:
|
||||
title: Records
|
||||
type: object
|
||||
properties:
|
||||
Record:
|
||||
$ref: '#/definitions/util.Record'
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
object.SignupItem:
|
||||
title: SignupItem
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
prompted:
|
||||
type: boolean
|
||||
required:
|
||||
type: boolean
|
||||
rule:
|
||||
type: string
|
||||
visible:
|
||||
type: boolean
|
||||
object.Token:
|
||||
title: Token
|
||||
type: object
|
||||
@ -772,12 +963,16 @@ definitions:
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
scope:
|
||||
type: string
|
||||
tokenType:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
object.TokenWrapper:
|
||||
title: TokenWrapper
|
||||
type: object
|
||||
@ -795,37 +990,85 @@ definitions:
|
||||
title: User
|
||||
type: object
|
||||
properties:
|
||||
address:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
affiliation:
|
||||
type: string
|
||||
avatar:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
dingtalk:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
facebook:
|
||||
type: string
|
||||
gitee:
|
||||
type: string
|
||||
github:
|
||||
type: string
|
||||
google:
|
||||
type: string
|
||||
hash:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
isAdmin:
|
||||
type: boolean
|
||||
isForbidden:
|
||||
type: boolean
|
||||
isGlobalAdmin:
|
||||
type: boolean
|
||||
language:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
passwordType:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
preHash:
|
||||
type: string
|
||||
properties:
|
||||
additionalProperties:
|
||||
type: string
|
||||
qq:
|
||||
type: string
|
||||
score:
|
||||
type: integer
|
||||
format: int64
|
||||
signupApplication:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
updatedTime:
|
||||
type: string
|
||||
wechat:
|
||||
type: string
|
||||
weibo:
|
||||
type: string
|
||||
util.Record:
|
||||
title: Record
|
||||
type: object
|
||||
properties:
|
||||
ip:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
requestTime:
|
||||
type: string
|
||||
requestUri:
|
||||
type: string
|
||||
urlpath:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
|
18
util/json.go
18
util/json.go
@ -1,3 +1,17 @@
|
||||
// Copyright 2021 The casbin 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 util
|
||||
|
||||
import "encoding/json"
|
||||
@ -11,3 +25,7 @@ func StructToJson(v interface{}) string {
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func JsonToStruct(data string, v interface{}) error {
|
||||
return json.Unmarshal([]byte(data), v)
|
||||
}
|
||||
|
14
util/log.go
14
util/log.go
@ -1,3 +1,17 @@
|
||||
// Copyright 2021 The casbin 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 util
|
||||
|
||||
import (
|
||||
|
47
util/record.go
Normal file
47
util/record.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2021 The casbin 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 util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
ClientIp string `xorm:"varchar(100)" json:"clientIp"`
|
||||
Timestamp string `xorm:"varchar(100)" json:"timestamp"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Username string `xorm:"varchar(100)" json:"username"`
|
||||
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
|
||||
Action string `xorm:"varchar(1000)" json:"action"`
|
||||
}
|
||||
|
||||
func Records(ctx *context.Context) *Record {
|
||||
ip := strings.Replace(getIPFromRequest(ctx.Request), ": ", "", -1)
|
||||
currenttime := GetCurrentTime()
|
||||
requesturi := ctx.Request.RequestURI
|
||||
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
|
||||
|
||||
record := Record{
|
||||
ClientIp: ip,
|
||||
Timestamp: currenttime,
|
||||
RequestUri: requesturi,
|
||||
Username: "",
|
||||
Organization: "",
|
||||
Action: action,
|
||||
}
|
||||
return &record
|
||||
}
|
@ -55,3 +55,39 @@ func GetMd5Hash(text string) string {
|
||||
hash := md5.Sum([]byte(text))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func IsStrsEmpty(strs ...string) bool {
|
||||
r := false
|
||||
for _, str := range strs {
|
||||
if len(str) == 0 {
|
||||
r = true
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func GetMaxLenStr(strs ...string) string {
|
||||
m := 0
|
||||
i := 0
|
||||
for j, str := range strs {
|
||||
l := len(str)
|
||||
if l > m {
|
||||
m = l
|
||||
i = j
|
||||
}
|
||||
}
|
||||
return strs[i]
|
||||
}
|
||||
|
||||
func GetMinLenStr(strs ...string) string {
|
||||
m := int(^uint(0) >> 1)
|
||||
i := 0
|
||||
for j, str := range strs {
|
||||
l := len(str)
|
||||
if l > m {
|
||||
m = l
|
||||
i = j
|
||||
}
|
||||
}
|
||||
return strs[i]
|
||||
}
|
||||
|
14
util/time.go
14
util/time.go
@ -1,3 +1,17 @@
|
||||
// Copyright 2021 The casbin 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 util
|
||||
|
||||
import (
|
||||
|
@ -27,8 +27,11 @@ import ProviderListPage from "./ProviderListPage";
|
||||
import ProviderEditPage from "./ProviderEditPage";
|
||||
import ApplicationListPage from "./ApplicationListPage";
|
||||
import ApplicationEditPage from "./ApplicationEditPage";
|
||||
import LdapEditPage from "./LdapEditPage";
|
||||
import LdapSyncPage from "./LdapSyncPage";
|
||||
import TokenListPage from "./TokenListPage";
|
||||
import TokenEditPage from "./TokenEditPage";
|
||||
import RecordListPage from "./RecordListPage";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import HomePage from "./basic/HomePage";
|
||||
import CustomGithubCorner from "./CustomGithubCorner";
|
||||
@ -223,7 +226,7 @@ class App extends Component {
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown key="200" overlay={menu} >
|
||||
<Dropdown key="200" overlay={menu} className="rightDropDown">
|
||||
<div className="ant-dropdown-link" style={{float: 'right', cursor: 'pointer'}}>
|
||||
{
|
||||
this.renderAvatar()
|
||||
@ -317,9 +320,16 @@ class App extends Component {
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="7">
|
||||
<Link to="/records">
|
||||
{i18next.t("general:Records")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
res.push(
|
||||
<Menu.Item key="6" onClick={() => window.location.href = "/swagger"}>
|
||||
<Menu.Item key="7" onClick={() => window.location.href = "/swagger"}>
|
||||
{i18next.t("general:Swagger")}
|
||||
</Menu.Item>
|
||||
);
|
||||
@ -374,6 +384,7 @@ class App extends Component {
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox/>
|
||||
</Menu>
|
||||
</Header>
|
||||
<Switch>
|
||||
@ -390,8 +401,11 @@ class App extends Component {
|
||||
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
@ -409,8 +423,7 @@ class App extends Component {
|
||||
textAlign: 'center',
|
||||
}
|
||||
}>
|
||||
<SelectLanguageBox/>
|
||||
Made with <span style={{color: 'rgb(255, 255, 255)'}}>❤️</span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casbin.org" rel="noreferrer">Casbin</a>
|
||||
Made with <span style={{color: 'rgb(255, 255, 255)'}}>❤️</span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a>
|
||||
</Footer>
|
||||
)
|
||||
}
|
||||
|
@ -39,3 +39,22 @@
|
||||
width: 100%;
|
||||
height: 70px; /* Footer height */
|
||||
}
|
||||
|
||||
.language_box {
|
||||
background: url("https://cdn.casbin.org/img/muti_language.svg");
|
||||
background-size: 25px, 25px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin: 22px 20px 16px 20px;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.rightDropDown{
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
@ -289,6 +289,7 @@ class ApplicationEditPage extends React.Component {
|
||||
title={i18next.t("general:Providers")}
|
||||
table={this.state.application.providers}
|
||||
providers={this.state.providers}
|
||||
application={this.state.application}
|
||||
onUpdateTable={(value) => { this.updateApplicationField('providers', value)}}
|
||||
/>
|
||||
</Col>
|
||||
@ -301,18 +302,22 @@ class ApplicationEditPage extends React.Component {
|
||||
this.renderPreview()
|
||||
}
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{Setting.getLabel(i18next.t("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<SignupTable
|
||||
title={i18next.t("application:Signup items")}
|
||||
table={this.state.application.signupItems}
|
||||
onUpdateTable={(value) => { this.updateApplicationField('signupItems', value)}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
!this.state.application.enableSignUp ? null : (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{Setting.getLabel(i18next.t("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<SignupTable
|
||||
title={i18next.t("application:Signup items")}
|
||||
table={this.state.application.signupItems}
|
||||
onUpdateTable={(value) => { this.updateApplicationField('signupItems', value)}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||
|
237
web/src/LdapEditPage.js
Normal file
237
web/src/LdapEditPage.js
Normal file
@ -0,0 +1,237 @@
|
||||
// Copyright 2021 The casbin 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.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
|
||||
import {EyeInvisibleOutlined, EyeTwoTone} from "@ant-design/icons";
|
||||
import * as LddpBackend from "./backend/LdapBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import i18n from "i18next";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
class LdapEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
ldapId: props.match.params.ldapId,
|
||||
ldap: null,
|
||||
organizations: [],
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getLdap();
|
||||
this.getOrganizations();
|
||||
}
|
||||
|
||||
getLdap() {
|
||||
LddpBackend.getLdap(this.state.ldapId)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
ldap: res.data
|
||||
})
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getOrganizations() {
|
||||
OrganizationBackend.getOrganizations("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
organizations: (res.msg === undefined) ? res : [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateLdapField(key, value) {
|
||||
this.setState((prevState) => {
|
||||
prevState.ldap[key] = value;
|
||||
return prevState;
|
||||
});
|
||||
}
|
||||
|
||||
renderAutoSyncWarn() {
|
||||
if (this.state.ldap.autoSync > 0) {
|
||||
return (
|
||||
<span style={{
|
||||
color: "#faad14",
|
||||
marginLeft: "20px"
|
||||
}}>{i18next.t("ldap:The Auto Sync option will sync all users to specify organization")}</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
renderLdap() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{i18next.t("ldap:Edit LDAP")}
|
||||
<Button type="primary" onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
|
||||
</div>
|
||||
} style={{marginLeft: "5px"}} type="inner">
|
||||
<Row style={{marginTop: "10px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)}
|
||||
value={this.state.ldap.owner} onChange={(value => {
|
||||
this.updateLdapField("owner", value);
|
||||
})}>
|
||||
{
|
||||
this.state.organizations.map((organization, index) => <Option key={index}
|
||||
value={organization.name}>{organization.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:ID"), i18next.t("general:ID - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Input value={this.state.ldap.id} disabled={true}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Server Name"), i18next.t("ldap:Server Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Input value={this.state.ldap.serverName} onChange={e => {
|
||||
this.updateLdapField("serverName", e.target.value);
|
||||
}}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Server Host"), i18next.t("ldap:Server Host - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Input value={this.state.ldap.host} onChange={e => {
|
||||
this.updateLdapField("host", e.target.value);
|
||||
}}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Server Port"), i18next.t("ldap:Server Port - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<InputNumber min={0} max={65535} formatter={value => value.replace(/\$\s?|(,*)/g, "")}
|
||||
value={this.state.ldap.port} onChange={value => {
|
||||
this.updateLdapField("port", value);
|
||||
}}/>
|
||||
</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"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Input value={this.state.ldap.baseDn} onChange={e => {
|
||||
this.updateLdapField("baseDn", e.target.value);
|
||||
}}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Admin"), i18next.t("ldap:Admin - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Input value={this.state.ldap.admin} onChange={e => {
|
||||
this.updateLdapField("admin", e.target.value);
|
||||
}}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Admin Password"), i18next.t("ldap:Admin Password - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Input.Password
|
||||
iconRender={visible => (visible ? <EyeTwoTone/> : <EyeInvisibleOutlined/>)} value={this.state.ldap.passwd}
|
||||
onChange={e => {
|
||||
this.updateLdapField("passwd", e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Auto Sync"), i18next.t("ldap:Auto Sync - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<InputNumber min={0} formatter={value => value.replace(/\$\s?|(,*)/g, "")} disabled={true}
|
||||
value={this.state.ldap.autoSync} onChange={value => {
|
||||
this.updateLdapField("autoSync", value);
|
||||
}}/><span> mins</span>
|
||||
{this.renderAutoSyncWarn()}
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
submitLdapEdit() {
|
||||
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;
|
||||
})
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Update LDAP server failed: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LdapEditPage;
|
193
web/src/LdapListPage.js
Normal file
193
web/src/LdapListPage.js
Normal file
@ -0,0 +1,193 @@
|
||||
// Copyright 2021 The casbin 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.
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Col, Popconfirm, Row, Table} from "antd";
|
||||
import * as Setting from "./Setting";
|
||||
import * as LdapBackend from "./backend/LdapBackend";
|
||||
import i18next from "i18next";
|
||||
|
||||
class LdapListPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
ldaps: null
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getLdaps()
|
||||
}
|
||||
|
||||
getLdaps() {
|
||||
LdapBackend.getLdaps("")
|
||||
.then((res) => {
|
||||
let ldapsData = [];
|
||||
if (res.status === "ok") {
|
||||
ldapsData = res.data;
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
this.setState((prevState) => {
|
||||
prevState.ldaps = ldapsData;
|
||||
return prevState;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
deleteLdap(index) {
|
||||
|
||||
}
|
||||
|
||||
renderTable(ldaps) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("ldap:Server Name"),
|
||||
dataIndex: "serverName",
|
||||
key: "serverName",
|
||||
width: "200px",
|
||||
sorter: (a, b) => a.serverName.localeCompare(b.serverName),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/ldaps/${record.id}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "140px",
|
||||
sorter: (a, b) => a.owner.localeCompare(b.owner),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Server"),
|
||||
dataIndex: "host",
|
||||
key: "host",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.host.localeCompare(b.host),
|
||||
render: (text, record, index) => {
|
||||
return `${text}:${record.port}`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Base DN"),
|
||||
dataIndex: "baseDn",
|
||||
key: "baseDn",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.baseDn.localeCompare(b.baseDn),
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Admin"),
|
||||
dataIndex: "admin",
|
||||
key: "admin",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.admin.localeCompare(b.admin),
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Auto Sync"),
|
||||
dataIndex: "autoSync",
|
||||
key: "autoSync",
|
||||
width: "100px",
|
||||
sorter: (a, b) => a.autoSync.localeCompare(b.autoSync),
|
||||
render: (text, record, index) => {
|
||||
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
|
||||
<span style={{color: "#52c41a"}}>{text + " mins"}</span>)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Last Sync"),
|
||||
dataIndex: "lastSync",
|
||||
key: "lastSync",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
|
||||
render: (text, record, index) => {
|
||||
return text
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
key: "op",
|
||||
width: "240px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||
type="primary"
|
||||
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete LDAP Config: ${record.serverName} ?`}
|
||||
onConfirm={() => this.deleteLdap(index)}
|
||||
>
|
||||
<Button style={{marginBottom: "10px"}}
|
||||
type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table columns={columns} dataSource={ldaps} rowKey="id" size="middle" bordered
|
||||
pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
<span>{i18next.t("general:LDAPs")}</span>
|
||||
<Button type="primary" size="small" style={{marginLeft: "10px"}}
|
||||
onClick={() => {
|
||||
this.addLdap()
|
||||
}}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={ldaps === null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{width: "100%"}}>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
{
|
||||
this.renderTable(this.state.ldaps)
|
||||
}
|
||||
</Col>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LdapListPage;
|
253
web/src/LdapSyncPage.js
Normal file
253
web/src/LdapSyncPage.js
Normal file
@ -0,0 +1,253 @@
|
||||
// Copyright 2021 The casbin 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.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Col, Row, Table, Popconfirm} from "antd";
|
||||
import * as Setting from "./Setting";
|
||||
import * as LdapBackend from "./backend/LdapBackend";
|
||||
import i18next from "i18next";
|
||||
|
||||
class LdapSyncPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
ldapId: props.match.params.ldapId,
|
||||
ldap: null,
|
||||
users: [],
|
||||
existUuids: [],
|
||||
selectedUsers: []
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getLdap()
|
||||
}
|
||||
|
||||
syncUsers() {
|
||||
let selectedUsers = this.state.selectedUsers;
|
||||
if (selectedUsers === null || selectedUsers.length === 0) {
|
||||
Setting.showMessage("error", "Please select al least 1 user first");
|
||||
return
|
||||
}
|
||||
|
||||
LdapBackend.syncUsers(this.state.ldap.owner, this.state.ldap.id, selectedUsers)
|
||||
.then((res => {
|
||||
if (res.status === "ok") {
|
||||
let exist = res.data.exist;
|
||||
let failed = res.data.failed;
|
||||
let existUser = [];
|
||||
let failedUser = [];
|
||||
|
||||
if ((!exist || exist.length === 0) && (!failed || failed.length === 0)) {
|
||||
Setting.goToLink(`/organizations/${this.state.ldap.owner}/users`);
|
||||
} else {
|
||||
if (exist && exist.length > 0) {
|
||||
exist.forEach(elem => {
|
||||
existUser.push(elem.cn);
|
||||
});
|
||||
Setting.showMessage("error", `User [${existUser}] is already exist`);
|
||||
}
|
||||
|
||||
if (failed && failed.length > 0) {
|
||||
failed.forEach(elem => {
|
||||
failedUser.push(elem.cn);
|
||||
})
|
||||
Setting.showMessage("error", `Sync [${failedUser}] failed`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
getLdap() {
|
||||
LdapBackend.getLdap(this.state.ldapId)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState((prevState) => {
|
||||
prevState.ldap = res.data;
|
||||
return prevState;
|
||||
})
|
||||
this.getLdapUser(res.data);
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
getLdapUser(ldap) {
|
||||
LdapBackend.getLdapUser(ldap)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState((prevState) => {
|
||||
prevState.users = res.data.users;
|
||||
return prevState;
|
||||
})
|
||||
this.getExistUsers(ldap.owner, res.data.users);
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getExistUsers(owner, users) {
|
||||
let 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) {
|
||||
let valTypesArray = [];
|
||||
|
||||
if (data !== null && data.length > 0) {
|
||||
data.forEach(elem => {
|
||||
let val = elem[key];
|
||||
if (!valTypesArray.includes(val)) {
|
||||
valTypesArray.push(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
return valTypesArray;
|
||||
}
|
||||
|
||||
buildFilter(data, key) {
|
||||
let filterArray = [];
|
||||
|
||||
if (data !== null && data.length > 0) {
|
||||
let valArray = this.buildValArray(data, key)
|
||||
valArray.forEach(elem => {
|
||||
filterArray.push({
|
||||
text: elem,
|
||||
value: elem,
|
||||
});
|
||||
});
|
||||
}
|
||||
return filterArray;
|
||||
}
|
||||
|
||||
renderTable(users) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("ldap:CN"),
|
||||
dataIndex: "cn",
|
||||
key: "cn",
|
||||
sorter: (a, b) => a.cn.localeCompare(b.cn),
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:UidNumber / Uid"),
|
||||
dataIndex: "uidNumber",
|
||||
key: "uidNumber",
|
||||
width: "200px",
|
||||
sorter: (a, b) => a.uidNumber.localeCompare(b.uidNumber),
|
||||
render: (text, record, index) => {
|
||||
return `${text} / ${record.uid}`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Group Id"),
|
||||
dataIndex: "groupId",
|
||||
key: "groupId",
|
||||
width: "140px",
|
||||
sorter: (a, b) => a.groupId.localeCompare(b.groupId),
|
||||
filters: this.buildFilter(this.state.users, "groupId"),
|
||||
onFilter: (value, record) => record.groupId.indexOf(value) === 0,
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Email"),
|
||||
dataIndex: "email",
|
||||
key: "email",
|
||||
width: "240px",
|
||||
sorter: (a, b) => a.email.localeCompare(b.email),
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Phone"),
|
||||
dataIndex: "phone",
|
||||
key: "phone",
|
||||
width: "160px",
|
||||
sorter: (a, b) => a.phone.localeCompare(b.phone),
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Address"),
|
||||
dataIndex: "address",
|
||||
key: "address",
|
||||
sorter: (a, b) => a.address.localeCompare(b.address),
|
||||
},
|
||||
];
|
||||
|
||||
const rowSelection = {
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
this.setState(prevState => {
|
||||
prevState.selectedUsers = selectedRows;
|
||||
return prevState;
|
||||
})
|
||||
},
|
||||
getCheckboxProps: record => ({
|
||||
disabled: this.state.existUuids.indexOf(record.uuid) !== -1,
|
||||
}),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered
|
||||
pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
<span>{this.state.ldap?.serverName}</span>
|
||||
<Popconfirm placement={"right"}
|
||||
title={`Please confirm to sync selected users`}
|
||||
onConfirm={() => this.syncUsers()}
|
||||
>
|
||||
<Button type="primary" size="small"
|
||||
style={{marginLeft: "10px"}}>{i18next.t("ldap:Sync")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)}
|
||||
loading={users === null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{width: "100%"}}>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
{
|
||||
this.renderTable(this.state.users)
|
||||
}
|
||||
</Col>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LdapSyncPage;
|
201
web/src/LdapTable.js
Normal file
201
web/src/LdapTable.js
Normal file
@ -0,0 +1,201 @@
|
||||
// Copyright 2021 The casbin 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.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Col, Popconfirm, Row, Table} from 'antd';
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import * as LdapBackend from "./backend/LdapBackend";
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
class LdapTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
};
|
||||
}
|
||||
|
||||
updateTable(table) {
|
||||
this.props.onUpdateTable(table);
|
||||
}
|
||||
|
||||
updateField(table, index, key, value) {
|
||||
table[index][key] = value;
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
newLdap() {
|
||||
return {
|
||||
id: "",
|
||||
owner: this.props.organizationName,
|
||||
createdTime: "",
|
||||
serverName: "Example LDAP Server",
|
||||
host: "example.com",
|
||||
port: 389,
|
||||
admin: "cn=admin,dc=example,dc=com",
|
||||
passwd: "123",
|
||||
baseDn: "ou=People,dc=example,dc=com",
|
||||
autosync: 0,
|
||||
lastSync: ""
|
||||
}
|
||||
}
|
||||
|
||||
addRow(table) {
|
||||
const newLdap = this.newLdap();
|
||||
LdapBackend.addLdap(newLdap)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", `Add LDAP server success`);
|
||||
if (table === undefined) {
|
||||
table = [];
|
||||
}
|
||||
table = Setting.addRow(table, res.data2);
|
||||
this.updateTable(table);
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Add LDAP server failed: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteRow(table, i) {
|
||||
LdapBackend.deleteLdap(table[i])
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", `Delete LDAP server success`);
|
||||
table = Setting.deleteRow(table, i);
|
||||
this.updateTable(table);
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Delete LDAP server failed: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderTable(table) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("ldap:Server Name"),
|
||||
dataIndex: "serverName",
|
||||
key: "serverName",
|
||||
width: "160px",
|
||||
sorter: (a, b) => a.serverName.localeCompare(b.serverName),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/ldaps/${record.id}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Server"),
|
||||
dataIndex: "host",
|
||||
key: "host",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.host.localeCompare(b.host),
|
||||
render: (text, record, index) => {
|
||||
return `${text}:${record.port}`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Base DN"),
|
||||
dataIndex: "baseDn",
|
||||
key: "baseDn",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.baseDn.localeCompare(b.baseDn),
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Auto Sync"),
|
||||
dataIndex: "autoSync",
|
||||
key: "autoSync",
|
||||
width: "120px",
|
||||
sorter: (a, b) => a.autoSync.localeCompare(b.autoSync),
|
||||
render: (text, record, index) => {
|
||||
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
|
||||
<span style={{color: "#52c41a"}}>{text + " mins"}</span>)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Last Sync"),
|
||||
dataIndex: "lastSync",
|
||||
key: "lastSync",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
|
||||
render: (text, record, index) => {
|
||||
return text
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
key: "op",
|
||||
width: "240px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||
type="primary"
|
||||
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete LDAP Config: ${record.serverName} ?`}
|
||||
onConfirm={() => this.deleteRow(table, index)}
|
||||
>
|
||||
<Button style={{marginBottom: "10px"}}
|
||||
type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table rowKey="id" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||
title={() => (
|
||||
<div>
|
||||
{this.props.title}
|
||||
<Button style={{marginRight: "5px"}} type="primary" size="small"
|
||||
onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{marginTop: '20px'}}>
|
||||
<Col span={24}>
|
||||
{
|
||||
this.renderTable(this.props.table)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default LdapTable;
|
@ -15,9 +15,11 @@
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Select} from 'antd';
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as LdapBackend from "./backend/LdapBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import LdapTable from "./LdapTable";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@ -28,11 +30,13 @@ class OrganizationEditPage extends React.Component {
|
||||
classes: props,
|
||||
organizationName: props.match.params.organizationName,
|
||||
organization: null,
|
||||
ldaps: null,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getOrganization();
|
||||
this.getLdaps();
|
||||
}
|
||||
|
||||
getOrganization() {
|
||||
@ -44,6 +48,21 @@ class OrganizationEditPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getLdaps() {
|
||||
LdapBackend.getLdaps(this.state.organizationName)
|
||||
.then(res => {
|
||||
let resdata = []
|
||||
if (res.status === "ok") {
|
||||
if (res.data !== null) {
|
||||
resdata = res.data;
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
ldaps: resdata
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
parseOrganizationField(key, value) {
|
||||
// if ([].includes(key)) {
|
||||
// value = Setting.myParseInt(value);
|
||||
@ -186,6 +205,20 @@ class OrganizationEditPage extends React.Component {
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}}>
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<LdapTable
|
||||
title={i18next.t("general:LDAPs")}
|
||||
table={this.state.ldaps}
|
||||
organizationName={this.state.organizationName}
|
||||
onUpdateTable={(value) => {
|
||||
this.setState({ldaps: value}) }}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@ -228,7 +261,8 @@ class OrganizationEditPage extends React.Component {
|
||||
<Col span={2}>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Button type="primary" size="large" onClick={this.submitOrganizationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
<Button type="primary" size="large"
|
||||
onClick={this.submitOrganizationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -120,7 +120,7 @@ class OrganizationListPage extends React.Component {
|
||||
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
||||
},
|
||||
{
|
||||
title: 'Favicon',
|
||||
title: i18next.t("organization:Favicon"),
|
||||
dataIndex: 'favicon',
|
||||
key: 'favicon',
|
||||
width: '50px',
|
||||
@ -160,6 +160,19 @@ class OrganizationListPage extends React.Component {
|
||||
width: '150px',
|
||||
sorter: (a, b) => a.passwordSalt.localeCompare(b.passwordSalt),
|
||||
},
|
||||
{
|
||||
title: i18next.t("organization:Default avatar"),
|
||||
dataIndex: 'defaultAvatar',
|
||||
key: 'defaultAvatar',
|
||||
width: '50px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<a target="_blank" rel="noreferrer" href={text}>
|
||||
<img src={text} alt={text} width={40} />
|
||||
</a>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
|
@ -74,7 +74,8 @@ class ProviderEditPage extends React.Component {
|
||||
{id: 'Facebook', name: 'Facebook'},
|
||||
{id: 'DingTalk', name: 'DingTalk'},
|
||||
{id: 'Weibo', name: 'Weibo'},
|
||||
{id: 'Gitee', name: 'Gitee'}
|
||||
{id: 'Gitee', name: 'Gitee'},
|
||||
{id: 'LinkedIn', name: 'LinkedIn'},
|
||||
]
|
||||
);
|
||||
} else if (provider.category === "Email") {
|
||||
|
@ -62,7 +62,7 @@ class ProviderTable extends React.Component {
|
||||
}
|
||||
|
||||
renderTable(table) {
|
||||
const columns = [
|
||||
let columns = [
|
||||
{
|
||||
title: i18next.t("provider:Name"),
|
||||
dataIndex: 'name',
|
||||
@ -73,11 +73,11 @@ class ProviderTable extends React.Component {
|
||||
value={text}
|
||||
onChange={value => {
|
||||
this.updateField(table, index, 'name', value);
|
||||
const provider = this.props.providers.filter(provider => provider.name === value)[0];
|
||||
const provider = Setting.getArrayItem(this.props.providers, "name", value);
|
||||
this.updateField(table, index, 'provider', provider);
|
||||
}} >
|
||||
{
|
||||
this.props.providers.filter(provider => table.filter(providerItem => providerItem.name === provider.name).length === 0).map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
|
||||
Setting.getDeduplicatedArray(this.props.providers, table, "name").map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
)
|
||||
@ -89,6 +89,10 @@ class ProviderTable extends React.Component {
|
||||
key: 'canSignUp',
|
||||
width: '120px',
|
||||
render: (text, record, index) => {
|
||||
if (record.provider?.category !== "OAuth") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch checked={text} onChange={checked => {
|
||||
this.updateField(table, index, 'canSignUp', checked);
|
||||
@ -102,6 +106,10 @@ class ProviderTable extends React.Component {
|
||||
key: 'canSignIn',
|
||||
width: '120px',
|
||||
render: (text, record, index) => {
|
||||
if (record.provider?.category !== "OAuth") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch checked={text} onChange={checked => {
|
||||
this.updateField(table, index, 'canSignIn', checked);
|
||||
@ -115,6 +123,10 @@ class ProviderTable extends React.Component {
|
||||
key: 'canUnlink',
|
||||
width: '120px',
|
||||
render: (text, record, index) => {
|
||||
if (record.provider?.category !== "OAuth") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch checked={text} onChange={checked => {
|
||||
this.updateField(table, index, 'canUnlink', checked);
|
||||
@ -128,6 +140,10 @@ class ProviderTable extends React.Component {
|
||||
key: 'prompted',
|
||||
width: '120px',
|
||||
render: (text, record, index) => {
|
||||
if (record.provider?.category !== "OAuth") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch checked={text} onChange={checked => {
|
||||
this.updateField(table, index, 'prompted', checked);
|
||||
@ -135,27 +151,27 @@ class ProviderTable extends React.Component {
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:alertType"),
|
||||
dataIndex: 'alertType',
|
||||
key: 'alertType',
|
||||
width: '120px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||
this.updateField(table, index, 'alertType', value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'None', name: 'None'},
|
||||
{id: 'Once', name: 'Once'},
|
||||
{id: 'Always', name: 'Always'},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
},
|
||||
// {
|
||||
// title: i18next.t("provider:alertType"),
|
||||
// dataIndex: 'alertType',
|
||||
// key: 'alertType',
|
||||
// width: '120px',
|
||||
// render: (text, record, index) => {
|
||||
// return (
|
||||
// <Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||
// this.updateField(table, index, 'alertType', value);
|
||||
// })}>
|
||||
// {
|
||||
// [
|
||||
// {id: 'None', name: 'None'},
|
||||
// {id: 'Once', name: 'Once'},
|
||||
// {id: 'Always', name: 'Always'},
|
||||
// ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
// }
|
||||
// </Select>
|
||||
// )
|
||||
// }
|
||||
// },
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
key: 'action',
|
||||
@ -178,8 +194,12 @@ class ProviderTable extends React.Component {
|
||||
},
|
||||
];
|
||||
|
||||
if (!this.props.application.enableSignUp || this.props.application.enablePassword) {
|
||||
columns = columns.filter(column => column.key !== "canSignUp");
|
||||
}
|
||||
|
||||
return (
|
||||
<Table rowKey="index" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||
<Table rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||
title={() => (
|
||||
<div>
|
||||
{this.props.title}
|
||||
|
159
web/src/RecordListPage.js
Normal file
159
web/src/RecordListPage.js
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2021 The casbin 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.
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Col, Row, Table} from 'antd';
|
||||
import * as Setting from "./Setting";
|
||||
import * as RecordBackend from "./backend/RecordBackend";
|
||||
import i18next from "i18next";
|
||||
|
||||
class RecordListPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
records: null,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getRecords();
|
||||
}
|
||||
|
||||
getRecords() {
|
||||
RecordBackend.getRecords()
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
records: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
newRecord() {
|
||||
return {
|
||||
id : "",
|
||||
Record:{
|
||||
clientIp:"",
|
||||
timestamp:"",
|
||||
organization:"",
|
||||
username:"",
|
||||
requestUri:"",
|
||||
action:"login",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
renderTable(records) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Client ip"),
|
||||
dataIndex: ['Record', 'clientIp'],
|
||||
key: 'id',
|
||||
width: '120px',
|
||||
sorter: (a, b) => a.Record.clientIp.localeCompare(b.Record.clientIp),
|
||||
render: (text, record, index) => {
|
||||
return text;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Timestamp"),
|
||||
dataIndex: ['Record', 'timestamp'],
|
||||
key: 'id',
|
||||
width: '160px',
|
||||
sorter: (a, b) => a.Record.timestamp.localeCompare(b.Record.timestamp),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: ['Record', 'organization'],
|
||||
key: 'id',
|
||||
width: '120px',
|
||||
sorter: (a, b) => a.Record.organization.localeCompare(b.Record.organization),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Username"),
|
||||
dataIndex: ['Record', 'username'],
|
||||
key: 'id',
|
||||
width: '160px',
|
||||
sorter: (a, b) => a.Record.username.localeCompare(b.Record.username),
|
||||
render: (text, record, index) => {
|
||||
return text;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Request uri"),
|
||||
dataIndex: ['Record', 'requestUri'],
|
||||
key: 'id',
|
||||
width: '160px',
|
||||
sorter: (a, b) => a.Record.requestUri.localeCompare(b.Record.requestUri),
|
||||
render: (text, record, index) => {
|
||||
return text;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: ['Record', 'action'],
|
||||
key: 'id',
|
||||
width: '160px',
|
||||
sorter: (a, b) => a.Record.action.localeCompare(b.Record.action),
|
||||
render: (text, record, index) => {
|
||||
return text;
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table columns={columns} dataSource={records} rowKey="id" size="middle" bordered pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Records")}
|
||||
</div>
|
||||
)}
|
||||
loading={records === null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{width: "100%"}}>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
{
|
||||
this.renderTable(this.state.records)
|
||||
}
|
||||
</Col>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RecordListPage;
|
@ -14,6 +14,13 @@
|
||||
|
||||
import React from "react";
|
||||
import * as Setting from "./Setting";
|
||||
import { Menu, Dropdown, message } from "antd";
|
||||
import { createFromIconfontCN } from '@ant-design/icons';
|
||||
import './App.less';
|
||||
|
||||
const IconFont = createFromIconfontCN({
|
||||
scriptUrl: '//at.alicdn.com/t/font_2680620_ffij16fkwdg.js',
|
||||
});
|
||||
|
||||
class SelectLanguageBox extends React.Component {
|
||||
constructor(props) {
|
||||
@ -23,47 +30,28 @@ class SelectLanguageBox extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
onClick(e) {
|
||||
Setting.changeLanguage(e.key);
|
||||
};
|
||||
|
||||
render() {
|
||||
const menu = (
|
||||
<Menu onClick={this.onClick.bind(this)}>
|
||||
<Menu.Item key="en" icon={<IconFont type="icon-en" />}>English</Menu.Item>
|
||||
<Menu.Item key="zh" icon={<IconFont type="icon-zh" />}>简体中文</Menu.Item>
|
||||
<Menu.Item key="fr" icon={<IconFont type="icon-fr" />}>Français</Menu.Item>
|
||||
<Menu.Item key="de" icon={<IconFont type="icon-de" />}>Deutsch</Menu.Item>
|
||||
<Menu.Item key="ja" icon={<IconFont type="icon-ja" />}>日本語</Menu.Item>
|
||||
<Menu.Item key="ko" icon={<IconFont type="icon-ko" />}>한국어</Menu.Item>
|
||||
<Menu.Item key="ru" icon={<IconFont type="icon-ru" />}>Русский</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<div align="center">
|
||||
<div className="box" style={{width: "600px"}}>
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a onClick={() => Setting.changeLanguage("en")} className="lang-selector">
|
||||
English
|
||||
</a>
|
||||
/
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a onClick={() => Setting.changeLanguage("zh")} className="lang-selector">
|
||||
简体中文
|
||||
</a>
|
||||
/
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a onClick={() => Setting.changeLanguage("fr")} className="lang-selector">
|
||||
Français
|
||||
</a>
|
||||
/
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a onClick={() => Setting.changeLanguage("de")} className="lang-selector">
|
||||
Deutsch
|
||||
</a>
|
||||
/
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a onClick={() => Setting.changeLanguage("ja")} className="lang-selector">
|
||||
日本語
|
||||
</a>
|
||||
/
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a onClick={() => Setting.changeLanguage("ko")} className="lang-selector">
|
||||
한국어
|
||||
</a>
|
||||
/
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a onClick={() => Setting.changeLanguage("ru")} className="lang-selector">
|
||||
Русский
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
<Dropdown overlay={menu} style={{cursor: "pointer"}}>
|
||||
<span className="language_box" />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,3 +448,13 @@ export function maskEmail(email) {
|
||||
|
||||
return `${username}@${domainTokens.join(".")}`;
|
||||
}
|
||||
|
||||
export function getArrayItem(array, key, value) {
|
||||
const res = array.filter(item => item[key] === value)[0];
|
||||
return res;
|
||||
}
|
||||
|
||||
export function getDeduplicatedArray(array, filterArray, key) {
|
||||
const res = array.filter(item => filterArray.filter(filterItem => filterItem[key] === item[key]).length === 0);
|
||||
return res;
|
||||
}
|
||||
|
@ -68,6 +68,18 @@ class SignupTable extends React.Component {
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text, record, index) => {
|
||||
const items = [
|
||||
{id: 'Username', name: 'Username'},
|
||||
{id: 'ID', name: 'ID'},
|
||||
{id: 'Display name', name: 'Display name'},
|
||||
{id: 'Affiliation', name: 'Affiliation'},
|
||||
{id: 'Email', name: 'Email'},
|
||||
{id: 'Password', name: 'Password'},
|
||||
{id: 'Confirm password', name: 'Confirm password'},
|
||||
{id: 'Phone', name: 'Phone'},
|
||||
{id: 'Agreement', name: 'Agreement'},
|
||||
];
|
||||
|
||||
return (
|
||||
<Select virtual={false} style={{width: '100%'}}
|
||||
value={text}
|
||||
@ -75,17 +87,7 @@ class SignupTable extends React.Component {
|
||||
this.updateField(table, index, 'name', value);
|
||||
}} >
|
||||
{
|
||||
[
|
||||
{id: 'Username', name: 'Username'},
|
||||
{id: 'ID', name: 'ID'},
|
||||
{id: 'Display name', name: 'Display name'},
|
||||
{id: 'Affiliation', name: 'Affiliation'},
|
||||
{id: 'Email', name: 'Email'},
|
||||
{id: 'Password', name: 'Password'},
|
||||
{id: 'Confirm password', name: 'Confirm password'},
|
||||
{id: 'Phone', name: 'Phone'},
|
||||
{id: 'Agreement', name: 'Agreement'},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
)
|
||||
@ -97,6 +99,10 @@ class SignupTable extends React.Component {
|
||||
key: 'visible',
|
||||
width: '120px',
|
||||
render: (text, record, index) => {
|
||||
if (record.name === "ID") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch checked={text} onChange={checked => {
|
||||
this.updateField(table, index, 'visible', checked);
|
||||
@ -132,6 +138,10 @@ class SignupTable extends React.Component {
|
||||
key: 'prompted',
|
||||
width: '120px',
|
||||
render: (text, record, index) => {
|
||||
if (record.name === "ID") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (record.visible) {
|
||||
return null;
|
||||
}
|
||||
@ -149,17 +159,29 @@ class SignupTable extends React.Component {
|
||||
key: 'rule',
|
||||
width: '120px',
|
||||
render: (text, record, index) => {
|
||||
let options = [];
|
||||
if (record.name === "ID") {
|
||||
options = [
|
||||
{id: 'Random', name: 'Random'},
|
||||
{id: 'Incremental', name: 'Incremental'},
|
||||
];
|
||||
} if (record.name === "Display name") {
|
||||
options = [
|
||||
{id: 'None', name: 'None'},
|
||||
{id: 'Personal', name: 'Personal'},
|
||||
];
|
||||
}
|
||||
|
||||
if (options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||
this.updateField(table, index, 'rule', value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'None', name: 'None'},
|
||||
{id: 'Random', name: 'Random'},
|
||||
{id: 'Incremental', name: 'Incremental'},
|
||||
{id: 'Personal', name: 'Personal'},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
)
|
||||
@ -188,7 +210,7 @@ class SignupTable extends React.Component {
|
||||
];
|
||||
|
||||
return (
|
||||
<Table rowKey="index" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||
<Table rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||
title={() => (
|
||||
<div>
|
||||
{this.props.title}
|
||||
|
@ -49,7 +49,7 @@ class UserEditPage extends React.Component {
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getUser();
|
||||
this.getOrganizations();
|
||||
this.getDefaultApplication();
|
||||
this.getUserApplication();
|
||||
}
|
||||
|
||||
getUser() {
|
||||
@ -70,8 +70,8 @@ class UserEditPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getDefaultApplication() {
|
||||
ApplicationBackend.getDefaultApplication("admin")
|
||||
getUserApplication() {
|
||||
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
|
||||
.then((application) => {
|
||||
this.setState({
|
||||
application: application,
|
||||
|
@ -146,7 +146,7 @@ class UserListPage extends React.Component {
|
||||
title: i18next.t("general:Display name"),
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
// width: '100px',
|
||||
width: '100px',
|
||||
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
||||
},
|
||||
{
|
||||
@ -245,6 +245,7 @@ class UserListPage extends React.Component {
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '190px',
|
||||
fixed: 'right',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
@ -263,7 +264,7 @@ class UserListPage extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
|
||||
<Table columns={columns} scroll={{x: 1300}} dataSource={users} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Users")}
|
||||
|
@ -278,7 +278,7 @@ class LoginPage extends React.Component {
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
||||
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
|
||||
{i18next.t("login:Auto login")}
|
||||
</Checkbox>
|
||||
|
@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Col, Result, Row} from "antd";
|
||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
@ -196,13 +195,11 @@ class PromptPage extends React.Component {
|
||||
title="Sign Up Error"
|
||||
subTitle={"You are unexpected to see this prompt page"}
|
||||
extra={[
|
||||
<Link onClick={() => {
|
||||
<Button type="primary" key="signin" onClick={() => {
|
||||
Setting.goToLogin(this, application);
|
||||
}}>
|
||||
<Button type="primary" key="signin">
|
||||
Sign In
|
||||
</Button>
|
||||
</Link>
|
||||
Sign In
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
</Result>
|
||||
@ -227,7 +224,7 @@ class PromptPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
<div style={{marginTop: "50px"}}>
|
||||
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true)}}>{i18next.t("signup:Submit and complete")}</Button>
|
||||
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true)}}>{i18next.t("code:Submit and complete")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
|
@ -47,6 +47,10 @@ const GiteeAuthScope = "user_info,emails";
|
||||
const GiteeAuthUri = "https://gitee.com/oauth/authorize";
|
||||
const GiteeAuthLogo = `${StaticBaseUrl}/img/social_gitee.png`;
|
||||
|
||||
const LinkedInAuthScope = "r_liteprofile%20r_emailaddress";
|
||||
const LinkedInAuthUri = "https://www.linkedin.com/oauth/v2/authorization";
|
||||
const LinkedInAuthLogo = `${StaticBaseUrl}/img/social_linkedin.png`;
|
||||
|
||||
export function getAuthLogo(provider) {
|
||||
if (provider.type === "Google") {
|
||||
return GoogleAuthLogo;
|
||||
@ -64,6 +68,8 @@ export function getAuthLogo(provider) {
|
||||
return WeiboAuthLogo;
|
||||
} else if (provider.type === "Gitee") {
|
||||
return GiteeAuthLogo;
|
||||
} else if (provider.type === "LinkedIn") {
|
||||
return LinkedInAuthLogo;
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,5 +96,7 @@ export function getAuthUrl(application, provider, method) {
|
||||
return `${WeiboAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${WeiboAuthScope}&response_type=code&state=${state}`;
|
||||
} else if (provider.type === "Gitee") {
|
||||
return `${GiteeAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${GiteeAuthScope}&response_type=code&state=${state}`;
|
||||
} else if (provider.type === "LinkedIn") {
|
||||
return `${LinkedInAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${LinkedInAuthScope}&response_type=code&state=${state}`
|
||||
}
|
||||
}
|
||||
|
@ -161,6 +161,7 @@ class SignupPage extends React.Component {
|
||||
return (
|
||||
<Form.Item
|
||||
name="username"
|
||||
key="username"
|
||||
label={i18next.t("signup:Username")}
|
||||
rules={[
|
||||
{
|
||||
@ -177,6 +178,7 @@ class SignupPage extends React.Component {
|
||||
return (
|
||||
<Form.Item
|
||||
name="name"
|
||||
key="name"
|
||||
label={signupItem.rule === "Personal" ? i18next.t("general:Personal name") : i18next.t("general:Display name")}
|
||||
rules={[
|
||||
{
|
||||
@ -193,6 +195,7 @@ class SignupPage extends React.Component {
|
||||
return (
|
||||
<Form.Item
|
||||
name="affiliation"
|
||||
key="affiliation"
|
||||
label={i18next.t("user:Affiliation")}
|
||||
rules={[
|
||||
{
|
||||
@ -210,6 +213,7 @@ class SignupPage extends React.Component {
|
||||
<React.Fragment>
|
||||
<Form.Item
|
||||
name="email"
|
||||
key="email"
|
||||
label={i18next.t("general:Email")}
|
||||
rules={[
|
||||
{
|
||||
@ -233,6 +237,7 @@ class SignupPage extends React.Component {
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="emailCode"
|
||||
key="emailCode"
|
||||
label={i18next.t("code:Email code")}
|
||||
rules={[{
|
||||
required: required,
|
||||
@ -253,6 +258,7 @@ class SignupPage extends React.Component {
|
||||
return (
|
||||
<Form.Item
|
||||
name="password"
|
||||
key="password"
|
||||
label={i18next.t("general:Password")}
|
||||
rules={[
|
||||
{
|
||||
@ -269,6 +275,7 @@ class SignupPage extends React.Component {
|
||||
return (
|
||||
<Form.Item
|
||||
name="confirm"
|
||||
key="confirm"
|
||||
label={i18next.t("signup:Confirm")}
|
||||
dependencies={['password']}
|
||||
hasFeedback
|
||||
@ -296,6 +303,7 @@ class SignupPage extends React.Component {
|
||||
<React.Fragment>
|
||||
<Form.Item
|
||||
name="phone"
|
||||
key="phone"
|
||||
label={i18next.t("general:Phone")}
|
||||
rules={[
|
||||
{
|
||||
@ -325,6 +333,7 @@ class SignupPage extends React.Component {
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="phoneCode"
|
||||
key="phoneCode"
|
||||
label={i18next.t("code:Phone code")}
|
||||
rules={[
|
||||
{
|
||||
@ -347,6 +356,7 @@ class SignupPage extends React.Component {
|
||||
return (
|
||||
<Form.Item
|
||||
name="agreement"
|
||||
key="agreement"
|
||||
valuePropName="checked"
|
||||
rules={[
|
||||
{
|
||||
@ -375,13 +385,11 @@ class SignupPage extends React.Component {
|
||||
title="Sign Up Error"
|
||||
subTitle={"The application does not allow to sign up new account"}
|
||||
extra={[
|
||||
<Link onClick={() => {
|
||||
<Button type="primary" key="signin" onClick={() => {
|
||||
Setting.goToLogin(this, application);
|
||||
}}>
|
||||
<Button type="primary" key="signin">
|
||||
Sign In
|
||||
</Button>
|
||||
</Link>
|
||||
Sign In
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
</Result>
|
||||
@ -431,11 +439,11 @@ class SignupPage extends React.Component {
|
||||
{i18next.t("account:Sign Up")}
|
||||
</Button>
|
||||
{i18next.t("signup:Have account?")}
|
||||
<Link onClick={() => {
|
||||
<a onClick={() => {
|
||||
Setting.goToLogin(this, application);
|
||||
}}>
|
||||
{i18next.t("signup:sign in now")}
|
||||
</Link>
|
||||
</a>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
)
|
||||
|
@ -28,8 +28,8 @@ export function getApplication(owner, name) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getDefaultApplication(owner) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-default-application?owner=${owner}`, {
|
||||
export function getUserApplication(owner, name) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-user-application?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "GET",
|
||||
credentials: "include"
|
||||
}).then(res => res.json());
|
||||
|
77
web/src/backend/LdapBackend.js
Normal file
77
web/src/backend/LdapBackend.js
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright 2021 The casbin 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.
|
||||
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
export function getLdaps(owner) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-ldaps?owner=${owner}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getLdap(id) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-ldap?id=${id}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function addLdap(body) {
|
||||
return fetch(`${Setting.ServerUrl}/api/add-ldap`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function deleteLdap(body) {
|
||||
return fetch(`${Setting.ServerUrl}/api/delete-ldap`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function updateLdap(body) {
|
||||
return fetch(`${Setting.ServerUrl}/api/update-ldap`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getLdapUser(body) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-ldap-user`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(body),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function syncUsers(owner, ldapId, body) {
|
||||
return fetch(`${Setting.ServerUrl}/api/sync-ldap-users?owner=${owner}&ldapId=${ldapId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(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),
|
||||
}).then(res => res.json());
|
||||
}
|
22
web/src/backend/RecordBackend.js
Normal file
22
web/src/backend/RecordBackend.js
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2021 The casbin 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.
|
||||
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
export function getRecords() {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-records`, {
|
||||
method: "GET",
|
||||
credentials: "include"
|
||||
}).then(res => res.json());
|
||||
}
|
@ -79,7 +79,7 @@ class HomePage extends React.Component {
|
||||
{
|
||||
items.map(item => {
|
||||
return (
|
||||
<SingleCard logo={item.logo} link={item.link} title={item.name} desc={item.organizer} time={item.createdTime} isSingle={items.length === 1} />
|
||||
<SingleCard logo={item.logo} link={item.link} title={item.name} desc={item.organizer} time={item.createdTime} isSingle={items.length === 1} key={item.name} />
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -1,3 +1,17 @@
|
||||
// Copyright 2021 The casbin 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.
|
||||
|
||||
import React from "react";
|
||||
import {Cascader, Col, Input, Row, Select} from 'antd';
|
||||
import i18next from "i18next";
|
||||
@ -87,7 +101,8 @@ class AffiliationSelect extends React.Component {
|
||||
) : (
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.props.user.affiliation} onChange={(value => {
|
||||
const name = value;
|
||||
const id = this.state.affiliationOptions.filter(affiliationOption => affiliationOption.name === name)[0].id;
|
||||
const affiliationOption = Setting.getArrayItem(this.state.affiliationOptions, "name", name);
|
||||
const id = affiliationOption.id;
|
||||
this.updateUserField('affiliation', name);
|
||||
this.updateUserField('score', id);
|
||||
})}>
|
||||
|
@ -30,9 +30,11 @@ export const CountDownInput = (props) => {
|
||||
const [checkType, setCheckType] = React.useState("");
|
||||
const [coolDown, setCoolDown] = React.useState(false);
|
||||
const [checkId, setCheckId] = React.useState("");
|
||||
const [buttonDisabled, setButtonDisabled] = React.useState(false);
|
||||
|
||||
const countDown = (leftTime) => {
|
||||
if (leftTime === 0) {
|
||||
setButtonDisabled(false);
|
||||
setCoolDown(false);
|
||||
setButtonText(defaultButtonText);
|
||||
return;
|
||||
@ -41,20 +43,13 @@ export const CountDownInput = (props) => {
|
||||
setTimeout(() => countDown(leftTime - 1), 1000);
|
||||
}
|
||||
|
||||
const clickButton = () => {
|
||||
if (coolDown) {
|
||||
Setting.showMessage("error", i18next.t("general:Cooling down"));
|
||||
return;
|
||||
}
|
||||
loadHumanCheck();
|
||||
}
|
||||
|
||||
const handleOk = () => {
|
||||
setVisible(false);
|
||||
onButtonClick(checkType, checkId, key, ...onButtonClickArgs).then(res => {
|
||||
setKey("");
|
||||
if (res) {
|
||||
setCoolDown(true);
|
||||
setButtonDisabled(true)
|
||||
countDown(coolDownTime);
|
||||
}
|
||||
})
|
||||
@ -124,13 +119,13 @@ export const CountDownInput = (props) => {
|
||||
placeholder={placeHolder}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
enterButton={
|
||||
<Button type={"primary"} disabled={disabled}>
|
||||
<Button type={"primary"} disabled={disabled || buttonDisabled}>
|
||||
<div style={{fontSize: 14}}>
|
||||
{buttonText}
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
onSearch={clickButton}
|
||||
onSearch={loadHumanCheck}
|
||||
/>
|
||||
<Modal
|
||||
closable={false}
|
||||
|
@ -12,6 +12,12 @@
|
||||
"User": "User",
|
||||
"Applications": "Applications",
|
||||
"Application": "Application",
|
||||
"Records": "Records",
|
||||
"Client ip": "Client ip",
|
||||
"Timestamp": "Timestamp",
|
||||
"Username": "Username",
|
||||
"Request uri": "Request uri",
|
||||
"LDAPs": "LDAPs",
|
||||
"Save": "Save",
|
||||
"Add": "Add",
|
||||
"Action": "Action",
|
||||
@ -267,5 +273,28 @@
|
||||
"Email/Phone": "Email/Phone",
|
||||
"Change Password": "Change Password",
|
||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
||||
},
|
||||
"ldap": {
|
||||
"Server Name": "Server Name",
|
||||
"Host": "Host",
|
||||
"Server": "Server",
|
||||
"Base DN": "Base DN",
|
||||
"Admin": "Admin",
|
||||
"Admin Password": "Admin Password",
|
||||
"Auto Sync": "Auto Sync",
|
||||
"Last Sync": "Last Sync",
|
||||
"Sync": "Sync",
|
||||
"ID": "ID",
|
||||
"Server Host": "Server Host",
|
||||
"Server Port": "Server Port",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Sync users": "Sync users",
|
||||
"Server Name - Tooltip": "LDAP server config display name",
|
||||
"Server Host - Tooltip": "LDAP server host",
|
||||
"Server Port - Tooltip": "LDAP server port",
|
||||
"Base DN - Tooltip": "LDAP search base DN",
|
||||
"Admin - Tooltip": "LDAP server admin CN or ID",
|
||||
"Admin Password - Tooltip": "LDAP server admin password",
|
||||
"Auto Sync - Tooltip": "Auto sync config, disable if is 0"
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,12 @@
|
||||
"User": "User",
|
||||
"Applications": "Applications",
|
||||
"Application": "Application",
|
||||
"Records": "Records",
|
||||
"Client ip": "Client ip",
|
||||
"Timestamp": "Timestamp",
|
||||
"Username": "Username",
|
||||
"Request uri": "Request uri",
|
||||
"LDAPs": "LDAPs",
|
||||
"Save": "Save",
|
||||
"Add": "Add",
|
||||
"Action": "Action",
|
||||
@ -277,5 +283,29 @@
|
||||
"Email/Phone": "Email/Phone",
|
||||
"Change Password": "Change Password",
|
||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
||||
},
|
||||
"ldap":
|
||||
{
|
||||
"Server Name": "Server Name",
|
||||
"Host": "Host",
|
||||
"Server": "Server",
|
||||
"Base DN": "Base DN",
|
||||
"Admin": "Admin",
|
||||
"Admin Password": "Admin Password",
|
||||
"Auto Sync": "Auto Sync",
|
||||
"Last Sync": "Last Sync",
|
||||
"Sync": "Sync",
|
||||
"ID": "ID",
|
||||
"Server Host": "Server Host",
|
||||
"Server Port": "Server Port",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Sync users": "Sync users",
|
||||
"Server Name - Tooltip": "LDAP server config display name",
|
||||
"Server Host - Tooltip": "LDAP server host",
|
||||
"Server Port - Tooltip": "LDAP server port",
|
||||
"Base DN - Tooltip": "LDAP search base DN",
|
||||
"Admin - Tooltip": "LDAP server admin CN or ID",
|
||||
"Admin Password - Tooltip": "LDAP server admin password",
|
||||
"Auto Sync - Tooltip": "Auto sync config, disable if is 0"
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,12 @@
|
||||
"User": "User",
|
||||
"Applications": "Applications",
|
||||
"Application": "Application",
|
||||
"Records": "Records",
|
||||
"Client ip": "Client ip",
|
||||
"Timestamp": "Timestamp",
|
||||
"Username": "Username",
|
||||
"Request uri": "Request uri",
|
||||
"LDAPs": "LDAPs",
|
||||
"Save": "Save",
|
||||
"Add": "Add",
|
||||
"Action": "Action",
|
||||
@ -267,5 +273,28 @@
|
||||
"Email/Phone": "Email/Phone",
|
||||
"Change Password": "Change Password",
|
||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
||||
},
|
||||
"ldap": {
|
||||
"Server Name": "Server Name",
|
||||
"Host": "Host",
|
||||
"Server": "Server",
|
||||
"Base DN": "Base DN",
|
||||
"Admin": "Admin",
|
||||
"Admin Password": "Admin Password",
|
||||
"Auto Sync": "Auto Sync",
|
||||
"Last Sync": "Last Sync",
|
||||
"Sync": "Sync",
|
||||
"ID": "ID",
|
||||
"Server Host": "Server Host",
|
||||
"Server Port": "Server Port",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Sync users": "Sync users",
|
||||
"Server Name - Tooltip": "LDAP server config display name",
|
||||
"Server Host - Tooltip": "LDAP server host",
|
||||
"Server Port - Tooltip": "LDAP server port",
|
||||
"Base DN - Tooltip": "LDAP search base DN",
|
||||
"Admin - Tooltip": "LDAP server admin CN or ID",
|
||||
"Admin Password - Tooltip": "LDAP server admin password",
|
||||
"Auto Sync - Tooltip": "Auto sync config, disable if is 0"
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,12 @@
|
||||
"User": "User",
|
||||
"Applications": "Applications",
|
||||
"Application": "Application",
|
||||
"Records": "Records",
|
||||
"Client ip": "Client ip",
|
||||
"Timestamp": "Timestamp",
|
||||
"Username": "Username",
|
||||
"Request uri": "Request uri",
|
||||
"LDAPs": "LDAPs",
|
||||
"Save": "Save",
|
||||
"Add": "Add",
|
||||
"Action": "Action",
|
||||
@ -267,5 +273,28 @@
|
||||
"Email/Phone": "Email/Phone",
|
||||
"Change Password": "Change Password",
|
||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
||||
},
|
||||
"ldap": {
|
||||
"Server Name": "Server Name",
|
||||
"Host": "Host",
|
||||
"Server": "Server",
|
||||
"Base DN": "Base DN",
|
||||
"Admin": "Admin",
|
||||
"Admin Password": "Admin Password",
|
||||
"Auto Sync": "Auto Sync",
|
||||
"Last Sync": "Last Sync",
|
||||
"Sync": "Sync",
|
||||
"ID": "ID",
|
||||
"Server Host": "Server Host",
|
||||
"Server Port": "Server Port",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Sync users": "Sync users",
|
||||
"Server Name - Tooltip": "LDAP server config display name",
|
||||
"Server Host - Tooltip": "LDAP server host",
|
||||
"Server Port - Tooltip": "LDAP server port",
|
||||
"Base DN - Tooltip": "LDAP search base DN",
|
||||
"Admin - Tooltip": "LDAP server admin CN or ID",
|
||||
"Admin Password - Tooltip": "LDAP server admin password",
|
||||
"Auto Sync - Tooltip": "Auto sync config, disable if is 0"
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,12 @@
|
||||
"User": "User",
|
||||
"Applications": "Applications",
|
||||
"Application": "Application",
|
||||
"Records": "Records",
|
||||
"Client ip": "Client ip",
|
||||
"Timestamp": "Timestamp",
|
||||
"Username": "Username",
|
||||
"Request uri": "Request uri",
|
||||
"LDAPs": "LDAPs",
|
||||
"Save": "Save",
|
||||
"Add": "Add",
|
||||
"Action": "Action",
|
||||
@ -267,5 +273,28 @@
|
||||
"Email/Phone": "Email/Phone",
|
||||
"Change Password": "Change Password",
|
||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
||||
},
|
||||
"ldap": {
|
||||
"Server Name": "Server Name",
|
||||
"Host": "Host",
|
||||
"Server": "Server",
|
||||
"Base DN": "Base DN",
|
||||
"Admin": "Admin",
|
||||
"Admin Password": "Admin Password",
|
||||
"Auto Sync": "Auto Sync",
|
||||
"Last Sync": "Last Sync",
|
||||
"Sync": "Sync",
|
||||
"ID": "ID",
|
||||
"Server Host": "Server Host",
|
||||
"Server Port": "Server Port",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Sync users": "Sync users",
|
||||
"Server Name - Tooltip": "LDAP server config display name",
|
||||
"Server Host - Tooltip": "LDAP server host",
|
||||
"Server Port - Tooltip": "LDAP server port",
|
||||
"Base DN - Tooltip": "LDAP search base DN",
|
||||
"Admin - Tooltip": "LDAP server admin CN or ID",
|
||||
"Admin Password - Tooltip": "LDAP server admin password",
|
||||
"Auto Sync - Tooltip": "Auto sync config, disable if is 0"
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,12 @@
|
||||
"User": "User",
|
||||
"Applications": "Applications",
|
||||
"Application": "Application",
|
||||
"Records": "Records",
|
||||
"Client ip": "Client ip",
|
||||
"Timestamp": "Timestamp",
|
||||
"Username": "Username",
|
||||
"Request uri": "Request uri",
|
||||
"LDAPs": "LDAPs",
|
||||
"Save": "Save",
|
||||
"Add": "Add",
|
||||
"Action": "Action",
|
||||
@ -267,5 +273,28 @@
|
||||
"Email/Phone": "Email/Phone",
|
||||
"Change Password": "Change Password",
|
||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
||||
},
|
||||
"ldap": {
|
||||
"Server Name": "Server Name",
|
||||
"Host": "Host",
|
||||
"Server": "Server",
|
||||
"Base DN": "Base DN",
|
||||
"Admin": "Admin",
|
||||
"Admin Password": "Admin Password",
|
||||
"Auto Sync": "Auto Sync",
|
||||
"Last Sync": "Last Sync",
|
||||
"Sync": "Sync",
|
||||
"ID": "ID",
|
||||
"Server Host": "Server Host",
|
||||
"Server Port": "Server Port",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Sync users": "Sync users",
|
||||
"Server Name - Tooltip": "LDAP server config display name",
|
||||
"Server Host - Tooltip": "LDAP server host",
|
||||
"Server Port - Tooltip": "LDAP server port",
|
||||
"Base DN - Tooltip": "LDAP search base DN",
|
||||
"Admin - Tooltip": "LDAP server admin CN or ID",
|
||||
"Admin Password - Tooltip": "LDAP server admin password",
|
||||
"Auto Sync - Tooltip": "Auto sync config, disable if is 0"
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,19 @@
|
||||
"Organizations": "组织",
|
||||
"Organizations - Tooltip": "Unique string-style identifier",
|
||||
"Organization": "组织",
|
||||
"Organization - Tooltip": "Unique string-style identifier",
|
||||
"Organization - Tooltip": "用户所属组织",
|
||||
"Providers": "提供商",
|
||||
"Providers - Tooltip": "第三方登录需要配置的提供方",
|
||||
"Providers - Tooltip": "可用于登录的第三方应用程序列表",
|
||||
"Users": "用户",
|
||||
"User": "用户",
|
||||
"Applications": "应用",
|
||||
"Application": "应用",
|
||||
"Records": "Records",
|
||||
"Client ip": "Client ip",
|
||||
"Timestamp": "Timestamp",
|
||||
"Username": "用户名",
|
||||
"Request uri": "Request uri",
|
||||
"LDAPs": "LDAPs",
|
||||
"Save": "保存",
|
||||
"Add": "添加",
|
||||
"Action": "操作",
|
||||
@ -42,9 +48,9 @@
|
||||
"Password salt - Tooltip": "用于密码加密的随机参数",
|
||||
"Password": "密码",
|
||||
"Email": "电子邮箱",
|
||||
"Email - Tooltip": "电子邮件:",
|
||||
"Email - Tooltip": "email",
|
||||
"Phone": "手机号",
|
||||
"Phone - Tooltip": "手机号",
|
||||
"Phone - Tooltip": "Phone",
|
||||
"Logo": "图标",
|
||||
"Logo - Tooltip": "应用程序向外展示的图标",
|
||||
"User containers": "用户容器",
|
||||
@ -53,14 +59,14 @@
|
||||
"Applications that requires authentication": "需要鉴权的应用",
|
||||
"Swagger": "API文档",
|
||||
"Phone Prefix": "手机号前缀",
|
||||
"Phone Prefix - Tooltip": "移动电话号码前缀,用于区分国家或地区",
|
||||
"Phone Prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
|
||||
"Enter the code": "输入验证码",
|
||||
"Captcha": "人机验证码",
|
||||
"Authorization code": "授权码",
|
||||
"Access token": "访问令牌",
|
||||
"Expires in": "有效期",
|
||||
"Scope": "范围",
|
||||
"Description": "Description",
|
||||
"Description": "描述",
|
||||
"Description - Tooltip": "与此有关的描述信息",
|
||||
"Token expire": "Token expire",
|
||||
"Token expire - Tooltip": "签发的令牌的授权时间",
|
||||
@ -100,7 +106,7 @@
|
||||
"Please input your verification code!": "请输入您的验证码",
|
||||
"Send Code": "发送验证码",
|
||||
"Empty Code": "验证码为空",
|
||||
"Code Sent": "验证码已发送",
|
||||
"Code Sent": "Code Sent",
|
||||
"Code You Received": "验证码",
|
||||
"Enter your code": "输入你的验证码",
|
||||
"You can only send one code in 60s.": "每分钟你只能发送一次验证码",
|
||||
@ -141,36 +147,36 @@
|
||||
"organization": {
|
||||
"Edit Organization": "修改组织",
|
||||
"Website URL": "网页地址",
|
||||
"Website URL - Tooltip": "唯一的、字符串式的ID"
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
"provider": {
|
||||
"App ID": "应用ID",
|
||||
"App ID - Tooltip": "唯一的、字符串式的ID",
|
||||
"App ID": "App ID",
|
||||
"App ID - Tooltip": "Unique string-style identifier",
|
||||
"Name": "名称",
|
||||
"Display name": "显示名称",
|
||||
"Category": "分类",
|
||||
"Category - Tooltip": "唯一的、字符串式的ID",
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Type": "类型",
|
||||
"Type - Tooltip": "唯一的、字符串式的ID",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "唯一的、字符串式的ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client secret": "Client secret",
|
||||
"Client secret - Tooltip": "唯一的、字符串式的ID",
|
||||
"Client secret - Tooltip": "Unique string-style identifier",
|
||||
"Host": "主机",
|
||||
"Host - Tooltip": "唯一的、字符串式的ID",
|
||||
"Host - Tooltip": "Unique string-style identifier",
|
||||
"Port": "端口号",
|
||||
"Port - Tooltip": "唯一的、字符串式的ID",
|
||||
"Port - Tooltip": "Unique string-style identifier",
|
||||
"Email Title": "邮件标题",
|
||||
"Email Title - Tooltip": "唯一的、字符串式的ID",
|
||||
"Email Title - Tooltip": "Unique string-style identifier",
|
||||
"Email Content": "邮件内容",
|
||||
"Email Content - Tooltip": "唯一的、字符串式的ID",
|
||||
"Email Content - Tooltip": "Unique string-style identifier",
|
||||
"Region ID": "地域ID",
|
||||
"Sign Name": "签名名称",
|
||||
"Sign Name - Tooltip": "唯一的、字符串式的ID",
|
||||
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||
"Template Code": "模板CODE",
|
||||
"Template Code - Tooltip": "唯一的、字符串式的ID",
|
||||
"Template Code - Tooltip": "Unique string-style identifier",
|
||||
"Provider URL": "提供商URL",
|
||||
"Provider URL - Tooltip": "唯一的、字符串式的ID",
|
||||
"Provider URL - Tooltip": "Unique string-style identifier",
|
||||
"Edit Provider": "修改提供商"
|
||||
},
|
||||
"user": {
|
||||
@ -181,12 +187,12 @@
|
||||
"Set password...": "设置密码...",
|
||||
"Modify password...": "修改密码...",
|
||||
"Address": "地址",
|
||||
"Address - Tooltip": "唯一的、字符串式的ID",
|
||||
"Address - Tooltip": "Unique string-style identifier",
|
||||
"Affiliation": "工作单位",
|
||||
"Affiliation - Tooltip": "唯一的、字符串式的ID",
|
||||
"Affiliation - Tooltip": "Unique string-style identifier",
|
||||
"Modify affiliation": "修改工作单位",
|
||||
"Tag": "标签",
|
||||
"Tag - Tooltip": "唯一的、字符串式的ID",
|
||||
"Tag - Tooltip": "Unique string-style identifier",
|
||||
"Third-party logins": "第三方登录",
|
||||
"Third-party logins - Tooltip": "使用第三方应用程序登录",
|
||||
"Properties": "属性",
|
||||
@ -195,9 +201,9 @@
|
||||
"Is admin": "是管理员",
|
||||
"Is admin - Tooltip": "是应用程序管理员",
|
||||
"Is global admin": "是全局管理员",
|
||||
"Is global admin - Tooltip": "是应用程序管理员",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Is forbidden": "被禁用",
|
||||
"Is forbidden - Tooltip": "账户是否已被禁用",
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Empty input!": "输入为空!",
|
||||
"Two passwords you typed do not match.": "两次输入的密码不匹配。",
|
||||
"Password Set": "密码已设置",
|
||||
@ -234,8 +240,8 @@
|
||||
"application": {
|
||||
"Edit Application": "修改应用",
|
||||
"Password ON": "Password ON",
|
||||
"Password ON - Tooltip": "是否允许密码登录",
|
||||
"Enable signup": "启用注册",
|
||||
"Password ON - Tooltip": "Whether to allow password login",
|
||||
"Enable signup": "Enable signup",
|
||||
"Enable signup - Tooltip": "是否允许用户注册",
|
||||
"Login page preview": "登录页面预览",
|
||||
"Test signup page..": "测试注册页面..",
|
||||
@ -245,7 +251,7 @@
|
||||
"Redirect URLs": "回调URLs",
|
||||
"Redirect URLs - Tooltip": "登录成功后重定向地址列表",
|
||||
"Signup items": "注册项",
|
||||
"Signup items - Tooltip": "注册用户注册时需要填写的项目"
|
||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register"
|
||||
},
|
||||
"forget": {
|
||||
"Please input your application!": "请输入您的应用名称!",
|
||||
@ -267,5 +273,28 @@
|
||||
"Email/Phone": "邮箱/手机号",
|
||||
"Change Password": "修改密码",
|
||||
"Choose email verification or mobile verification": "选择邮箱验证或手机验证"
|
||||
},
|
||||
"ldap": {
|
||||
"Server Name": "Server Name",
|
||||
"Host": "Host",
|
||||
"Server": "Server",
|
||||
"Base DN": "Base DN",
|
||||
"Admin": "Admin",
|
||||
"Admin Password": "Admin Password",
|
||||
"Auto Sync": "Auto Sync",
|
||||
"Last Sync": "Last Sync",
|
||||
"Sync": "Sync",
|
||||
"ID": "ID",
|
||||
"Server Host": "Server Host",
|
||||
"Server Port": "Server Port",
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Sync users": "Sync users",
|
||||
"Server Name - Tooltip": "LDAP server config display name",
|
||||
"Server Host - Tooltip": "LDAP server host",
|
||||
"Server Port - Tooltip": "LDAP server port",
|
||||
"Base DN - Tooltip": "LDAP search base DN",
|
||||
"Admin - Tooltip": "LDAP server admin CN or ID",
|
||||
"Admin Password - Tooltip": "LDAP server admin password",
|
||||
"Auto Sync - Tooltip": "Auto sync config, disable if is 0"
|
||||
}
|
||||
}
|
||||
|
@ -1217,6 +1217,13 @@
|
||||
semver "^7.3.2"
|
||||
webpack-merge "^4.2.2"
|
||||
|
||||
"@crowdin/cli@^3.6.4":
|
||||
version "3.6.4"
|
||||
resolved "https://registry.yarnpkg.com/@crowdin/cli/-/cli-3.6.4.tgz#37e872fbbc85fdfb55e8deba4e35d4393a802aad"
|
||||
integrity sha512-kFqTf1dFhtSMfF4YWyJ6EAKjjtBEVtHhX2f9cBYc8I51KMTFIOzqeArKFL2qhhq/1Ition2wuE1z7ySwA341+w==
|
||||
dependencies:
|
||||
shelljs "^0.8.4"
|
||||
|
||||
"@csstools/convert-colors@^1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
|
||||
@ -5489,6 +5496,18 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0:
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob@^7.0.0:
|
||||
version "7.1.7"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
|
||||
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
@ -6073,6 +6092,11 @@ internal-slot@^1.0.3:
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
interpret@^1.0.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
|
||||
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
|
||||
|
||||
ip-regex@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
||||
@ -10047,6 +10071,13 @@ readdirp@~3.5.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
rechoir@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
|
||||
integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=
|
||||
dependencies:
|
||||
resolve "^1.1.6"
|
||||
|
||||
recursive-readdir@2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f"
|
||||
@ -10302,9 +10333,9 @@ resolve@1.18.1:
|
||||
is-core-module "^2.0.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2, resolve@^1.8.1:
|
||||
resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2, resolve@^1.8.1:
|
||||
version "1.20.0"
|
||||
resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
||||
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
||||
dependencies:
|
||||
is-core-module "^2.2.0"
|
||||
@ -10706,6 +10737,15 @@ shell-quote@1.7.2:
|
||||
resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
|
||||
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
|
||||
|
||||
shelljs@^0.8.4:
|
||||
version "0.8.4"
|
||||
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
|
||||
integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==
|
||||
dependencies:
|
||||
glob "^7.0.0"
|
||||
interpret "^1.0.0"
|
||||
rechoir "^0.6.2"
|
||||
|
||||
shellwords@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
|
||||
|
Reference in New Issue
Block a user