Compare commits

...

20 Commits

Author SHA1 Message Date
40039e0412 feat: add twilio SMS (#1159) 2022-09-25 17:58:12 +08:00
116420adb2 feat: revert "feat: fix openid address format" (#1158)
This reverts commit a447d64bf2.
2022-09-25 09:58:45 +08:00
07c1e3b836 feat: support simple LDAP server (#1155)
* feat:Support simple ldap server

* fix:fix review problems

* fix:fix review problems
2022-09-24 21:48:29 +08:00
a447d64bf2 fix: openid address format (#1157) 2022-09-24 15:34:11 +08:00
4116b1d305 feat: fix google login flash bug (#1147) 2022-09-23 16:03:09 +08:00
1490044295 fix: add returnUrl for user edit page (#1152)
* feat: add redirect param

Signed-off-by: magicwind <2814461814@qq.com>

* Update UserEditPage.js

Signed-off-by: magicwind <2814461814@qq.com>
Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-09-23 12:01:21 +08:00
79f2af405a fix: check whether to use go proxy in build (#1149) 2022-09-22 22:14:25 +08:00
575a248c41 Add TestGetEmailsForUsers() 2022-09-22 20:51:50 +08:00
7083904634 Improve isValidPersonName() 2022-09-21 21:35:39 +08:00
3d50255060 feat: login background image display (#1145) 2022-09-20 23:06:24 +08:00
e295da774f Improve record list page 2022-09-18 23:11:40 +08:00
a3cee496b4 Add add-record API 2022-09-18 17:35:34 +08:00
084a5c3e6b Show logs to org admin 2022-09-18 16:16:45 +08:00
6670450439 Update CI node-version to 16 2022-09-18 15:52:12 +08:00
e1331f314d Add RequireSignedInUser() 2022-09-18 15:43:49 +08:00
604033aa02 feat: use Casdoor to manage Casbin DB table (#1100)
* feat: use Casdoor to manage Casbin DB table

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: remove trivial codes

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* chore: go fmt

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* feat: support role definition

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: i18n

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: i18n

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-09-14 22:14:13 +08:00
729c20393c fix: missing providers and org in GetDefaultApplication (#1123)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-09-13 22:54:05 +08:00
a90b27b74a Fix admin UI issues 2022-09-13 21:32:18 +08:00
5707e38912 feat: add batchSize to conf (#1120) 2022-09-13 20:31:22 +08:00
ed959bd8c7 feat: improve login page style (#1119)
Signed-off-by: magicwind <2814461814@qq.com>

Signed-off-by: magicwind <2814461814@qq.com>
2022-09-12 00:01:18 +08:00
48 changed files with 1698 additions and 198 deletions

View File

@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: with:
node-version: '14.17.0' node-version: 16
# cache # cache
- uses: c-hive/gha-yarn-cache@v2 - uses: c-hive/gha-yarn-cache@v2
with: with:
@ -89,7 +89,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 12 node-version: 16
- name: Fetch Previous version - name: Fetch Previous version
id: get-previous-tag id: get-previous-tag

View File

@ -30,7 +30,7 @@ func InitAuthz() {
var err error var err error
tableNamePrefix := conf.GetConfigString("tableNamePrefix") tableNamePrefix := conf.GetConfigString("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true) a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -89,6 +89,7 @@ p, *, *, GET, /api/get-organization-applications, *, *
p, *, *, GET, /api/get-user, *, * p, *, *, GET, /api/get-user, *, *
p, *, *, GET, /api/get-user-application, *, * p, *, *, GET, /api/get-user-application, *, *
p, *, *, GET, /api/get-resources, *, * p, *, *, GET, /api/get-resources, *, *
p, *, *, GET, /api/get-records, *, *
p, *, *, GET, /api/get-product, *, * p, *, *, GET, /api/get-product, *, *
p, *, *, POST, /api/buy-product, *, * p, *, *, POST, /api/buy-product, *, *
p, *, *, GET, /api/get-payment, *, * p, *, *, GET, /api/get-payment, *, *

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
#try to connect to google to determine whether user need to use proxy #try to connect to google to determine whether user need to use proxy
curl www.google.com -o /dev/null --connect-timeout 5 2 > /dev/null curl www.google.com -o /dev/null --connect-timeout 5 2> /dev/null
if [ $? == 0 ] if [ $? == 0 ]
then then
echo "Successfully connected to Google, no need to use Go proxy" echo "Successfully connected to Google, no need to use Go proxy"

BIN
casdoor Normal file

Binary file not shown.

View File

@ -18,3 +18,5 @@ logPostOnly = true
origin = origin =
staticBaseUrl = "https://cdn.casbin.org" staticBaseUrl = "https://cdn.casbin.org"
isDemoMode = false isDemoMode = false
batchSize = 100
ldapServerPort = 389

View File

@ -24,6 +24,19 @@ import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
func init() {
// this array contains the beego configuration items that may be modified via env
presetConfigItems := []string{"httpport", "appname"}
for _, key := range presetConfigItems {
if value, ok := os.LookupEnv(key); ok {
err := beego.AppConfig.Set(key, value)
if err != nil {
panic(err)
}
}
}
}
func GetConfigString(key string) string { func GetConfigString(key string) string {
if value, ok := os.LookupEnv(key); ok { if value, ok := os.LookupEnv(key); ok {
return value return value
@ -55,17 +68,7 @@ func GetConfigInt64(key string) (int64, error) {
return num, err return num, err
} }
func init() { func GetConfigDataSourceName() string {
// this array contains the beego configuration items that may be modified via env
presetConfigItems := []string{"httpport", "appname"}
for _, key := range presetConfigItems {
if value, ok := os.LookupEnv(key); ok {
beego.AppConfig.Set(key, value)
}
}
}
func GetBeegoConfDataSourceName() string {
dataSourceName := GetConfigString("dataSourceName") dataSourceName := GetConfigString("dataSourceName")
runningInDocker := os.Getenv("RUNNING_IN_DOCKER") runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
@ -84,3 +87,11 @@ func GetBeegoConfDataSourceName() string {
func IsDemoMode() bool { func IsDemoMode() bool {
return strings.ToLower(GetConfigString("isDemoMode")) == "true" return strings.ToLower(GetConfigString("isDemoMode")) == "true"
} }
func GetConfigBatchSize() int {
res, err := strconv.Atoi(GetConfigString("batchSize"))
if err != nil {
res = 100
}
return res
}

View File

@ -258,17 +258,11 @@ func (c *ApiController) Logout() {
// @Success 200 {object} controllers.Response The Response object // @Success 200 {object} controllers.Response The Response object
// @router /get-account [get] // @router /get-account [get]
func (c *ApiController) GetAccount() { func (c *ApiController) GetAccount() {
userId, ok := c.RequireSignedIn() user, ok := c.RequireSignedInUser()
if !ok { if !ok {
return return
} }
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
managedAccounts := c.Input().Get("managedAccounts") managedAccounts := c.Input().Get("managedAccounts")
if managedAccounts == "1" { if managedAccounts == "1" {
user = object.ExtendManagedAccountsWithUser(user) user = object.ExtendManagedAccountsWithUser(user)
@ -294,18 +288,16 @@ func (c *ApiController) GetAccount() {
// @Success 200 {object} object.Userinfo The Response object // @Success 200 {object} object.Userinfo The Response object
// @router /userinfo [get] // @router /userinfo [get]
func (c *ApiController) GetUserinfo() { func (c *ApiController) GetUserinfo() {
userId, ok := c.RequireSignedIn() user, ok := c.RequireSignedInUser()
if !ok { if !ok {
return return
} }
scope, aud := c.GetSessionOidc() scope, aud := c.GetSessionOidc()
host := c.Ctx.Request.Host host := c.Ctx.Request.Host
resp, err := object.GetUserInfo(userId, scope, aud, host) userInfo := object.GetUserInfo(user, scope, aud, host)
if err != nil {
c.ResponseError(err.Error()) c.Data["json"] = userInfo
return
}
c.Data["json"] = resp
c.ServeJSON() c.ServeJSON()
} }

View File

@ -0,0 +1,94 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func (c *ApiController) GetCasbinAdapters() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetCasbinAdapters(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, field, value)))
adapters := object.GetPaginationCasbinAdapters(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(adapters, paginator.Nums())
}
}
func (c *ApiController) GetCasbinAdapter() {
id := c.Input().Get("id")
c.Data["json"] = object.GetCasbinAdapter(id)
c.ServeJSON()
}
func (c *ApiController) UpdateCasbinAdapter() {
id := c.Input().Get("id")
var casbinAdapter object.CasbinAdapter
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateCasbinAdapter(id, &casbinAdapter))
c.ServeJSON()
}
func (c *ApiController) AddCasbinAdapter() {
var casbinAdapter object.CasbinAdapter
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddCasbinAdapter(&casbinAdapter))
c.ServeJSON()
}
func (c *ApiController) DeleteCasbinAdapter() {
var casbinAdapter object.CasbinAdapter
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteCasbinAdapter(&casbinAdapter))
c.ServeJSON()
}
func (c *ApiController) SyncPolicies() {
id := c.Input().Get("id")
adapter := object.GetCasbinAdapter(id)
c.Data["json"] = object.SyncPolicies(adapter)
c.ServeJSON()
}

131
controllers/ldapserver.go Normal file
View File

@ -0,0 +1,131 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/forestmgy/ldapserver"
"github.com/lor00x/goldap/message"
)
func StartLdapServer() {
server := ldapserver.NewServer()
routes := ldapserver.NewRouteMux()
routes.Bind(handleBind)
routes.Search(handleSearch).Label(" SEARCH****")
server.Handle(routes)
go server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
// When CTRL+C, SIGINT and SIGTERM signal occurs
// Then stop server gracefully
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
close(ch)
server.Stop()
}
func handleBind(w ldapserver.ResponseWriter, m *ldapserver.Message) {
r := m.GetBindRequest()
res := ldapserver.NewBindResponse(ldapserver.LDAPResultSuccess)
if r.AuthenticationChoice() == "simple" {
bindusername, bindorg, err := object.GetNameAndOrgFromDN(string(r.Name()))
if err != "" {
log.Printf("Bind failed ,ErrMsg=%s", err)
res.SetResultCode(ldapserver.LDAPResultInvalidDNSyntax)
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
w.Write(res)
return
}
bindpassword := string(r.AuthenticationSimple())
binduser, err := object.CheckUserPassword(bindorg, bindusername, bindpassword)
if err != "" {
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
res.SetResultCode(ldapserver.LDAPResultInvalidCredentials)
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
w.Write(res)
return
}
if bindorg == "built-in" {
m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true
} else if binduser.IsAdmin {
m.Client.IsOrgAdmin = true
}
m.Client.IsAuthenticated = true
m.Client.UserName = bindusername
m.Client.OrgName = bindorg
} else {
res.SetResultCode(ldapserver.LDAPResultAuthMethodNotSupported)
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
}
w.Write(res)
}
func handleSearch(w ldapserver.ResponseWriter, m *ldapserver.Message) {
res := ldapserver.NewSearchResultDoneResponse(ldapserver.LDAPResultSuccess)
if !m.Client.IsAuthenticated {
res.SetResultCode(ldapserver.LDAPResultUnwillingToPerform)
w.Write(res)
return
}
r := m.GetSearchRequest()
if r.FilterString() == "(objectClass=*)" {
w.Write(res)
return
}
name, org, errCode := object.GetUserNameAndOrgFromBaseDnAndFilter(string(r.BaseObject()), r.FilterString())
if errCode != ldapserver.LDAPResultSuccess {
res.SetResultCode(errCode)
w.Write(res)
return
}
// Handle Stop Signal (server stop / client disconnected / Abandoned request....)
select {
case <-m.Done:
log.Print("Leaving handleSearch...")
return
default:
}
users, errCode := object.GetFilteredUsers(m, name, org)
if errCode != ldapserver.LDAPResultSuccess {
res.SetResultCode(errCode)
w.Write(res)
return
}
for i := 0; i < len(users); i++ {
user := users[i]
dn := fmt.Sprintf("cn=%s,%s", user.DisplayName, string(r.BaseObject()))
e := ldapserver.NewSearchResultEntry(dn)
e.AddAttribute("cn", message.AttributeValue(user.Name))
e.AddAttribute("uid", message.AttributeValue(user.Name))
e.AddAttribute("email", message.AttributeValue(user.Email))
e.AddAttribute("mobile", message.AttributeValue(user.Phone))
// e.AddAttribute("postalAddress", message.AttributeValue(user.Address[0]))
w.Write(e)
}
w.Write(res)
}

View File

@ -29,7 +29,7 @@ type LinkForm struct {
// @router /unlink [post] // @router /unlink [post]
// @Tag Login API // @Tag Login API
func (c *ApiController) Unlink() { func (c *ApiController) Unlink() {
userId, ok := c.RequireSignedIn() user, ok := c.RequireSignedInUser()
if !ok { if !ok {
return return
} }
@ -44,7 +44,6 @@ func (c *ApiController) Unlink() {
// the user will be unlinked from the provider // the user will be unlinked from the provider
unlinkedUser := form.User unlinkedUser := form.User
user := object.GetUser(userId)
if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin { if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin {
// if the user is not the same as the one we are unlinking, we need to make sure the user is the global admin. // if the user is not the same as the one we are unlinking, we need to make sure the user is the global admin.

View File

@ -55,13 +55,12 @@ func (c *ApiController) GetPermissions() {
// @Success 200 {array} object.Permission The Response object // @Success 200 {array} object.Permission The Response object
// @router /get-permissions-by-submitter [get] // @router /get-permissions-by-submitter [get]
func (c *ApiController) GetPermissionsBySubmitter() { func (c *ApiController) GetPermissionsBySubmitter() {
userId, ok := c.RequireSignedIn() user, ok := c.RequireSignedInUser()
if !ok { if !ok {
return return
} }
owner, username := util.GetOwnerAndNameFromId(userId) permissions := object.GetPermissionsBySubmitter(user.Owner, user.Name)
permissions := object.GetPermissionsBySubmitter(owner, username)
c.ResponseOk(permissions, len(permissions)) c.ResponseOk(permissions, len(permissions))
return return
} }

View File

@ -15,6 +15,8 @@
package controllers package controllers
import ( import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination" "github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@ -29,6 +31,11 @@ import (
// @Success 200 {object} object.Record The Response object // @Success 200 {object} object.Record The Response object
// @router /get-records [get] // @router /get-records [get]
func (c *ApiController) GetRecords() { func (c *ApiController) GetRecords() {
organization, ok := c.RequireAdmin()
if !ok {
return
}
limit := c.Input().Get("pageSize") limit := c.Input().Get("pageSize")
page := c.Input().Get("p") page := c.Input().Get("p")
field := c.Input().Get("field") field := c.Input().Get("field")
@ -40,8 +47,9 @@ func (c *ApiController) GetRecords() {
c.ServeJSON() c.ServeJSON()
} else { } else {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount(field, value))) filterRecord := &object.Record{Organization: organization}
records := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder) paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount(field, value, filterRecord)))
records := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder, filterRecord)
c.ResponseOk(records, paginator.Nums()) c.ResponseOk(records, paginator.Nums())
} }
} }
@ -66,3 +74,22 @@ func (c *ApiController) GetRecordsByFilter() {
c.Data["json"] = object.GetRecordsByField(record) c.Data["json"] = object.GetRecordsByField(record)
c.ServeJSON() c.ServeJSON()
} }
// AddRecord
// @Title AddRecord
// @Tag Record API
// @Description add a record
// @Param body body object.Record true "The details of the record"
// @Success 200 {object} controllers.Response The Response object
// @router /add-record [post]
func (c *ApiController) AddRecord() {
var record object.Record
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddRecord(&record))
c.ServeJSON()
}

View File

@ -75,6 +75,34 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
return userId, true return userId, true
} }
// RequireSignedInUser ...
func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
userId, ok := c.RequireSignedIn()
if !ok {
return nil, false
}
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return nil, false
}
return user, true
}
// RequireAdmin ...
func (c *ApiController) RequireAdmin() (string, bool) {
user, ok := c.RequireSignedInUser()
if !ok {
return "", false
}
if user.Owner == "built-in" {
return "", true
}
return user.Owner, true
}
func getInitScore() (int, error) { func getInitScore() (int, error) {
return strconv.Atoi(conf.GetConfigString("initScore")) return strconv.Atoi(conf.GetConfigString("initScore"))
} }

View File

@ -148,17 +148,11 @@ func (c *ApiController) SendVerificationCode() {
// @Title ResetEmailOrPhone // @Title ResetEmailOrPhone
// @router /api/reset-email-or-phone [post] // @router /api/reset-email-or-phone [post]
func (c *ApiController) ResetEmailOrPhone() { func (c *ApiController) ResetEmailOrPhone() {
userId, ok := c.RequireSignedIn() user, ok := c.RequireSignedInUser()
if !ok { if !ok {
return return
} }
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
destType := c.Ctx.Request.Form.Get("type") destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest") dest := c.Ctx.Request.Form.Get("dest")
code := c.Ctx.Request.Form.Get("code") code := c.Ctx.Request.Form.Get("code")

4
go.mod
View File

@ -10,12 +10,13 @@ require (
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.30.1 github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.5.1 github.com/casbin/xorm-adapter/v2 v2.5.1
github.com/casdoor/go-sms-sender v0.3.0 github.com/casdoor/go-sms-sender v0.4.0
github.com/casdoor/goth v1.69.0-FIX2 github.com/casdoor/goth v1.69.0-FIX2
github.com/casdoor/oss v1.2.0 github.com/casdoor/oss v1.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
github.com/forestmgy/ldapserver v1.0.9
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-ldap/ldap/v3 v3.3.0
github.com/go-pay/gopay v1.5.72 github.com/go-pay/gopay v1.5.72
@ -26,6 +27,7 @@ require (
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v0.9.0 github.com/lestrrat-go/jwx v0.9.0
github.com/lib/pq v1.8.0 github.com/lib/pq v1.8.0
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/qiangmzsx/string-adapter/v2 v2.1.0 github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1

23
go.sum
View File

@ -98,8 +98,8 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM= github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54= github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.3.0 h1:c4bWVcKZhO2L3Xu1oy7aeVkCK6HRJkW/b5K1xU9mV60= github.com/casdoor/go-sms-sender v0.4.0 h1:gqlSWq0Z6cymOGOqSGfKDi/C1ZmDy+91JdCaUHUN9qo=
github.com/casdoor/go-sms-sender v0.3.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk= github.com/casdoor/go-sms-sender v0.4.0/go.mod h1:kBykbqwgRDXbXdMAIxmZKinVM1WjdqEbej5LAbUbcfI=
github.com/casdoor/goth v1.69.0-FIX2 h1:RgfIMkL9kekylgxHHK2ZY8ASAwOGns2HVlaBwLu7Bcs= github.com/casdoor/goth v1.69.0-FIX2 h1:RgfIMkL9kekylgxHHK2ZY8ASAwOGns2HVlaBwLu7Bcs=
github.com/casdoor/goth v1.69.0-FIX2/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s= github.com/casdoor/goth v1.69.0-FIX2/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8= github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=
@ -137,6 +137,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/forestmgy/ldapserver v1.0.9 h1:eMJKsN7Q/c9u074FVXfpPuTp3vOyH+O7Zpe6KfCCMUc=
github.com/forestmgy/ldapserver v1.0.9/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
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=
@ -173,6 +175,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
@ -188,8 +192,9 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -300,6 +305,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuMxhatuSzHaljStc4gjDeKycxy0A=
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0= github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
@ -400,6 +407,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -417,6 +425,8 @@ github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03O
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/twilio/twilio-go v0.26.0 h1:wFW4oTe3/LKt6bvByP7eio8JsjtaLHjMQKOUEzQry7U=
github.com/twilio/twilio-go v0.26.0/go.mod h1:lz62Hopu4vicpQ056H5TJ0JE4AP0rS3sQ35/ejmgOwE=
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA= github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4= github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
@ -427,6 +437,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
@ -482,6 +493,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -516,6 +528,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
@ -537,6 +550,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -573,7 +587,9 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -639,6 +655,7 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -23,6 +23,7 @@ import (
_ "github.com/astaxie/beego/session/redis" _ "github.com/astaxie/beego/session/redis"
"github.com/casdoor/casdoor/authz" "github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/controllers"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/routers" "github.com/casdoor/casdoor/routers"
@ -76,5 +77,8 @@ func main() {
port := beego.AppConfig.DefaultInt("httpport", 8000) port := beego.AppConfig.DefaultInt("httpport", 8000)
// logs.SetLevel(logs.LevelInformational) // logs.SetLevel(logs.LevelInformational)
logs.SetLogFuncCall(false) logs.SetLogFuncCall(false)
go controllers.StartLdapServer()
beego.Run(fmt.Sprintf(":%v", port)) beego.Run(fmt.Sprintf(":%v", port))
} }

View File

@ -43,7 +43,7 @@ func InitConfig() {
} }
func InitAdapter(createDatabase bool) { func InitAdapter(createDatabase bool) {
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName")) adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
if createDatabase { if createDatabase {
adapter.CreateDatabase() adapter.CreateDatabase()
} }
@ -145,6 +145,11 @@ func (a *Adapter) createTable() {
panic(err) panic(err)
} }
err = a.Engine.Sync2(new(CasbinAdapter))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Provider)) err = a.Engine.Sync2(new(Provider))
if err != nil { if err != nil {
panic(err) panic(err)

217
object/casbin_adapter.go Normal file
View File

@ -0,0 +1,217 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"strings"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type CasbinAdapter struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Organization string `xorm:"varchar(100)" json:"organization"`
Type string `xorm:"varchar(100)" json:"type"`
Model string `xorm:"varchar(100)" json:"model"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"`
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
Database string `xorm:"varchar(100)" json:"database"`
Table string `xorm:"varchar(100)" json:"table"`
IsEnabled bool `json:"isEnabled"`
Adapter *xormadapter.Adapter `xorm:"-" json:"-"`
}
func GetCasbinAdapterCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&CasbinAdapter{})
if err != nil {
panic(err)
}
return int(count)
}
func GetCasbinAdapters(owner string) []*CasbinAdapter {
adapters := []*CasbinAdapter{}
err := adapter.Engine.Where("owner = ?", owner).Find(&adapters)
if err != nil {
panic(err)
}
return adapters
}
func GetPaginationCasbinAdapters(owner string, page, limit int, field, value, sort, order string) []*CasbinAdapter {
session := GetSession(owner, page, limit, field, value, sort, order)
adapters := []*CasbinAdapter{}
err := session.Find(&adapters)
if err != nil {
panic(err)
}
return adapters
}
func getCasbinAdapter(owner, name string) *CasbinAdapter {
if owner == "" || name == "" {
return nil
}
casbinAdapter := CasbinAdapter{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&casbinAdapter)
if err != nil {
panic(err)
}
if existed {
return &casbinAdapter
} else {
return nil
}
}
func GetCasbinAdapter(id string) *CasbinAdapter {
owner, name := util.GetOwnerAndNameFromId(id)
return getCasbinAdapter(owner, name)
}
func UpdateCasbinAdapter(id string, casbinAdapter *CasbinAdapter) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getCasbinAdapter(owner, name) == nil {
return false
}
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
if casbinAdapter.Password == "***" {
session.Omit("password")
}
affected, err := session.Update(casbinAdapter)
if err != nil {
panic(err)
}
return affected != 0
}
func AddCasbinAdapter(casbinAdapter *CasbinAdapter) bool {
affected, err := adapter.Engine.Insert(casbinAdapter)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteCasbinAdapter(casbinAdapter *CasbinAdapter) bool {
affected, err := adapter.Engine.ID(core.PK{casbinAdapter.Owner, casbinAdapter.Name}).Delete(&CasbinAdapter{})
if err != nil {
panic(err)
}
return affected != 0
}
func (casbinAdapter *CasbinAdapter) GetId() string {
return fmt.Sprintf("%s/%s", casbinAdapter.Owner, casbinAdapter.Name)
}
func (casbinAdapter *CasbinAdapter) getTable() string {
if casbinAdapter.DatabaseType == "mssql" {
return fmt.Sprintf("[%s]", casbinAdapter.Table)
} else {
return casbinAdapter.Table
}
}
func safeReturn(policy []string, i int) string {
if len(policy) > i {
return policy[i]
} else {
return ""
}
}
func matrixToCasbinRules(pType string, policies [][]string) []*xormadapter.CasbinRule {
res := []*xormadapter.CasbinRule{}
for _, policy := range policies {
line := xormadapter.CasbinRule{
PType: pType,
V0: safeReturn(policy, 0),
V1: safeReturn(policy, 1),
V2: safeReturn(policy, 2),
V3: safeReturn(policy, 3),
V4: safeReturn(policy, 4),
V5: safeReturn(policy, 5),
}
res = append(res, &line)
}
return res
}
func SyncPolicies(casbinAdapter *CasbinAdapter) []*xormadapter.CasbinRule {
// init Adapter
if casbinAdapter.Adapter == nil {
var dataSourceName string
if casbinAdapter.DatabaseType == "mssql" {
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", casbinAdapter.User, casbinAdapter.Password, casbinAdapter.Host, casbinAdapter.Port, casbinAdapter.Database)
} else if casbinAdapter.DatabaseType == "postgres" {
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", casbinAdapter.User, casbinAdapter.Password, casbinAdapter.Host, casbinAdapter.Port, casbinAdapter.Database)
} else {
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", casbinAdapter.User, casbinAdapter.Password, casbinAdapter.Host, casbinAdapter.Port)
}
if !isCloudIntranet {
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
}
casbinAdapter.Adapter, _ = xormadapter.NewAdapterByEngineWithTableName(NewAdapter(casbinAdapter.DatabaseType, dataSourceName, casbinAdapter.Database).Engine, casbinAdapter.getTable(), "")
}
// init Model
modelObj := getModel(casbinAdapter.Owner, casbinAdapter.Model)
m, err := model.NewModelFromString(modelObj.ModelText)
if err != nil {
panic(err)
}
// init Enforcer
enforcer, err := casbin.NewEnforcer(m, casbinAdapter.Adapter)
if err != nil {
panic(err)
}
policies := matrixToCasbinRules("p", enforcer.GetPolicy())
if strings.Contains(modelObj.ModelText, "[role_definition]") {
policies = append(policies, matrixToCasbinRules("g", enforcer.GetGroupingPolicy())...)
}
return policies
}

74
object/ldapserver.go Normal file
View File

@ -0,0 +1,74 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"log"
"strings"
"github.com/forestmgy/ldapserver"
)
func GetNameAndOrgFromDN(DN string) (string, string, string) {
DNValue := strings.Split(DN, ",")
if len(DNValue) == 1 || strings.ToLower(DNValue[0])[0] != 'c' || strings.ToLower(DNValue[1])[0] != 'o' {
return "", "", "please use correct Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
}
return DNValue[0][3:], DNValue[1][3:], ""
}
func GetUserNameAndOrgFromBaseDnAndFilter(baseDN, filter string) (string, string, int) {
if !strings.Contains(baseDN, "ou=") || !strings.Contains(filter, "cn=") {
return "", "", ldapserver.LDAPResultInvalidDNSyntax
}
name := getUserNameFromFilter(filter)
_, org, _ := GetNameAndOrgFromDN(fmt.Sprintf("cn=%s,", name) + baseDN)
errCode := ldapserver.LDAPResultSuccess
return name, org, errCode
}
func getUserNameFromFilter(filter string) string {
nameIndex := strings.Index(filter, "cn=")
var name string
for i := nameIndex + 3; filter[i] != ')'; i++ {
name = name + string(filter[i])
}
return name
}
func GetFilteredUsers(m *ldapserver.Message, name, org string) ([]*User, int) {
var filteredUsers []*User
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
if m.Client.OrgName == "built-in" && org == "*" {
filteredUsers = GetGlobalUsers()
return filteredUsers, ldapserver.LDAPResultSuccess
} else if m.Client.OrgName == "built-in" || org == m.Client.OrgName {
filteredUsers = GetUsers(org)
return filteredUsers, ldapserver.LDAPResultSuccess
} else {
return nil, ldapserver.LDAPResultInsufficientAccessRights
}
} else {
hasPermission, err := CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), org, true)
if !hasPermission {
log.Printf("ErrMsg = %v", err.Error())
return nil, ldapserver.LDAPResultInsufficientAccessRights
}
user := getUser(org, name)
filteredUsers = append(filteredUsers, user)
return filteredUsers, ldapserver.LDAPResultSuccess
}
}

View File

@ -238,11 +238,16 @@ func GetDefaultApplication(id string) *Application {
return nil return nil
} }
defaultApplication := applications[0]
for _, application := range applications { for _, application := range applications {
if application.EnableSignUp { if application.EnableSignUp {
return application defaultApplication = application
break
} }
} }
return applications[0] extendApplicationWithProviders(defaultApplication)
extendApplicationWithOrg(defaultApplication)
return defaultApplication
} }

View File

@ -29,7 +29,7 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
tableName = permission.Adapter tableName = permission.Adapter
} }
tableNamePrefix := conf.GetConfigString("tableNamePrefix") tableNamePrefix := conf.GetConfigString("tableNamePrefix")
adapter, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), tableName, tableNamePrefix, true) adapter, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName()+conf.GetConfigString("dbName"), tableName, tableNamePrefix, true)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -101,9 +101,9 @@ func AddRecord(record *Record) bool {
return affected != 0 return affected != 0
} }
func GetRecordCount(field, value string) int { func GetRecordCount(field, value string, filterRecord *Record) int {
session := GetSession("", -1, -1, field, value, "", "") session := GetSession("", -1, -1, field, value, "", "")
count, err := session.Count(&Record{}) count, err := session.Count(filterRecord)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -121,10 +121,10 @@ func GetRecords() []*Record {
return records return records
} }
func GetPaginationRecords(offset, limit int, field, value, sortField, sortOrder string) []*Record { func GetPaginationRecords(offset, limit int, field, value, sortField, sortOrder string, filterRecord *Record) []*Record {
records := []*Record{} records := []*Record{}
session := GetSession("", offset, limit, field, value, sortField, sortOrder) session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&records) err := session.Find(&records, filterRecord)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/webauthn" "github.com/duo-labs/webauthn/webauthn"
"xorm.io/core" "xorm.io/core"
@ -487,7 +488,7 @@ func AddUsers(users []*User) bool {
} }
func AddUsersInBatch(users []*User) bool { func AddUsersInBatch(users []*User) bool {
batchSize := 1000 batchSize := conf.GetConfigBatchSize()
if len(users) == 0 { if len(users) == 0 {
return false return false
@ -521,12 +522,7 @@ func DeleteUser(user *User) bool {
return affected != 0 return affected != 0
} }
func GetUserInfo(userId string, scope string, aud string, host string) (*Userinfo, error) { func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
user := GetUser(userId)
if user == nil {
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
}
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
resp := Userinfo{ resp := Userinfo{
@ -548,7 +544,7 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
if strings.Contains(scope, "phone") { if strings.Contains(scope, "phone") {
resp.Phone = user.Phone resp.Phone = user.Phone
} }
return &resp, nil return &resp
} }
func LinkUserAccount(user *User, field string, value string) bool { func LinkUserAccount(user *User, field string, value string) bool {

View File

@ -17,6 +17,7 @@ package object
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@ -108,3 +109,24 @@ func TestGetUserByField(t *testing.T) {
t.Log("no user found") t.Log("no user found")
} }
} }
func TestGetEmailsForUsers(t *testing.T) {
InitConfig()
emailMap := map[string]int{}
emails := []string{}
users := GetUsers("built-in")
for _, user := range users {
if user.Email == "" {
continue
}
if _, ok := emailMap[user.Email]; !ok {
emailMap[user.Email] = 1
emails = append(emails, user.Email)
}
}
text := strings.Join(emails, "\n")
println(text)
}

View File

@ -97,6 +97,13 @@ func initAPI() {
beego.Router("/api/add-model", &controllers.ApiController{}, "POST:AddModel") beego.Router("/api/add-model", &controllers.ApiController{}, "POST:AddModel")
beego.Router("/api/delete-model", &controllers.ApiController{}, "POST:DeleteModel") beego.Router("/api/delete-model", &controllers.ApiController{}, "POST:DeleteModel")
beego.Router("/api/get-adapters", &controllers.ApiController{}, "GET:GetCasbinAdapters")
beego.Router("/api/get-adapter", &controllers.ApiController{}, "GET:GetCasbinAdapter")
beego.Router("/api/update-adapter", &controllers.ApiController{}, "POST:UpdateCasbinAdapter")
beego.Router("/api/add-adapter", &controllers.ApiController{}, "POST:AddCasbinAdapter")
beego.Router("/api/delete-adapter", &controllers.ApiController{}, "POST:DeleteCasbinAdapter")
beego.Router("/api/sync-policies", &controllers.ApiController{}, "GET:SyncPolicies")
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword") beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword") beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone") beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
@ -148,6 +155,7 @@ func initAPI() {
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords") beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter") beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord")
beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks") beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook") beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")

412
web/src/AdapterEditPage.js Normal file
View File

@ -0,0 +1,412 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch, Table, Tooltip} from "antd";
import * as AdapterBackend from "./backend/AdapterBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import "codemirror/lib/codemirror.css";
import * as ModelBackend from "./backend/ModelBackend";
import {EditOutlined, MinusOutlined} from "@ant-design/icons";
require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript");
const {Option} = Select;
class AdapterEditPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
adapterName: props.match.params.adapterName,
adapter: null,
organizations: [],
models: [],
policyLists: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
UNSAFE_componentWillMount() {
this.getAdapter();
this.getOrganizations();
}
getAdapter() {
AdapterBackend.getAdapter(this.state.organizationName, this.state.adapterName)
.then((adapter) => {
this.setState({
adapter: adapter,
});
this.getModels(adapter.owner);
});
}
getOrganizations() {
OrganizationBackend.getOrganizations(this.state.organizationName)
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
});
});
}
getModels(organizationName) {
ModelBackend.getModels(organizationName)
.then((res) => {
this.setState({
models: res,
});
});
}
parseAdapterField(key, value) {
if (["port"].includes(key)) {
value = Setting.myParseInt(value);
}
return value;
}
updateAdapterField(key, value) {
value = this.parseAdapterField(key, value);
const adapter = this.state.adapter;
adapter[key] = value;
this.setState({
adapter: adapter,
});
}
synPolicies() {
this.setState({loading: true});
AdapterBackend.syncPolicies(this.state.adapter.owner, this.state.adapter.name)
.then((res) => {
this.setState({loading: false, policyLists: res});
})
.catch(error => {
this.setState({loading: false});
Setting.showMessage("error", `Adapter failed to get policies: ${error}`);
});
}
renderTable(table) {
const columns = [
{
title: "Rule Type",
dataIndex: "PType",
key: "PType",
width: "100px",
},
{
title: "V0",
dataIndex: "V0",
key: "V0",
width: "100px",
},
{
title: "V1",
dataIndex: "V1",
key: "V1",
width: "100px",
},
{
title: "V2",
dataIndex: "V2",
key: "V2",
width: "100px",
},
{
title: "V3",
dataIndex: "V3",
key: "V3",
width: "100px",
},
{
title: "V4",
dataIndex: "V4",
key: "V4",
width: "100px",
},
{
title: "V5",
dataIndex: "V5",
key: "V5",
width: "100px",
},
{
title: "Option",
key: "option",
width: "100px",
render: (text, record, index) => {
return (
<div>
<Tooltip placement="topLeft" title="Edit">
<Button style={{marginRight: "0.5rem"}} icon={<EditOutlined />} size="small" />
</Tooltip>
<Tooltip placement="topLeft" title="Delete">
<Button icon={<MinusOutlined />} size="small" />
</Tooltip>
</div>
);
},
}];
return (
<div>
<Table
pagination={{
defaultPageSize: 10,
}}
columns={columns} dataSource={table} rowKey="name" size="middle" bordered
loading={this.state.loading}
/>
</div>
);
}
renderAdapter() {
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("adapter:New Adapter") : i18next.t("adapter:Edit Adapter")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitAdapterEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitAdapterEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteAdapter()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {this.updateadapterField("organization", 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={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.name} onChange={e => {
this.updateAdapterField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.type} onChange={(value => {
this.updateAdapterField("type", value);
const adapter = this.state.adapter;
// adapter["tableColumns"] = Setting.getAdapterTableColumns(this.state.adapter);
this.setState({
adapter: adapter,
});
})}>
{
["Database"]
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.host} onChange={e => {
this.updateAdapterField("host", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.adapter.port} onChange={value => {
this.updateAdapterField("port", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.user} onChange={e => {
this.updateAdapterField("user", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.password} onChange={e => {
this.updateAdapterField("password", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("syncer:Database type"), i18next.t("syncer:Database type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.databaseType} onChange={(value => {this.updateAdapterField("databaseType", value);})}>
{
[
{id: "mysql", name: "MySQL"},
{id: "postgres", name: "PostgreSQL"},
{id: "mssql", name: "SQL Server"},
{id: "oracle", name: "Oracle"},
{id: "sqlite3", name: "Sqlite 3"},
].map((databaseType, index) => <Option key={index} value={databaseType.id}>{databaseType.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("syncer:Database"), i18next.t("syncer:Database - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.database} onChange={e => {
this.updateAdapterField("database", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.table}
disabled={this.state.adapter.type === "Keycloak"} onChange={e => {
this.updateAdapterField("table", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Model"), i18next.t("general:Model - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.model} onChange={(model => {
this.updateAdapterField("model", model);
})}>
{
this.state.models.map((model, index) => <Option key={index} value={model.name}>{model.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("adapter:Policies"), i18next.t("adapter:Policies - Tooltip"))} :
</Col>
<Col span={2}>
<Button type="primary" onClick={() => {this.synPolicies();}}>
{i18next.t("adapter:Sync")}
</Button>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
</Col>
<Col span={22} >
{
this.renderTable(this.state.policyLists)
}
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.adapter.isEnabled} onChange={checked => {
this.updateAdapterField("isEnabled", checked);
}} />
</Col>
</Row>
</Card>
);
}
submitAdapterEdit(willExist) {
const adapter = Setting.deepCopy(this.state.adapter);
AdapterBackend.updateAdapter(this.state.adapter.owner, this.state.adapterName, adapter)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
this.setState({
adapterName: this.state.adapter.name,
});
if (willExist) {
this.props.history.push("/adapters");
} else {
this.props.history.push(`/adapters/${this.state.adapter.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updateAdapterField("name", this.state.adapterName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
});
}
deleteAdapter() {
AdapterBackend.deleteAdapter(this.state.adapter)
.then(() => {
this.props.history.push("/adapters");
})
.catch(error => {
Setting.showMessage("error", `adapter failed to delete: ${error}`);
});
}
render() {
return (
<div>
{
this.state.adapter !== null ? this.renderAdapter() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitAdapterEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitAdapterEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteAdapter()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
</div>
);
}
}
export default AdapterEditPage;

261
web/src/AdapterListPage.js Normal file
View File

@ -0,0 +1,261 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as AdapterBackend from "./backend/AdapterBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
class AdapterListPage extends BaseListPage {
newAdapter() {
const randomName = Setting.getRandomName();
return {
owner: "built-in",
name: `adapter_${randomName}`,
createdTime: moment().format(),
organization: "built-in",
type: "Database",
host: "localhost",
port: 3306,
user: "root",
password: "123456",
databaseType: "mysql",
database: "dbName",
table: "tableName",
isEnabled: false,
};
}
addAdapter() {
const newAdapter = this.newAdapter();
AdapterBackend.addAdapter(newAdapter)
.then((res) => {
this.props.history.push({pathname: `/adapters/${newAdapter.owner}/${newAdapter.name}`, mode: "add"});
}
)
.catch(error => {
Setting.showMessage("error", `Adapter failed to add: ${error}`);
});
}
deleteAdapter(i) {
AdapterBackend.deleteAdapter(this.state.data[i])
.then((res) => {
Setting.showMessage("success", "Adapter deleted successfully");
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
}
)
.catch(error => {
Setting.showMessage("error", `Adapter failed to delete: ${error}`);
});
}
renderTable(adapters) {
const columns = [
{
title: i18next.t("general:Organization"),
dataIndex: "organization",
key: "organization",
width: "120px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "150px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/adapters/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Created time"),
dataIndex: "createdTime",
key: "createdTime",
width: "160px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("provider:Type"),
dataIndex: "type",
key: "type",
width: "100px",
sorter: true,
filterMultiple: false,
filters: [
{text: "Database", value: "Database"},
],
},
{
title: i18next.t("provider:Host"),
dataIndex: "host",
key: "host",
width: "120px",
sorter: true,
...this.getColumnSearchProps("host"),
},
{
title: i18next.t("provider:Port"),
dataIndex: "port",
key: "port",
width: "100px",
sorter: true,
...this.getColumnSearchProps("port"),
},
{
title: i18next.t("general:User"),
dataIndex: "user",
key: "user",
width: "120px",
sorter: true,
...this.getColumnSearchProps("user"),
},
{
title: i18next.t("general:Password"),
dataIndex: "password",
key: "password",
width: "120px",
sorter: true,
...this.getColumnSearchProps("password"),
},
{
title: i18next.t("syncer:Database type"),
dataIndex: "databaseType",
key: "databaseType",
width: "120px",
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
},
{
title: i18next.t("syncer:Database"),
dataIndex: "database",
key: "database",
width: "120px",
sorter: true,
},
{
title: i18next.t("syncer:Table"),
dataIndex: "table",
key: "table",
width: "120px",
sorter: true,
},
{
title: i18next.t("general:Is enabled"),
dataIndex: "isEnabled",
key: "isEnabled",
width: "120px",
sorter: true,
render: (text, record, index) => {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
},
},
{
title: i18next.t("general:Action"),
dataIndex: "",
key: "op",
width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/adapters/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete adapter: ${record.name} ?`}
onConfirm={() => this.deleteAdapter(index)}
>
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);
},
},
];
const paginationProps = {
total: this.state.pagination.total,
showQuickJumper: true,
showSizeChanger: true,
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
};
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={adapters} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Adapters")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addAdapter.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
loading={this.state.loading}
onChange={this.handleTableChange}
/>
</div>
);
}
fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
if (params.type !== undefined && params.type !== null) {
field = "type";
value = params.type;
}
this.setState({loading: true});
AdapterBackend.getAdapters("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
total: res.data2,
},
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
}
});
};
}
export default AdapterListPage;

View File

@ -72,6 +72,8 @@ import CasLogout from "./auth/CasLogout";
import ModelListPage from "./ModelListPage"; import ModelListPage from "./ModelListPage";
import ModelEditPage from "./ModelEditPage"; import ModelEditPage from "./ModelEditPage";
import SystemInfo from "./SystemInfo"; import SystemInfo from "./SystemInfo";
import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage";
const {Header, Footer} = Layout; const {Header, Footer} = Layout;
@ -123,6 +125,8 @@ class App extends Component {
this.setState({selectedMenuKey: "/permissions"}); this.setState({selectedMenuKey: "/permissions"});
} else if (uri.includes("/models")) { } else if (uri.includes("/models")) {
this.setState({selectedMenuKey: "/models"}); this.setState({selectedMenuKey: "/models"});
} else if (uri.includes("/adapters")) {
this.setState({selectedMenuKey: "/adapters"});
} else if (uri.includes("/providers")) { } else if (uri.includes("/providers")) {
this.setState({selectedMenuKey: "/providers"}); this.setState({selectedMenuKey: "/providers"});
} else if (uri.includes("/applications")) { } else if (uri.includes("/applications")) {
@ -405,6 +409,13 @@ class App extends Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push(
<Menu.Item key="/adapters">
<Link to="/adapters">
{i18next.t("general:Adapters")}
</Link>
</Menu.Item>
);
res.push( res.push(
<Menu.Item key="/providers"> <Menu.Item key="/providers">
<Link to="/providers"> <Link to="/providers">
@ -421,7 +432,7 @@ class App extends Component {
); );
} }
if (Setting.isAdminUser(this.state.account)) { if (Setting.isLocalAdminUser(this.state.account)) {
res.push( res.push(
<Menu.Item key="/resources"> <Menu.Item key="/resources">
<Link to="/resources"> <Link to="/resources">
@ -429,13 +440,6 @@ class App extends Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push(
<Menu.Item key="/tokens">
<Link to="/tokens">
{i18next.t("general:Tokens")}
</Link>
</Menu.Item>
);
res.push( res.push(
<Menu.Item key="/records"> <Menu.Item key="/records">
<Link to="/records"> <Link to="/records">
@ -443,6 +447,16 @@ class App extends Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
}
if (Setting.isAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/tokens">
<Link to="/tokens">
{i18next.t("general:Tokens")}
</Link>
</Menu.Item>
);
res.push( res.push(
<Menu.Item key="/webhooks"> <Menu.Item key="/webhooks">
<Link to="/webhooks"> <Link to="/webhooks">
@ -544,6 +558,8 @@ class App extends Component {
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} /> <Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} /> <Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} /> <Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} /> <Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<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} />)} />
@ -657,15 +673,18 @@ class App extends Component {
// https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/ // https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/
return ( return (
<Footer id="footer" style={ <>
{ {!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
borderTop: "1px solid #e8e8e8", <Footer id="footer" style={
backgroundColor: "white", {
textAlign: "center", borderTop: "1px solid #e8e8e8",
} backgroundColor: "white",
}> textAlign: "center",
Made with <span style={{color: "rgb(255, 255, 255)"}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a> }
</Footer> }>
Made with <span style={{color: "rgb(255, 255, 255)"}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a>
</Footer>
</>
); );
} }
@ -681,8 +700,8 @@ class App extends Component {
renderPage() { renderPage() {
if (this.isDoorPages()) { if (this.isDoorPages()) {
return ( return (
<div style={{position: "relative", minHeight: "100vh"}}> <div style={{display: "flex", flexDirection: "column", height: "100%"}}>
<div id="content-wrap" style={{flexDirection: "column"}}> <div id="login-content-wrap" style={{flexDirection: "column"}}>
<Switch> <Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} /> <Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} /> <Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} />
@ -745,7 +764,6 @@ class App extends Component {
const organization = this.state.account.organization; const organization = this.state.account.organization;
return ( return (
<React.Fragment> <React.Fragment>
<div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />
<Helmet> <Helmet>
<title>{organization.displayName}</title> <title>{organization.displayName}</title>
<link rel="icon" href={organization.favicon} /> <link rel="icon" href={organization.favicon} />

View File

@ -28,22 +28,32 @@
color: #61dafb; color: #61dafb;
} }
#root{
height: 100%;
}
#parent-area { #parent-area {
position: relative; display: flex;
flex-direction: column;
height: 100%;
min-height: 100vh; min-height: 100vh;
background-color: #f5f5f5; background-color: #f5f5f5;
display: flex;
} }
#content-wrap { #content-wrap {
padding-bottom: 70px; /* Footer height */
display: flex; display: flex;
flex: 1 1 0;
align-items: stretch; align-items: stretch;
width: 100%; width: 100%;
} }
#login-content-wrap {
display: flex;
flex: 1 1 0;
width: 100%;
}
#footer { #footer {
position: absolute;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
height: 70px; /* Footer height */ height: 70px; /* Footer height */
@ -78,6 +88,8 @@
} }
.loginBackground { .loginBackground {
height: 100%;
background: #ffffff no-repeat; background: #ffffff no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
background-attachment: fixed;
} }

View File

@ -39,7 +39,7 @@ require("codemirror/mode/css/css");
const {Option} = Select; const {Option} = Select;
const templete = { const template = {
padding: "30px", padding: "30px",
border: "2px solid #ffffff", border: "2px solid #ffffff",
borderRadius: "7px", borderRadius: "7px",
@ -157,7 +157,7 @@ class ApplicationEditPage extends React.Component {
} }
renderApplication() { renderApplication() {
const preview = JSON.stringify(templete, null, 2); const preview = JSON.stringify(template, null, 2);
return ( return (
<Card size="small" title={ <Card size="small" title={
<div> <div>
@ -562,10 +562,10 @@ class ApplicationEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Preview")}: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={23} > <Col span={22} >
<a target="_blank" rel="noreferrer" href={this.state.application.formBackgroundUrl}> <a target="_blank" rel="noreferrer" href={this.state.application.formBackgroundUrl}>
<img src={this.state.application.formBackgroundUrl} alt={this.state.application.formBackgroundUrl} height={90} style={{marginBottom: "20px"}} /> <img src={this.state.application.formBackgroundUrl} alt={this.state.application.formBackgroundUrl} height={90} style={{marginBottom: "20px"}} />
</a> </a>

View File

@ -53,6 +53,7 @@ class ApplicationListPage extends BaseListPage {
redirectUris: ["http://localhost:9000/callback"], redirectUris: ["http://localhost:9000/callback"],
tokenFormat: "JWT", tokenFormat: "JWT",
expireInHours: 24 * 7, expireInHours: 24 * 7,
formOffset: 8,
}; };
} }

View File

@ -567,16 +567,20 @@ class ProviderEditPage extends React.Component {
</React.Fragment> </React.Fragment>
) : this.state.provider.category === "SMS" ? ( ) : this.state.provider.category === "SMS" ? (
<React.Fragment> <React.Fragment>
<Row style={{marginTop: "20px"}} > {this.state.provider.type === "Twilio SMS" ?
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> null :
{Setting.getLabel(i18next.t("provider:Sign Name"), i18next.t("provider:Sign Name - Tooltip"))} : (<Row style={{marginTop: "20px"}} >
</Col> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
<Col span={22} > {Setting.getLabel(i18next.t("provider:Sign Name"), i18next.t("provider:Sign Name - Tooltip"))} :
<Input value={this.state.provider.signName} onChange={e => { </Col>
this.updateProviderField("signName", e.target.value); <Col span={22} >
}} /> <Input value={this.state.provider.signName} onChange={e => {
</Col> this.updateProviderField("signName", e.target.value);
</Row> }} />
</Col>
</Row>
)
}
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Template Code"), i18next.t("provider:Template Code - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Template Code"), i18next.t("provider:Template Code - Tooltip"))} :
@ -611,7 +615,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}}> <Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={2}></Col> <Col style={{marginTop: "5px"}} span={2} />
<Col span={2}> <Col span={2}>
<Button type="primary" onClick={() => { <Button type="primary" onClick={() => {
try { try {

View File

@ -44,7 +44,7 @@ class RecordListPage extends BaseListPage {
} }
renderTable(records) { renderTable(records) {
const columns = [ let columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
@ -174,6 +174,10 @@ class RecordListPage extends BaseListPage {
}, },
]; ];
if (Setting.isLocalAdminUser(this.props.account)) {
columns = columns.filter(column => column.key !== "name" && column.key !== "organization");
}
const paginationProps = { const paginationProps = {
total: this.state.pagination.total, total: this.state.pagination.total,
pageSize: this.state.pagination.pageSize, pageSize: this.state.pagination.pageSize,

View File

@ -49,6 +49,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_huawei.png`, logo: `${StaticBaseUrl}/img/social_huawei.png`,
url: "https://www.huaweicloud.com/product/msgsms.html", url: "https://www.huaweicloud.com/product/msgsms.html",
}, },
"Twilio SMS": {
logo: `${StaticBaseUrl}/img/social_twilio.png`,
url: "https://www.twilio.com/messaging",
},
"Mock SMS": { "Mock SMS": {
logo: `${StaticBaseUrl}/img/social_default.png`, logo: `${StaticBaseUrl}/img/social_default.png`,
url: "", url: "",
@ -229,9 +233,11 @@ export function getSignupItem(application, itemName) {
} }
export function isValidPersonName(personName) { export function isValidPersonName(personName) {
// https://blog.css8.cn/post/14210975.html return personName !== "";
const personNameRegex = /^[\u4e00-\u9fa5]{2,6}$/;
return personNameRegex.test(personName); // // https://blog.css8.cn/post/14210975.html
// const personNameRegex = /^[\u4e00-\u9fa5]{2,6}$/;
// return personNameRegex.test(personName);
} }
export function isValidIdCard(idCard) { export function isValidIdCard(idCard) {
@ -651,6 +657,7 @@ export function getProviderTypeOptions(category) {
{id: "Tencent Cloud SMS", name: "Tencent Cloud SMS"}, {id: "Tencent Cloud SMS", name: "Tencent Cloud SMS"},
{id: "Volc Engine SMS", name: "Volc Engine SMS"}, {id: "Volc Engine SMS", name: "Volc Engine SMS"},
{id: "Huawei Cloud SMS", name: "Huawei Cloud SMS"}, {id: "Huawei Cloud SMS", name: "Huawei Cloud SMS"},
{id: "Twilio SMS", name: "Twilio SMS"},
] ]
); );
} else if (category === "Storage") { } else if (category === "Storage") {
@ -921,6 +928,14 @@ export function scrollToDiv(divId) {
} }
} }
export function inIframe() {
try {
return window !== window.parent;
} catch (e) {
return true;
}
}
export function getSyncerTableColumns(syncer) { export function getSyncerTableColumns(syncer) {
switch (syncer.type) { switch (syncer.type) {
case "Keycloak": case "Keycloak":

View File

@ -101,6 +101,11 @@ class UserEditPage extends React.Component {
}); });
} }
getReturnUrl() {
const searchParams = new URLSearchParams(this.props.location.search);
return searchParams.get("returnUrl");
}
parseUserField(key, value) { parseUserField(key, value) {
// if ([].includes(key)) { // if ([].includes(key)) {
// value = Setting.myParseInt(value); // value = Setting.myParseInt(value);
@ -499,7 +504,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isAdmin} onChange={checked => { <Switch disabled={this.state.user.owner === "built-in"} checked={this.state.user.isAdmin} onChange={checked => {
this.updateUserField("isAdmin", checked); this.updateUserField("isAdmin", checked);
}} /> }} />
</Col> </Col>
@ -512,7 +517,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isGlobalAdmin} onChange={checked => { <Switch disabled={this.state.user.owner === "built-in"} checked={this.state.user.isGlobalAdmin} onChange={checked => {
this.updateUserField("isGlobalAdmin", checked); this.updateUserField("isGlobalAdmin", checked);
}} /> }} />
</Col> </Col>
@ -616,6 +621,13 @@ class UserEditPage extends React.Component {
} else { } else {
this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`); this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`);
} }
} else {
if (willExist) {
const returnUrl = this.getReturnUrl();
if (returnUrl) {
window.location.href = returnUrl;
}
}
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);

View File

@ -41,8 +41,9 @@ class UserListPage extends BaseListPage {
newUser() { newUser() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const owner = (this.state.organizationName !== undefined) ? this.state.organizationName : this.props.account.owner;
return { return {
owner: "built-in", // this.props.account.username, owner: owner,
name: `user_${randomName}`, name: `user_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
type: "normal-user", type: "normal-user",
@ -56,8 +57,8 @@ class UserListPage extends BaseListPage {
affiliation: "Example Inc.", affiliation: "Example Inc.",
tag: "staff", tag: "staff",
region: "", region: "",
isAdmin: false, isAdmin: (owner === "built-in"),
isGlobalAdmin: false, isGlobalAdmin: (owner === "built-in"),
IsForbidden: false, IsForbidden: false,
isDeleted: false, isDeleted: false,
properties: {}, properties: {},
@ -326,6 +327,7 @@ class UserListPage extends BaseListPage {
width: "190px", width: "190px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
const disabled = (record.owner === this.props.account.owner && record.name === this.props.account.name);
return ( return (
<div> <div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
@ -333,7 +335,7 @@ class UserListPage extends BaseListPage {
title={`Sure to delete user: ${record.name} ?`} title={`Sure to delete user: ${record.name} ?`}
onConfirm={() => this.deleteUser(index)} onConfirm={() => this.deleteUser(index)}
> >
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button> <Button disabled={disabled} style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
); );

View File

@ -349,49 +349,54 @@ class LoginPage extends React.Component {
> >
</Form.Item> </Form.Item>
{this.renderMethodChoiceBox()} {this.renderMethodChoiceBox()}
<Form.Item <Row style={{minHeight: 130, alignItems: "center"}}>
name="username" <Col span={24}>
rules={[ <Form.Item
{ name="username"
required: true, rules={[
message: i18next.t("login:Please input your username, Email or phone!"), {
}, required: true,
{ message: i18next.t("login:Please input your username, Email or phone!"),
validator: (_, value) => { },
if (this.state.isCodeSignin) { {
if (this.state.email !== "" && !Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) { validator: (_, value) => {
this.setState({validEmailOrPhone: false}); if (this.state.isCodeSignin) {
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!")); if (this.state.email !== "" && !Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
} this.setState({validEmailOrPhone: false});
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
}
if (Setting.isValidPhone(this.state.username)) { if (Setting.isValidPhone(this.state.username)) {
this.setState({validPhone: true}); this.setState({validPhone: true});
} }
if (Setting.isValidEmail(this.state.username)) { if (Setting.isValidEmail(this.state.username)) {
this.setState({validEmail: true}); this.setState({validEmail: true});
} }
} }
this.setState({validEmailOrPhone: true}); this.setState({validEmailOrPhone: true});
return Promise.resolve(); return Promise.resolve();
}, },
}, },
]} ]}
> >
<Input <Input
prefix={<UserOutlined className="site-form-item-icon" />} id = "input"
placeholder={this.state.isCodeSignin ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")} prefix={<UserOutlined className="site-form-item-icon" />}
disabled={!application.enablePassword} placeholder={this.state.isCodeSignin ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
onChange={e => { disabled={!application.enablePassword}
this.setState({ onChange={e => {
username: e.target.value, this.setState({
}); username: e.target.value,
}} });
/> }}
</Form.Item> />
{ </Form.Item>
this.renderPasswordOrCodeInput() </Col>
} {
this.renderPasswordOrCodeInput()
}
</Row>
<Form.Item> <Form.Item>
<Form.Item name="autoSignin" valuePropName="checked" noStyle> <Form.Item name="autoSignin" valuePropName="checked" noStyle>
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}> <Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
@ -633,28 +638,32 @@ class LoginPage extends React.Component {
const application = this.getApplicationObj(); const application = this.getApplicationObj();
if (this.state.loginMethod === "password") { if (this.state.loginMethod === "password") {
return this.state.isCodeSignin ? ( return this.state.isCodeSignin ? (
<Form.Item <Col span={24}>
name="code" <Form.Item
rules={[{required: true, message: i18next.t("login:Please input your code!")}]} name="code"
> rules={[{required: true, message: i18next.t("login:Please input your code!")}]}
<CountDownInput >
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone} <CountDownInput
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]} disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
application={application} onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
/> application={application}
</Form.Item> />
</Form.Item>
</Col>
) : ( ) : (
<Form.Item <Col span={24}>
name="password" <Form.Item
rules={[{required: true, message: i18next.t("login:Please input your password!")}]} name="password"
> rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
<Input >
prefix={<LockOutlined className="site-form-item-icon" />} <Input
type="password" prefix={<LockOutlined className="site-form-item-icon" />}
placeholder={i18next.t("login:Password")} type="password"
disabled={!application.enablePassword} placeholder={i18next.t("login:Password")}
/> disabled={!application.enablePassword}
</Form.Item> />
</Form.Item>
</Col>
); );
} }
} }
@ -695,29 +704,32 @@ class LoginPage extends React.Component {
); );
} }
const formStyle = Setting.parseObject(application.formCss); const formStyle = Setting.inIframe() ? null : Setting.parseObject(application.formCss);
return ( return (
<div className="loginBackground" style={{backgroundImage: `url(${application.formBackgroundUrl})`}}> <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
<Row> <CustomGithubCorner />
<Col span={8} offset={application.formOffset === 0 ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}}> <Row >
<Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}}>
<div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center", ...formStyle}}> <div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center", ...formStyle}}>
{ <div>
Setting.renderHelmet(application) {
} Setting.renderHelmet(application)
<CustomGithubCorner /> }
{ {
Setting.renderLogo(application) Setting.renderLogo(application)
} }
{/* {*/} {/* {*/}
{/* this.state.clientId !== null ? "Redirect" : null*/} {/* this.state.clientId !== null ? "Redirect" : null*/}
{/* }*/} {/* }*/}
{ {
this.renderSignedInBox() this.renderSignedInBox()
} }
{ {
this.renderForm(application) this.renderForm(application)
} }
</div>
</div> </div>
</Col> </Col>
</Row> </Row>

View File

@ -614,15 +614,15 @@ class SignupPage extends React.Component {
); );
} }
const formStyle = Setting.parseObject(application.formCss); const formStyle = Setting.inIframe() ? null : Setting.parseObject(application.formCss);
return ( return (
<div className="loginBackground" style={{backgroundImage: `url(${application.formBackgroundUrl})`}}> <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
<CustomGithubCorner /> <CustomGithubCorner />
&nbsp; &nbsp;
<Row> <Row>
<Col span={8} offset={application.formOffset === 0 ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}} > <Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}} >
<div style={{marginTop: "10px", marginBottom: "30px", textAlign: "center", ...formStyle}}> <div style={{marginBottom: "10px", textAlign: "center", ...formStyle}}>
{ {
Setting.renderHelmet(application) Setting.renderHelmet(application)
} }

View File

@ -0,0 +1,63 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as Setting from "../Setting";
export function getAdapters(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-adapters?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function getAdapter(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-adapter?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function updateAdapter(owner, name, Adapter) {
const newAdapter = Setting.deepCopy(Adapter);
return fetch(`${Setting.ServerUrl}/api/update-adapter?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newAdapter),
}).then(res => res.json());
}
export function addAdapter(Adapter) {
const newAdapter = Setting.deepCopy(Adapter);
return fetch(`${Setting.ServerUrl}/api/add-adapter`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newAdapter),
}).then(res => res.json());
}
export function deleteAdapter(Adapter) {
const newAdapter = Setting.deepCopy(Adapter);
return fetch(`${Setting.ServerUrl}/api/delete-adapter`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newAdapter),
}).then(res => res.json());
}
export function syncPolicies(owner, name) {
return fetch(`${Setting.ServerUrl}/api/sync-policies?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}

View File

@ -5,6 +5,13 @@
"My Account": "Mein Konto", "My Account": "Mein Konto",
"Sign Up": "Registrieren" "Sign Up": "Registrieren"
}, },
"adapter": {
"Edit Adapter": "Edit Adapter",
"New Adapter": "New Adapter",
"Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip",
"Sync": "Sync"
},
"application": { "application": {
"Background URL": "Background URL", "Background URL": "Background URL",
"Background URL - Tooltip": "Background URL - Tooltip", "Background URL - Tooltip": "Background URL - Tooltip",
@ -107,6 +114,7 @@
"Action": "Aktion", "Action": "Aktion",
"Adapter": "Adapter", "Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip", "Adapter - Tooltip": "Adapter - Tooltip",
"Adapters": "Adapters",
"Add": "Neu", "Add": "Neu",
"Affiliation URL": "Affiliation-URL", "Affiliation URL": "Affiliation-URL",
"Affiliation URL - Tooltip": "Unique string-style identifier", "Affiliation URL - Tooltip": "Unique string-style identifier",

View File

@ -5,6 +5,13 @@
"My Account": "My Account", "My Account": "My Account",
"Sign Up": "Sign Up" "Sign Up": "Sign Up"
}, },
"adapter": {
"Edit Adapter": "Edit Adapter",
"New Adapter": "New Adapter",
"Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip",
"Sync": "Sync"
},
"application": { "application": {
"Background URL": "Background URL", "Background URL": "Background URL",
"Background URL - Tooltip": "Background URL - Tooltip", "Background URL - Tooltip": "Background URL - Tooltip",
@ -107,6 +114,7 @@
"Action": "Action", "Action": "Action",
"Adapter": "Adapter", "Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip", "Adapter - Tooltip": "Adapter - Tooltip",
"Adapters": "Adapters",
"Add": "Add", "Add": "Add",
"Affiliation URL": "Affiliation URL", "Affiliation URL": "Affiliation URL",
"Affiliation URL - Tooltip": "Affiliation URL - Tooltip", "Affiliation URL - Tooltip": "Affiliation URL - Tooltip",

View File

@ -5,6 +5,13 @@
"My Account": "Mon Compte", "My Account": "Mon Compte",
"Sign Up": "S'inscrire" "Sign Up": "S'inscrire"
}, },
"adapter": {
"Edit Adapter": "Edit Adapter",
"New Adapter": "New Adapter",
"Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip",
"Sync": "Sync"
},
"application": { "application": {
"Background URL": "Background URL", "Background URL": "Background URL",
"Background URL - Tooltip": "Background URL - Tooltip", "Background URL - Tooltip": "Background URL - Tooltip",
@ -107,6 +114,7 @@
"Action": "Action", "Action": "Action",
"Adapter": "Adapter", "Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip", "Adapter - Tooltip": "Adapter - Tooltip",
"Adapters": "Adapters",
"Add": "Ajouter", "Add": "Ajouter",
"Affiliation URL": "URL d'affiliation", "Affiliation URL": "URL d'affiliation",
"Affiliation URL - Tooltip": "Unique string-style identifier", "Affiliation URL - Tooltip": "Unique string-style identifier",

View File

@ -5,6 +5,13 @@
"My Account": "マイアカウント", "My Account": "マイアカウント",
"Sign Up": "新規登録" "Sign Up": "新規登録"
}, },
"adapter": {
"Edit Adapter": "Edit Adapter",
"New Adapter": "New Adapter",
"Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip",
"Sync": "Sync"
},
"application": { "application": {
"Background URL": "Background URL", "Background URL": "Background URL",
"Background URL - Tooltip": "Background URL - Tooltip", "Background URL - Tooltip": "Background URL - Tooltip",
@ -107,6 +114,7 @@
"Action": "アクション", "Action": "アクション",
"Adapter": "Adapter", "Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip", "Adapter - Tooltip": "Adapter - Tooltip",
"Adapters": "Adapters",
"Add": "追加", "Add": "追加",
"Affiliation URL": "アフィリエイトURL", "Affiliation URL": "アフィリエイトURL",
"Affiliation URL - Tooltip": "Unique string-style identifier", "Affiliation URL - Tooltip": "Unique string-style identifier",

View File

@ -5,6 +5,13 @@
"My Account": "My Account", "My Account": "My Account",
"Sign Up": "Sign Up" "Sign Up": "Sign Up"
}, },
"adapter": {
"Edit Adapter": "Edit Adapter",
"New Adapter": "New Adapter",
"Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip",
"Sync": "Sync"
},
"application": { "application": {
"Background URL": "Background URL", "Background URL": "Background URL",
"Background URL - Tooltip": "Background URL - Tooltip", "Background URL - Tooltip": "Background URL - Tooltip",
@ -107,6 +114,7 @@
"Action": "Action", "Action": "Action",
"Adapter": "Adapter", "Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip", "Adapter - Tooltip": "Adapter - Tooltip",
"Adapters": "Adapters",
"Add": "Add", "Add": "Add",
"Affiliation URL": "Affiliation URL", "Affiliation URL": "Affiliation URL",
"Affiliation URL - Tooltip": "Unique string-style identifier", "Affiliation URL - Tooltip": "Unique string-style identifier",

View File

@ -5,6 +5,13 @@
"My Account": "Мой аккаунт", "My Account": "Мой аккаунт",
"Sign Up": "Регистрация" "Sign Up": "Регистрация"
}, },
"adapter": {
"Edit Adapter": "Edit Adapter",
"New Adapter": "New Adapter",
"Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip",
"Sync": "Sync"
},
"application": { "application": {
"Background URL": "Background URL", "Background URL": "Background URL",
"Background URL - Tooltip": "Background URL - Tooltip", "Background URL - Tooltip": "Background URL - Tooltip",
@ -107,6 +114,7 @@
"Action": "Действие", "Action": "Действие",
"Adapter": "Adapter", "Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip", "Adapter - Tooltip": "Adapter - Tooltip",
"Adapters": "Adapters",
"Add": "Добавить", "Add": "Добавить",
"Affiliation URL": "URL-адрес партнёра", "Affiliation URL": "URL-адрес партнёра",
"Affiliation URL - Tooltip": "Unique string-style identifier", "Affiliation URL - Tooltip": "Unique string-style identifier",

View File

@ -5,6 +5,13 @@
"My Account": "我的账户", "My Account": "我的账户",
"Sign Up": "注册" "Sign Up": "注册"
}, },
"adapter": {
"Edit Adapter": "编辑适配器",
"New Adapter": "添加适配器",
"Policies": "策略",
"Policies - Tooltip": "策略",
"Sync": "同步"
},
"application": { "application": {
"Background URL": "背景图URL", "Background URL": "背景图URL",
"Background URL - Tooltip": "登录页背景图的链接", "Background URL - Tooltip": "登录页背景图的链接",
@ -107,6 +114,7 @@
"Action": "操作", "Action": "操作",
"Adapter": "适配器", "Adapter": "适配器",
"Adapter - Tooltip": "策略存储的表名", "Adapter - Tooltip": "策略存储的表名",
"Adapters": "适配器",
"Add": "添加", "Add": "添加",
"Affiliation URL": "工作单位URL", "Affiliation URL": "工作单位URL",
"Affiliation URL - Tooltip": "工作单位URL", "Affiliation URL - Tooltip": "工作单位URL",