mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-09 01:13:41 +08:00
Compare commits
40 Commits
v1.951.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:
|
with:
|
||||||
commit_message: Sync from crowdin
|
commit_message: Sync from crowdin
|
||||||
file_pattern: web/src/locales/*
|
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
|
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 (Dev Environment)
|
||||||
|
|
||||||
- Run backend (in port 8000):
|
- Run backend (in port 8000):
|
||||||
|
@ -82,7 +82,7 @@ p, *, *, GET, /api/get-application, *, *
|
|||||||
p, *, *, GET, /api/get-users, *, *
|
p, *, *, GET, /api/get-users, *, *
|
||||||
p, *, *, GET, /api/get-user, *, *
|
p, *, *, GET, /api/get-user, *, *
|
||||||
p, *, *, GET, /api/get-organizations, *, *
|
p, *, *, GET, /api/get-organizations, *, *
|
||||||
p, *, *, GET, /api/get-default-application, *, *
|
p, *, *, GET, /api/get-user-application, *, *
|
||||||
p, *, *, GET, /api/get-default-providers, *, *
|
p, *, *, GET, /api/get-default-providers, *, *
|
||||||
p, *, *, POST, /api/upload-avatar, *, *
|
p, *, *, POST, /api/upload-avatar, *, *
|
||||||
p, *, *, POST, /api/unlink, *, *
|
p, *, *, POST, /api/unlink, *, *
|
||||||
|
@ -52,6 +52,8 @@ type RequestForm struct {
|
|||||||
EmailCode string `json:"emailCode"`
|
EmailCode string `json:"emailCode"`
|
||||||
PhoneCode string `json:"phoneCode"`
|
PhoneCode string `json:"phoneCode"`
|
||||||
PhonePrefix string `json:"phonePrefix"`
|
PhonePrefix string `json:"phonePrefix"`
|
||||||
|
|
||||||
|
AutoSignin bool `json:"autoSignin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
@ -78,8 +80,8 @@ type HumanCheck struct {
|
|||||||
func (c *ApiController) Signup() {
|
func (c *ApiController) Signup() {
|
||||||
var resp Response
|
var resp Response
|
||||||
|
|
||||||
if c.GetSessionUser() != "" {
|
if c.GetSessionUsername() != "" {
|
||||||
c.ResponseErrorWithData("Please sign out first before signing up", c.GetSessionUser())
|
c.ResponseErrorWithData("Please sign out first before signing up", c.GetSessionUsername())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +163,7 @@ func (c *ApiController) Signup() {
|
|||||||
|
|
||||||
if application.HasPromptPage() {
|
if application.HasPromptPage() {
|
||||||
// The prompt page needs the user to be signed in
|
// The prompt page needs the user to be signed in
|
||||||
c.SetSessionUser(user.GetId())
|
c.SetSessionUsername(user.GetId())
|
||||||
}
|
}
|
||||||
|
|
||||||
object.DisableVerificationCode(form.Email)
|
object.DisableVerificationCode(form.Email)
|
||||||
@ -181,10 +183,11 @@ func (c *ApiController) Signup() {
|
|||||||
func (c *ApiController) Logout() {
|
func (c *ApiController) Logout() {
|
||||||
var resp Response
|
var resp Response
|
||||||
|
|
||||||
user := c.GetSessionUser()
|
user := c.GetSessionUsername()
|
||||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||||
|
|
||||||
c.SetSessionUser("")
|
c.SetSessionUsername("")
|
||||||
|
c.SetSessionData(nil)
|
||||||
|
|
||||||
resp = Response{Status: "ok", Msg: "", Data: user}
|
resp = Response{Status: "ok", Msg: "", Data: user}
|
||||||
|
|
||||||
|
@ -44,23 +44,19 @@ func (c *ApiController) GetApplication() {
|
|||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Title GetDefaultApplication
|
// @Title GetUserApplication
|
||||||
// @Description get the detail of the default application
|
// @Description get the detail of the user's application
|
||||||
// @Param owner query string true "The owner of the application."
|
// @Param id query string true "The id of the user"
|
||||||
// @Success 200 {object} object.Application The Response object
|
// @Success 200 {object} object.Application The Response object
|
||||||
// @router /get-default-application [get]
|
// @router /get-user-application [get]
|
||||||
func (c *ApiController) GetDefaultApplication() {
|
func (c *ApiController) GetUserApplication() {
|
||||||
//owner := c.Input().Get("owner")
|
id := c.Input().Get("id")
|
||||||
|
user := object.GetUser(id)
|
||||||
if c.GetSessionUser() == "" {
|
if user == nil {
|
||||||
c.Data["json"] = nil
|
c.ResponseError("No such user.")
|
||||||
c.ServeJSON()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
username := c.GetSessionUser()
|
|
||||||
user := object.GetUser(username)
|
|
||||||
|
|
||||||
c.Data["json"] = object.GetApplicationByUser(user)
|
c.Data["json"] = object.GetApplicationByUser(user)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/casdoor/casdoor/idp"
|
"github.com/casdoor/casdoor/idp"
|
||||||
@ -38,7 +39,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
userId := user.GetId()
|
userId := user.GetId()
|
||||||
resp := &Response{}
|
resp := &Response{}
|
||||||
if form.Type == ResponseTypeLogin {
|
if form.Type == ResponseTypeLogin {
|
||||||
c.SetSessionUser(userId)
|
c.SetSessionUsername(userId)
|
||||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||||
resp = &Response{Status: "ok", Msg: "", Data: userId}
|
resp = &Response{Status: "ok", Msg: "", Data: userId}
|
||||||
} else if form.Type == ResponseTypeCode {
|
} else if form.Type == ResponseTypeCode {
|
||||||
@ -53,11 +54,21 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
|
|
||||||
if application.HasPromptPage() {
|
if application.HasPromptPage() {
|
||||||
// The prompt page needs the user to be signed in
|
// The prompt page needs the user to be signed in
|
||||||
c.SetSessionUser(userId)
|
c.SetSessionUsername(userId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
|
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
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,8 +119,8 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
if form.Username != "" {
|
if form.Username != "" {
|
||||||
if form.Type == ResponseTypeLogin {
|
if form.Type == ResponseTypeLogin {
|
||||||
if c.GetSessionUser() != "" {
|
if c.GetSessionUsername() != "" {
|
||||||
resp = &Response{Status: "error", Msg: "Please log out first before signing in", Data: c.GetSessionUser()}
|
resp = &Response{Status: "error", Msg: "Please log out first before signing in", Data: c.GetSessionUsername()}
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
return
|
return
|
||||||
@ -181,6 +192,12 @@ func (c *ApiController) Login() {
|
|||||||
} else {
|
} else {
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
resp = c.HandleLoggedIn(application, user, &form)
|
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 != "" {
|
} else if form.Provider != "" {
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
@ -252,6 +269,12 @@ func (c *ApiController) Login() {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
resp = c.HandleLoggedIn(application, user, &form)
|
resp = c.HandleLoggedIn(application, user, &form)
|
||||||
|
|
||||||
|
record := util.Records(c.Ctx)
|
||||||
|
record.Organization = application.Organization
|
||||||
|
record.Username = user.Name
|
||||||
|
|
||||||
|
object.AddRecord(record)
|
||||||
} else {
|
} else {
|
||||||
// Sign up via OAuth
|
// Sign up via OAuth
|
||||||
if !application.EnableSignUp {
|
if !application.EnableSignUp {
|
||||||
@ -294,10 +317,16 @@ func (c *ApiController) Login() {
|
|||||||
object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||||
|
|
||||||
resp = c.HandleLoggedIn(application, user, &form)
|
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}
|
//resp = &Response{Status: "ok", Msg: "", Data: res}
|
||||||
} else { // form.Method != "signup"
|
} else { // form.Method != "signup"
|
||||||
userId := c.GetSessionUser()
|
userId := c.GetSessionUsername()
|
||||||
if userId == "" {
|
if userId == "" {
|
||||||
resp = &Response{Status: "error", Msg: "The account does not exist", Data: userInfo}
|
resp = &Response{Status: "error", Msg: "The account does not exist", Data: userInfo}
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
|
@ -14,13 +14,32 @@
|
|||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import "github.com/astaxie/beego"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
type ApiController struct {
|
type ApiController struct {
|
||||||
beego.Controller
|
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")
|
user := c.GetSession("username")
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return ""
|
return ""
|
||||||
@ -29,10 +48,34 @@ func (c *ApiController) GetSessionUser() string {
|
|||||||
return user.(string)
|
return user.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) SetSessionUser(user string) {
|
func (c *ApiController) SetSessionUsername(user string) {
|
||||||
c.SetSession("username", user)
|
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 {
|
func wrapActionResponse(affected bool) *Response {
|
||||||
if affected {
|
if affected {
|
||||||
return &Response{Status: "ok", Msg: "", Data: "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")
|
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
||||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||||
|
|
||||||
requestUserId := c.GetSessionUser()
|
requestUserId := c.GetSessionUsername()
|
||||||
if requestUserId == "" {
|
if requestUserId == "" {
|
||||||
c.ResponseError("Please login first.")
|
c.ResponseError("Please login first.")
|
||||||
return
|
return
|
||||||
@ -223,7 +223,7 @@ func (c *ApiController) SetPassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SetSessionUser("")
|
c.SetSessionUsername("")
|
||||||
|
|
||||||
targetUser.Password = newPassword
|
targetUser.Password = newPassword
|
||||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||||
|
@ -60,7 +60,7 @@ func (c *ApiController) ResponseErrorWithData(error string, data interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) RequireSignedIn() (string, bool) {
|
func (c *ApiController) RequireSignedIn() (string, bool) {
|
||||||
userId := c.GetSessionUser()
|
userId := c.GetSessionUsername()
|
||||||
if userId == "" {
|
if userId == "" {
|
||||||
resp := Response{Status: "error", Msg: "Please sign in first"}
|
resp := Response{Status: "error", Msg: "Please sign in first"}
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
|
|
||||||
func (c *ApiController) getCurrentUser() *object.User {
|
func (c *ApiController) getCurrentUser() *object.User {
|
||||||
var user *object.User
|
var user *object.User
|
||||||
userId := c.GetSessionUser()
|
userId := c.GetSessionUsername()
|
||||||
if userId == "" {
|
if userId == "" {
|
||||||
user = nil
|
user = nil
|
||||||
} else {
|
} 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/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
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/go-sql-driver/mysql v1.5.0
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/jinzhu/configor v1.2.1 // indirect
|
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=
|
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 h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
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/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 h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
|
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.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.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.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
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=
|
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-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-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-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 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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=
|
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)
|
return NewWeiBoIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "Gitee" {
|
} else if providerType == "Gitee" {
|
||||||
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
|
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
|
} else if providerType == "LinkedIn" {
|
||||||
|
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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.StaticFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoLoginFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoLoginFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||||
|
|
||||||
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
||||||
beego.BConfig.WebConfig.Session.SessionProvider = "file"
|
beego.BConfig.WebConfig.Session.SessionProvider = "file"
|
||||||
|
@ -133,4 +133,13 @@ func (a *Adapter) createTable() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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,8 +119,12 @@ func GetApplicationByOrganizationName(organization string) *Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetApplicationByUser(user *User) *Application {
|
func GetApplicationByUser(user *User) *Application {
|
||||||
|
if user.SignupApplication != "" {
|
||||||
|
return getApplication("admin", user.SignupApplication)
|
||||||
|
} else {
|
||||||
return GetApplicationByOrganizationName(user.Owner)
|
return GetApplicationByOrganizationName(user.Owner)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetApplicationByClientId(clientId string) *Application {
|
func GetApplicationByClientId(clientId string) *Application {
|
||||||
application := Application{}
|
application := 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
|
package object
|
||||||
|
|
||||||
import (
|
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
|
package object
|
||||||
|
|
||||||
import "github.com/casdoor/casdoor/util"
|
import "github.com/casdoor/casdoor/util"
|
||||||
@ -6,6 +20,7 @@ func InitDb() {
|
|||||||
initBuiltInOrganization()
|
initBuiltInOrganization()
|
||||||
initBuiltInUser()
|
initBuiltInUser()
|
||||||
initBuiltInApplication()
|
initBuiltInApplication()
|
||||||
|
initBuiltInLdap()
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBuiltInOrganization() {
|
func initBuiltInOrganization() {
|
||||||
@ -20,6 +35,9 @@ func initBuiltInOrganization() {
|
|||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
DisplayName: "Built-in Organization",
|
DisplayName: "Built-in Organization",
|
||||||
WebsiteUrl: "https://example.com",
|
WebsiteUrl: "https://example.com",
|
||||||
|
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
||||||
|
PhonePrefix: "86",
|
||||||
|
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||||
PasswordType: "plain",
|
PasswordType: "plain",
|
||||||
}
|
}
|
||||||
AddOrganization(organization)
|
AddOrganization(organization)
|
||||||
@ -36,11 +54,13 @@ func initBuiltInUser() {
|
|||||||
Name: "admin",
|
Name: "admin",
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
Id: util.GenerateId(),
|
Id: util.GenerateId(),
|
||||||
|
Type: "normal-user",
|
||||||
Password: "123",
|
Password: "123",
|
||||||
DisplayName: "Admin",
|
DisplayName: "Admin",
|
||||||
Avatar: "https://casbin.org/img/casbin.svg",
|
Avatar: "https://casbin.org/img/casbin.svg",
|
||||||
Email: "admin@example.com",
|
Email: "admin@example.com",
|
||||||
Phone: "1-12345678",
|
Phone: "12345678910",
|
||||||
|
Address: []string{},
|
||||||
Affiliation: "Example Inc.",
|
Affiliation: "Example Inc.",
|
||||||
Tag: "staff",
|
Tag: "staff",
|
||||||
IsAdmin: true,
|
IsAdmin: true,
|
||||||
@ -74,3 +94,24 @@ func initBuiltInApplication() {
|
|||||||
}
|
}
|
||||||
AddApplication(application)
|
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"`
|
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
||||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||||
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
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"`
|
Properties map[string]string `json:"properties"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +143,9 @@ func UpdateUser(id string, user *User) bool {
|
|||||||
|
|
||||||
user.UpdateUserHash()
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
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
|
package object
|
||||||
|
|
||||||
import (
|
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/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
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-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-providers", &controllers.ApiController{}, "GET:GetProviders")
|
||||||
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
|
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-applications", &controllers.ApiController{}, "GET:GetApplications")
|
||||||
beego.Router("/api/get-application", &controllers.ApiController{}, "GET:GetApplication")
|
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/update-application", &controllers.ApiController{}, "POST:UpdateApplication")
|
||||||
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
|
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
|
||||||
beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication")
|
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/add-token", &controllers.ApiController{}, "POST:AddToken")
|
||||||
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
||||||
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
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": {
|
"/api/get-global-users": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"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": {
|
"/api/get-token": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -700,18 +810,65 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/register": {
|
"/api/set-password": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"api"
|
"api"
|
||||||
],
|
],
|
||||||
"description": "register a new user",
|
"description": "set password",
|
||||||
"operationId": "ApiController.Register",
|
"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": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"in": "formData",
|
"in": "formData",
|
||||||
"name": "username",
|
"name": "username",
|
||||||
"description": "The username to register",
|
"description": "The username to sign up",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -965,7 +1122,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"api"
|
"api"
|
||||||
],
|
],
|
||||||
"description": "register a new user",
|
"description": "upload avatar",
|
||||||
"operationId": "ApiController.UploadAvatar",
|
"operationId": "ApiController.UploadAvatar",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@ -995,7 +1152,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"1471.0xc0003bd890.false": {
|
"1671.0xc00044ab10.false": {
|
||||||
|
"title": "false",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"1705.0xc00044ab40.false": {
|
||||||
"title": "false",
|
"title": "false",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
@ -1008,7 +1169,10 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/1471.0xc0003bd890.false"
|
"$ref": "#/definitions/1671.0xc00044ab10.false"
|
||||||
|
},
|
||||||
|
"data2": {
|
||||||
|
"$ref": "#/definitions/1705.0xc00044ab40.false"
|
||||||
},
|
},
|
||||||
"msg": {
|
"msg": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -1023,7 +1187,10 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/1471.0xc0003bd890.false"
|
"$ref": "#/definitions/1671.0xc00044ab10.false"
|
||||||
|
},
|
||||||
|
"data2": {
|
||||||
|
"$ref": "#/definitions/1705.0xc00044ab40.false"
|
||||||
},
|
},
|
||||||
"msg": {
|
"msg": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -1037,6 +1204,9 @@
|
|||||||
"title": "Application",
|
"title": "Application",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"affiliationUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"clientId": {
|
"clientId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -1062,6 +1232,9 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
|
"forgetUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"homepageUrl": {
|
"homepageUrl": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -1074,19 +1247,16 @@
|
|||||||
"organization": {
|
"organization": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"organizationObj": {
|
||||||
|
"$ref": "#/definitions/object.Organization"
|
||||||
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"providerObjs": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/object.Provider"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"providers": {
|
"providers": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/object.ProviderItem"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redirectUris": {
|
"redirectUris": {
|
||||||
@ -1094,6 +1264,18 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"signinUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"signupItems": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/object.SignupItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"signupUrl": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1104,15 +1286,30 @@
|
|||||||
"createdTime": {
|
"createdTime": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"defaultAvatar": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"displayName": {
|
"displayName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"favicon": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"passwordSalt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"passwordType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phonePrefix": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"websiteUrl": {
|
"websiteUrl": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -1122,32 +1319,121 @@
|
|||||||
"title": "Provider",
|
"title": "Provider",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"appId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"clientId": {
|
"clientId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"clientSecret": {
|
"clientSecret": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"createdTime": {
|
"createdTime": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"displayName": {
|
"displayName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"host": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
"providerUrl": {
|
"providerUrl": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"regionId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"signName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"templateCode": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string"
|
"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": {
|
"object.Token": {
|
||||||
"title": "Token",
|
"title": "Token",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -1171,6 +1457,9 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"organization": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -1179,6 +1468,9 @@
|
|||||||
},
|
},
|
||||||
"tokenType": {
|
"tokenType": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1205,6 +1497,12 @@
|
|||||||
"title": "User",
|
"title": "User",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"affiliation": {
|
"affiliation": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -1214,27 +1512,45 @@
|
|||||||
"createdTime": {
|
"createdTime": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"dingtalk": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"displayName": {
|
"displayName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"facebook": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"gitee": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"github": {
|
"github": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"google": {
|
"google": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"hash": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"isAdmin": {
|
"isAdmin": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"isForbidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"isGlobalAdmin": {
|
"isGlobalAdmin": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"language": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -1244,17 +1560,65 @@
|
|||||||
"password": {
|
"password": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"passwordType": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"phone": {
|
"phone": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"preHash": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"qq": {
|
"qq": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"score": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"signupApplication": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"tag": {
|
"tag": {
|
||||||
"type": "string"
|
"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
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.Application'
|
$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:
|
/api/get-global-users:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -319,6 +358,39 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.Provider'
|
$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:
|
/api/get-token:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -456,16 +528,48 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/register:
|
/api/set-password:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- api
|
- api
|
||||||
description: register a new user
|
description: set password
|
||||||
operationId: ApiController.Register
|
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:
|
parameters:
|
||||||
- in: formData
|
- in: formData
|
||||||
name: username
|
name: username
|
||||||
description: The username to register
|
description: The username to sign up
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- in: formData
|
||||||
@ -633,7 +737,7 @@ paths:
|
|||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- api
|
- api
|
||||||
description: register a new user
|
description: upload avatar
|
||||||
operationId: ApiController.UploadAvatar
|
operationId: ApiController.UploadAvatar
|
||||||
parameters:
|
parameters:
|
||||||
- in: formData
|
- in: formData
|
||||||
@ -652,7 +756,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
definitions:
|
definitions:
|
||||||
1471.0xc0003bd890.false:
|
1671.0xc00044ab10.false:
|
||||||
|
title: "false"
|
||||||
|
type: object
|
||||||
|
1705.0xc00044ab40.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
RequestForm:
|
RequestForm:
|
||||||
@ -663,7 +770,9 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/1471.0xc0003bd890.false'
|
$ref: '#/definitions/1671.0xc00044ab10.false'
|
||||||
|
data2:
|
||||||
|
$ref: '#/definitions/1705.0xc00044ab40.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
status:
|
status:
|
||||||
@ -673,7 +782,9 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/1471.0xc0003bd890.false'
|
$ref: '#/definitions/1671.0xc00044ab10.false'
|
||||||
|
data2:
|
||||||
|
$ref: '#/definitions/1705.0xc00044ab40.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
status:
|
status:
|
||||||
@ -682,6 +793,8 @@ definitions:
|
|||||||
title: Application
|
title: Application
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
affiliationUrl:
|
||||||
|
type: string
|
||||||
clientId:
|
clientId:
|
||||||
type: string
|
type: string
|
||||||
clientSecret:
|
clientSecret:
|
||||||
@ -699,6 +812,8 @@ definitions:
|
|||||||
expireInHours:
|
expireInHours:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
forgetUrl:
|
||||||
|
type: string
|
||||||
homepageUrl:
|
homepageUrl:
|
||||||
type: string
|
type: string
|
||||||
logo:
|
logo:
|
||||||
@ -707,54 +822,130 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
organization:
|
organization:
|
||||||
type: string
|
type: string
|
||||||
|
organizationObj:
|
||||||
|
$ref: '#/definitions/object.Organization'
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
providerObjs:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/object.Provider'
|
|
||||||
providers:
|
providers:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
$ref: '#/definitions/object.ProviderItem'
|
||||||
redirectUris:
|
redirectUris:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
signinUrl:
|
||||||
|
type: string
|
||||||
|
signupItems:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.SignupItem'
|
||||||
|
signupUrl:
|
||||||
|
type: string
|
||||||
object.Organization:
|
object.Organization:
|
||||||
title: Organization
|
title: Organization
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
|
defaultAvatar:
|
||||||
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
|
favicon:
|
||||||
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
|
passwordSalt:
|
||||||
|
type: string
|
||||||
|
passwordType:
|
||||||
|
type: string
|
||||||
|
phonePrefix:
|
||||||
|
type: string
|
||||||
websiteUrl:
|
websiteUrl:
|
||||||
type: string
|
type: string
|
||||||
object.Provider:
|
object.Provider:
|
||||||
title: Provider
|
title: Provider
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
appId:
|
||||||
|
type: string
|
||||||
|
category:
|
||||||
|
type: string
|
||||||
clientId:
|
clientId:
|
||||||
type: string
|
type: string
|
||||||
clientSecret:
|
clientSecret:
|
||||||
type: string
|
type: string
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
|
port:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
providerUrl:
|
providerUrl:
|
||||||
type: string
|
type: string
|
||||||
|
regionId:
|
||||||
|
type: string
|
||||||
|
signName:
|
||||||
|
type: string
|
||||||
|
templateCode:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
type:
|
type:
|
||||||
type: string
|
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:
|
object.Token:
|
||||||
title: Token
|
title: Token
|
||||||
type: object
|
type: object
|
||||||
@ -772,12 +963,16 @@ definitions:
|
|||||||
format: int64
|
format: int64
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
organization:
|
||||||
|
type: string
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
scope:
|
scope:
|
||||||
type: string
|
type: string
|
||||||
tokenType:
|
tokenType:
|
||||||
type: string
|
type: string
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
object.TokenWrapper:
|
object.TokenWrapper:
|
||||||
title: TokenWrapper
|
title: TokenWrapper
|
||||||
type: object
|
type: object
|
||||||
@ -795,37 +990,85 @@ definitions:
|
|||||||
title: User
|
title: User
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
address:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
affiliation:
|
affiliation:
|
||||||
type: string
|
type: string
|
||||||
avatar:
|
avatar:
|
||||||
type: string
|
type: string
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
|
dingtalk:
|
||||||
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
|
facebook:
|
||||||
|
type: string
|
||||||
|
gitee:
|
||||||
|
type: string
|
||||||
github:
|
github:
|
||||||
type: string
|
type: string
|
||||||
google:
|
google:
|
||||||
type: string
|
type: string
|
||||||
|
hash:
|
||||||
|
type: string
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
isAdmin:
|
isAdmin:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
isForbidden:
|
||||||
|
type: boolean
|
||||||
isGlobalAdmin:
|
isGlobalAdmin:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
language:
|
||||||
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
passwordType:
|
|
||||||
type: string
|
|
||||||
phone:
|
phone:
|
||||||
type: string
|
type: string
|
||||||
|
preHash:
|
||||||
|
type: string
|
||||||
|
properties:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
qq:
|
qq:
|
||||||
type: string
|
type: string
|
||||||
|
score:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
signupApplication:
|
||||||
|
type: string
|
||||||
tag:
|
tag:
|
||||||
type: string
|
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
|
package util
|
||||||
|
|
||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
@ -11,3 +25,7 @@ func StructToJson(v interface{}) string {
|
|||||||
|
|
||||||
return string(data)
|
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
|
package util
|
||||||
|
|
||||||
import (
|
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))
|
hash := md5.Sum([]byte(text))
|
||||||
return hex.EncodeToString(hash[:])
|
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
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -27,8 +27,11 @@ import ProviderListPage from "./ProviderListPage";
|
|||||||
import ProviderEditPage from "./ProviderEditPage";
|
import ProviderEditPage from "./ProviderEditPage";
|
||||||
import ApplicationListPage from "./ApplicationListPage";
|
import ApplicationListPage from "./ApplicationListPage";
|
||||||
import ApplicationEditPage from "./ApplicationEditPage";
|
import ApplicationEditPage from "./ApplicationEditPage";
|
||||||
|
import LdapEditPage from "./LdapEditPage";
|
||||||
|
import LdapSyncPage from "./LdapSyncPage";
|
||||||
import TokenListPage from "./TokenListPage";
|
import TokenListPage from "./TokenListPage";
|
||||||
import TokenEditPage from "./TokenEditPage";
|
import TokenEditPage from "./TokenEditPage";
|
||||||
|
import RecordListPage from "./RecordListPage";
|
||||||
import AccountPage from "./account/AccountPage";
|
import AccountPage from "./account/AccountPage";
|
||||||
import HomePage from "./basic/HomePage";
|
import HomePage from "./basic/HomePage";
|
||||||
import CustomGithubCorner from "./CustomGithubCorner";
|
import CustomGithubCorner from "./CustomGithubCorner";
|
||||||
@ -223,7 +226,7 @@ class App extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown key="200" overlay={menu} >
|
<Dropdown key="200" overlay={menu} className="rightDropDown">
|
||||||
<div className="ant-dropdown-link" style={{float: 'right', cursor: 'pointer'}}>
|
<div className="ant-dropdown-link" style={{float: 'right', cursor: 'pointer'}}>
|
||||||
{
|
{
|
||||||
this.renderAvatar()
|
this.renderAvatar()
|
||||||
@ -317,9 +320,16 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
res.push(
|
||||||
|
<Menu.Item key="7">
|
||||||
|
<Link to="/records">
|
||||||
|
{i18next.t("general:Records")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="6" onClick={() => window.location.href = "/swagger"}>
|
<Menu.Item key="7" onClick={() => window.location.href = "/swagger"}>
|
||||||
{i18next.t("general:Swagger")}
|
{i18next.t("general:Swagger")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
@ -374,6 +384,7 @@ class App extends Component {
|
|||||||
{
|
{
|
||||||
this.renderAccount()
|
this.renderAccount()
|
||||||
}
|
}
|
||||||
|
<SelectLanguageBox/>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Header>
|
</Header>
|
||||||
<Switch>
|
<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="/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" 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="/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" 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="/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>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -409,8 +423,7 @@ class App extends Component {
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
<SelectLanguageBox/>
|
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>
|
||||||
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>
|
|
||||||
</Footer>
|
</Footer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -39,3 +39,22 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 70px; /* Footer height */
|
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")}
|
title={i18next.t("general:Providers")}
|
||||||
table={this.state.application.providers}
|
table={this.state.application.providers}
|
||||||
providers={this.state.providers}
|
providers={this.state.providers}
|
||||||
|
application={this.state.application}
|
||||||
onUpdateTable={(value) => { this.updateApplicationField('providers', value)}}
|
onUpdateTable={(value) => { this.updateApplicationField('providers', value)}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
@ -301,6 +302,8 @@ class ApplicationEditPage extends React.Component {
|
|||||||
this.renderPreview()
|
this.renderPreview()
|
||||||
}
|
}
|
||||||
</Row>
|
</Row>
|
||||||
|
{
|
||||||
|
!this.state.application.enableSignUp ? null : (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={2}>
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} :
|
{Setting.getLabel(i18next.t("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} :
|
||||||
@ -313,6 +316,8 @@ class ApplicationEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={2}>
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
{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 React from "react";
|
||||||
import {Button, Card, Col, Input, Row, Select} from 'antd';
|
import {Button, Card, Col, Input, Row, Select} from 'antd';
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as LdapBackend from "./backend/LdapBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {LinkOutlined} from "@ant-design/icons";
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
|
import LdapTable from "./LdapTable";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@ -28,11 +30,13 @@ class OrganizationEditPage extends React.Component {
|
|||||||
classes: props,
|
classes: props,
|
||||||
organizationName: props.match.params.organizationName,
|
organizationName: props.match.params.organizationName,
|
||||||
organization: null,
|
organization: null,
|
||||||
|
ldaps: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getOrganization();
|
this.getOrganization();
|
||||||
|
this.getLdaps();
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrganization() {
|
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) {
|
parseOrganizationField(key, value) {
|
||||||
// if ([].includes(key)) {
|
// if ([].includes(key)) {
|
||||||
// value = Setting.myParseInt(value);
|
// value = Setting.myParseInt(value);
|
||||||
@ -186,6 +205,20 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -228,7 +261,8 @@ class OrganizationEditPage extends React.Component {
|
|||||||
<Col span={2}>
|
<Col span={2}>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={18}>
|
<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>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,7 +120,7 @@ class OrganizationListPage extends React.Component {
|
|||||||
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Favicon',
|
title: i18next.t("organization:Favicon"),
|
||||||
dataIndex: 'favicon',
|
dataIndex: 'favicon',
|
||||||
key: 'favicon',
|
key: 'favicon',
|
||||||
width: '50px',
|
width: '50px',
|
||||||
@ -160,6 +160,19 @@ class OrganizationListPage extends React.Component {
|
|||||||
width: '150px',
|
width: '150px',
|
||||||
sorter: (a, b) => a.passwordSalt.localeCompare(b.passwordSalt),
|
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"),
|
title: i18next.t("general:Action"),
|
||||||
dataIndex: '',
|
dataIndex: '',
|
||||||
|
@ -74,7 +74,8 @@ class ProviderEditPage extends React.Component {
|
|||||||
{id: 'Facebook', name: 'Facebook'},
|
{id: 'Facebook', name: 'Facebook'},
|
||||||
{id: 'DingTalk', name: 'DingTalk'},
|
{id: 'DingTalk', name: 'DingTalk'},
|
||||||
{id: 'Weibo', name: 'Weibo'},
|
{id: 'Weibo', name: 'Weibo'},
|
||||||
{id: 'Gitee', name: 'Gitee'}
|
{id: 'Gitee', name: 'Gitee'},
|
||||||
|
{id: 'LinkedIn', name: 'LinkedIn'},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (provider.category === "Email") {
|
} else if (provider.category === "Email") {
|
||||||
|
@ -62,7 +62,7 @@ class ProviderTable extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTable(table) {
|
renderTable(table) {
|
||||||
const columns = [
|
let columns = [
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:Name"),
|
title: i18next.t("provider:Name"),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
@ -73,11 +73,11 @@ class ProviderTable extends React.Component {
|
|||||||
value={text}
|
value={text}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
this.updateField(table, index, 'name', 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.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>
|
</Select>
|
||||||
)
|
)
|
||||||
@ -89,6 +89,10 @@ class ProviderTable extends React.Component {
|
|||||||
key: 'canSignUp',
|
key: 'canSignUp',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
if (record.provider?.category !== "OAuth") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch checked={text} onChange={checked => {
|
<Switch checked={text} onChange={checked => {
|
||||||
this.updateField(table, index, 'canSignUp', checked);
|
this.updateField(table, index, 'canSignUp', checked);
|
||||||
@ -102,6 +106,10 @@ class ProviderTable extends React.Component {
|
|||||||
key: 'canSignIn',
|
key: 'canSignIn',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
if (record.provider?.category !== "OAuth") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch checked={text} onChange={checked => {
|
<Switch checked={text} onChange={checked => {
|
||||||
this.updateField(table, index, 'canSignIn', checked);
|
this.updateField(table, index, 'canSignIn', checked);
|
||||||
@ -115,6 +123,10 @@ class ProviderTable extends React.Component {
|
|||||||
key: 'canUnlink',
|
key: 'canUnlink',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
if (record.provider?.category !== "OAuth") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch checked={text} onChange={checked => {
|
<Switch checked={text} onChange={checked => {
|
||||||
this.updateField(table, index, 'canUnlink', checked);
|
this.updateField(table, index, 'canUnlink', checked);
|
||||||
@ -128,6 +140,10 @@ class ProviderTable extends React.Component {
|
|||||||
key: 'prompted',
|
key: 'prompted',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
if (record.provider?.category !== "OAuth") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch checked={text} onChange={checked => {
|
<Switch checked={text} onChange={checked => {
|
||||||
this.updateField(table, index, 'prompted', checked);
|
this.updateField(table, index, 'prompted', checked);
|
||||||
@ -135,27 +151,27 @@ class ProviderTable extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: i18next.t("provider:alertType"),
|
// title: i18next.t("provider:alertType"),
|
||||||
dataIndex: 'alertType',
|
// dataIndex: 'alertType',
|
||||||
key: 'alertType',
|
// key: 'alertType',
|
||||||
width: '120px',
|
// width: '120px',
|
||||||
render: (text, record, index) => {
|
// render: (text, record, index) => {
|
||||||
return (
|
// return (
|
||||||
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
// <Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||||
this.updateField(table, index, 'alertType', value);
|
// this.updateField(table, index, 'alertType', value);
|
||||||
})}>
|
// })}>
|
||||||
{
|
// {
|
||||||
[
|
// [
|
||||||
{id: 'None', name: 'None'},
|
// {id: 'None', name: 'None'},
|
||||||
{id: 'Once', name: 'Once'},
|
// {id: 'Once', name: 'Once'},
|
||||||
{id: 'Always', name: 'Always'},
|
// {id: 'Always', name: 'Always'},
|
||||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
// ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
}
|
// }
|
||||||
</Select>
|
// </Select>
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Action"),
|
title: i18next.t("general:Action"),
|
||||||
key: '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 (
|
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={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{this.props.title}
|
{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 React from "react";
|
||||||
import * as Setting from "./Setting";
|
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 {
|
class SelectLanguageBox extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -23,47 +30,28 @@ class SelectLanguageBox extends React.Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClick(e) {
|
||||||
|
Setting.changeLanguage(e.key);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
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 (
|
return (
|
||||||
<div align="center">
|
<Dropdown overlay={menu} style={{cursor: "pointer"}}>
|
||||||
<div className="box" style={{width: "600px"}}>
|
<span className="language_box" />
|
||||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
</Dropdown>
|
||||||
<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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,3 +448,13 @@ export function maskEmail(email) {
|
|||||||
|
|
||||||
return `${username}@${domainTokens.join(".")}`;
|
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,14 +68,7 @@ class SignupTable extends React.Component {
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
const items = [
|
||||||
<Select virtual={false} style={{width: '100%'}}
|
|
||||||
value={text}
|
|
||||||
onChange={value => {
|
|
||||||
this.updateField(table, index, 'name', value);
|
|
||||||
}} >
|
|
||||||
{
|
|
||||||
[
|
|
||||||
{id: 'Username', name: 'Username'},
|
{id: 'Username', name: 'Username'},
|
||||||
{id: 'ID', name: 'ID'},
|
{id: 'ID', name: 'ID'},
|
||||||
{id: 'Display name', name: 'Display name'},
|
{id: 'Display name', name: 'Display name'},
|
||||||
@ -85,7 +78,16 @@ class SignupTable extends React.Component {
|
|||||||
{id: 'Confirm password', name: 'Confirm password'},
|
{id: 'Confirm password', name: 'Confirm password'},
|
||||||
{id: 'Phone', name: 'Phone'},
|
{id: 'Phone', name: 'Phone'},
|
||||||
{id: 'Agreement', name: 'Agreement'},
|
{id: 'Agreement', name: 'Agreement'},
|
||||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}}
|
||||||
|
value={text}
|
||||||
|
onChange={value => {
|
||||||
|
this.updateField(table, index, 'name', value);
|
||||||
|
}} >
|
||||||
|
{
|
||||||
|
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
)
|
)
|
||||||
@ -97,6 +99,10 @@ class SignupTable extends React.Component {
|
|||||||
key: 'visible',
|
key: 'visible',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
if (record.name === "ID") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch checked={text} onChange={checked => {
|
<Switch checked={text} onChange={checked => {
|
||||||
this.updateField(table, index, 'visible', checked);
|
this.updateField(table, index, 'visible', checked);
|
||||||
@ -132,6 +138,10 @@ class SignupTable extends React.Component {
|
|||||||
key: 'prompted',
|
key: 'prompted',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
if (record.name === "ID") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (record.visible) {
|
if (record.visible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -149,17 +159,29 @@ class SignupTable extends React.Component {
|
|||||||
key: 'rule',
|
key: 'rule',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
render: (text, record, index) => {
|
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 (
|
return (
|
||||||
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||||
this.updateField(table, index, 'rule', value);
|
this.updateField(table, index, 'rule', value);
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
[
|
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
{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>)
|
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
)
|
)
|
||||||
@ -188,7 +210,7 @@ class SignupTable extends React.Component {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
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={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{this.props.title}
|
{this.props.title}
|
||||||
|
@ -49,7 +49,7 @@ class UserEditPage extends React.Component {
|
|||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getUser();
|
this.getUser();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getDefaultApplication();
|
this.getUserApplication();
|
||||||
}
|
}
|
||||||
|
|
||||||
getUser() {
|
getUser() {
|
||||||
@ -70,8 +70,8 @@ class UserEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultApplication() {
|
getUserApplication() {
|
||||||
ApplicationBackend.getDefaultApplication("admin")
|
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
|
||||||
.then((application) => {
|
.then((application) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
application: application,
|
application: application,
|
||||||
|
@ -146,7 +146,7 @@ class UserListPage extends React.Component {
|
|||||||
title: i18next.t("general:Display name"),
|
title: i18next.t("general:Display name"),
|
||||||
dataIndex: 'displayName',
|
dataIndex: 'displayName',
|
||||||
key: 'displayName',
|
key: 'displayName',
|
||||||
// width: '100px',
|
width: '100px',
|
||||||
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -245,6 +245,7 @@ class UserListPage extends React.Component {
|
|||||||
dataIndex: '',
|
dataIndex: '',
|
||||||
key: 'op',
|
key: 'op',
|
||||||
width: '190px',
|
width: '190px',
|
||||||
|
fixed: 'right',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -263,7 +264,7 @@ class UserListPage extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("general:Users")}
|
{i18next.t("general:Users")}
|
||||||
|
@ -278,7 +278,7 @@ class LoginPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<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}>
|
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
|
||||||
{i18next.t("login:Auto login")}
|
{i18next.t("login:Auto login")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
|
||||||
import {Button, Col, Result, Row} from "antd";
|
import {Button, Col, Result, Row} from "antd";
|
||||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||||
import * as UserBackend from "../backend/UserBackend";
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
@ -196,13 +195,11 @@ class PromptPage extends React.Component {
|
|||||||
title="Sign Up Error"
|
title="Sign Up Error"
|
||||||
subTitle={"You are unexpected to see this prompt page"}
|
subTitle={"You are unexpected to see this prompt page"}
|
||||||
extra={[
|
extra={[
|
||||||
<Link onClick={() => {
|
<Button type="primary" key="signin" onClick={() => {
|
||||||
Setting.goToLogin(this, application);
|
Setting.goToLogin(this, application);
|
||||||
}}>
|
}}>
|
||||||
<Button type="primary" key="signin">
|
|
||||||
Sign In
|
Sign In
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
</Result>
|
</Result>
|
||||||
@ -227,7 +224,7 @@ class PromptPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<div style={{marginTop: "50px"}}>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -47,6 +47,10 @@ const GiteeAuthScope = "user_info,emails";
|
|||||||
const GiteeAuthUri = "https://gitee.com/oauth/authorize";
|
const GiteeAuthUri = "https://gitee.com/oauth/authorize";
|
||||||
const GiteeAuthLogo = `${StaticBaseUrl}/img/social_gitee.png`;
|
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) {
|
export function getAuthLogo(provider) {
|
||||||
if (provider.type === "Google") {
|
if (provider.type === "Google") {
|
||||||
return GoogleAuthLogo;
|
return GoogleAuthLogo;
|
||||||
@ -64,6 +68,8 @@ export function getAuthLogo(provider) {
|
|||||||
return WeiboAuthLogo;
|
return WeiboAuthLogo;
|
||||||
} else if (provider.type === "Gitee") {
|
} else if (provider.type === "Gitee") {
|
||||||
return GiteeAuthLogo;
|
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}`;
|
return `${WeiboAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${WeiboAuthScope}&response_type=code&state=${state}`;
|
||||||
} else if (provider.type === "Gitee") {
|
} else if (provider.type === "Gitee") {
|
||||||
return `${GiteeAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${GiteeAuthScope}&response_type=code&state=${state}`;
|
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 (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="username"
|
name="username"
|
||||||
|
key="username"
|
||||||
label={i18next.t("signup:Username")}
|
label={i18next.t("signup:Username")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@ -177,6 +178,7 @@ class SignupPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="name"
|
name="name"
|
||||||
|
key="name"
|
||||||
label={signupItem.rule === "Personal" ? i18next.t("general:Personal name") : i18next.t("general:Display name")}
|
label={signupItem.rule === "Personal" ? i18next.t("general:Personal name") : i18next.t("general:Display name")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@ -193,6 +195,7 @@ class SignupPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="affiliation"
|
name="affiliation"
|
||||||
|
key="affiliation"
|
||||||
label={i18next.t("user:Affiliation")}
|
label={i18next.t("user:Affiliation")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@ -210,6 +213,7 @@ class SignupPage extends React.Component {
|
|||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="email"
|
name="email"
|
||||||
|
key="email"
|
||||||
label={i18next.t("general:Email")}
|
label={i18next.t("general:Email")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@ -233,6 +237,7 @@ class SignupPage extends React.Component {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="emailCode"
|
name="emailCode"
|
||||||
|
key="emailCode"
|
||||||
label={i18next.t("code:Email code")}
|
label={i18next.t("code:Email code")}
|
||||||
rules={[{
|
rules={[{
|
||||||
required: required,
|
required: required,
|
||||||
@ -253,6 +258,7 @@ class SignupPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
|
key="password"
|
||||||
label={i18next.t("general:Password")}
|
label={i18next.t("general:Password")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@ -269,6 +275,7 @@ class SignupPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="confirm"
|
name="confirm"
|
||||||
|
key="confirm"
|
||||||
label={i18next.t("signup:Confirm")}
|
label={i18next.t("signup:Confirm")}
|
||||||
dependencies={['password']}
|
dependencies={['password']}
|
||||||
hasFeedback
|
hasFeedback
|
||||||
@ -296,6 +303,7 @@ class SignupPage extends React.Component {
|
|||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="phone"
|
name="phone"
|
||||||
|
key="phone"
|
||||||
label={i18next.t("general:Phone")}
|
label={i18next.t("general:Phone")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@ -325,6 +333,7 @@ class SignupPage extends React.Component {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="phoneCode"
|
name="phoneCode"
|
||||||
|
key="phoneCode"
|
||||||
label={i18next.t("code:Phone code")}
|
label={i18next.t("code:Phone code")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@ -347,6 +356,7 @@ class SignupPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="agreement"
|
name="agreement"
|
||||||
|
key="agreement"
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@ -375,13 +385,11 @@ class SignupPage extends React.Component {
|
|||||||
title="Sign Up Error"
|
title="Sign Up Error"
|
||||||
subTitle={"The application does not allow to sign up new account"}
|
subTitle={"The application does not allow to sign up new account"}
|
||||||
extra={[
|
extra={[
|
||||||
<Link onClick={() => {
|
<Button type="primary" key="signin" onClick={() => {
|
||||||
Setting.goToLogin(this, application);
|
Setting.goToLogin(this, application);
|
||||||
}}>
|
}}>
|
||||||
<Button type="primary" key="signin">
|
|
||||||
Sign In
|
Sign In
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
</Result>
|
</Result>
|
||||||
@ -431,11 +439,11 @@ class SignupPage extends React.Component {
|
|||||||
{i18next.t("account:Sign Up")}
|
{i18next.t("account:Sign Up")}
|
||||||
</Button>
|
</Button>
|
||||||
{i18next.t("signup:Have account?")}
|
{i18next.t("signup:Have account?")}
|
||||||
<Link onClick={() => {
|
<a onClick={() => {
|
||||||
Setting.goToLogin(this, application);
|
Setting.goToLogin(this, application);
|
||||||
}}>
|
}}>
|
||||||
{i18next.t("signup:sign in now")}
|
{i18next.t("signup:sign in now")}
|
||||||
</Link>
|
</a>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
|
@ -28,8 +28,8 @@ export function getApplication(owner, name) {
|
|||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultApplication(owner) {
|
export function getUserApplication(owner, name) {
|
||||||
return fetch(`${Setting.ServerUrl}/api/get-default-application?owner=${owner}`, {
|
return fetch(`${Setting.ServerUrl}/api/get-user-application?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include"
|
credentials: "include"
|
||||||
}).then(res => res.json());
|
}).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 => {
|
items.map(item => {
|
||||||
return (
|
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 React from "react";
|
||||||
import {Cascader, Col, Input, Row, Select} from 'antd';
|
import {Cascader, Col, Input, Row, Select} from 'antd';
|
||||||
import i18next from "i18next";
|
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 => {
|
<Select virtual={false} style={{width: '100%'}} value={this.props.user.affiliation} onChange={(value => {
|
||||||
const name = 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('affiliation', name);
|
||||||
this.updateUserField('score', id);
|
this.updateUserField('score', id);
|
||||||
})}>
|
})}>
|
||||||
|
@ -30,9 +30,11 @@ export const CountDownInput = (props) => {
|
|||||||
const [checkType, setCheckType] = React.useState("");
|
const [checkType, setCheckType] = React.useState("");
|
||||||
const [coolDown, setCoolDown] = React.useState(false);
|
const [coolDown, setCoolDown] = React.useState(false);
|
||||||
const [checkId, setCheckId] = React.useState("");
|
const [checkId, setCheckId] = React.useState("");
|
||||||
|
const [buttonDisabled, setButtonDisabled] = React.useState(false);
|
||||||
|
|
||||||
const countDown = (leftTime) => {
|
const countDown = (leftTime) => {
|
||||||
if (leftTime === 0) {
|
if (leftTime === 0) {
|
||||||
|
setButtonDisabled(false);
|
||||||
setCoolDown(false);
|
setCoolDown(false);
|
||||||
setButtonText(defaultButtonText);
|
setButtonText(defaultButtonText);
|
||||||
return;
|
return;
|
||||||
@ -41,20 +43,13 @@ export const CountDownInput = (props) => {
|
|||||||
setTimeout(() => countDown(leftTime - 1), 1000);
|
setTimeout(() => countDown(leftTime - 1), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clickButton = () => {
|
|
||||||
if (coolDown) {
|
|
||||||
Setting.showMessage("error", i18next.t("general:Cooling down"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loadHumanCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
onButtonClick(checkType, checkId, key, ...onButtonClickArgs).then(res => {
|
onButtonClick(checkType, checkId, key, ...onButtonClickArgs).then(res => {
|
||||||
setKey("");
|
setKey("");
|
||||||
if (res) {
|
if (res) {
|
||||||
setCoolDown(true);
|
setCoolDown(true);
|
||||||
|
setButtonDisabled(true)
|
||||||
countDown(coolDownTime);
|
countDown(coolDownTime);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -124,13 +119,13 @@ export const CountDownInput = (props) => {
|
|||||||
placeholder={placeHolder}
|
placeholder={placeHolder}
|
||||||
onChange={e => onChange(e.target.value)}
|
onChange={e => onChange(e.target.value)}
|
||||||
enterButton={
|
enterButton={
|
||||||
<Button type={"primary"} disabled={disabled}>
|
<Button type={"primary"} disabled={disabled || buttonDisabled}>
|
||||||
<div style={{fontSize: 14}}>
|
<div style={{fontSize: 14}}>
|
||||||
{buttonText}
|
{buttonText}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
onSearch={clickButton}
|
onSearch={loadHumanCheck}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
closable={false}
|
closable={false}
|
||||||
|
@ -12,6 +12,12 @@
|
|||||||
"User": "User",
|
"User": "User",
|
||||||
"Applications": "Applications",
|
"Applications": "Applications",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
|
"Records": "Records",
|
||||||
|
"Client ip": "Client ip",
|
||||||
|
"Timestamp": "Timestamp",
|
||||||
|
"Username": "Username",
|
||||||
|
"Request uri": "Request uri",
|
||||||
|
"LDAPs": "LDAPs",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
@ -267,5 +273,28 @@
|
|||||||
"Email/Phone": "Email/Phone",
|
"Email/Phone": "Email/Phone",
|
||||||
"Change Password": "Change Password",
|
"Change Password": "Change Password",
|
||||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
"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",
|
"User": "User",
|
||||||
"Applications": "Applications",
|
"Applications": "Applications",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
|
"Records": "Records",
|
||||||
|
"Client ip": "Client ip",
|
||||||
|
"Timestamp": "Timestamp",
|
||||||
|
"Username": "Username",
|
||||||
|
"Request uri": "Request uri",
|
||||||
|
"LDAPs": "LDAPs",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
@ -277,5 +283,29 @@
|
|||||||
"Email/Phone": "Email/Phone",
|
"Email/Phone": "Email/Phone",
|
||||||
"Change Password": "Change Password",
|
"Change Password": "Change Password",
|
||||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
"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",
|
"User": "User",
|
||||||
"Applications": "Applications",
|
"Applications": "Applications",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
|
"Records": "Records",
|
||||||
|
"Client ip": "Client ip",
|
||||||
|
"Timestamp": "Timestamp",
|
||||||
|
"Username": "Username",
|
||||||
|
"Request uri": "Request uri",
|
||||||
|
"LDAPs": "LDAPs",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
@ -267,5 +273,28 @@
|
|||||||
"Email/Phone": "Email/Phone",
|
"Email/Phone": "Email/Phone",
|
||||||
"Change Password": "Change Password",
|
"Change Password": "Change Password",
|
||||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
"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",
|
"User": "User",
|
||||||
"Applications": "Applications",
|
"Applications": "Applications",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
|
"Records": "Records",
|
||||||
|
"Client ip": "Client ip",
|
||||||
|
"Timestamp": "Timestamp",
|
||||||
|
"Username": "Username",
|
||||||
|
"Request uri": "Request uri",
|
||||||
|
"LDAPs": "LDAPs",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
@ -267,5 +273,28 @@
|
|||||||
"Email/Phone": "Email/Phone",
|
"Email/Phone": "Email/Phone",
|
||||||
"Change Password": "Change Password",
|
"Change Password": "Change Password",
|
||||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
"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",
|
"User": "User",
|
||||||
"Applications": "Applications",
|
"Applications": "Applications",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
|
"Records": "Records",
|
||||||
|
"Client ip": "Client ip",
|
||||||
|
"Timestamp": "Timestamp",
|
||||||
|
"Username": "Username",
|
||||||
|
"Request uri": "Request uri",
|
||||||
|
"LDAPs": "LDAPs",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
@ -267,5 +273,28 @@
|
|||||||
"Email/Phone": "Email/Phone",
|
"Email/Phone": "Email/Phone",
|
||||||
"Change Password": "Change Password",
|
"Change Password": "Change Password",
|
||||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
"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",
|
"User": "User",
|
||||||
"Applications": "Applications",
|
"Applications": "Applications",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
|
"Records": "Records",
|
||||||
|
"Client ip": "Client ip",
|
||||||
|
"Timestamp": "Timestamp",
|
||||||
|
"Username": "Username",
|
||||||
|
"Request uri": "Request uri",
|
||||||
|
"LDAPs": "LDAPs",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
@ -267,5 +273,28 @@
|
|||||||
"Email/Phone": "Email/Phone",
|
"Email/Phone": "Email/Phone",
|
||||||
"Change Password": "Change Password",
|
"Change Password": "Change Password",
|
||||||
"Choose email verification or mobile verification": "Choose email verification or mobile verification"
|
"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": "组织",
|
||||||
"Organizations - Tooltip": "Unique string-style identifier",
|
"Organizations - Tooltip": "Unique string-style identifier",
|
||||||
"Organization": "组织",
|
"Organization": "组织",
|
||||||
"Organization - Tooltip": "Unique string-style identifier",
|
"Organization - Tooltip": "用户所属组织",
|
||||||
"Providers": "提供商",
|
"Providers": "提供商",
|
||||||
"Providers - Tooltip": "第三方登录需要配置的提供方",
|
"Providers - Tooltip": "可用于登录的第三方应用程序列表",
|
||||||
"Users": "用户",
|
"Users": "用户",
|
||||||
"User": "用户",
|
"User": "用户",
|
||||||
"Applications": "应用",
|
"Applications": "应用",
|
||||||
"Application": "应用",
|
"Application": "应用",
|
||||||
|
"Records": "Records",
|
||||||
|
"Client ip": "Client ip",
|
||||||
|
"Timestamp": "Timestamp",
|
||||||
|
"Username": "用户名",
|
||||||
|
"Request uri": "Request uri",
|
||||||
|
"LDAPs": "LDAPs",
|
||||||
"Save": "保存",
|
"Save": "保存",
|
||||||
"Add": "添加",
|
"Add": "添加",
|
||||||
"Action": "操作",
|
"Action": "操作",
|
||||||
@ -42,9 +48,9 @@
|
|||||||
"Password salt - Tooltip": "用于密码加密的随机参数",
|
"Password salt - Tooltip": "用于密码加密的随机参数",
|
||||||
"Password": "密码",
|
"Password": "密码",
|
||||||
"Email": "电子邮箱",
|
"Email": "电子邮箱",
|
||||||
"Email - Tooltip": "电子邮件:",
|
"Email - Tooltip": "email",
|
||||||
"Phone": "手机号",
|
"Phone": "手机号",
|
||||||
"Phone - Tooltip": "手机号",
|
"Phone - Tooltip": "Phone",
|
||||||
"Logo": "图标",
|
"Logo": "图标",
|
||||||
"Logo - Tooltip": "应用程序向外展示的图标",
|
"Logo - Tooltip": "应用程序向外展示的图标",
|
||||||
"User containers": "用户容器",
|
"User containers": "用户容器",
|
||||||
@ -53,14 +59,14 @@
|
|||||||
"Applications that requires authentication": "需要鉴权的应用",
|
"Applications that requires authentication": "需要鉴权的应用",
|
||||||
"Swagger": "API文档",
|
"Swagger": "API文档",
|
||||||
"Phone Prefix": "手机号前缀",
|
"Phone Prefix": "手机号前缀",
|
||||||
"Phone Prefix - Tooltip": "移动电话号码前缀,用于区分国家或地区",
|
"Phone Prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
|
||||||
"Enter the code": "输入验证码",
|
"Enter the code": "输入验证码",
|
||||||
"Captcha": "人机验证码",
|
"Captcha": "人机验证码",
|
||||||
"Authorization code": "授权码",
|
"Authorization code": "授权码",
|
||||||
"Access token": "访问令牌",
|
"Access token": "访问令牌",
|
||||||
"Expires in": "有效期",
|
"Expires in": "有效期",
|
||||||
"Scope": "范围",
|
"Scope": "范围",
|
||||||
"Description": "Description",
|
"Description": "描述",
|
||||||
"Description - Tooltip": "与此有关的描述信息",
|
"Description - Tooltip": "与此有关的描述信息",
|
||||||
"Token expire": "Token expire",
|
"Token expire": "Token expire",
|
||||||
"Token expire - Tooltip": "签发的令牌的授权时间",
|
"Token expire - Tooltip": "签发的令牌的授权时间",
|
||||||
@ -100,7 +106,7 @@
|
|||||||
"Please input your verification code!": "请输入您的验证码",
|
"Please input your verification code!": "请输入您的验证码",
|
||||||
"Send Code": "发送验证码",
|
"Send Code": "发送验证码",
|
||||||
"Empty Code": "验证码为空",
|
"Empty Code": "验证码为空",
|
||||||
"Code Sent": "验证码已发送",
|
"Code Sent": "Code Sent",
|
||||||
"Code You Received": "验证码",
|
"Code You Received": "验证码",
|
||||||
"Enter your code": "输入你的验证码",
|
"Enter your code": "输入你的验证码",
|
||||||
"You can only send one code in 60s.": "每分钟你只能发送一次验证码",
|
"You can only send one code in 60s.": "每分钟你只能发送一次验证码",
|
||||||
@ -141,36 +147,36 @@
|
|||||||
"organization": {
|
"organization": {
|
||||||
"Edit Organization": "修改组织",
|
"Edit Organization": "修改组织",
|
||||||
"Website URL": "网页地址",
|
"Website URL": "网页地址",
|
||||||
"Website URL - Tooltip": "唯一的、字符串式的ID"
|
"Website URL - Tooltip": "Unique string-style identifier"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"App ID": "应用ID",
|
"App ID": "App ID",
|
||||||
"App ID - Tooltip": "唯一的、字符串式的ID",
|
"App ID - Tooltip": "Unique string-style identifier",
|
||||||
"Name": "名称",
|
"Name": "名称",
|
||||||
"Display name": "显示名称",
|
"Display name": "显示名称",
|
||||||
"Category": "分类",
|
"Category": "分类",
|
||||||
"Category - Tooltip": "唯一的、字符串式的ID",
|
"Category - Tooltip": "Unique string-style identifier",
|
||||||
"Type": "类型",
|
"Type": "类型",
|
||||||
"Type - Tooltip": "唯一的、字符串式的ID",
|
"Type - Tooltip": "Unique string-style identifier",
|
||||||
"Client ID": "Client ID",
|
"Client ID": "Client ID",
|
||||||
"Client ID - Tooltip": "唯一的、字符串式的ID",
|
"Client ID - Tooltip": "Unique string-style identifier",
|
||||||
"Client secret": "Client secret",
|
"Client secret": "Client secret",
|
||||||
"Client secret - Tooltip": "唯一的、字符串式的ID",
|
"Client secret - Tooltip": "Unique string-style identifier",
|
||||||
"Host": "主机",
|
"Host": "主机",
|
||||||
"Host - Tooltip": "唯一的、字符串式的ID",
|
"Host - Tooltip": "Unique string-style identifier",
|
||||||
"Port": "端口号",
|
"Port": "端口号",
|
||||||
"Port - Tooltip": "唯一的、字符串式的ID",
|
"Port - Tooltip": "Unique string-style identifier",
|
||||||
"Email Title": "邮件标题",
|
"Email Title": "邮件标题",
|
||||||
"Email Title - Tooltip": "唯一的、字符串式的ID",
|
"Email Title - Tooltip": "Unique string-style identifier",
|
||||||
"Email Content": "邮件内容",
|
"Email Content": "邮件内容",
|
||||||
"Email Content - Tooltip": "唯一的、字符串式的ID",
|
"Email Content - Tooltip": "Unique string-style identifier",
|
||||||
"Region ID": "地域ID",
|
"Region ID": "地域ID",
|
||||||
"Sign Name": "签名名称",
|
"Sign Name": "签名名称",
|
||||||
"Sign Name - Tooltip": "唯一的、字符串式的ID",
|
"Sign Name - Tooltip": "Unique string-style identifier",
|
||||||
"Template Code": "模板CODE",
|
"Template Code": "模板CODE",
|
||||||
"Template Code - Tooltip": "唯一的、字符串式的ID",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Provider URL": "提供商URL",
|
"Provider URL": "提供商URL",
|
||||||
"Provider URL - Tooltip": "唯一的、字符串式的ID",
|
"Provider URL - Tooltip": "Unique string-style identifier",
|
||||||
"Edit Provider": "修改提供商"
|
"Edit Provider": "修改提供商"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
@ -181,12 +187,12 @@
|
|||||||
"Set password...": "设置密码...",
|
"Set password...": "设置密码...",
|
||||||
"Modify password...": "修改密码...",
|
"Modify password...": "修改密码...",
|
||||||
"Address": "地址",
|
"Address": "地址",
|
||||||
"Address - Tooltip": "唯一的、字符串式的ID",
|
"Address - Tooltip": "Unique string-style identifier",
|
||||||
"Affiliation": "工作单位",
|
"Affiliation": "工作单位",
|
||||||
"Affiliation - Tooltip": "唯一的、字符串式的ID",
|
"Affiliation - Tooltip": "Unique string-style identifier",
|
||||||
"Modify affiliation": "修改工作单位",
|
"Modify affiliation": "修改工作单位",
|
||||||
"Tag": "标签",
|
"Tag": "标签",
|
||||||
"Tag - Tooltip": "唯一的、字符串式的ID",
|
"Tag - Tooltip": "Unique string-style identifier",
|
||||||
"Third-party logins": "第三方登录",
|
"Third-party logins": "第三方登录",
|
||||||
"Third-party logins - Tooltip": "使用第三方应用程序登录",
|
"Third-party logins - Tooltip": "使用第三方应用程序登录",
|
||||||
"Properties": "属性",
|
"Properties": "属性",
|
||||||
@ -195,9 +201,9 @@
|
|||||||
"Is admin": "是管理员",
|
"Is admin": "是管理员",
|
||||||
"Is admin - Tooltip": "是应用程序管理员",
|
"Is admin - Tooltip": "是应用程序管理员",
|
||||||
"Is global admin": "是全局管理员",
|
"Is global admin": "是全局管理员",
|
||||||
"Is global admin - Tooltip": "是应用程序管理员",
|
"Is global admin - Tooltip": "Is the application global administrator",
|
||||||
"Is forbidden": "被禁用",
|
"Is forbidden": "被禁用",
|
||||||
"Is forbidden - Tooltip": "账户是否已被禁用",
|
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||||
"Empty input!": "输入为空!",
|
"Empty input!": "输入为空!",
|
||||||
"Two passwords you typed do not match.": "两次输入的密码不匹配。",
|
"Two passwords you typed do not match.": "两次输入的密码不匹配。",
|
||||||
"Password Set": "密码已设置",
|
"Password Set": "密码已设置",
|
||||||
@ -234,8 +240,8 @@
|
|||||||
"application": {
|
"application": {
|
||||||
"Edit Application": "修改应用",
|
"Edit Application": "修改应用",
|
||||||
"Password ON": "Password ON",
|
"Password ON": "Password ON",
|
||||||
"Password ON - Tooltip": "是否允许密码登录",
|
"Password ON - Tooltip": "Whether to allow password login",
|
||||||
"Enable signup": "启用注册",
|
"Enable signup": "Enable signup",
|
||||||
"Enable signup - Tooltip": "是否允许用户注册",
|
"Enable signup - Tooltip": "是否允许用户注册",
|
||||||
"Login page preview": "登录页面预览",
|
"Login page preview": "登录页面预览",
|
||||||
"Test signup page..": "测试注册页面..",
|
"Test signup page..": "测试注册页面..",
|
||||||
@ -245,7 +251,7 @@
|
|||||||
"Redirect URLs": "回调URLs",
|
"Redirect URLs": "回调URLs",
|
||||||
"Redirect URLs - Tooltip": "登录成功后重定向地址列表",
|
"Redirect URLs - Tooltip": "登录成功后重定向地址列表",
|
||||||
"Signup items": "注册项",
|
"Signup items": "注册项",
|
||||||
"Signup items - Tooltip": "注册用户注册时需要填写的项目"
|
"Signup items - Tooltip": "Signup items that need to be filled in when users register"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
"Please input your application!": "请输入您的应用名称!",
|
"Please input your application!": "请输入您的应用名称!",
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1217,6 +1217,13 @@
|
|||||||
semver "^7.3.2"
|
semver "^7.3.2"
|
||||||
webpack-merge "^4.2.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":
|
"@csstools/convert-colors@^1.4.0":
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
|
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:
|
dependencies:
|
||||||
is-glob "^4.0.1"
|
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:
|
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"
|
version "7.1.6"
|
||||||
resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
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"
|
has "^1.0.3"
|
||||||
side-channel "^1.0.4"
|
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:
|
ip-regex@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
||||||
@ -10047,6 +10071,13 @@ readdirp@~3.5.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
picomatch "^2.2.1"
|
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:
|
recursive-readdir@2.2.2:
|
||||||
version "2.2.2"
|
version "2.2.2"
|
||||||
resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f"
|
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"
|
is-core-module "^2.0.0"
|
||||||
path-parse "^1.0.6"
|
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"
|
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==
|
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module "^2.2.0"
|
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"
|
resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
|
||||||
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
|
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:
|
shellwords@^0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
|
resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
|
||||||
|
Reference in New Issue
Block a user