Compare commits

...

51 Commits

Author SHA1 Message Date
UsherFall
c54b54ca19 fix: Adjust custom http to notification provider (#2237)
* feat: Adjust custom http to notification provider

* fix go linter

* update ProviderEditPage

* update ProviderEditPage
2023-08-20 21:04:30 +08:00
Yaodong Yu
f0e097e138 feat: fix home page (#2236)
* fix: home page

* fix: home page
2023-08-20 00:58:39 +08:00
Yang Luo
25ec1bdfa8 Fix bug in getUserOrganization() 2023-08-20 00:53:51 +08:00
Yang Luo
ea7718d7b7 Use Casvisor for records 2023-08-20 00:44:01 +08:00
Yang Luo
463fa8b636 Add ormer_session.go 2023-08-19 18:41:08 +08:00
Yang Luo
11895902f4 Move getCreateDatabaseFlag() to ormer 2023-08-19 16:44:34 +08:00
Yang Luo
15269d3315 Refactor out conf_quota.go 2023-08-19 16:39:21 +08:00
Yang Luo
4468859795 Improve sendTest msg 2023-08-19 12:47:51 +08:00
UsherFall
914128a78a fix: Support Telegram Notification provider (#2225)
* fear: support telegram provider

* fix: fix telegram logo

* fix: fix telegram bot package

* Update telegram.go

* Update notification.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-19 12:33:00 +08:00
Yaodong Yu
e5a189e0f4 fix: remove isGlobalAdmin field in user (#2235)
* refactor: remove isGlobalAdmin field in user

* fix: upload xlsx

* fix: remove field in account table
2023-08-19 12:23:15 +08:00
Yang Luo
a07216d0e1 Improve contentType parsing in downloadImage() 2023-08-19 02:35:45 +08:00
haiwu
fec54944dd feat: fix CAS login bug (#2230)
* fix: cas login

* fix: cas login

* feat: rollback get-default-app change

* fix : move cas restrict logic to GetApplicationLogin()

* fix: format code

* fix: fix getOAuthGetParameters for cas

* fix: fix getOAuthGetParameters for cas

* fix: cas login
2023-08-19 01:15:41 +08:00
hsluoyz
a2db61cc1a chore: Revert "feat: restrict redirectUrls for CAS login" (#2234)
This reverts commit b7a37126ad.
2023-08-19 00:30:35 +08:00
Yaodong Yu
134541acde chore: put some dev dependency package to right place (#2232) 2023-08-18 22:17:16 +08:00
Yaodong Yu
59fca0342e chore: fix yarn build warning (#2231) 2023-08-18 21:25:57 +08:00
Yang Luo
abfc464155 Remove isEnabled for model, adapter and enforcer, improve UI 2023-08-18 19:22:47 +08:00
Yaodong Yu
a41f6880a2 feat: move policy table from adapter to enforcer and improve it (#2228)
* feat: improve policiy table

* feat: add connection test in AdapterEditPage.js

* feat: update button style
2023-08-18 19:00:21 +08:00
Yaodong Yu
d12117324c feat: support admin to enable MFA for other users (#2221)
* feat: support admin enable user sms and email mfa

* chore: update ci

* chore: update ci
2023-08-17 17:19:24 +08:00
hsluoyz
1a6c9fbf69 Fix typo in README 2023-08-17 14:47:09 +08:00
hsluoyz
dd60d79af9 Fix typo in README 2023-08-17 14:46:10 +08:00
Yang Luo
73d314c7fe Add MfaTotpPeriodInSeconds param 2023-08-16 21:48:54 +08:00
Yaodong Yu
27959e0f6f fix: fix crash in UserEditPage.js 2023-08-16 15:57:48 +08:00
Baihhh
47f40c5b24 feat: support 3 more UI languages (#2218)
Signed-off-by: baihhh <2542274498@qq.com>
2023-08-16 15:54:34 +08:00
haiwu
2ff9020884 feat: support Stripe payment provider (#2204)
* feat: add stripe payment provider

* feat: support stripe payment

* feat: delete todo comment

* feat: remove description struct

* feat: change outOrderId->orderId
2023-08-15 00:16:30 +08:00
Yang Luo
abaf4ca8d9 Make GetDashboard() faster 2023-08-14 15:43:09 +08:00
8ff0cfd6ec feat: support dashboard in homepage (#2207)
* feat: support dashboard

* feat: support dashboard
2023-08-14 15:31:29 +08:00
Yang Luo
7a2a40edcc Improve table columns 2023-08-14 12:19:02 +08:00
Yang Luo
b7a001ea39 Fix property empty issue 2023-08-14 12:09:50 +08:00
haiwu
891e8e21d8 feat: support Web3-Onboard provider (#2209)
* feat: add Web3-Onboard idp

* feat: update Web3-Onboard logo

* feat: update package.json

* feat: remove unused package

* feat: add yarn build param --max_old_space_size=4096

* feat: remove log

* feat: add Wallet configure

* feat: remove hardware wallets
2023-08-13 23:58:57 +08:00
Baihhh
80b0d26813 fix: synchronize update the syncers (#2201)
Signed-off-by: baihhh <2542274498@qq.com>
2023-08-13 22:30:57 +08:00
Yaodong Yu
db4ac60bb6 feat: fix LDAP mobile field incorrect mapped (#2206) 2023-08-12 13:45:26 +08:00
Yang Luo
33a922f026 Add custom HTTP SMS provider 2023-08-12 12:52:53 +08:00
Yang Luo
9f65053d04 Improve i18n 2023-08-12 02:44:38 +08:00
Yang Luo
be969e5efa Fix typo 2023-08-11 22:18:35 +08:00
Yang Luo
9156bd426b ci: Show provider.displayName in signin button 2023-08-11 16:29:52 +08:00
Yang Luo
fe4a4328aa feat: refactor code in InitApi() 2023-08-11 16:17:29 +08:00
Yaodong Yu
9899022bcd fix: check enforcer should not be nil (#2199)
* fix: check enforcer should not be nil

* fix: check enforcer should not be nil

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-11 12:31:49 +08:00
Yaodong Yu
1a9d02be46 feat: use the casbin model to store relationships between users and groups (#2178)
* fix:reslove conflict

* fix: remove interface
2023-08-11 10:59:18 +08:00
Yang Luo
eafaa135b4 Change builtInAvailableField back to 5 2023-08-11 02:45:11 +08:00
Yang Luo
6746551447 Improve error message in InitEnforcer() 2023-08-11 02:36:29 +08:00
Yang Luo
3cb46c3628 Add isKey to syncer's table 2023-08-09 00:33:04 +08:00
Yaodong Yu
558bcf95d6 feat: save policy in adapter edit page (#2190)
* fix: save policy in adapter

* fix: disable edit for builtin adapter
2023-08-09 00:12:53 +08:00
Yang Luo
bb937c30c1 Fix empty cert in getPaymentProvider() 2023-08-08 22:37:48 +08:00
Baihhh
8dfdf7f767 ci: add GoogleCloud and QiNiu in Storage (#2188)
* feat: add GoogleCloud and QiNiu in Storage

Signed-off-by: baihhh <2542274498@qq.com>

* Update qiniu_cloud.go

* Update storage.go

---------

Signed-off-by: baihhh <2542274498@qq.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-08 22:34:55 +08:00
Yang Luo
62b2082e82 Add getUserOrganization() to user edit page 2023-08-08 21:58:27 +08:00
Yang Luo
a1806439f8 Add UserPrincipalName and MemberOf to get-ldap-users API 2023-08-08 20:18:47 +08:00
Yang Luo
01e58158b7 feat: Remove useless code 2023-08-08 19:16:55 +08:00
Yaodong Yu
15427ad9d6 fix: fix add provider error (#2184) 2023-08-07 17:22:32 +08:00
YunShu
d058f78dc6 fix: fix broken links (#2181) 2023-08-07 01:02:03 +08:00
UsherFall
fd9dbf8251 feat: add multiple SMS providers (#2182)
* feat: add amazon sns and azure acs provider

* feat: add msg91 sms provider

* feat: add infobip sms provider

* feat: add ucloud sms provider

* feat: add baidu cloud sms provider

* fix: fix logo and azure acs
2023-08-07 00:59:17 +08:00
Yaodong Yu
3220a04fa9 fix: use org/groupName replace groupName (#2180) 2023-08-06 20:16:44 +08:00
153 changed files with 11610 additions and 2551 deletions

View File

@@ -110,7 +110,7 @@ jobs:
with: with:
start: yarn start start: yarn start
wait-on: 'http://localhost:7001' wait-on: 'http://localhost:7001'
wait-on-timeout: 180 wait-on-timeout: 210
working-directory: ./web working-directory: ./web
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3

View File

@@ -11,7 +11,7 @@
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casdoor/casdoor/workflows/Build/badge.svg?style=flat-square"> <img alt="GitHub Workflow Status (branch)" src="https://github.com/casdoor/casdoor/workflows/Build/badge.svg?style=flat-square">
</a> </a>
<a href="https://github.com/casdoor/casdoor/releases/latest"> <a href="https://github.com/casdoor/casdoor/releases/latest">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casdoor.svg"> <img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
</a> </a>
<a href="https://hub.docker.com/repository/docker/casbin/casdoor"> <a href="https://hub.docker.com/repository/docker/casbin/casdoor">
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen"> <img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
@@ -23,16 +23,16 @@
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square"> <img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
</a> </a>
<a href="https://github.com/casdoor/casdoor/blob/master/LICENSE"> <a href="https://github.com/casdoor/casdoor/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/casbin/casdoor?style=flat-square" alt="license"> <img src="https://img.shields.io/github/license/casdoor/casdoor?style=flat-square" alt="license">
</a> </a>
<a href="https://github.com/casdoor/casdoor/issues"> <a href="https://github.com/casdoor/casdoor/issues">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casdoor?style=flat-square"> <img alt="GitHub issues" src="https://img.shields.io/github/issues/casdoor/casdoor?style=flat-square">
</a> </a>
<a href="#"> <a href="#">
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square"> <img alt="GitHub stars" src="https://img.shields.io/github/stars/casdoor/casdoor?style=flat-square">
</a> </a>
<a href="https://github.com/casdoor/casdoor/network"> <a href="https://github.com/casdoor/casdoor/network">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square"> <img alt="GitHub forks" src="https://img.shields.io/github/forks/casdoor/casdoor?style=flat-square">
</a> </a>
<a href="https://crowdin.com/project/casdoor-site"> <a href="https://crowdin.com/project/casdoor-site">
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg"> <img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">

View File

@@ -27,13 +27,7 @@ import (
var Enforcer *casbin.Enforcer var Enforcer *casbin.Enforcer
func InitApi() { func InitApi() {
var err error e, err := object.GetInitializedEnforcer(util.GetId("built-in", "api-enforcer-built-in"))
e, err := object.GetEnforcer(util.GetId("built-in", "api-enforcer-built-in"))
if err != nil {
panic(err)
}
err = e.InitEnforcer()
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -15,7 +15,7 @@
package conf package conf
import ( import (
"encoding/json" "fmt"
"os" "os"
"runtime" "runtime"
"strconv" "strconv"
@@ -24,15 +24,6 @@ import (
"github.com/beego/beego" "github.com/beego/beego"
) )
type Quota struct {
Organization int `json:"organization"`
User int `json:"user"`
Application int `json:"application"`
Provider int `json:"provider"`
}
var quota = &Quota{-1, -1, -1, -1}
func init() { func init() {
// this array contains the beego configuration items that may be modified via env // this array contains the beego configuration items that may be modified via env
presetConfigItems := []string{"httpport", "appname"} presetConfigItems := []string{"httpport", "appname"}
@@ -44,17 +35,6 @@ func init() {
} }
} }
} }
initQuota()
}
func initQuota() {
res := beego.AppConfig.String("quota")
if res != "" {
err := json.Unmarshal([]byte(res), quota)
if err != nil {
panic(err)
}
}
} }
func GetConfigString(key string) string { func GetConfigString(key string) string {
@@ -67,7 +47,7 @@ func GetConfigString(key string) string {
if key == "staticBaseUrl" { if key == "staticBaseUrl" {
res = "https://cdn.casbin.org" res = "https://cdn.casbin.org"
} else if key == "logConfig" { } else if key == "logConfig" {
res = "{\"filename\": \"logs/casdoor.log\", \"maxdays\":99999, \"perm\":\"0770\"}" res = fmt.Sprintf("{\"filename\": \"logs/%s.log\", \"maxdays\":99999, \"perm\":\"0770\"}", beego.AppConfig.String("appname"))
} }
} }
@@ -129,10 +109,6 @@ func GetConfigBatchSize() int {
return res return res
} }
func GetConfigQuota() *Quota {
return quota
}
func GetConfigRealDataSourceName(driverName string) string { func GetConfigRealDataSourceName(driverName string) string {
var dataSourceName string var dataSourceName string
if driverName != "mysql" { if driverName != "mysql" {

48
conf/conf_quota.go Normal file
View File

@@ -0,0 +1,48 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conf
import (
"encoding/json"
"github.com/beego/beego"
)
type Quota struct {
Organization int `json:"organization"`
User int `json:"user"`
Application int `json:"application"`
Provider int `json:"provider"`
}
var quota = &Quota{-1, -1, -1, -1}
func init() {
initQuota()
}
func initQuota() {
res := beego.AppConfig.String("quota")
if res != "" {
err := json.Unmarshal([]byte(res), quota)
if err != nil {
panic(err)
}
}
}
func GetConfigQuota() *Quota {
return quota
}

View File

@@ -171,7 +171,6 @@ func (c *ApiController) Signup() {
Region: authForm.Region, Region: authForm.Region,
Score: initScore, Score: initScore,
IsAdmin: false, IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false, IsForbidden: false,
IsDeleted: false, IsDeleted: false,
SignupApplication: application.Name, SignupApplication: application.Name,

View File

@@ -20,7 +20,6 @@ import (
"github.com/beego/beego/utils/pagination" "github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
) )
// GetAdapters // GetAdapters
@@ -144,92 +143,3 @@ func (c *ApiController) DeleteAdapter() {
c.Data["json"] = wrapActionResponse(object.DeleteAdapter(&adapter)) c.Data["json"] = wrapActionResponse(object.DeleteAdapter(&adapter))
c.ServeJSON() c.ServeJSON()
} }
func (c *ApiController) GetPolicies() {
id := c.Input().Get("id")
adapter, err := object.GetAdapter(id)
if err != nil {
c.ResponseError(err.Error())
return
}
policies, err := object.GetPolicies(adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(policies)
}
func (c *ApiController) UpdatePolicy() {
id := c.Input().Get("id")
adapter, err := object.GetAdapter(id)
if err != nil {
c.ResponseError(err.Error())
return
}
var policies []xormadapter.CasbinRule
err = json.Unmarshal(c.Ctx.Input.RequestBody, &policies)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UpdatePolicy(util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) AddPolicy() {
id := c.Input().Get("id")
adapter, err := object.GetAdapter(id)
if err != nil {
c.ResponseError(err.Error())
return
}
var policy xormadapter.CasbinRule
err = json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.AddPolicy(util.CasbinToSlice(policy), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) RemovePolicy() {
id := c.Input().Get("id")
adapter, err := object.GetAdapter(id)
if err != nil {
c.ResponseError(err.Error())
return
}
var policy xormadapter.CasbinRule
err = json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.RemovePolicy(util.CasbinToSlice(policy), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}

View File

@@ -70,7 +70,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
} }
// check user's tag // check user's tag
if !user.IsGlobalAdmin && !user.IsAdmin && len(application.Tags) > 0 { if !user.IsGlobalAdmin() && !user.IsAdmin && len(application.Tags) > 0 {
// only users with the tag that is listed in the application tags can login // only users with the tag that is listed in the application tags can login
if !util.InSlice(application.Tags, user.Tag) { if !util.InSlice(application.Tags, user.Tag) {
c.ResponseError(fmt.Sprintf(c.T("auth:User's tag: %s is not listed in the application's tags"), user.Tag)) c.ResponseError(fmt.Sprintf(c.T("auth:User's tag: %s is not listed in the application's tags"), user.Tag))
@@ -187,11 +187,34 @@ func (c *ApiController) GetApplicationLogin() {
redirectUri := c.Input().Get("redirectUri") redirectUri := c.Input().Get("redirectUri")
scope := c.Input().Get("scope") scope := c.Input().Get("scope")
state := c.Input().Get("state") state := c.Input().Get("state")
id := c.Input().Get("id")
loginType := c.Input().Get("type")
msg, application, err := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state, c.GetAcceptLanguage()) var application *object.Application
if err != nil { var msg string
c.ResponseError(err.Error()) var err error
return if loginType == "code" {
msg, application, err = object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
} else if loginType == "cas" {
application, err = object.GetApplication(id)
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), id))
return
}
err = object.CheckCasLogin(application, c.GetAcceptLanguage(), redirectUri)
if err != nil {
c.ResponseError(err.Error())
return
}
} }
application = object.GetMaskedApplication(application, "") application = object.GetMaskedApplication(application, "")
@@ -566,7 +589,6 @@ func (c *ApiController) Login() {
Region: userInfo.CountryCode, Region: userInfo.CountryCode,
Score: initScore, Score: initScore,
IsAdmin: false, IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false, IsForbidden: false,
IsDeleted: false, IsDeleted: false,
SignupApplication: application.Name, SignupApplication: application.Name,

View File

@@ -79,7 +79,7 @@ func (c *ApiController) isGlobalAdmin() (bool, *object.User) {
return false, nil return false, nil
} }
return user.Owner == "built-in" || user.IsGlobalAdmin, user return user.IsGlobalAdmin(), user
} }
func (c *ApiController) getCurrentUser() *object.User { func (c *ApiController) getCurrentUser() *object.User {

View File

@@ -45,13 +45,7 @@ func (c *ApiController) Enforce() {
} }
if enforcerId != "" { if enforcerId != "" {
enforcer, err := object.GetEnforcer(enforcerId) enforcer, err := object.GetInitializedEnforcer(enforcerId)
if err != nil {
c.ResponseError(err.Error())
return
}
err = enforcer.InitEnforcer()
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -155,13 +149,7 @@ func (c *ApiController) BatchEnforce() {
} }
if enforcerId != "" { if enforcerId != "" {
enforcer, err := object.GetEnforcer(enforcerId) enforcer, err := object.GetInitializedEnforcer(enforcerId)
if err != nil {
c.ResponseError(err.Error())
return
}
err = enforcer.InitEnforcer()
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@@ -20,6 +20,7 @@ import (
"github.com/beego/beego/utils/pagination" "github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
) )
// GetEnforcers // GetEnforcers
@@ -74,6 +75,7 @@ func (c *ApiController) GetEnforcers() {
// @router /get-enforcer [get] // @router /get-enforcer [get]
func (c *ApiController) GetEnforcer() { func (c *ApiController) GetEnforcer() {
id := c.Input().Get("id") id := c.Input().Get("id")
loadModelCfg := c.Input().Get("loadModelCfg")
enforcer, err := object.GetEnforcer(id) enforcer, err := object.GetEnforcer(id)
if err != nil { if err != nil {
@@ -81,6 +83,13 @@ func (c *ApiController) GetEnforcer() {
return return
} }
if loadModelCfg == "true" {
err := enforcer.LoadModelCfg()
if err != nil {
return
}
}
c.ResponseOk(enforcer) c.ResponseOk(enforcer)
} }
@@ -143,3 +152,88 @@ func (c *ApiController) DeleteEnforcer() {
c.Data["json"] = wrapActionResponse(object.DeleteEnforcer(&enforcer)) c.Data["json"] = wrapActionResponse(object.DeleteEnforcer(&enforcer))
c.ServeJSON() c.ServeJSON()
} }
func (c *ApiController) GetPolicies() {
id := c.Input().Get("id")
adapterId := c.Input().Get("adapterId")
if adapterId != "" {
adapter, err := object.GetAdapter(adapterId)
if err != nil {
c.ResponseError(err.Error())
return
}
err = adapter.InitAdapter()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
return
}
policies, err := object.GetPolicies(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(policies)
}
func (c *ApiController) UpdatePolicy() {
id := c.Input().Get("id")
var policies []xormadapter.CasbinRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policies)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UpdatePolicy(id, util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]))
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) AddPolicy() {
id := c.Input().Get("id")
var policy xormadapter.CasbinRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.AddPolicy(id, util.CasbinToSlice(policy))
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) RemovePolicy() {
id := c.Input().Get("id")
var policy xormadapter.CasbinRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.RemovePolicy(id, util.CasbinToSlice(policy))
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}

View File

@@ -0,0 +1,35 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import "github.com/casdoor/casdoor/object"
// GetDashboard
// @Title GetDashboard
// @Tag GetDashboard API
// @Description get information of dashboard
// @Success 200 {object} controllers.Response The Response object
// @router /get-dashboard [get]
func (c *ApiController) GetDashboard() {
owner := c.Input().Get("owner")
data, err := object.GetDashboard(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(data)
}

View File

@@ -45,13 +45,13 @@ 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
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.
c.ResponseError(c.T("link:You are not the global admin, you can't unlink other users")) c.ResponseError(c.T("link:You are not the global admin, you can't unlink other users"))
return return
} }
if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin { if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin() {
// if the user is unlinking themselves, should check the provider can be unlinked, if not, we should return an error. // if the user is unlinking themselves, should check the provider can be unlinked, if not, we should return an error.
application, err := object.GetApplicationByUser(user) application, err := object.GetApplicationByUser(user)
if err != nil { if err != nil {

View File

@@ -183,8 +183,6 @@ func (c *ApiController) DeleteOrganization() {
func (c *ApiController) GetDefaultApplication() { func (c *ApiController) GetDefaultApplication() {
userId := c.GetSessionUsername() userId := c.GetSessionUsername()
id := c.Input().Get("id") id := c.Input().Get("id")
redirectUri := c.Input().Get("redirectUri")
typ := c.Input().Get("type")
application, err := object.GetDefaultApplication(id) application, err := object.GetDefaultApplication(id)
if err != nil { if err != nil {
@@ -192,14 +190,6 @@ func (c *ApiController) GetDefaultApplication() {
return return
} }
if typ == "cas" {
err = object.CheckCasRestrict(application, c.GetAcceptLanguage(), redirectUri)
if err != nil {
c.ResponseError(err.Error())
return
}
}
maskedApplication := object.GetMaskedApplication(application, userId) maskedApplication := object.GetMaskedApplication(application, userId)
c.ResponseOk(maskedApplication) c.ResponseOk(maskedApplication)
} }

View File

@@ -1,121 +0,0 @@
// Copyright 2021 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/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetRecords
// @Title GetRecords
// @Tag Record API
// @Description get all records
// @Param pageSize query string true "The size of each page"
// @Param p query string true "The number of the page"
// @Success 200 {object} object.Record The Response object
// @router /get-records [get]
func (c *ApiController) GetRecords() {
organization, ok := c.RequireAdmin()
if !ok {
return
}
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")
organizationName := c.Input().Get("organizationName")
if limit == "" || page == "" {
records, err := object.GetRecords()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(records)
} else {
limit := util.ParseInt(limit)
if c.IsGlobalAdmin() && organizationName != "" {
organization = organizationName
}
filterRecord := &object.Record{Organization: organization}
count, err := object.GetRecordCount(field, value, filterRecord)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
records, err := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder, filterRecord)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(records, paginator.Nums())
}
}
// GetRecordsByFilter
// @Tag Record API
// @Title GetRecordsByFilter
// @Description get records by filter
// @Param filter body string true "filter Record message"
// @Success 200 {object} object.Record The Response object
// @router /get-records-filter [post]
func (c *ApiController) GetRecordsByFilter() {
body := string(c.Ctx.Input.RequestBody)
record := &object.Record{}
err := util.JsonToStruct(body, record)
if err != nil {
c.ResponseError(err.Error())
return
}
records, err := object.GetRecordsByField(record)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(records)
}
// 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

@@ -361,6 +361,9 @@ func (c *ApiController) UploadResource() {
return return
} }
if user.Properties == nil {
user.Properties = map[string]string{}
}
user.Properties[tag] = fileUrl user.Properties[tag] = fileUrl
user.Properties["isIdCardVerified"] = "false" user.Properties["isIdCardVerified"] = "false"
_, err = object.UpdateUser(user.GetId(), user, []string{"properties"}, false) _, err = object.UpdateUser(user.GetId(), user, []string{"properties"}, false)

View File

@@ -40,6 +40,10 @@ type SmsForm struct {
OrgId string `json:"organizationId"` // e.g. "admin/built-in" OrgId string `json:"organizationId"` // e.g. "admin/built-in"
} }
type NotificationForm struct {
Content string `json:"content"`
}
// SendEmail // SendEmail
// @Title SendEmail // @Title SendEmail
// @Tag Service API // @Tag Service API
@@ -140,10 +144,12 @@ func (c *ApiController) SendSms() {
return return
} }
invalidReceivers := getInvalidSmsReceivers(smsForm) if provider.Type != "Custom HTTP SMS" {
if len(invalidReceivers) != 0 { invalidReceivers := getInvalidSmsReceivers(smsForm)
c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), strings.Join(invalidReceivers, ", "))) if len(invalidReceivers) != 0 {
return c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), strings.Join(invalidReceivers, ", ")))
return
}
} }
err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...) err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...)
@@ -154,3 +160,33 @@ func (c *ApiController) SendSms() {
c.ResponseOk() c.ResponseOk()
} }
// SendNotification
// @Title SendNotification
// @Tag Service API
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param from body controllers.NotificationForm true "Details of the notification request"
// @Success 200 {object} Response object
// @router /api/send-notification [post]
func (c *ApiController) SendNotification() {
provider, err := c.GetProviderFromContext("Notification")
if err != nil {
c.ResponseError(err.Error())
return
}
var notificationForm NotificationForm
err = json.Unmarshal(c.Ctx.Input.RequestBody, &notificationForm)
if err != nil {
c.ResponseError(err.Error())
return
}
err = object.SendNotification(provider, notificationForm.Content)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}

View File

@@ -90,7 +90,7 @@ func (c *ApiController) GetUsers() {
if limit == "" || page == "" { if limit == "" || page == "" {
if groupName != "" { if groupName != "" {
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(groupName)) maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(util.GetId(owner, groupName)))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -567,6 +567,22 @@ func (c *ApiController) RemoveUserFromGroup() {
name := c.Ctx.Request.Form.Get("name") name := c.Ctx.Request.Form.Get("name")
groupName := c.Ctx.Request.Form.Get("groupName") groupName := c.Ctx.Request.Form.Get("groupName")
c.Data["json"] = wrapActionResponse(object.RemoveUserFromGroup(owner, name, groupName)) organization, err := object.GetOrganization(util.GetId("admin", owner))
c.ServeJSON() if err != nil {
return
}
item := object.GetAccountItemByName("Groups", organization)
res, msg := object.CheckAccountItemModifyRule(item, c.IsAdmin(), c.GetAcceptLanguage())
if !res {
c.ResponseError(msg)
return
}
affected, err := object.DeleteGroupForUser(util.GetId(owner, name), groupName)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(affected)
} }

28
go.mod
View File

@@ -12,10 +12,11 @@ require (
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/casbin/casbin v1.9.1 // indirect github.com/casbin/casbin v1.9.1 // indirect
github.com/casbin/casbin/v2 v2.30.1 github.com/casbin/casbin/v2 v2.30.1
github.com/casdoor/go-sms-sender v0.6.1 github.com/casdoor/go-sms-sender v0.12.0
github.com/casdoor/gomail/v2 v2.0.1 github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/oss v1.2.1 github.com/casdoor/oss v1.3.0
github.com/casdoor/xorm-adapter/v3 v3.0.4 github.com/casdoor/xorm-adapter/v3 v3.0.4
github.com/casvisor/casvisor-go-sdk v1.0.3
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.9.0 github.com/denisenkom/go-mssqldb v0.9.0
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
@@ -26,24 +27,22 @@ require (
github.com/go-mysql-org/go-mysql v1.7.0 github.com/go-mysql-org/go-mysql v1.7.0
github.com/go-pay/gopay v1.5.72 github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/go-webauthn/webauthn v0.6.0 github.com/go-webauthn/webauthn v0.6.0
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.7.3 // indirect
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 v1.2.21 github.com/lestrrat-go/jwx v1.2.21
github.com/lib/pq v1.10.2 github.com/lib/pq v1.10.9
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/markbates/goth v1.75.2 github.com/markbates/goth v1.75.2
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/nikoksr/notify v0.41.0
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/nyaruka/phonenumbers v1.1.5 github.com/nyaruka/phonenumbers v1.1.5
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.11.1 github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.2.0 github.com/prometheus/client_model v0.3.0
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
github.com/russellhaering/gosaml2 v0.9.0 github.com/russellhaering/gosaml2 v0.9.0
@@ -53,7 +52,8 @@ require (
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.8.4
github.com/stripe/stripe-go/v74 v74.29.0
github.com/tealeg/xlsx v1.0.5 github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4 github.com/thanhpk/randstr v1.0.4
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
@@ -61,12 +61,10 @@ require (
github.com/xorm-io/core v0.7.4 github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6 github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.6.0 golang.org/x/crypto v0.11.0
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/net v0.13.0
golang.org/x/net v0.7.0 golang.org/x/oauth2 v0.10.0
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0 // indirect modernc.org/sqlite v1.18.2
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84
) )

1592
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,9 @@ func TestGenerateI18nFrontend(t *testing.T) {
applyToOtherLanguage("frontend", "it", data) applyToOtherLanguage("frontend", "it", data)
applyToOtherLanguage("frontend", "ms", data) applyToOtherLanguage("frontend", "ms", data)
applyToOtherLanguage("frontend", "tr", data) applyToOtherLanguage("frontend", "tr", data)
applyToOtherLanguage("frontend", "ar", data)
applyToOtherLanguage("frontend", "he", data)
applyToOtherLanguage("frontend", "fi", data)
} }
func TestGenerateI18nBackend(t *testing.T) { func TestGenerateI18nBackend(t *testing.T) {
@@ -55,4 +58,7 @@ func TestGenerateI18nBackend(t *testing.T) {
applyToOtherLanguage("backend", "it", data) applyToOtherLanguage("backend", "it", data)
applyToOtherLanguage("backend", "ms", data) applyToOtherLanguage("backend", "ms", data)
applyToOtherLanguage("backend", "tr", data) applyToOtherLanguage("backend", "tr", data)
applyToOtherLanguage("backend", "ar", data)
applyToOtherLanguage("backend", "he", data)
applyToOtherLanguage("backend", "fi", data)
} }

142
i18n/locales/ar/data.json Normal file
View File

@@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

142
i18n/locales/fi/data.json Normal file
View File

@@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

142
i18n/locales/he/data.json Normal file
View File

@@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@@ -132,7 +132,7 @@ func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
"type": "User", "type": "User",
"blog": null, "blog": null,
"weibo": null, "weibo": null,
"bio": "个人博客https://gitee.com/xxx/xxx/pages", "bio": "bio",
"public_repos": 2, "public_repos": 2,
"public_gists": 0, "public_gists": 0,
"followers": 0, "followers": 0,

View File

@@ -24,20 +24,10 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const Web3AuthTokenKey = "web3AuthToken"
type MetaMaskIdProvider struct { type MetaMaskIdProvider struct {
Client *http.Client Client *http.Client
} }
type Web3AuthToken struct {
Address string `json:"address"`
Nonce string `json:"nonce"`
CreateAt uint64 `json:"createAt"`
TypedData string `json:"typedData"`
Signature string `json:"signature"` // signature for typed data
}
func NewMetaMaskIdProvider() *MetaMaskIdProvider { func NewMetaMaskIdProvider() *MetaMaskIdProvider {
idp := &MetaMaskIdProvider{} idp := &MetaMaskIdProvider{}
return idp return idp

View File

@@ -111,6 +111,8 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
case "MetaMask": case "MetaMask":
return NewMetaMaskIdProvider() return NewMetaMaskIdProvider()
case "Web3Onboard":
return NewWeb3OnboardIdProvider()
default: default:
if isGothSupport(idpInfo.Type) { if isGothSupport(idpInfo.Type) {
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)

103
idp/web3onboard.go Normal file
View File

@@ -0,0 +1,103 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"golang.org/x/oauth2"
)
const Web3AuthTokenKey = "web3AuthToken"
type Web3AuthToken struct {
Address string `json:"address"`
Nonce string `json:"nonce"`
CreateAt uint64 `json:"createAt"`
TypedData string `json:"typedData"` // typed data use for application
Signature string `json:"signature"` // signature for typed data
WalletType string `json:"walletType"` // e.g."MetaMask", "Coinbase"
}
type Web3OnboardIdProvider struct {
Client *http.Client
}
func NewWeb3OnboardIdProvider() *Web3OnboardIdProvider {
idp := &Web3OnboardIdProvider{}
return idp
}
func (idp *Web3OnboardIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *Web3OnboardIdProvider) GetToken(code string) (*oauth2.Token, error) {
web3AuthToken := Web3AuthToken{}
if err := json.Unmarshal([]byte(code), &web3AuthToken); err != nil {
return nil, err
}
token := &oauth2.Token{
AccessToken: fmt.Sprintf("%v:%v", Web3AuthTokenKey, web3AuthToken.Address),
TokenType: "Bearer",
Expiry: time.Now().AddDate(0, 1, 0),
}
token = token.WithExtra(map[string]interface{}{
Web3AuthTokenKey: web3AuthToken,
})
return token, nil
}
func (idp *Web3OnboardIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
web3AuthToken, ok := token.Extra(Web3AuthTokenKey).(Web3AuthToken)
if !ok {
return nil, errors.New("invalid web3AuthToken")
}
fmtAddress := fmt.Sprintf("%v_%v",
strings.ReplaceAll(strings.TrimSpace(web3AuthToken.WalletType), " ", "_"),
web3AuthToken.Address,
)
userInfo := &UserInfo{
Id: fmtAddress,
Username: fmtAddress,
DisplayName: fmtAddress,
AvatarUrl: fmt.Sprintf("metamask:%v", forceEthereumAddress(web3AuthToken.Address)),
}
return userInfo, nil
}
func forceEthereumAddress(address string) string {
// The required address to general MetaMask avatar is a string of length 42 that represents an Ethereum address.
// This function is used to force any address as an Ethereum address
address = strings.TrimSpace(address)
var builder strings.Builder
for _, ch := range address {
builder.WriteRune(ch)
}
for len(builder.String()) < 42 {
builder.WriteString("0")
}
if len(builder.String()) > 42 {
return builder.String()[:42]
}
return builder.String()
}

View File

@@ -9,11 +9,11 @@
"passwordType": "plain", "passwordType": "plain",
"passwordSalt": "", "passwordSalt": "",
"passwordOptions": ["AtLeast6"], "passwordOptions": ["AtLeast6"],
"countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR"], "countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR", "DZ", "IL", "PH"],
"defaultAvatar": "", "defaultAvatar": "",
"defaultApplication": "", "defaultApplication": "",
"tags": [], "tags": [],
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr"], "languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "fi"],
"masterPassword": "", "masterPassword": "",
"initScore": 2000, "initScore": 2000,
"enableSoftDeletion": false, "enableSoftDeletion": false,
@@ -123,7 +123,6 @@
"score": 2000, "score": 2000,
"ranking": 1, "ranking": 1,
"isAdmin": true, "isAdmin": true,
"isGlobalAdmin": true,
"isForbidden": false, "isForbidden": false,
"isDeleted": false, "isDeleted": false,
"signupApplication": "", "signupApplication": "",

View File

@@ -62,7 +62,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
return return
} }
if bindOrg == "built-in" || bindUser.IsGlobalAdmin { if bindOrg == "built-in" || bindUser.IsGlobalAdmin() {
m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true
} else if bindUser.IsAdmin { } else if bindUser.IsAdmin {
m.Client.IsOrgAdmin = true m.Client.IsOrgAdmin = true

16
main.go
View File

@@ -15,7 +15,6 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"github.com/beego/beego" "github.com/beego/beego"
@@ -30,17 +29,10 @@ import (
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
func getCreateDatabaseFlag() bool {
res := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
flag.Parse()
return *res
}
func main() { func main() {
createDatabase := getCreateDatabaseFlag() object.InitFlag()
object.InitAdapter()
object.InitAdapter(createDatabase) object.CreateTables()
object.CreateTables(createDatabase)
object.DoMigration() object.DoMigration()
object.InitDb() object.InitDb()
@@ -49,6 +41,8 @@ func main() {
object.InitLdapAutoSynchronizer() object.InitLdapAutoSynchronizer()
proxy.InitHttpClient() proxy.InitHttpClient()
authz.InitApi() authz.InitApi()
object.InitUserManager()
object.InitCasvisorConfig()
util.SafeGoroutine(func() { object.RunSyncUsersJob() }) util.SafeGoroutine(func() { object.RunSyncUsersJob() })

View File

@@ -0,0 +1,73 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"bytes"
"context"
"fmt"
"net/http"
"github.com/casdoor/casdoor/proxy"
)
type HttpNotificationClient struct {
endpoint string
method string
paramName string
}
func NewCustomHttpProvider(endpoint string, method string, paramName string) (*HttpNotificationClient, error) {
client := &HttpNotificationClient{
endpoint: endpoint,
method: method,
paramName: paramName,
}
return client, nil
}
func (c *HttpNotificationClient) Send(ctx context.Context, subject string, content string) error {
var err error
httpClient := proxy.DefaultHttpClient
req, err := http.NewRequest(c.method, c.endpoint, bytes.NewBufferString(content))
if err != nil {
return err
}
if c.method == "POST" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.PostForm = map[string][]string{
c.paramName: {content},
}
} else if c.method == "GET" {
q := req.URL.Query()
q.Add(c.paramName, content)
req.URL.RawQuery = q.Encode()
}
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("SendMessage() error, custom HTTP Notification request failed with status: %s", resp.Status)
}
return err
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved. // Copyright 2023 The Casdoor Authors. All Rights Reserved.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -12,18 +12,16 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package util package notification
import xormadapter "github.com/casdoor/xorm-adapter/v3" import "github.com/nikoksr/notify"
func CasbinToSlice(casbinRule xormadapter.CasbinRule) []string { func GetNotificationProvider(typ string, appId string, receiver string, method string, title string) (notify.Notifier, error) {
s := []string{ if typ == "Telegram" {
casbinRule.V0, return NewTelegramProvider(appId, receiver)
casbinRule.V1, } else if typ == "Custom HTTP" {
casbinRule.V2, return NewCustomHttpProvider(receiver, method, title)
casbinRule.V3,
casbinRule.V4,
casbinRule.V5,
} }
return s
return nil, nil
} }

42
notification/telegram.go Normal file
View File

@@ -0,0 +1,42 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"strconv"
"github.com/casdoor/casdoor/proxy"
api "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/telegram"
)
func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, error) {
client, err := api.NewBotAPIWithClient(apiToken, proxy.ProxyHttpClient)
if err != nil {
return nil, err
}
t := &telegram.Telegram{}
t.SetClient(client)
chatId, err := strconv.ParseInt(chatIdStr, 10, 64)
if err != nil {
return nil, err
}
t.AddReceivers(chatId)
return t, nil
}

View File

@@ -18,11 +18,11 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/casbin/casbin/v2/model"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3" xormadapter "github.com/casdoor/xorm-adapter/v3"
"github.com/xorm-io/core" "github.com/xorm-io/core"
"github.com/xorm-io/xorm"
) )
type Adapter struct { type Adapter struct {
@@ -40,8 +40,6 @@ type Adapter struct {
Table string `xorm:"varchar(100)" json:"table"` Table string `xorm:"varchar(100)" json:"table"`
TableNamePrefix string `xorm:"varchar(100)" json:"tableNamePrefix"` TableNamePrefix string `xorm:"varchar(100)" json:"tableNamePrefix"`
IsEnabled bool `json:"isEnabled"`
*xormadapter.Adapter `xorm:"-" json:"-"` *xormadapter.Adapter `xorm:"-" json:"-"`
} }
@@ -149,20 +147,23 @@ func (adapter *Adapter) getTable() string {
} }
} }
func (adapter *Adapter) initAdapter() error { func (adapter *Adapter) InitAdapter() error {
if adapter.Adapter == nil { if adapter.Adapter == nil {
var dataSourceName string var dataSourceName string
if adapter.builtInAdapter() { if adapter.builtInAdapter() {
dataSourceName = conf.GetConfigString("dataSourceName") dataSourceName = conf.GetConfigString("dataSourceName")
if adapter.DatabaseType == "mysql" {
dataSourceName = dataSourceName + adapter.Database
}
} else { } else {
switch adapter.DatabaseType { switch adapter.DatabaseType {
case "mssql": case "mssql":
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User, dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database) adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "mysql": case "mysql":
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", adapter.User, dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port) adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "postgres": case "postgres":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", adapter.User, dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database) adapter.Password, adapter.Host, adapter.Port, adapter.Database)
@@ -181,7 +182,8 @@ func (adapter *Adapter) initAdapter() error {
} }
var err error var err error
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(NewAdapter(adapter.DatabaseType, dataSourceName, adapter.Database).Engine, adapter.getTable(), adapter.TableNamePrefix) engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName)
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix)
if err != nil { if err != nil {
return err return err
} }
@@ -209,116 +211,10 @@ func adapterChangeTrigger(oldName string, newName string) error {
return session.Commit() return session.Commit()
} }
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 GetPolicies(adapter *Adapter) ([]*xormadapter.CasbinRule, error) {
err := adapter.initAdapter()
if err != nil {
return nil, err
}
casbinModel := getModelDef()
err = adapter.LoadPolicy(casbinModel)
if err != nil {
return nil, err
}
policies := matrixToCasbinRules("p", casbinModel.GetPolicy("p", "p"))
policies = append(policies, matrixToCasbinRules("g", casbinModel.GetPolicy("g", "g"))...)
return policies, nil
}
func UpdatePolicy(oldPolicy, newPolicy []string, adapter *Adapter) (bool, error) {
err := adapter.initAdapter()
if err != nil {
return false, err
}
casbinModel := getModelDef()
err = adapter.LoadPolicy(casbinModel)
if err != nil {
return false, err
}
affected := casbinModel.UpdatePolicy("p", "p", oldPolicy, newPolicy)
if err != nil {
return affected, err
}
return affected, nil
}
func AddPolicy(policy []string, adapter *Adapter) (bool, error) {
err := adapter.initAdapter()
if err != nil {
return false, err
}
casbinModel := getModelDef()
err = adapter.LoadPolicy(casbinModel)
if err != nil {
return false, err
}
casbinModel.AddPolicy("p", "p", policy)
return true, nil
}
func RemovePolicy(policy []string, adapter *Adapter) (bool, error) {
err := adapter.initAdapter()
if err != nil {
return false, err
}
casbinModel := getModelDef()
err = adapter.LoadPolicy(casbinModel)
if err != nil {
return false, err
}
affected := casbinModel.RemovePolicy("p", "p", policy)
if err != nil {
return affected, err
}
return affected, nil
}
func (adapter *Adapter) builtInAdapter() bool { func (adapter *Adapter) builtInAdapter() bool {
if adapter.Owner != "built-in" { if adapter.Owner != "built-in" {
return false return false
} }
return adapter.Name == "permission-adapter-built-in" || adapter.Name == "api-adapter-built-in" return adapter.Name == "user-adapter-built-in" || adapter.Name == "api-adapter-built-in"
}
func getModelDef() model.Model {
casbinModel := model.NewModel()
casbinModel.AddDef("p", "p", "_, _, _, _, _, _")
casbinModel.AddDef("g", "g", "_, _, _, _, _, _")
return casbinModel
} }

View File

@@ -141,7 +141,7 @@ func checkSigninErrorTimes(user *User, lang string) string {
// reset the error times // reset the error times
user.SigninWrongTimes = 0 user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, user.IsGlobalAdmin) UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
} }
return "" return ""
@@ -319,7 +319,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
if requestUser == nil { if requestUser == nil {
return false, fmt.Errorf(i18n.Translate(lang, "check:Session outdated, please login again")) return false, fmt.Errorf(i18n.Translate(lang, "check:Session outdated, please login again"))
} }
if requestUser.IsGlobalAdmin { if requestUser.IsGlobalAdmin() {
hasPermission = true hasPermission = true
} else if requestUserId == userId { } else if requestUserId == userId {
hasPermission = true hasPermission = true

View File

@@ -42,7 +42,7 @@ func resetUserSigninErrorTimes(user *User) {
return return
} }
user.SigninWrongTimes = 0 user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin) UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
} }
func recordSigninErrorInfo(user *User, lang string, options ...bool) string { func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
@@ -61,7 +61,7 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
} }
// update user // update user
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin) UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
if leftChances == 0 && enableCaptcha { if leftChances == 0 && enableCaptcha {
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect")) return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))

View File

@@ -15,10 +15,12 @@
package object package object
import ( import (
"errors" "fmt"
"github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/config"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
"github.com/xorm-io/core" "github.com/xorm-io/core"
) )
@@ -30,10 +32,10 @@ type Enforcer struct {
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
Description string `xorm:"varchar(100)" json:"description"` Description string `xorm:"varchar(100)" json:"description"`
Model string `xorm:"varchar(100)" json:"model"` Model string `xorm:"varchar(100)" json:"model"`
Adapter string `xorm:"varchar(100)" json:"adapter"` Adapter string `xorm:"varchar(100)" json:"adapter"`
IsEnabled bool `json:"isEnabled"`
ModelCfg map[string]string `xorm:"-" json:"modelCfg"`
*casbin.Enforcer *casbin.Enforcer
} }
@@ -120,44 +122,131 @@ func DeleteEnforcer(enforcer *Enforcer) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func (enforcer *Enforcer) GetId() string {
return fmt.Sprintf("%s/%s", enforcer.Owner, enforcer.Name)
}
func (enforcer *Enforcer) InitEnforcer() error { func (enforcer *Enforcer) InitEnforcer() error {
if enforcer.Enforcer == nil { if enforcer.Enforcer != nil {
if enforcer == nil { return nil
return errors.New("enforcer is nil") }
}
if enforcer.Model == "" || enforcer.Adapter == "" {
return errors.New("missing model or adapter")
}
var err error if enforcer.Model == "" {
var m *Model return fmt.Errorf("the model for enforcer: %s should not be empty", enforcer.GetId())
var a *Adapter }
if enforcer.Adapter == "" {
return fmt.Errorf("the adapter for enforcer: %s should not be empty", enforcer.GetId())
}
if m, err = GetModel(enforcer.Model); err != nil { m, err := GetModel(enforcer.Model)
return err if err != nil {
} else if m == nil { return err
return errors.New("model not found") } else if m == nil {
} return fmt.Errorf("the model: %s for enforcer: %s is not found", enforcer.Model, enforcer.GetId())
if a, err = GetAdapter(enforcer.Adapter); err != nil { }
return err
} else if a == nil {
return errors.New("adapter not found")
}
err = m.initModel() a, err := GetAdapter(enforcer.Adapter)
if err != nil { if err != nil {
return err return err
} } else if a == nil {
err = a.initAdapter() return fmt.Errorf("the adapter: %s for enforcer: %s is not found", enforcer.Adapter, enforcer.GetId())
if err != nil { }
return err
}
casbinEnforcer, err := casbin.NewEnforcer(m.Model, a.Adapter) err = m.initModel()
if err != nil { if err != nil {
return err return err
} }
enforcer.Enforcer = casbinEnforcer err = a.InitAdapter()
if err != nil {
return err
}
casbinEnforcer, err := casbin.NewEnforcer(m.Model, a.Adapter)
if err != nil {
return err
}
enforcer.Enforcer = casbinEnforcer
return nil
}
func GetInitializedEnforcer(enforcerId string) (*Enforcer, error) {
enforcer, err := GetEnforcer(enforcerId)
if err != nil {
return nil, err
} else if enforcer == nil {
return nil, fmt.Errorf("the enforcer: %s is not found", enforcerId)
}
err = enforcer.InitEnforcer()
if err != nil {
return nil, err
}
return enforcer, nil
}
func GetPolicies(id string) ([]*xormadapter.CasbinRule, error) {
enforcer, err := GetInitializedEnforcer(id)
if err != nil {
return nil, err
}
policies := util.MatrixToCasbinRules("p", enforcer.GetPolicy())
if enforcer.GetModel()["g"] != nil {
policies = append(policies, util.MatrixToCasbinRules("g", enforcer.GetGroupingPolicy())...)
}
return policies, nil
}
func UpdatePolicy(id string, oldPolicy, newPolicy []string) (bool, error) {
enforcer, err := GetInitializedEnforcer(id)
if err != nil {
return false, err
}
return enforcer.UpdatePolicy(oldPolicy, newPolicy)
}
func AddPolicy(id string, policy []string) (bool, error) {
enforcer, err := GetInitializedEnforcer(id)
if err != nil {
return false, err
}
return enforcer.AddPolicy(policy)
}
func RemovePolicy(id string, policy []string) (bool, error) {
enforcer, err := GetInitializedEnforcer(id)
if err != nil {
return false, err
}
return enforcer.RemovePolicy(policy)
}
func (enforcer *Enforcer) LoadModelCfg() error {
if enforcer.ModelCfg != nil {
return nil
}
model, err := GetModel(enforcer.Model)
if err != nil {
return err
} else if model == nil {
return fmt.Errorf("the model: %s for enforcer: %s is not found", enforcer.Model, enforcer.GetId())
}
cfg, err := config.NewConfigFromText(model.ModelText)
if err != nil {
return err
}
enforcer.ModelCfg = make(map[string]string)
enforcer.ModelCfg["p"] = cfg.String("policy_definition::p")
if cfg.String("role_definition::g") != "" {
enforcer.ModelCfg["g"] = cfg.String("role_definition::g")
} }
return nil return nil

140
object/get-dashboard.go Normal file
View File

@@ -0,0 +1,140 @@
// Copyright 2021 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 (
"sync"
"time"
)
type Dashboard struct {
OrganizationCounts []int `json:"organizationCounts"`
UserCounts []int `json:"userCounts"`
ProviderCounts []int `json:"providerCounts"`
ApplicationCounts []int `json:"applicationCounts"`
SubscriptionCounts []int `json:"subscriptionCounts"`
}
func GetDashboard(owner string) (*Dashboard, error) {
dashboard := &Dashboard{
OrganizationCounts: make([]int, 31),
UserCounts: make([]int, 31),
ProviderCounts: make([]int, 31),
ApplicationCounts: make([]int, 31),
SubscriptionCounts: make([]int, 31),
}
var wg sync.WaitGroup
organizations := []Organization{}
users := []User{}
providers := []Provider{}
applications := []Application{}
subscriptions := []Subscription{}
wg.Add(5)
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&users, &User{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&providers, &Provider{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&applications, &Application{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&subscriptions, &Subscription{Owner: owner}); err != nil {
panic(err)
}
}()
wg.Wait()
nowTime := time.Now()
for i := 30; i >= 0; i-- {
cutTime := nowTime.AddDate(0, 0, -i)
dashboard.OrganizationCounts[30-i] = countCreatedBefore(organizations, cutTime)
dashboard.UserCounts[30-i] = countCreatedBefore(users, cutTime)
dashboard.ProviderCounts[30-i] = countCreatedBefore(providers, cutTime)
dashboard.ApplicationCounts[30-i] = countCreatedBefore(applications, cutTime)
dashboard.SubscriptionCounts[30-i] = countCreatedBefore(subscriptions, cutTime)
}
return dashboard, nil
}
func countCreatedBefore(objects interface{}, before time.Time) int {
count := 0
switch obj := objects.(type) {
case []Organization:
for _, o := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", o.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []User:
for _, u := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", u.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Provider:
for _, p := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Application:
for _, a := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", a.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Subscription:
for _, s := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", s.CreatedTime)
if createdTime.Before(before) {
count++
}
}
}
return count
}

View File

@@ -164,7 +164,7 @@ func DeleteGroup(group *Group) (bool, error) {
return false, errors.New("group has children group") return false, errors.New("group has children group")
} }
if count, err := GetGroupUserCount(group.Name, "", ""); err != nil { if count, err := GetGroupUserCount(group.GetId(), "", ""); err != nil {
return false, err return false, err
} else if count > 0 { } else if count > 0 {
return false, errors.New("group has users") return false, errors.New("group has users")
@@ -214,39 +214,33 @@ func ConvertToTreeData(groups []*Group, parentId string) []*Group {
return treeData return treeData
} }
func RemoveUserFromGroup(owner, name, groupName string) (bool, error) { func GetGroupUserCount(groupId string, field, value string) (int64, error) {
user, err := getUser(owner, name) owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
if err != nil { if err != nil {
return false, err return 0, err
}
if user == nil {
return false, errors.New("user not exist")
} }
user.Groups = util.DeleteVal(user.Groups, groupName)
affected, err := updateUser(user.GetId(), user, []string{"groups"})
if err != nil {
return false, err
}
return affected != 0, err
}
func GetGroupUserCount(groupName string, field, value string) (int64, error) {
if field == "" && value == "" { if field == "" && value == "" {
return ormer.Engine.Where(builder.Like{"`groups`", groupName}). return int64(len(names)), nil
Count(&User{})
} else { } else {
return ormer.Engine.Table("user"). return ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupName}). Where("owner = ?", owner).In("name", names).
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%"). And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count() Count()
} }
} }
func GetPaginationGroupUsers(groupName string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) { func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
users := []*User{} users := []*User{}
owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
if err != nil {
return nil, err
}
session := ormer.Engine.Table("user"). session := ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupName + "\""}) Where("owner = ?", owner).In("name", names)
if offset != -1 && limit != -1 { if offset != -1 && limit != -1 {
session.Limit(limit, offset) session.Limit(limit, offset)
@@ -265,7 +259,7 @@ func GetPaginationGroupUsers(groupName string, offset, limit int, field, value,
session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField))) session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
} }
err := session.Find(&users) err = session.Find(&users)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -273,15 +267,15 @@ func GetPaginationGroupUsers(groupName string, offset, limit int, field, value,
return users, nil return users, nil
} }
func GetGroupUsers(groupName string) ([]*User, error) { func GetGroupUsers(groupId string) ([]*User, error) {
users := []*User{} users := []*User{}
err := ormer.Engine.Table("user"). owner, _ := util.GetOwnerAndNameFromId(groupId)
Where(builder.Like{"`groups`", groupName + "\""}). names, err := userEnforcer.GetUserNamesByGroupName(groupId)
Find(&users)
err = ormer.Engine.Where("owner = ?", owner).In("name", names).Find(&users)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return users, nil return users, nil
} }

View File

@@ -73,7 +73,6 @@ func getBuiltInAccountItems() []*AccountItem {
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
@@ -145,7 +144,6 @@ func initBuiltInUser() {
Score: 2000, Score: 2000,
Ranking: 1, Ranking: 1,
IsAdmin: true, IsAdmin: true,
IsGlobalAdmin: true,
IsForbidden: false, IsForbidden: false,
IsDeleted: false, IsDeleted: false,
SignupApplication: "app-built-in", SignupApplication: "app-built-in",
@@ -318,7 +316,6 @@ func initBuiltInUserModel() {
Name: "user-model-built-in", Name: "user-model-built-in",
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Model", DisplayName: "Built-in Model",
IsEnabled: true,
ModelText: `[request_definition] ModelText: `[request_definition]
r = sub, obj, act r = sub, obj, act
@@ -376,7 +373,6 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
Name: "api-model-built-in", Name: "api-model-built-in",
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
DisplayName: "API Model", DisplayName: "API Model",
IsEnabled: true,
ModelText: modelText, ModelText: modelText,
} }
_, err = AddModel(model) _, err = AddModel(model)
@@ -435,7 +431,6 @@ func initBuiltInUserAdapter() {
TableNamePrefix: conf.GetConfigString("tableNamePrefix"), TableNamePrefix: conf.GetConfigString("tableNamePrefix"),
Database: conf.GetConfigString("dbName"), Database: conf.GetConfigString("dbName"),
Table: "casbin_user_rule", Table: "casbin_user_rule",
IsEnabled: true,
} }
_, err = AddAdapter(adapter) _, err = AddAdapter(adapter)
if err != nil { if err != nil {
@@ -462,7 +457,6 @@ func initBuiltInApiAdapter() {
TableNamePrefix: conf.GetConfigString("tableNamePrefix"), TableNamePrefix: conf.GetConfigString("tableNamePrefix"),
Database: conf.GetConfigString("dbName"), Database: conf.GetConfigString("dbName"),
Table: "casbin_api_rule", Table: "casbin_api_rule",
IsEnabled: true,
} }
_, err = AddAdapter(adapter) _, err = AddAdapter(adapter)
if err != nil { if err != nil {
@@ -484,10 +478,9 @@ func initBuiltInUserEnforcer() {
Owner: "built-in", Owner: "built-in",
Name: "user-enforcer-built-in", Name: "user-enforcer-built-in",
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
DisplayName: "Permission Enforcer", DisplayName: "User Enforcer",
Model: "built-in/user-model-built-in", Model: "built-in/user-model-built-in",
Adapter: "built-in/user-adapter-built-in", Adapter: "built-in/user-adapter-built-in",
IsEnabled: true,
} }
_, err = AddEnforcer(enforcer) _, err = AddEnforcer(enforcer)
@@ -513,7 +506,6 @@ func initBuiltInApiEnforcer() {
DisplayName: "API Enforcer", DisplayName: "API Enforcer",
Model: "built-in/api-model-built-in", Model: "built-in/api-model-built-in",
Adapter: "built-in/api-adapter-built-in", Adapter: "built-in/api-adapter-built-in",
IsEnabled: true,
} }
_, err = AddEnforcer(enforcer) _, err = AddEnforcer(enforcer)

View File

@@ -41,19 +41,20 @@ type LdapUser struct {
GidNumber string `json:"gidNumber"` GidNumber string `json:"gidNumber"`
// Gcn string // Gcn string
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
UserPrincipalName string `json:"userPrincipalName"`
DisplayName string `json:"displayName"` DisplayName string `json:"displayName"`
Mail string Mail string
Email string `json:"email"` Email string `json:"email"`
EmailAddress string EmailAddress string
TelephoneNumber string TelephoneNumber string
Mobile string Mobile string `json:"mobile"`
MobileTelephoneNumber string MobileTelephoneNumber string
RegisteredAddress string RegisteredAddress string
PostalAddress string PostalAddress string
GroupId string `json:"groupId"` GroupId string `json:"groupId"`
Phone string `json:"phone"` Address string `json:"address"`
Address string `json:"address"` MemberOf string `json:"memberOf"`
} }
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) { func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
@@ -168,6 +169,8 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]LdapUser, error) {
user.Uuid = attribute.Values[0] user.Uuid = attribute.Values[0]
case "objectGUID": case "objectGUID":
user.Uuid = attribute.Values[0] user.Uuid = attribute.Values[0]
case "userPrincipalName":
user.UserPrincipalName = attribute.Values[0]
case "displayName": case "displayName":
user.DisplayName = attribute.Values[0] user.DisplayName = attribute.Values[0]
case "mail": case "mail":
@@ -186,6 +189,8 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]LdapUser, error) {
user.RegisteredAddress = attribute.Values[0] user.RegisteredAddress = attribute.Values[0]
case "postalAddress": case "postalAddress":
user.PostalAddress = attribute.Values[0] user.PostalAddress = attribute.Values[0]
case "memberOf":
user.MemberOf = attribute.Values[0]
} }
} }
ldapUsers = append(ldapUsers, user) ldapUsers = append(ldapUsers, user)
@@ -312,7 +317,7 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
DisplayName: syncUser.buildLdapDisplayName(), DisplayName: syncUser.buildLdapDisplayName(),
Avatar: organization.DefaultAvatar, Avatar: organization.DefaultAvatar,
Email: syncUser.Email, Email: syncUser.Email,
Phone: syncUser.Phone, Phone: syncUser.Mobile,
Address: []string{syncUser.Address}, Address: []string{syncUser.Address},
Affiliation: affiliation, Affiliation: affiliation,
Tag: tag, Tag: tag,

View File

@@ -84,7 +84,7 @@ func MfaRecover(user *User, recoveryCode string) error {
return fmt.Errorf("recovery code not found") return fmt.Errorf("recovery code not found")
} }
_, err := UpdateUser(user.GetId(), user, []string{"recovery_codes"}, user.IsAdminUser()) _, err := UpdateUser(user.GetId(), user, []string{"recovery_codes"}, false)
if err != nil { if err != nil {
return err return err
} }
@@ -181,7 +181,7 @@ func DisabledMultiFactorAuth(user *User) error {
func SetPreferredMultiFactorAuth(user *User, mfaType string) error { func SetPreferredMultiFactorAuth(user *User, mfaType string) error {
user.PreferredMfaType = mfaType user.PreferredMfaType = mfaType
_, err := UpdateUser(user.GetId(), user, []string{"preferred_mfa_type"}, user.IsAdminUser()) _, err := UpdateUser(user.GetId(), user, []string{"preferred_mfa_type"}, false)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -17,6 +17,7 @@ package object
import ( import (
"errors" "errors"
"fmt" "fmt"
"time"
"github.com/beego/beego" "github.com/beego/beego"
"github.com/beego/beego/context" "github.com/beego/beego/context"
@@ -25,7 +26,10 @@ import (
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
) )
const MfaTotpSecretSession = "mfa_totp_secret" const (
MfaTotpSecretSession = "mfa_totp_secret"
MfaTotpPeriodInSeconds = 30
)
type TotpMfa struct { type TotpMfa struct {
Config *MfaProps Config *MfaProps
@@ -76,7 +80,13 @@ func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
if secret == nil { if secret == nil {
return errors.New("totp secret is missing") return errors.New("totp secret is missing")
} }
result := totp.Validate(passcode, secret.(string))
result, _ := totp.ValidateCustom(passcode, secret.(string), time.Now().UTC(), totp.ValidateOpts{
Period: MfaTotpPeriodInSeconds,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
if result { if result {
return nil return nil
@@ -133,7 +143,7 @@ func NewTotpMfaUtil(config *MfaProps) *TotpMfa {
return &TotpMfa{ return &TotpMfa{
Config: config, Config: config,
period: 30, period: MfaTotpPeriodInSeconds,
secretSize: 20, secretSize: 20,
digits: otp.DigitsSix, digits: otp.DigitsSix,
} }

View File

@@ -30,7 +30,6 @@ type Model struct {
Description string `xorm:"varchar(100)" json:"description"` Description string `xorm:"varchar(100)" json:"description"`
ModelText string `xorm:"mediumtext" json:"modelText"` ModelText string `xorm:"mediumtext" json:"modelText"`
IsEnabled bool `json:"isEnabled"`
model.Model `xorm:"-" json:"-"` model.Model `xorm:"-" json:"-"`
} }

42
object/notification.go Normal file
View File

@@ -0,0 +1,42 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"context"
"github.com/casdoor/casdoor/notification"
"github.com/nikoksr/notify"
)
func getNotificationClient(provider *Provider) (notify.Notifier, error) {
var client notify.Notifier
client, err := notification.GetNotificationProvider(provider.Type, provider.AppId, provider.Receiver, provider.Method, provider.Title)
if err != nil {
return nil, err
}
return client, nil
}
func SendNotification(provider *Provider, content string) error {
client, err := getNotificationClient(provider)
if err != nil {
return err
}
err = client.Send(context.Background(), "", content)
return err
}

View File

@@ -103,7 +103,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
SubjectTypesSupported: []string{"public"}, SubjectTypesSupported: []string{"public"},
IdTokenSigningAlgValuesSupported: []string{"RS256"}, IdTokenSigningAlgValuesSupported: []string{"RS256"},
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"}, ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isGlobalAdmin", "isForbidden", "signupApplication", "ldap"}, ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isForbidden", "signupApplication", "ldap"},
RequestParameterSupported: true, RequestParameterSupported: true,
RequestObjectSigningAlgValuesSupported: []string{"HS256", "HS384", "HS512"}, RequestObjectSigningAlgValuesSupported: []string{"HS256", "HS384", "HS512"},
EndSessionEndpoint: fmt.Sprintf("%s/api/logout", originBackend), EndSessionEndpoint: fmt.Sprintf("%s/api/logout", originBackend),

View File

@@ -437,14 +437,6 @@ func organizationChangeTrigger(oldName string, newName string) error {
return err return err
} }
record := new(Record)
record.Owner = newName
record.Organization = newName
_, err = session.Where("organization=?", oldName).Update(record)
if err != nil {
return err
}
resource := new(Resource) resource := new(Resource)
resource.Owner = newName resource.Owner = newName
_, err = session.Where("owner=?", oldName).Update(resource) _, err = session.Where("owner=?", oldName).Update(resource)

View File

@@ -16,13 +16,13 @@ package object
import ( import (
"database/sql" "database/sql"
"flag"
"fmt" "fmt"
"runtime" "runtime"
"strings" "strings"
"github.com/beego/beego" "github.com/beego/beego"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3" xormadapter "github.com/casdoor/xorm-adapter/v3"
_ "github.com/denisenkom/go-mssqldb" // db = mssql _ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql _ "github.com/go-sql-driver/mysql" // db = mysql
@@ -32,7 +32,24 @@ import (
_ "modernc.org/sqlite" // db = sqlite _ "modernc.org/sqlite" // db = sqlite
) )
var ormer *Ormer var (
ormer *Ormer = nil
isCreateDatabaseDefined = false
createDatabase = true
)
func InitFlag() {
if !isCreateDatabaseDefined {
isCreateDatabaseDefined = true
createDatabase = getCreateDatabaseFlag()
}
}
func getCreateDatabaseFlag() bool {
res := flag.Bool("createDatabase", false, "true if you need to create database")
flag.Parse()
return *res
}
func InitConfig() { func InitConfig() {
err := beego.LoadAppConfig("ini", "../conf/app.conf") err := beego.LoadAppConfig("ini", "../conf/app.conf")
@@ -42,12 +59,12 @@ func InitConfig() {
beego.BConfig.WebConfig.Session.SessionOn = true beego.BConfig.WebConfig.Session.SessionOn = true
InitAdapter(true) InitAdapter()
CreateTables(true) CreateTables()
DoMigration() DoMigration()
} }
func InitAdapter(createDatabase bool) { func InitAdapter() {
if createDatabase { if createDatabase {
err := createDatabaseForPostgres(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName")) err := createDatabaseForPostgres(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
if err != nil { if err != nil {
@@ -62,7 +79,7 @@ func InitAdapter(createDatabase bool) {
ormer.Engine.SetTableMapper(tbMapper) ormer.Engine.SetTableMapper(tbMapper)
} }
func CreateTables(createDatabase bool) { func CreateTables() {
if createDatabase { if createDatabase {
err := ormer.CreateDatabase() err := ormer.CreateDatabase()
if err != nil { if err != nil {
@@ -229,11 +246,6 @@ func (a *Ormer) createTable() {
panic(err) panic(err)
} }
err = a.Engine.Sync2(new(Record))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Webhook)) err = a.Engine.Sync2(new(Webhook))
if err != nil { if err != nil {
panic(err) panic(err)
@@ -294,76 +306,3 @@ func (a *Ormer) createTable() {
panic(err) panic(err)
} }
} }
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := ormer.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
session = session.And("owner=?", owner)
}
if field != "" && value != "" {
if util.FilterField(field) {
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
return session
}
func GetSessionForUser(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := ormer.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
if offset == -1 {
session = session.And("owner=?", owner)
} else {
session = session.And("a.owner=?", owner)
}
}
if field != "" && value != "" {
if util.FilterField(field) {
if offset != -1 {
field = fmt.Sprintf("a.%s", field)
}
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tableName := tableNamePrefix + "user"
if offset == -1 {
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
} else {
if sortOrder == "ascend" {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Asc("a." + util.SnakeString(sortField))
} else {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Desc("a." + util.SnakeString(sortField))
}
}
return session
}

96
object/ormer_session.go Normal file
View File

@@ -0,0 +1,96 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/xorm"
)
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := ormer.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
session = session.And("owner=?", owner)
}
if field != "" && value != "" {
if util.FilterField(field) {
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
return session
}
func GetSessionForUser(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := ormer.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
if offset == -1 {
session = session.And("owner=?", owner)
} else {
session = session.And("a.owner=?", owner)
}
}
if field != "" && value != "" {
if util.FilterField(field) {
if offset != -1 {
field = fmt.Sprintf("a.%s", field)
}
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tableName := tableNamePrefix + "user"
if offset == -1 {
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
} else {
if sortOrder == "ascend" {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Asc("a." + util.SnakeString(sortField))
} else {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Desc("a." + util.SnakeString(sortField))
}
}
return session
}

View File

@@ -58,7 +58,7 @@ type PermissionRule struct {
Id string `xorm:"varchar(100) index not null default ''" json:"id"` Id string `xorm:"varchar(100) index not null default ''" json:"id"`
} }
const builtInAvailableField = 10 const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
func (p *Permission) GetId() string { func (p *Permission) GetId() string {
return util.GetId(p.Owner, p.Name) return util.GetId(p.Owner, p.Name)

View File

@@ -309,8 +309,7 @@ func GetAllRoles(userId string) []string {
func GetBuiltInModel(modelText string) (model.Model, error) { func GetBuiltInModel(modelText string) (model.Model, error) {
if modelText == "" { if modelText == "" {
modelText = ` modelText = `[request_definition]
[request_definition]
r = sub, obj, act r = sub, obj, act
[policy_definition] [policy_definition]

View File

@@ -36,7 +36,7 @@ type Provider struct {
Type string `xorm:"varchar(100)" json:"type"` Type string `xorm:"varchar(100)" json:"type"`
SubType string `xorm:"varchar(100)" json:"subType"` SubType string `xorm:"varchar(100)" json:"subType"`
Method string `xorm:"varchar(100)" json:"method"` Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"` ClientId string `xorm:"varchar(200)" json:"clientId"`
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"` ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"` ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"` ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
@@ -254,7 +254,8 @@ func DeleteProvider(provider *Provider) (bool, error) {
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) { func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
cert := &Cert{} cert := &Cert{}
if p.Cert != "" { if p.Cert != "" {
cert, err := getCert(p.Owner, p.Cert) var err error
cert, err = getCert(p.Owner, p.Cert)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@@ -21,6 +21,7 @@ import (
"github.com/beego/beego/context" "github.com/beego/beego/context"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/casvisor/casvisor-go-sdk/casvisorsdk"
) )
var logPostOnly bool var logPostOnly bool
@@ -30,26 +31,10 @@ func init() {
} }
type Record struct { type Record struct {
Id int `xorm:"int notnull pk autoincr" json:"id"` casvisorsdk.Record
Owner string `xorm:"varchar(100) index" json:"owner"`
Name string `xorm:"varchar(100) index" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Organization string `xorm:"varchar(100)" json:"organization"`
ClientIp string `xorm:"varchar(100)" json:"clientIp"`
User string `xorm:"varchar(100)" json:"user"`
Method string `xorm:"varchar(100)" json:"method"`
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
Action string `xorm:"varchar(1000)" json:"action"`
Object string `xorm:"-" json:"object"`
ExtendedUser *User `xorm:"-" json:"extendedUser"`
IsTriggered bool `json:"isTriggered"`
} }
func NewRecord(ctx *context.Context) *Record { func NewRecord(ctx *context.Context) *casvisorsdk.Record {
ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1) ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1)
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1) action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"}) requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"})
@@ -62,7 +47,7 @@ func NewRecord(ctx *context.Context) *Record {
object = string(ctx.Input.RequestBody) object = string(ctx.Input.RequestBody)
} }
record := Record{ record := casvisorsdk.Record{
Name: util.GenerateId(), Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
ClientIp: ip, ClientIp: ip,
@@ -76,7 +61,7 @@ func NewRecord(ctx *context.Context) *Record {
return &record return &record
} }
func AddRecord(record *Record) bool { func AddRecord(record *casvisorsdk.Record) bool {
if logPostOnly { if logPostOnly {
if record.Method == "GET" { if record.Method == "GET" {
return false return false
@@ -96,51 +81,19 @@ func AddRecord(record *Record) bool {
fmt.Println(errWebhook) fmt.Println(errWebhook)
} }
affected, err := ormer.Engine.Insert(record) if casvisorsdk.GetClient() == nil {
return false
}
affected, err := casvisorsdk.AddRecord(record)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return affected != 0 return affected
} }
func GetRecordCount(field, value string, filterRecord *Record) (int64, error) { func SendWebhooks(record *casvisorsdk.Record) error {
session := GetSession("", -1, -1, field, value, "", "")
return session.Count(filterRecord)
}
func GetRecords() ([]*Record, error) {
records := []*Record{}
err := ormer.Engine.Desc("id").Find(&records)
if err != nil {
return records, err
}
return records, nil
}
func GetPaginationRecords(offset, limit int, field, value, sortField, sortOrder string, filterRecord *Record) ([]*Record, error) {
records := []*Record{}
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&records, filterRecord)
if err != nil {
return records, err
}
return records, nil
}
func GetRecordsByField(record *Record) ([]*Record, error) {
records := []*Record{}
err := ormer.Engine.Find(&records, record)
if err != nil {
return records, err
}
return records, nil
}
func SendWebhooks(record *Record) error {
webhooks, err := getWebhooksByOrganization(record.Organization) webhooks, err := getWebhooksByOrganization(record.Organization)
if err != nil { if err != nil {
return err return err
@@ -160,17 +113,16 @@ func SendWebhooks(record *Record) error {
} }
if matched { if matched {
var user *User
if webhook.IsUserExtended { if webhook.IsUserExtended {
user, err := getUser(record.Organization, record.User) user, err = getUser(record.Organization, record.User)
user, err = GetMaskedUser(user, false, err) user, err = GetMaskedUser(user, false, err)
if err != nil { if err != nil {
return err return err
} }
record.ExtendedUser = user
} }
err := sendWebhook(webhook, record) err = sendWebhook(webhook, record, user)
if err != nil { if err != nil {
return err return err
} }

50
object/record_casvisor.go Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"strings"
"github.com/casvisor/casvisor-go-sdk/casvisorsdk"
)
func getCasvisorApplication() *Application {
applications, err := GetApplications("admin")
if err != nil {
panic(err)
}
for _, application := range applications {
if strings.Contains(strings.ToLower(application.Name), "casvisor") {
return application
}
}
return nil
}
func InitCasvisorConfig() {
application := getCasvisorApplication()
if application == nil {
return
}
casvisorEndpoint := application.HomepageUrl
clientId := application.ClientId
clientSecret := application.ClientSecret
casdoorOrganization := application.Organization
casdoorApplication := application.Name
casvisorsdk.InitConfig(casvisorEndpoint, clientId, clientSecret, casdoorOrganization, casdoorApplication)
}

View File

@@ -24,7 +24,7 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
var client sender.SmsClient var client sender.SmsClient
var err error var err error
if provider.Type == sender.HuaweiCloud { if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId) client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
} else { } else {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId) client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)

View File

@@ -25,6 +25,7 @@ type TableColumn struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
CasdoorName string `json:"casdoorName"` CasdoorName string `json:"casdoorName"`
IsKey bool `json:"isKey"`
IsHashed bool `json:"isHashed"` IsHashed bool `json:"isHashed"`
Values []string `json:"values"` Values []string `json:"values"`
} }
@@ -44,7 +45,6 @@ type Syncer struct {
DatabaseType string `xorm:"varchar(100)" json:"databaseType"` DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
Database string `xorm:"varchar(100)" json:"database"` Database string `xorm:"varchar(100)" json:"database"`
Table string `xorm:"varchar(100)" json:"table"` Table string `xorm:"varchar(100)" json:"table"`
TablePrimaryKey string `xorm:"varchar(100)" json:"tablePrimaryKey"`
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"` TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
AffiliationTable string `xorm:"varchar(100)" json:"affiliationTable"` AffiliationTable string `xorm:"varchar(100)" json:"affiliationTable"`
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"` AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
@@ -229,6 +229,27 @@ func (syncer *Syncer) getTable() string {
} }
} }
func (syncer *Syncer) getKey() string {
key := "id"
hasKey := false
hasId := false
for _, tableColumn := range syncer.TableColumns {
if tableColumn.IsKey {
hasKey = true
key = tableColumn.Name
}
if tableColumn.Name == "id" {
hasId = true
}
}
if !hasKey && !hasId {
key = syncer.TableColumns[0].Name
}
return key
}
func RunSyncer(syncer *Syncer) { func RunSyncer(syncer *Syncer) {
syncer.initAdapter() syncer.initAdapter()
syncer.syncUsers() syncer.syncUsers()

View File

@@ -20,16 +20,24 @@ import (
) )
func (syncer *Syncer) syncUsers() { func (syncer *Syncer) syncUsers() {
if len(syncer.TableColumns) == 0 {
return
}
fmt.Printf("Running syncUsers()..\n") fmt.Printf("Running syncUsers()..\n")
users, userMap, userNameMap := syncer.getUserMap() users, _, _ := syncer.getUserMap()
oUsers, oUserMap, err := syncer.getOriginalUserMap() oUsers, oUserMap, err := syncer.getOriginalUserMap()
if err != nil { if err != nil {
fmt.Printf(err.Error()) fmt.Printf(err.Error())
timestamp := time.Now().Format("2006-01-02 15:04:05") timestamp := time.Now().Format("2006-01-02 15:04:05")
line := fmt.Sprintf("[%s] %s\n", timestamp, err.Error()) line := fmt.Sprintf("[%s] %s\n", timestamp, err.Error())
updateSyncerErrorText(syncer, line) _, err = updateSyncerErrorText(syncer, line)
if err != nil {
panic(err)
}
return return
} }
@@ -40,48 +48,68 @@ func (syncer *Syncer) syncUsers() {
_, affiliationMap, err = syncer.getAffiliationMap() _, affiliationMap, err = syncer.getAffiliationMap()
} }
key := syncer.getKey()
myUsers := map[string]*User{}
for _, m := range users {
myUsers[syncer.getUserValue(m, key)] = m
}
newUsers := []*User{} newUsers := []*User{}
for _, oUser := range oUsers { for _, oUser := range oUsers {
id := oUser.Id primary := syncer.getUserValue(oUser, key)
if _, ok := userMap[id]; !ok {
if _, ok := userNameMap[oUser.Name]; !ok {
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
}
} else {
user := userMap[id]
oHash := syncer.calculateHash(oUser)
if _, ok := myUsers[primary]; !ok {
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
} else {
user := myUsers[primary]
oHash := syncer.calculateHash(oUser)
if user.Hash == user.PreHash { if user.Hash == user.PreHash {
if user.Hash != oHash { if user.Hash != oHash {
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap) updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash updatedUser.Hash = oHash
updatedUser.PreHash = oHash updatedUser.PreHash = oHash
syncer.updateUserForOriginalFields(updatedUser) _, err = syncer.updateUserForOriginalByFields(updatedUser, key)
if err != nil {
panic(err)
}
fmt.Printf("Update from oUser to user: %v\n", updatedUser) fmt.Printf("Update from oUser to user: %v\n", updatedUser)
} }
} else { } else {
if user.PreHash == oHash { if user.PreHash == oHash {
if !syncer.IsReadOnly { if !syncer.IsReadOnly {
updatedOUser := syncer.createOriginalUserFromUser(user) updatedOUser := syncer.createOriginalUserFromUser(user)
syncer.updateUser(updatedOUser) _, err = syncer.updateUser(updatedOUser)
if err != nil {
panic(err)
}
fmt.Printf("Update from user to oUser: %v\n", updatedOUser) fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
} }
// update preHash // update preHash
user.PreHash = user.Hash user.PreHash = user.Hash
SetUserField(user, "pre_hash", user.PreHash) _, err = SetUserField(user, "pre_hash", user.PreHash)
if err != nil {
panic(err)
}
} else { } else {
if user.Hash == oHash { if user.Hash == oHash {
// update preHash // update preHash
user.PreHash = user.Hash user.PreHash = user.Hash
SetUserField(user, "pre_hash", user.PreHash) _, err = SetUserField(user, "pre_hash", user.PreHash)
if err != nil {
panic(err)
}
} else { } else {
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap) updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash updatedUser.Hash = oHash
updatedUser.PreHash = oHash updatedUser.PreHash = oHash
syncer.updateUserForOriginalFields(updatedUser) _, err = syncer.updateUserForOriginalByFields(updatedUser, key)
if err != nil {
panic(err)
}
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser) fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
} }
} }

View File

@@ -80,16 +80,6 @@ func (syncer *Syncer) addUser(user *OriginalUser) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
/*func (syncer *Syncer) getOriginalColumns() []string {
res := []string{}
for _, tableColumn := range syncer.TableColumns {
if tableColumn.CasdoorName != "Id" {
res = append(res, tableColumn.Name)
}
}
return res
}*/
func (syncer *Syncer) getCasdoorColumns() []string { func (syncer *Syncer) getCasdoorColumns() []string {
res := []string{} res := []string{}
for _, tableColumn := range syncer.TableColumns { for _, tableColumn := range syncer.TableColumns {
@@ -102,12 +92,14 @@ func (syncer *Syncer) getCasdoorColumns() []string {
} }
func (syncer *Syncer) updateUser(user *OriginalUser) (bool, error) { func (syncer *Syncer) updateUser(user *OriginalUser) (bool, error) {
key := syncer.getKey()
m := syncer.getMapFromOriginalUser(user) m := syncer.getMapFromOriginalUser(user)
pkValue := m[syncer.TablePrimaryKey] pkValue := m[key]
delete(m, syncer.TablePrimaryKey) delete(m, key)
setString := syncer.getSqlSetStringFromMap(m) setString := syncer.getSqlSetStringFromMap(m)
sql := fmt.Sprintf("update %s set %s where %s = %s", syncer.getTable(), setString, syncer.TablePrimaryKey, pkValue) sql := fmt.Sprintf("update %s set %s where %s = %s", syncer.getTable(), setString, key, pkValue)
res, err := syncer.Ormer.Engine.Exec(sql) res, err := syncer.Ormer.Engine.Exec(sql)
if err != nil { if err != nil {
return false, err return false, err
@@ -142,6 +134,34 @@ func (syncer *Syncer) updateUserForOriginalFields(user *User) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
return affected != 0, nil
}
func (syncer *Syncer) updateUserForOriginalByFields(user *User, key string) (bool, error) {
var err error
oldUser := User{}
existed, err := ormer.Engine.Where(key+" = ? and owner = ?", syncer.getUserValue(user, key), user.Owner).Get(&oldUser)
if err != nil {
return false, err
}
if !existed {
return false, nil
}
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
user.PermanentAvatar, err = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, true)
if err != nil {
return false, err
}
}
columns := syncer.getCasdoorColumns()
columns = append(columns, "affiliation", "hash", "pre_hash")
affected, err := ormer.Engine.Where(key+" = ? and owner = ?", syncer.getUserValue(&oldUser, key), oldUser.Owner).Cols(columns...).Update(user)
if err != nil {
return false, err
}
return affected != 0, nil return affected != 0, nil
} }

View File

@@ -17,6 +17,7 @@ package object
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -153,8 +154,6 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
user.IsOnline = util.ParseBool(value) user.IsOnline = util.ParseBool(value)
case "IsAdmin": case "IsAdmin":
user.IsAdmin = util.ParseBool(value) user.IsAdmin = util.ParseBool(value)
case "IsGlobalAdmin":
user.IsGlobalAdmin = util.ParseBool(value)
case "IsForbidden": case "IsForbidden":
user.IsForbidden = util.ParseBool(value) user.IsForbidden = util.ParseBool(value)
case "IsDeleted": case "IsDeleted":
@@ -164,6 +163,33 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
} }
} }
func (syncer *Syncer) getUserValue(user *User, key string) string {
jsonData, _ := json.Marshal(user)
var mapData map[string]interface{}
if err := json.Unmarshal(jsonData, &mapData); err != nil {
fmt.Println("conversion failed:", err)
return user.Id
}
value := mapData[util.SnakeToCamel(key)]
if str, ok := value.(string); ok {
return str
} else {
if value != nil {
valType := reflect.TypeOf(value)
typeName := valType.Name()
switch typeName {
case "bool":
return strconv.FormatBool(value.(bool))
case "int":
return strconv.Itoa(value.(int))
}
}
return user.Id
}
}
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*OriginalUser { func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*OriginalUser {
users := []*OriginalUser{} users := []*OriginalUser{}
for _, result := range results { for _, result := range results {
@@ -261,7 +287,6 @@ func (syncer *Syncer) getMapFromOriginalUser(user *OriginalUser) map[string]stri
m["IsDefaultAvatar"] = util.BoolToString(user.IsDefaultAvatar) m["IsDefaultAvatar"] = util.BoolToString(user.IsDefaultAvatar)
m["IsOnline"] = util.BoolToString(user.IsOnline) m["IsOnline"] = util.BoolToString(user.IsOnline)
m["IsAdmin"] = util.BoolToString(user.IsAdmin) m["IsAdmin"] = util.BoolToString(user.IsAdmin)
m["IsGlobalAdmin"] = util.BoolToString(user.IsGlobalAdmin)
m["IsForbidden"] = util.BoolToString(user.IsForbidden) m["IsForbidden"] = util.BoolToString(user.IsForbidden)
m["IsDeleted"] = util.BoolToString(user.IsDeleted) m["IsDeleted"] = util.BoolToString(user.IsDeleted)
m["CreatedIp"] = user.CreatedIp m["CreatedIp"] = user.CreatedIp

View File

@@ -824,7 +824,6 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
Type: "normal-user", Type: "normal-user",
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
IsAdmin: false, IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false, IsForbidden: false,
IsDeleted: false, IsDeleted: false,
Properties: map[string]string{ Properties: map[string]string{

View File

@@ -123,7 +123,7 @@ var stToServiceResponse sync.Map
// pgt is short for proxy granting ticket // pgt is short for proxy granting ticket
var pgtToServiceResponse sync.Map var pgtToServiceResponse sync.Map
func CheckCasRestrict(application *Application, lang string, service string) error { func CheckCasLogin(application *Application, lang string, service string) error {
if len(application.RedirectUris) > 0 && !application.IsRedirectUriValid(service) { if len(application.RedirectUris) > 0 && !application.IsRedirectUriValid(service) {
return fmt.Errorf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), service) return fmt.Errorf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), service)
} }

View File

@@ -73,7 +73,6 @@ type UserWithoutThirdIdp struct {
IsDefaultAvatar bool `json:"isDefaultAvatar"` IsDefaultAvatar bool `json:"isDefaultAvatar"`
IsOnline bool `json:"isOnline"` IsOnline bool `json:"isOnline"`
IsAdmin bool `json:"isAdmin"` IsAdmin bool `json:"isAdmin"`
IsGlobalAdmin bool `json:"isGlobalAdmin"`
IsForbidden bool `json:"isForbidden"` IsForbidden bool `json:"isForbidden"`
IsDeleted bool `json:"isDeleted"` IsDeleted bool `json:"isDeleted"`
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"` SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
@@ -154,7 +153,6 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
IsDefaultAvatar: user.IsDefaultAvatar, IsDefaultAvatar: user.IsDefaultAvatar,
IsOnline: user.IsOnline, IsOnline: user.IsOnline,
IsAdmin: user.IsAdmin, IsAdmin: user.IsAdmin,
IsGlobalAdmin: user.IsGlobalAdmin,
IsForbidden: user.IsForbidden, IsForbidden: user.IsForbidden,
IsDeleted: user.IsDeleted, IsDeleted: user.IsDeleted,
SignupApplication: user.SignupApplication, SignupApplication: user.SignupApplication,

View File

@@ -29,6 +29,19 @@ const (
UserPropertiesWechatOpenId = "wechatOpenId" UserPropertiesWechatOpenId = "wechatOpenId"
) )
const UserEnforcerId = "built-in/user-enforcer-built-in"
var userEnforcer *UserGroupEnforcer
func InitUserManager() {
enforcer, err := GetInitializedEnforcer(UserEnforcerId)
if err != nil {
panic(err)
}
userEnforcer = NewUserGroupEnforcer(enforcer.Enforcer)
}
type User struct { type User struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
@@ -70,7 +83,6 @@ type User struct {
IsDefaultAvatar bool `json:"isDefaultAvatar"` IsDefaultAvatar bool `json:"isDefaultAvatar"`
IsOnline bool `json:"isOnline"` IsOnline bool `json:"isOnline"`
IsAdmin bool `json:"isAdmin"` IsAdmin bool `json:"isAdmin"`
IsGlobalAdmin bool `json:"isGlobalAdmin"`
IsForbidden bool `json:"isForbidden"` IsForbidden bool `json:"isForbidden"`
IsDeleted bool `json:"isDeleted"` IsDeleted bool `json:"isDeleted"`
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"` SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
@@ -157,6 +169,7 @@ type User struct {
Yandex string `xorm:"yandex varchar(100)" json:"yandex"` Yandex string `xorm:"yandex varchar(100)" json:"yandex"`
Zoom string `xorm:"zoom varchar(100)" json:"zoom"` Zoom string `xorm:"zoom varchar(100)" json:"zoom"`
MetaMask string `xorm:"metamask varchar(100)" json:"metamask"` MetaMask string `xorm:"metamask varchar(100)" json:"metamask"`
Web3Onboard string `xorm:"web3onboard varchar(100)" json:"web3onboard"`
Custom string `xorm:"custom varchar(100)" json:"custom"` Custom string `xorm:"custom varchar(100)" json:"custom"`
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"` WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
@@ -230,7 +243,7 @@ func GetUserCount(owner, field, value string, groupName string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "") session := GetSession(owner, -1, -1, field, value, "", "")
if groupName != "" { if groupName != "" {
return GetGroupUserCount(groupName, field, value) return GetGroupUserCount(util.GetId(owner, groupName), field, value)
} }
return session.Count(&User{}) return session.Count(&User{})
@@ -274,7 +287,7 @@ func GetPaginationUsers(owner string, offset, limit int, field, value, sortField
users := []*User{} users := []*User{}
if groupName != "" { if groupName != "" {
return GetPaginationGroupUsers(groupName, offset, limit, field, value, sortField, sortOrder) return GetPaginationGroupUsers(util.GetId(owner, groupName), offset, limit, field, value, sortField, sortOrder)
} }
session := GetSessionForUser(owner, offset, limit, field, value, sortField, sortOrder) session := GetSessionForUser(owner, offset, limit, field, value, sortField, sortOrder)
@@ -516,7 +529,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
columns = []string{ columns = []string{
"owner", "display_name", "avatar", "owner", "display_name", "avatar",
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application", "location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret",
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs", "github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon", "baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
@@ -531,6 +544,13 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
columns = append(columns, "name", "email", "phone", "country_code") columns = append(columns, "name", "email", "phone", "country_code")
} }
if util.ContainsString(columns, "groups") {
_, err := userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
if err != nil {
return false, err
}
}
affected, err := updateUser(id, user, columns) affected, err := updateUser(id, user, columns)
if err != nil { if err != nil {
return false, err return false, err
@@ -778,6 +798,10 @@ func ExtendUserWithRolesAndPermissions(user *User) (err error) {
return return
} }
func DeleteGroupForUser(user string, group string) (bool, error) {
return userEnforcer.DeleteGroupForUser(user, group)
}
func userChangeTrigger(oldName string, newName string) error { func userChangeTrigger(oldName string, newName string) error {
session := ormer.Engine.NewSession() session := ormer.Engine.NewSession()
defer session.Close() defer session.Close()
@@ -866,5 +890,13 @@ func (user *User) IsApplicationAdmin(application *Application) bool {
return false return false
} }
return (user.Owner == application.Organization && user.IsAdmin) || user.IsGlobalAdmin return (user.Owner == application.Organization && user.IsAdmin) || user.IsGlobalAdmin()
}
func (user *User) IsGlobalAdmin() bool {
if user == nil {
return false
}
return user.Owner == "built-in"
} }

View File

@@ -18,6 +18,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"mime"
"net/http" "net/http"
"strings" "strings"
@@ -58,22 +59,15 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
if strings.Contains(contentType, "text/html") { if strings.Contains(contentType, "text/html") {
fileExtension = ".html" fileExtension = ".html"
} else { } else {
switch contentType { fileExtensions, err := mime.ExtensionsByType(contentType)
case "image/jpeg": if err != nil {
fileExtension = ".jpg" return nil, "", err
case "image/png":
fileExtension = ".png"
case "image/gif":
fileExtension = ".gif"
case "image/vnd.microsoft.icon":
fileExtension = ".ico"
case "image/x-icon":
fileExtension = ".ico"
case "image/svg+xml":
fileExtension = ".svg"
default:
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
} }
if fileExtensions == nil {
return nil, "", fmt.Errorf("fileExtensions is nil")
}
fileExtension = fileExtensions[0]
} }
// Save the image to a bytes.Buffer // Save the image to a bytes.Buffer

95
object/user_enforcer.go Normal file
View File

@@ -0,0 +1,95 @@
package object
import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/errors"
"github.com/casdoor/casdoor/util"
)
type UserGroupEnforcer struct {
// use rbac model implement use group, the enforcer can also implement user role
enforcer *casbin.Enforcer
}
func NewUserGroupEnforcer(enforcer *casbin.Enforcer) *UserGroupEnforcer {
return &UserGroupEnforcer{
enforcer: enforcer,
}
}
func (e *UserGroupEnforcer) AddGroupForUser(user string, group string) (bool, error) {
return e.enforcer.AddRoleForUser(user, GetGroupWithPrefix(group))
}
func (e *UserGroupEnforcer) AddGroupsForUser(user string, groups []string) (bool, error) {
g := make([]string, len(groups))
for i, group := range groups {
g[i] = GetGroupWithPrefix(group)
}
return e.enforcer.AddRolesForUser(user, g)
}
func (e *UserGroupEnforcer) DeleteGroupForUser(user string, group string) (bool, error) {
return e.enforcer.DeleteRoleForUser(user, GetGroupWithPrefix(group))
}
func (e *UserGroupEnforcer) DeleteGroupsForUser(user string) (bool, error) {
return e.enforcer.DeleteRolesForUser(user)
}
func (e *UserGroupEnforcer) GetGroupsForUser(user string) ([]string, error) {
groups, err := e.enforcer.GetRolesForUser(user)
for i, group := range groups {
groups[i] = GetGroupWithoutPrefix(group)
}
return groups, err
}
func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) {
users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group))
if err != nil {
if err == errors.ERR_NAME_NOT_FOUND {
return []string{}, nil
}
return nil, err
}
return users, nil
}
func GetGroupWithPrefix(group string) string {
return "group:" + group
}
func GetGroupWithoutPrefix(group string) string {
return group[len("group:"):]
}
func (e *UserGroupEnforcer) GetUserNamesByGroupName(groupName string) ([]string, error) {
var names []string
userIds, err := e.GetAllUsersByGroup(groupName)
if err != nil {
return nil, err
}
for _, userId := range userIds {
_, name := util.GetOwnerAndNameFromIdNoCheck(userId)
names = append(names, name)
}
return names, nil
}
func (e *UserGroupEnforcer) UpdateGroupsForUser(user string, groups []string) (bool, error) {
_, err := e.DeleteGroupsForUser(user)
if err != nil {
return false, err
}
affected, err := e.AddGroupsForUser(user, groups)
if err != nil {
return false, err
}
return affected, nil
}

View File

@@ -124,15 +124,14 @@ func UploadUsers(owner string, fileId string) (bool, error) {
IsDefaultAvatar: false, IsDefaultAvatar: false,
IsOnline: parseLineItemBool(&line, 31), IsOnline: parseLineItemBool(&line, 31),
IsAdmin: parseLineItemBool(&line, 32), IsAdmin: parseLineItemBool(&line, 32),
IsGlobalAdmin: parseLineItemBool(&line, 33), IsForbidden: parseLineItemBool(&line, 33),
IsForbidden: parseLineItemBool(&line, 34), IsDeleted: parseLineItemBool(&line, 34),
IsDeleted: parseLineItemBool(&line, 35), SignupApplication: parseLineItem(&line, 35),
SignupApplication: parseLineItem(&line, 36),
Hash: "", Hash: "",
PreHash: "", PreHash: "",
CreatedIp: parseLineItem(&line, 37), CreatedIp: parseLineItem(&line, 36),
LastSigninTime: parseLineItem(&line, 38), LastSigninTime: parseLineItem(&line, 37),
LastSigninIp: parseLineItem(&line, 39), LastSigninIp: parseLineItem(&line, 38),
Ldap: "", Ldap: "",
Properties: map[string]string{}, Properties: map[string]string{},
} }

View File

@@ -310,10 +310,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
item := GetAccountItemByName("Is admin", organization) item := GetAccountItemByName("Is admin", organization)
itemsChanged = append(itemsChanged, item) itemsChanged = append(itemsChanged, item)
} }
if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin {
item := GetAccountItemByName("Is global admin", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsForbidden != newUser.IsForbidden { if oldUser.IsForbidden != newUser.IsForbidden {
item := GetAccountItemByName("Is forbidden", organization) item := GetAccountItemByName("Is forbidden", organization)
itemsChanged = append(itemsChanged, item) itemsChanged = append(itemsChanged, item)
@@ -351,5 +348,5 @@ func (user *User) IsAdminUser() bool {
return false return false
} }
return user.IsAdmin || user.IsGlobalAdmin return user.IsAdmin || user.IsGlobalAdmin()
} }

View File

@@ -19,12 +19,22 @@ import (
"strings" "strings"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/casvisor/casvisor-go-sdk/casvisorsdk"
) )
func sendWebhook(webhook *Webhook, record *Record) error { func sendWebhook(webhook *Webhook, record *casvisorsdk.Record, extendedUser *User) error {
client := &http.Client{} client := &http.Client{}
body := strings.NewReader(util.StructToJson(record)) type RecordEx struct {
casvisorsdk.Record
ExtendedUser *User `xorm:"-" json:"extendedUser"`
}
recordEx := &RecordEx{
Record: *record,
ExtendedUser: extendedUser,
}
body := strings.NewReader(util.StructToJson(recordEx))
req, err := http.NewRequest(webhook.Method, webhook.Url, body) req, err := http.NewRequest(webhook.Method, webhook.Url, body)
if err != nil { if err != nil {

View File

@@ -90,7 +90,7 @@ func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, auth
ProductName: productName, ProductName: productName,
ProductDisplayName: productDisplayName, ProductDisplayName: productDisplayName,
ProviderName: providerName, ProviderName: providerName,
OutOrderId: orderId, OrderId: orderId,
PaymentStatus: PaymentStatePaid, PaymentStatus: PaymentStatePaid,
Price: price, Price: price,
PaymentName: paymentName, PaymentName: paymentName,

View File

@@ -255,7 +255,7 @@ func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorit
ProductName: productName, ProductName: productName,
ProductDisplayName: productDisplayName, ProductDisplayName: productDisplayName,
ProviderName: providerName, ProviderName: providerName,
OutOrderId: orderId, OrderId: orderId,
Price: price, Price: price,
PaymentStatus: PaymentStatePaid, PaymentStatus: PaymentStatePaid,
PaymentName: paymentName, PaymentName: paymentName,

View File

@@ -52,13 +52,12 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro
func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
// https://github.com/go-pay/gopay/blob/main/doc/paypal.md // https://github.com/go-pay/gopay/blob/main/doc/paypal.md
priceStr := strconv.FormatFloat(price, 'f', 2, 64)
units := make([]*paypal.PurchaseUnit, 0, 1) units := make([]*paypal.PurchaseUnit, 0, 1)
unit := &paypal.PurchaseUnit{ unit := &paypal.PurchaseUnit{
ReferenceId: util.GetRandomString(16), ReferenceId: util.GetRandomString(16),
Amount: &paypal.Amount{ Amount: &paypal.Amount{
CurrencyCode: currency, // e.g."USD" CurrencyCode: currency, // e.g."USD"
Value: priceStr, // e.g."100.00" Value: priceFloat64ToString(price), // e.g."100.00"
}, },
Description: joinAttachString([]string{productDisplayName, productName, providerName}), Description: joinAttachString([]string{productDisplayName, productName, providerName}),
} }
@@ -147,16 +146,15 @@ func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, auth
paymentStatus = PaymentStateError paymentStatus = PaymentStateError
} }
notifyResult = &NotifyResult{ notifyResult = &NotifyResult{
PaymentStatus: paymentStatus, PaymentStatus: paymentStatus,
PaymentName: paymentName, PaymentName: paymentName,
ProductName: productName, ProductName: productName,
ProductDisplayName: productDisplayName, ProductDisplayName: productDisplayName,
ProviderName: providerName, ProviderName: providerName,
Price: price, Price: price,
Currency: currency, Currency: currency,
OutOrderId: orderId, OrderId: orderId,
} }
return notifyResult, nil return notifyResult, nil
} }

View File

@@ -24,6 +24,7 @@ const (
PaymentStatePaid PaymentState = "Paid" PaymentStatePaid PaymentState = "Paid"
PaymentStateCreated PaymentState = "Created" PaymentStateCreated PaymentState = "Created"
PaymentStateCanceled PaymentState = "Canceled" PaymentStateCanceled PaymentState = "Canceled"
PaymentStateTimeout PaymentState = "Timeout"
PaymentStateError PaymentState = "Error" PaymentStateError PaymentState = "Error"
) )
@@ -32,13 +33,13 @@ type NotifyResult struct {
PaymentStatus PaymentState PaymentStatus PaymentState
NotifyMessage string NotifyMessage string
ProviderName string
ProductName string ProductName string
ProductDisplayName string ProductDisplayName string
ProviderName string
Price float64 Price float64
Currency string Currency string
OutOrderId string OrderId string
} }
type PaymentProvider interface { type PaymentProvider interface {
@@ -75,6 +76,12 @@ func GetPaymentProvider(typ string, clientId string, clientSecret string, host s
return nil, err return nil, err
} }
return pp, nil return pp, nil
} else if typ == "Stripe" {
pp, err := NewStripePaymentProvider(clientId, clientSecret)
if err != nil {
return nil, err
}
return pp, nil
} }
return nil, nil return nil, nil

166
pp/stripe.go Normal file
View File

@@ -0,0 +1,166 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pp
import (
"fmt"
"net/http"
"time"
"github.com/casdoor/casdoor/conf"
"github.com/stripe/stripe-go/v74"
stripeCheckout "github.com/stripe/stripe-go/v74/checkout/session"
stripeIntent "github.com/stripe/stripe-go/v74/paymentintent"
stripePrice "github.com/stripe/stripe-go/v74/price"
stripeProduct "github.com/stripe/stripe-go/v74/product"
)
type StripePaymentProvider struct {
PublishableKey string
SecretKey string
isProd bool
}
func NewStripePaymentProvider(PublishableKey, SecretKey string) (*StripePaymentProvider, error) {
isProd := false
if conf.GetConfigString("runmode") == "prod" {
isProd = true
}
pp := &StripePaymentProvider{
PublishableKey: PublishableKey,
SecretKey: SecretKey,
isProd: isProd,
}
stripe.Key = pp.SecretKey
return pp, nil
}
func (pp *StripePaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (payUrl string, orderId string, err error) {
// Create a temp product
description := joinAttachString([]string{productName, productDisplayName, providerName})
productParams := &stripe.ProductParams{
Name: stripe.String(productDisplayName),
Description: stripe.String(description),
DefaultPriceData: &stripe.ProductDefaultPriceDataParams{
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
Currency: stripe.String(currency),
},
}
sProduct, err := stripeProduct.New(productParams)
if err != nil {
return "", "", err
}
// Create a price for an existing product
priceParams := &stripe.PriceParams{
Currency: stripe.String(currency),
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
Product: stripe.String(sProduct.ID),
}
sPrice, err := stripePrice.New(priceParams)
if err != nil {
return "", "", err
}
// Create a Checkout Session
checkoutParams := &stripe.CheckoutSessionParams{
LineItems: []*stripe.CheckoutSessionLineItemParams{
{
Price: stripe.String(sPrice.ID),
Quantity: stripe.Int64(1),
},
},
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
SuccessURL: stripe.String(returnUrl),
CancelURL: stripe.String(returnUrl),
ClientReferenceID: stripe.String(paymentName),
ExpiresAt: stripe.Int64(time.Now().Add(30 * time.Minute).Unix()),
}
checkoutParams.AddMetadata("product_description", description)
sCheckout, err := stripeCheckout.New(checkoutParams)
if err != nil {
return "", "", err
}
return sCheckout.URL, sCheckout.ID, nil
}
func (pp *StripePaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
notifyResult := &NotifyResult{}
sCheckout, err := stripeCheckout.Get(orderId, nil)
if err != nil {
return nil, err
}
switch sCheckout.Status {
case "open":
// The checkout session is still in progress. Payment processing has not started
notifyResult.PaymentStatus = PaymentStateCreated
return notifyResult, nil
case "complete":
// The checkout session is complete. Payment processing may still be in progress
case "expired":
// The checkout session has expired. No further processing will occur
notifyResult.PaymentStatus = PaymentStateTimeout
return notifyResult, nil
default:
notifyResult.PaymentStatus = PaymentStateError
notifyResult.NotifyMessage = fmt.Sprintf("unexpected stripe checkout status: %v", sCheckout.Status)
return notifyResult, nil
}
switch sCheckout.PaymentStatus {
case "paid":
// Skip
case "unpaid":
notifyResult.PaymentStatus = PaymentStateCreated
return notifyResult, nil
default:
notifyResult.PaymentStatus = PaymentStateError
notifyResult.NotifyMessage = fmt.Sprintf("unexpected stripe checkout payment status: %v", sCheckout.PaymentStatus)
return notifyResult, nil
}
// Once payment is successful, the Checkout Session will contain a reference to the successful `PaymentIntent`
sIntent, err := stripeIntent.Get(sCheckout.PaymentIntent.ID, nil)
var (
productName string
productDisplayName string
providerName string
)
if description, ok := sCheckout.Metadata["product_description"]; ok {
productName, productDisplayName, providerName, _ = parseAttachString(description)
}
notifyResult = &NotifyResult{
PaymentName: sCheckout.ClientReferenceID,
PaymentStatus: PaymentStatePaid,
ProductName: productName,
ProductDisplayName: productDisplayName,
ProviderName: providerName,
Price: priceInt64ToFloat64(sIntent.Amount),
Currency: string(sIntent.Currency),
OrderId: orderId,
}
return notifyResult, nil
}
func (pp *StripePaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
return "", nil
}
func (pp *StripePaymentProvider) GetResponseError(err error) string {
if err == nil {
return "success"
} else {
return "fail"
}
}

View File

@@ -16,6 +16,8 @@ package pp
import ( import (
"fmt" "fmt"
"math"
"strconv"
"strings" "strings"
) )
@@ -35,3 +37,15 @@ func parseAttachString(s string) (string, string, string, error) {
} }
return tokens[0], tokens[1], tokens[2], nil return tokens[0], tokens[1], tokens[2], nil
} }
func priceInt64ToFloat64(price int64) float64 {
return float64(price) / 100
}
func priceFloat64ToInt64(price float64) int64 {
return int64(math.Round(price * 100))
}
func priceFloat64ToString(price float64) string {
return strconv.FormatFloat(price, 'f', 2, 64)
}

View File

@@ -113,7 +113,7 @@ func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, auth
ProductName: productName, ProductName: productName,
ProductDisplayName: productDisplayName, ProductDisplayName: productDisplayName,
ProviderName: providerName, ProviderName: providerName,
OutOrderId: orderId, OrderId: orderId,
Price: price, Price: price,
PaymentStatus: PaymentStatePaid, PaymentStatus: PaymentStatePaid,
PaymentName: paymentName, PaymentName: paymentName,

View File

@@ -51,6 +51,7 @@ func initAPI() {
beego.Router("/api/signup", &controllers.ApiController{}, "POST:Signup") beego.Router("/api/signup", &controllers.ApiController{}, "POST:Signup")
beego.Router("/api/login", &controllers.ApiController{}, "POST:Login") beego.Router("/api/login", &controllers.ApiController{}, "POST:Login")
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin") beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
beego.Router("/api/get-dashboard", &controllers.ApiController{}, "GET:GetDashboard")
beego.Router("/api/logout", &controllers.ApiController{}, "GET,POST:Logout") beego.Router("/api/logout", &controllers.ApiController{}, "GET,POST:Logout")
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount") beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo") beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo")
@@ -180,9 +181,6 @@ func initAPI() {
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken") beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken") beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken") beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord")
beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions") beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions")
beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession") beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession")
@@ -247,6 +245,7 @@ func initAPI() {
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail") beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms") beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
beego.Router("/api/send-notification", &controllers.ApiController{}, "POST:SendNotification")
beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "Get:WebAuthnSignupBegin") beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "Get:WebAuthnSignupBegin")
beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "Post:WebAuthnSignupFinish") beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "Post:WebAuthnSignupFinish")

View File

@@ -55,12 +55,6 @@ func StaticFilter(ctx *context.Context) {
path += urlPath path += urlPath
} }
path2 := strings.TrimPrefix(path, "web/build/images/")
if util.FileExist(path2) {
makeGzipResponse(ctx.ResponseWriter, ctx.Request, path2)
return
}
if !util.FileExist(path) { if !util.FileExist(path) {
path = "web/build/index.html" path = "web/build/index.html"
} }

31
storage/google_cloud.go Normal file
View File

@@ -0,0 +1,31 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"github.com/casdoor/oss"
"github.com/casdoor/oss/googlecloud"
)
func NewGoogleCloudStorageProvider(clientId string, clientSecret string, bucket string, endpoint string) oss.StorageInterface {
sp, _ := googlecloud.New(&googlecloud.Config{
AccessID: clientId,
AccessKey: clientSecret,
Bucket: bucket,
Endpoint: endpoint,
})
return sp
}

32
storage/qiniu_cloud.go Normal file
View File

@@ -0,0 +1,32 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"github.com/casdoor/oss"
"github.com/casdoor/oss/qiniu"
)
func NewQiniuCloudKodoStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
sp := qiniu.New(&qiniu.Config{
AccessID: clientId,
AccessKey: clientSecret,
Region: region,
Bucket: bucket,
Endpoint: endpoint,
})
return sp
}

View File

@@ -30,6 +30,10 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint) return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Azure Blob": case "Azure Blob":
return NewAzureBlobStorageProvider(clientId, clientSecret, region, bucket, endpoint) return NewAzureBlobStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Qiniu Cloud Kodo":
return NewQiniuCloudKodoStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Google Cloud Storage":
return NewGoogleCloudStorageProvider(clientId, clientSecret, bucket, endpoint)
} }
return nil return nil

View File

@@ -131,34 +131,6 @@
} }
} }
}, },
"/api/add-chat": {
"post": {
"tags": [
"Chat API"
],
"description": "add chat",
"operationId": "ApiController.AddChat",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the chat",
"required": true,
"schema": {
"$ref": "#/definitions/object.Chat"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/add-enforcer": { "/api/add-enforcer": {
"post": { "post": {
"tags": [ "tags": [
@@ -243,34 +215,6 @@
} }
} }
}, },
"/api/add-message": {
"post": {
"tags": [
"Message API"
],
"description": "add message",
"operationId": "ApiController.AddMessage",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the message",
"required": true,
"schema": {
"$ref": "#/definitions/object.Message"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/add-model": { "/api/add-model": {
"post": { "post": {
"tags": [ "tags": [
@@ -1077,34 +1021,6 @@
} }
} }
}, },
"/api/delete-chat": {
"post": {
"tags": [
"Chat API"
],
"description": "delete chat",
"operationId": "ApiController.DeleteChat",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the chat",
"required": true,
"schema": {
"$ref": "#/definitions/object.Chat"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/delete-enforcer": { "/api/delete-enforcer": {
"post": { "post": {
"tags": [ "tags": [
@@ -1189,34 +1105,6 @@
} }
} }
}, },
"/api/delete-message": {
"post": {
"tags": [
"Message API"
],
"description": "delete message",
"operationId": "ApiController.DeleteMessage",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the message",
"required": true,
"schema": {
"$ref": "#/definitions/object.Message"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/delete-mfa/": { "/api/delete-mfa/": {
"post": { "post": {
"tags": [ "tags": [
@@ -1964,56 +1852,18 @@
} }
} }
}, },
"/api/get-chat": { "/api/get-dashboard": {
"get": { "get": {
"tags": [ "tags": [
"Chat API" "GetDashboard API"
],
"description": "get chat",
"operationId": "ApiController.GetChat",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the chat",
"required": true,
"type": "string"
}
], ],
"description": "get information of dashboard",
"operationId": "ApiController.GetDashboard",
"responses": { "responses": {
"200": { "200": {
"description": "The Response object", "description": "The Response object",
"schema": { "schema": {
"$ref": "#/definitions/object.Chat" "$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/get-chats": {
"get": {
"tags": [
"Chat API"
],
"description": "get chats",
"operationId": "ApiController.GetChats",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of chats",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Chat"
}
} }
} }
} }
@@ -2319,87 +2169,6 @@
} }
} }
}, },
"/api/get-message": {
"get": {
"tags": [
"Message API"
],
"description": "get message",
"operationId": "ApiController.GetMessage",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the message",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.Message"
}
}
}
}
},
"/api/get-message-answer": {
"get": {
"tags": [
"Message API"
],
"description": "get message answer",
"operationId": "ApiController.GetMessageAnswer",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the message",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.Message"
}
}
}
}
},
"/api/get-messages": {
"get": {
"tags": [
"Message API"
],
"description": "get messages",
"operationId": "ApiController.GetMessages",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of messages",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Message"
}
}
}
}
}
},
"/api/get-model": { "/api/get-model": {
"get": { "get": {
"tags": [ "tags": [
@@ -4481,41 +4250,6 @@
} }
} }
}, },
"/api/update-chat": {
"post": {
"tags": [
"Chat API"
],
"description": "update chat",
"operationId": "ApiController.UpdateChat",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the chat",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "body",
"description": "The details of the chat",
"required": true,
"schema": {
"$ref": "#/definitions/object.Chat"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/update-enforcer": { "/api/update-enforcer": {
"post": { "post": {
"tags": [ "tags": [
@@ -4614,41 +4348,6 @@
} }
} }
}, },
"/api/update-message": {
"post": {
"tags": [
"Message API"
],
"description": "update message",
"operationId": "ApiController.UpdateMessage",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the message",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "body",
"description": "The details of the message",
"required": true,
"schema": {
"$ref": "#/definitions/object.Message"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/update-model": { "/api/update-model": {
"post": { "post": {
"tags": [ "tags": [
@@ -5431,6 +5130,14 @@
} }
}, },
"definitions": { "definitions": {
"1183.0xc000639290.false": {
"title": "false",
"type": "object"
},
"1217.0xc0006392c0.false": {
"title": "false",
"type": "object"
},
"LaravelResponse": { "LaravelResponse": {
"title": "LaravelResponse", "title": "LaravelResponse",
"type": "object" "type": "object"
@@ -5484,16 +5191,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"additionalProperties": { "$ref": "#/definitions/1183.0xc000639290.false"
"description": "support string, struct or []struct",
"type": "string"
}
}, },
"data2": { "data2": {
"additionalProperties": { "$ref": "#/definitions/1217.0xc0006392c0.false"
"description": "support string, struct or []struct",
"type": "string"
}
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@@ -5531,10 +5232,18 @@
"title": "JSONWebKey", "title": "JSONWebKey",
"type": "object" "type": "object"
}, },
"model.Model": {
"title": "Model",
"type": "object"
},
"object": { "object": {
"title": "object", "title": "object",
"type": "object" "type": "object"
}, },
"object.\u0026{197049 0xc000a2cd50 false}": {
"title": "\u0026{197049 0xc000a2cd50 false}",
"type": "object"
},
"object.AccountItem": { "object.AccountItem": {
"title": "AccountItem", "title": "AccountItem",
"type": "object", "type": "object",
@@ -5557,16 +5266,41 @@
"title": "Adapter", "title": "Adapter",
"type": "object", "type": "object",
"properties": { "properties": {
"Engine": { "createdTime": {
"$ref": "#/definitions/xorm.Engine"
},
"dataSourceName": {
"type": "string" "type": "string"
}, },
"dbName": { "database": {
"type": "string" "type": "string"
}, },
"driverName": { "databaseType": {
"type": "string"
},
"host": {
"type": "string"
},
"name": {
"type": "string"
},
"owner": {
"type": "string"
},
"password": {
"type": "string"
},
"port": {
"type": "integer",
"format": "int64"
},
"table": {
"type": "string"
},
"tableNamePrefix": {
"type": "string"
},
"type": {
"type": "string"
},
"user": {
"type": "string" "type": "string"
} }
} }
@@ -5728,7 +5462,7 @@
"title": "CasbinRequest", "title": "CasbinRequest",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/object.CasbinRequest" "$ref": "#/definitions/object.\u0026{197049 0xc000a2cd50 false}"
} }
}, },
"object.Cert": { "object.Cert": {
@@ -5778,52 +5512,6 @@
} }
} }
}, },
"object.Chat": {
"title": "Chat",
"type": "object",
"properties": {
"category": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"displayName": {
"type": "string"
},
"messageCount": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"organization": {
"type": "string"
},
"owner": {
"type": "string"
},
"type": {
"type": "string"
},
"updatedTime": {
"type": "string"
},
"user1": {
"type": "string"
},
"user2": {
"type": "string"
},
"users": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"object.Enforce": { "object.Enforce": {
"title": "Enforce", "title": "Enforce",
"type": "object" "type": "object"
@@ -5844,12 +5532,14 @@
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"isEnabled": {
"type": "boolean"
},
"model": { "model": {
"type": "string" "type": "string"
}, },
"modelCfg": {
"additionalProperties": {
"type": "string"
}
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@@ -6084,36 +5774,6 @@
} }
} }
}, },
"object.Message": {
"title": "Message",
"type": "object",
"properties": {
"author": {
"type": "string"
},
"chat": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"name": {
"type": "string"
},
"organization": {
"type": "string"
},
"owner": {
"type": "string"
},
"replyTo": {
"type": "string"
},
"text": {
"type": "string"
}
}
},
"object.MfaItem": { "object.MfaItem": {
"title": "MfaItem", "title": "MfaItem",
"type": "object", "type": "object",
@@ -6169,9 +5829,6 @@
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"isEnabled": {
"type": "boolean"
},
"modelText": { "modelText": {
"type": "string" "type": "string"
}, },
@@ -6349,6 +6006,24 @@
} }
} }
}, },
"object.Ormer": {
"title": "Ormer",
"type": "object",
"properties": {
"Engine": {
"$ref": "#/definitions/xorm.Engine"
},
"dataSourceName": {
"type": "string"
},
"dbName": {
"type": "string"
},
"driverName": {
"type": "string"
}
}
},
"object.Payment": { "object.Payment": {
"title": "Payment", "title": "Payment",
"type": "object", "type": "object",
@@ -6386,7 +6061,7 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"organization": { "outOrderId": {
"type": "string" "type": "string"
}, },
"owner": { "owner": {
@@ -6424,7 +6099,7 @@
"type": "string" "type": "string"
}, },
"state": { "state": {
"type": "string" "$ref": "#/definitions/pp.PaymentState"
}, },
"tag": { "tag": {
"type": "string" "type": "string"
@@ -7124,9 +6799,6 @@
"$ref": "#/definitions/object.TableColumn" "$ref": "#/definitions/object.TableColumn"
} }
}, },
"tablePrimaryKey": {
"type": "string"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@@ -7145,6 +6817,9 @@
"isHashed": { "isHashed": {
"type": "boolean" "type": "boolean"
}, },
"isKey": {
"type": "boolean"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@@ -7464,9 +7139,6 @@
"isForbidden": { "isForbidden": {
"type": "boolean" "type": "boolean"
}, },
"isGlobalAdmin": {
"type": "boolean"
},
"isOnline": { "isOnline": {
"type": "boolean" "type": "boolean"
}, },
@@ -7692,6 +7364,9 @@
"vk": { "vk": {
"type": "string" "type": "string"
}, },
"web3onboard": {
"type": "string"
},
"webauthnCredentials": { "webauthnCredentials": {
"type": "array", "type": "array",
"items": { "items": {
@@ -7811,6 +7486,18 @@
} }
} }
}, },
"pp.PaymentState": {
"title": "PaymentState",
"type": "string",
"enum": [
"PaymentStatePaid = \"Paid\"",
"PaymentStateCreated = \"Created\"",
"PaymentStateCanceled = \"Canceled\"",
"PaymentStateTimeout = \"Timeout\"",
"PaymentStateError = \"Error\""
],
"example": "Paid"
},
"protocol.CredentialAssertion": { "protocol.CredentialAssertion": {
"title": "CredentialAssertion", "title": "CredentialAssertion",
"type": "object" "type": "object"
@@ -7871,6 +7558,10 @@
"xorm.Engine": { "xorm.Engine": {
"title": "Engine", "title": "Engine",
"type": "object" "type": "object"
},
"xormadapter.Adapter": {
"title": "Adapter",
"type": "object"
} }
}, },
"securityDefinitions": { "securityDefinitions": {
@@ -7879,9 +7570,5 @@
"name": "Authorization", "name": "Authorization",
"in": "header" "in": "header"
} }
},
"externalDocs": {
"description": "Find out more about Casdoor",
"url": "https://casdoor.org/"
} }
} }

View File

@@ -85,24 +85,6 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/add-chat:
post:
tags:
- Chat API
description: add chat
operationId: ApiController.AddChat
parameters:
- in: body
name: body
description: The details of the chat
required: true
schema:
$ref: '#/definitions/object.Chat'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-enforcer: /api/add-enforcer:
post: post:
tags: tags:
@@ -157,24 +139,6 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/add-message:
post:
tags:
- Message API
description: add message
operationId: ApiController.AddMessage
parameters:
- in: body
name: body
description: The details of the message
required: true
schema:
$ref: '#/definitions/object.Message'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-model: /api/add-model:
post: post:
tags: tags:
@@ -696,24 +660,6 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/delete-chat:
post:
tags:
- Chat API
description: delete chat
operationId: ApiController.DeleteChat
parameters:
- in: body
name: body
description: The details of the chat
required: true
schema:
$ref: '#/definitions/object.Chat'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-enforcer: /api/delete-enforcer:
post: post:
tags: tags:
@@ -768,24 +714,6 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/delete-message:
post:
tags:
- Message API
description: delete message
operationId: ApiController.DeleteMessage
parameters:
- in: body
name: body
description: The details of the message
required: true
schema:
$ref: '#/definitions/object.Message'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-mfa/: /api/delete-mfa/:
post: post:
tags: tags:
@@ -1271,42 +1199,17 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.Cert' $ref: '#/definitions/object.Cert'
/api/get-chat: /api/get-dashboard:
get: get:
tags: tags:
- Chat API - GetDashboard API
description: get chat description: get information of dashboard
operationId: ApiController.GetChat operationId: ApiController.GetDashboard
parameters:
- in: query
name: id
description: The id ( owner/name ) of the chat
required: true
type: string
responses: responses:
"200": "200":
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.Chat' $ref: '#/definitions/controllers.Response'
/api/get-chats:
get:
tags:
- Chat API
description: get chats
operationId: ApiController.GetChats
parameters:
- in: query
name: owner
description: The owner of chats
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Chat'
/api/get-default-application: /api/get-default-application:
get: get:
tags: tags:
@@ -1503,59 +1406,6 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.Ldap' $ref: '#/definitions/object.Ldap'
/api/get-message:
get:
tags:
- Message API
description: get message
operationId: ApiController.GetMessage
parameters:
- in: query
name: id
description: The id ( owner/name ) of the message
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Message'
/api/get-message-answer:
get:
tags:
- Message API
description: get message answer
operationId: ApiController.GetMessageAnswer
parameters:
- in: query
name: id
description: The id ( owner/name ) of the message
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Message'
/api/get-messages:
get:
tags:
- Message API
description: get messages
operationId: ApiController.GetMessages
parameters:
- in: query
name: owner
description: The owner of messages
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Message'
/api/get-model: /api/get-model:
get: get:
tags: tags:
@@ -2925,29 +2775,6 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/update-chat:
post:
tags:
- Chat API
description: update chat
operationId: ApiController.UpdateChat
parameters:
- in: query
name: id
description: The id ( owner/name ) of the chat
required: true
type: string
- in: body
name: body
description: The details of the chat
required: true
schema:
$ref: '#/definitions/object.Chat'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-enforcer: /api/update-enforcer:
post: post:
tags: tags:
@@ -3012,29 +2839,6 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/update-message:
post:
tags:
- Message API
description: update message
operationId: ApiController.UpdateMessage
parameters:
- in: query
name: id
description: The id ( owner/name ) of the message
required: true
type: string
- in: body
name: body
description: The details of the message
required: true
schema:
$ref: '#/definitions/object.Message'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-model: /api/update-model:
post: post:
tags: tags:
@@ -3549,6 +3353,12 @@ paths:
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
definitions: definitions:
1183.0xc000639290.false:
title: "false"
type: object
1217.0xc0006392c0.false:
title: "false"
type: object
LaravelResponse: LaravelResponse:
title: LaravelResponse title: LaravelResponse
type: object type: object
@@ -3588,13 +3398,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
additionalProperties: $ref: '#/definitions/1183.0xc000639290.false'
description: support string, struct or []struct
type: string
data2: data2:
additionalProperties: $ref: '#/definitions/1217.0xc0006392c0.false'
description: support string, struct or []struct
type: string
msg: msg:
type: string type: string
name: name:
@@ -3618,9 +3424,15 @@ definitions:
jose.JSONWebKey: jose.JSONWebKey:
title: JSONWebKey title: JSONWebKey
type: object type: object
model.Model:
title: Model
type: object
object: object:
title: object title: object
type: object type: object
object.&{197049 0xc000a2cd50 false}:
title: '&{197049 0xc000a2cd50 false}'
type: object
object.AccountItem: object.AccountItem:
title: AccountItem title: AccountItem
type: object type: object
@@ -3637,13 +3449,30 @@ definitions:
title: Adapter title: Adapter
type: object type: object
properties: properties:
Engine: createdTime:
$ref: '#/definitions/xorm.Engine'
dataSourceName:
type: string type: string
dbName: database:
type: string type: string
driverName: databaseType:
type: string
host:
type: string
name:
type: string
owner:
type: string
password:
type: string
port:
type: integer
format: int64
table:
type: string
tableNamePrefix:
type: string
type:
type: string
user:
type: string type: string
object.Application: object.Application:
title: Application title: Application
@@ -3752,7 +3581,7 @@ definitions:
title: CasbinRequest title: CasbinRequest
type: array type: array
items: items:
$ref: '#/definitions/object.CasbinRequest' $ref: '#/definitions/object.&{197049 0xc000a2cd50 false}'
object.Cert: object.Cert:
title: Cert title: Cert
type: object type: object
@@ -3785,37 +3614,6 @@ definitions:
type: string type: string
type: type:
type: string type: string
object.Chat:
title: Chat
type: object
properties:
category:
type: string
createdTime:
type: string
displayName:
type: string
messageCount:
type: integer
format: int64
name:
type: string
organization:
type: string
owner:
type: string
type:
type: string
updatedTime:
type: string
user1:
type: string
user2:
type: string
users:
type: array
items:
type: string
object.Enforce: object.Enforce:
title: Enforce title: Enforce
type: object type: object
@@ -3831,10 +3629,11 @@ definitions:
type: string type: string
displayName: displayName:
type: string type: string
isEnabled:
type: boolean
model: model:
type: string type: string
modelCfg:
additionalProperties:
type: string
name: name:
type: string type: string
owner: owner:
@@ -3992,26 +3791,6 @@ definitions:
type: string type: string
username: username:
type: string type: string
object.Message:
title: Message
type: object
properties:
author:
type: string
chat:
type: string
createdTime:
type: string
name:
type: string
organization:
type: string
owner:
type: string
replyTo:
type: string
text:
type: string
object.MfaItem: object.MfaItem:
title: MfaItem title: MfaItem
type: object type: object
@@ -4050,8 +3829,6 @@ definitions:
type: string type: string
displayName: displayName:
type: string type: string
isEnabled:
type: boolean
modelText: modelText:
type: string type: string
name: name:
@@ -4169,6 +3946,18 @@ definitions:
$ref: '#/definitions/object.ThemeData' $ref: '#/definitions/object.ThemeData'
websiteUrl: websiteUrl:
type: string type: string
object.Ormer:
title: Ormer
type: object
properties:
Engine:
$ref: '#/definitions/xorm.Engine'
dataSourceName:
type: string
dbName:
type: string
driverName:
type: string
object.Payment: object.Payment:
title: Payment title: Payment
type: object type: object
@@ -4195,7 +3984,7 @@ definitions:
type: string type: string
name: name:
type: string type: string
organization: outOrderId:
type: string type: string
owner: owner:
type: string type: string
@@ -4221,7 +4010,7 @@ definitions:
returnUrl: returnUrl:
type: string type: string
state: state:
type: string $ref: '#/definitions/pp.PaymentState'
tag: tag:
type: string type: string
type: type:
@@ -4692,8 +4481,6 @@ definitions:
type: array type: array
items: items:
$ref: '#/definitions/object.TableColumn' $ref: '#/definitions/object.TableColumn'
tablePrimaryKey:
type: string
type: type:
type: string type: string
user: user:
@@ -4706,6 +4493,8 @@ definitions:
type: string type: string
isHashed: isHashed:
type: boolean type: boolean
isKey:
type: boolean
name: name:
type: string type: string
type: type:
@@ -4920,8 +4709,6 @@ definitions:
type: boolean type: boolean
isForbidden: isForbidden:
type: boolean type: boolean
isGlobalAdmin:
type: boolean
isOnline: isOnline:
type: boolean type: boolean
kakao: kakao:
@@ -5073,6 +4860,8 @@ definitions:
type: string type: string
vk: vk:
type: string type: string
web3onboard:
type: string
webauthnCredentials: webauthnCredentials:
type: array type: array
items: items:
@@ -5151,6 +4940,16 @@ definitions:
type: string type: string
url: url:
type: string type: string
pp.PaymentState:
title: PaymentState
type: string
enum:
- PaymentStatePaid = "Paid"
- PaymentStateCreated = "Created"
- PaymentStateCanceled = "Canceled"
- PaymentStateTimeout = "Timeout"
- PaymentStateError = "Error"
example: Paid
protocol.CredentialAssertion: protocol.CredentialAssertion:
title: CredentialAssertion title: CredentialAssertion
type: object type: object
@@ -5195,11 +4994,11 @@ definitions:
xorm.Engine: xorm.Engine:
title: Engine title: Engine
type: object type: object
xormadapter.Adapter:
title: Adapter
type: object
securityDefinitions: securityDefinitions:
AccessToken: AccessToken:
type: apiKey type: apiKey
name: Authorization name: Authorization
in: header in: header
externalDocs:
description: Find out more about Casdoor
url: https://casdoor.org/

63
util/casbin.go Normal file
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.
package util
import xormadapter "github.com/casdoor/xorm-adapter/v3"
func CasbinToSlice(casbinRule xormadapter.CasbinRule) []string {
s := []string{
casbinRule.V0,
casbinRule.V1,
casbinRule.V2,
casbinRule.V3,
casbinRule.V4,
casbinRule.V5,
}
// remove empty strings from end, for update model policy map
for i := len(s) - 1; i >= 0; i-- {
if s[i] != "" {
s = s[:i+1]
break
}
}
return s
}
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
}

View File

@@ -88,6 +88,17 @@ func CamelToSnakeCase(camel string) string {
return strings.ReplaceAll(buf.String(), " ", "") return strings.ReplaceAll(buf.String(), " ", "")
} }
func SnakeToCamel(snake string) string {
words := strings.Split(snake, "_")
for i := range words {
words[i] = strings.ToLower(words[i])
if i > 0 {
words[i] = strings.Title(words[i])
}
}
return strings.Join(words, "")
}
func GetOwnerAndNameFromId(id string) (string, string) { func GetOwnerAndNameFromId(id string) (string, string) {
tokens := strings.Split(id, "/") tokens := strings.Split(id, "/")
if len(tokens) != 2 { if len(tokens) != 2 {

View File

@@ -51,31 +51,46 @@ module.exports = {
}, },
], ],
webpack: { webpack: {
// use polyfill Buffer with Webpack 5 configure: {
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5 // ignore webpack warnings by source-map-loader
// https://craco.js.org/docs/configuration/webpack/ // https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546
configure: (webpackConfig, { env, paths }) => { ignoreWarnings: [
webpackConfig.resolve.fallback = { function ignoreSourcemapsloaderWarnings(warning) {
// "process": require.resolve('process/browser'), return (
// "util": require.resolve("util/"), warning.module &&
// "url": require.resolve("url/"), warning.module.resource.includes('node_modules') &&
// "zlib": require.resolve("browserify-zlib"), warning.details &&
// "stream": require.resolve("stream-browserify"), warning.details.includes('source-map-loader')
// "http": require.resolve("stream-http"), )
// "https": require.resolve("https-browserify"), },
// "assert": require.resolve("assert/"), ],
"buffer": require.resolve('buffer/'), // use polyfill Buffer with Webpack 5
"process": false, // https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
"util": false, // https://craco.js.org/docs/configuration/webpack/
"url": false, resolve: {
"zlib": false, fallback: {
"stream": false, // "process": require.resolve('process/browser'),
"http": false, // "util": require.resolve("util/"),
"https": false, // "url": require.resolve("url/"),
"assert": false, // "zlib": require.resolve("browserify-zlib"),
"buffer": false, // "stream": require.resolve("stream-browserify"),
}; // "http": require.resolve("stream-http"),
return webpackConfig; // "https": require.resolve("https-browserify"),
// "assert": require.resolve("assert/"),
"buffer": require.resolve('buffer/'),
"process": false,
"util": false,
"url": false,
"zlib": false,
"stream": false,
"http": false,
"https": false,
"assert": false,
"buffer": false,
"crypto": false,
"os": false,
},
}
}, },
} }
}; };

View File

@@ -10,9 +10,16 @@
"@ctrl/tinycolor": "^3.5.0", "@ctrl/tinycolor": "^3.5.0",
"@emotion/react": "^11.10.5", "@emotion/react": "^11.10.5",
"@metamask/eth-sig-util": "^6.0.0", "@metamask/eth-sig-util": "^6.0.0",
"@testing-library/jest-dom": "^4.2.4", "@web3-onboard/coinbase": "^2.2.5",
"@testing-library/react": "^9.3.2", "@web3-onboard/core": "^2.20.5",
"@testing-library/user-event": "^7.1.2", "@web3-onboard/frontier": "^2.0.4",
"@web3-onboard/gnosis": "^2.1.10",
"@web3-onboard/infinity-wallet": "^2.0.4",
"@web3-onboard/injected-wallets": "^2.10.4",
"@web3-onboard/react": "^2.8.10",
"@web3-onboard/sequence": "^2.0.8",
"@web3-onboard/taho": "^2.0.5",
"@web3-onboard/trust": "^2.0.4",
"antd": "5.2.3", "antd": "5.2.3",
"antd-token-previewer": "^1.1.0-22", "antd-token-previewer": "^1.1.0-22",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -20,7 +27,8 @@
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0", "core-js": "^3.25.0",
"craco-less": "^2.0.0", "craco-less": "^2.0.0",
"eslint-plugin-unused-imports": "^2.0.0", "echarts": "^5.4.3",
"ethers": "5.6.9",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18n-iso-countries": "^7.0.0", "i18n-iso-countries": "^7.0.0",
"i18next": "^19.8.9", "i18next": "^19.8.9",
@@ -44,7 +52,7 @@
}, },
"scripts": { "scripts": {
"start": "cross-env PORT=7001 craco start", "start": "cross-env PORT=7001 craco start",
"build": "craco build", "build": "craco --max_old_space_size=4096 build",
"test": "craco test", "test": "craco test",
"eject": "craco eject", "eject": "craco eject",
"crowdin:sync": "crowdin upload && crowdin download", "crowdin:sync": "crowdin upload && crowdin download",
@@ -72,16 +80,21 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.13", "@babel/core": "^7.18.13",
"@babel/eslint-parser": "^7.18.9", "@babel/eslint-parser": "^7.18.9",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-react": "^7.18.6", "@babel/preset-react": "^7.18.6",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cypress": "^12.5.1", "cypress": "^12.5.1",
"eslint": "8.22.0", "eslint": "8.22.0",
"eslint-plugin-react": "^7.31.1", "eslint-plugin-react": "^7.31.1",
"eslint-plugin-unused-imports": "^2.0.0",
"husky": "^4.3.8", "husky": "^4.3.8",
"lint-staged": "^13.0.3", "lint-staged": "^13.0.3",
"stylelint": "^14.11.0", "stylelint": "^14.11.0",
"stylelint-config-recommended-less": "^1.0.4", "stylelint-config-recommended-less": "^1.0.4",
"stylelint-config-standard": "^28.0.0" "stylelint-config-standard": "^28.0.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2"
}, },
"lint-staged": { "lint-staged": {
"src/**/*.{css,less}": [ "src/**/*.{css,less}": [

View File

@@ -13,17 +13,12 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd"; import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
import * as AdapterBackend from "./backend/AdapterBackend"; import * as AdapterBackend from "./backend/AdapterBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import "codemirror/lib/codemirror.css";
import PolicyTable from "./table/PoliciyTable";
require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript");
const {Option} = Select; const {Option} = Select;
class AdapterEditPage extends React.Component { class AdapterEditPage extends React.Component {
@@ -232,20 +227,23 @@ class AdapterEditPage extends React.Component {
</Row> </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("adapter:Policies"), i18next.t("adapter:Policies - Tooltip"))} : {Setting.getLabel(i18next.t("provider:DB Test"), i18next.t("provider:DB Test - Tooltip"))} :
</Col> </Col>
<Col span={22}> <Col span={2} >
<PolicyTable owner={this.state.organizationName} name={this.state.adapterName} mode={this.state.mode} /> <Button type={"primary"} onClick={() => {
</Col> AdapterBackend.getPolicies("", "", `${this.state.organizationName}/${this.state.adapterName}`)
</Row> .then((res) => {
<Row style={{marginTop: "20px"}} > if (res.status === "ok") {
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}> Setting.showMessage("success", i18next.t("syncer:Connect successfully"));
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} : } else {
</Col> Setting.showMessage("error", i18next.t("syncer:Failed to connect") + ": " + res.msg);
<Col span={1} > }
<Switch checked={this.state.adapter.isEnabled} onChange={checked => { })
this.updateAdapterField("isEnabled", checked); .catch(error => {
}} /> Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
}>{i18next.t("syncer:Test DB Connection")}</Button>
</Col> </Col>
</Row> </Row>
</Card> </Card>

View File

@@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Switch, Table} from "antd"; import {Button, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as AdapterBackend from "./backend/AdapterBackend"; import * as AdapterBackend from "./backend/AdapterBackend";
@@ -38,7 +38,6 @@ class AdapterListPage extends BaseListPage {
databaseType: "mysql", databaseType: "mysql",
database: "dbName", database: "dbName",
table: "tableName", table: "tableName",
isEnabled: false,
}; };
} }
@@ -183,18 +182,6 @@ class AdapterListPage extends BaseListPage {
width: "120px", width: "120px",
sorter: true, 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"), title: i18next.t("general:Action"),
dataIndex: "", dataIndex: "",

View File

@@ -15,6 +15,8 @@
import React, {Component} from "react"; import React, {Component} from "react";
import "./App.less"; import "./App.less";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import Dashboard from "./basic/Dashboard";
import ShortcutsPage from "./basic/ShortcutsPage";
import {MfaRuleRequired} from "./Setting"; import {MfaRuleRequired} from "./Setting";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs"; import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
@@ -43,7 +45,6 @@ import LdapEditPage from "./LdapEditPage";
import LdapSyncPage from "./LdapSyncPage"; import LdapSyncPage from "./LdapSyncPage";
import TokenListPage from "./TokenListPage"; import TokenListPage from "./TokenListPage";
import TokenEditPage from "./TokenEditPage"; import TokenEditPage from "./TokenEditPage";
import RecordListPage from "./RecordListPage";
import WebhookListPage from "./WebhookListPage"; import WebhookListPage from "./WebhookListPage";
import WebhookEditPage from "./WebhookEditPage"; import WebhookEditPage from "./WebhookEditPage";
import SyncerListPage from "./SyncerListPage"; import SyncerListPage from "./SyncerListPage";
@@ -70,7 +71,7 @@ import SessionListPage from "./SessionListPage";
import MfaSetupPage from "./auth/MfaSetupPage"; import MfaSetupPage from "./auth/MfaSetupPage";
import SystemInfo from "./SystemInfo"; import SystemInfo from "./SystemInfo";
import AccountPage from "./account/AccountPage"; import AccountPage from "./account/AccountPage";
import HomePage from "./basic/HomePage"; import AppListPage from "./basic/AppListPage";
import CustomGithubCorner from "./common/CustomGithubCorner"; import CustomGithubCorner from "./common/CustomGithubCorner";
import * as Conf from "./Conf"; import * as Conf from "./Conf";
@@ -149,14 +150,14 @@ class App extends Component {
this.setState({ this.setState({
uri: uri, uri: uri,
}); });
if (uri === "/") { if (uri === "/" || uri.includes("/shortcuts") || uri.includes("/apps")) {
this.setState({selectedMenuKey: "/"}); this.setState({selectedMenuKey: "/home"});
} else if (uri.includes("/organizations") || uri.includes("/trees") || uri.includes("/users") || uri.includes("/groups")) { } else if (uri.includes("/organizations") || uri.includes("/trees") || uri.includes("/users") || uri.includes("/groups")) {
this.setState({selectedMenuKey: "/orgs"}); this.setState({selectedMenuKey: "/orgs"});
} else if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {
this.setState({selectedMenuKey: "/auth"});
} else if (uri.includes("/applications") || uri.includes("/providers") || uri.includes("/resources") || uri.includes("/certs")) { } else if (uri.includes("/applications") || uri.includes("/providers") || uri.includes("/resources") || uri.includes("/certs")) {
this.setState({selectedMenuKey: "/identity"}); this.setState({selectedMenuKey: "/identity"});
} else if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {
this.setState({selectedMenuKey: "/auth"});
} else if (uri.includes("/records") || uri.includes("/tokens") || uri.includes("/sessions")) { } else if (uri.includes("/records") || uri.includes("/tokens") || uri.includes("/sessions")) {
this.setState({selectedMenuKey: "/logs"}); this.setState({selectedMenuKey: "/logs"});
} else if (uri.includes("/products") || uri.includes("/payments") || uri.includes("/plans") || uri.includes("/pricings") || uri.includes("/subscriptions")) { } else if (uri.includes("/products") || uri.includes("/payments") || uri.includes("/plans") || uri.includes("/pricings") || uri.includes("/subscriptions")) {
@@ -351,7 +352,7 @@ class App extends Component {
} }
&nbsp; &nbsp;
&nbsp; &nbsp;
{Setting.isMobile() ? null : Setting.getNameAtLeast(this.state.account.displayName)} &nbsp; <DownOutlined /> {Setting.isMobile() ? null : Setting.getShortText(Setting.getNameAtLeast(this.state.account.displayName), 30)} &nbsp; <DownOutlined />
&nbsp; &nbsp;
&nbsp; &nbsp;
&nbsp; &nbsp;
@@ -401,7 +402,13 @@ class App extends Component {
return []; return [];
} }
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/", <HomeTwoTone />)); res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/home", <HomeTwoTone />, [
Setting.getItem(<Link to="/">{i18next.t("general:Dashboard")}</Link>, "/"),
Setting.getItem(<Link to="/shortcuts">{i18next.t("general:Shortcuts")}</Link>, "/shortcuts"),
Setting.getItem(<Link to="/apps">{i18next.t("general:Apps")}</Link>, "/apps"),
].filter(item => {
return Setting.isLocalAdminUser(this.state.account);
})));
if (Setting.isLocalAdminUser(this.state.account)) { if (Setting.isLocalAdminUser(this.state.account)) {
if (Conf.ShowGithubCorner) { if (Conf.ShowGithubCorner) {
@@ -418,6 +425,13 @@ class App extends Component {
Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>, "/users"), Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>, "/users"),
])); ]));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone />, [
Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>, "/applications"),
Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>, "/providers"),
Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, "/resources"),
Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>, "/certs"),
]));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone />, [ res.push(Setting.getItem(<Link style={{color: "black"}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone />, [
Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>, "/roles"), Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>, "/roles"),
Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>, "/permissions"), Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>, "/permissions"),
@@ -431,20 +445,11 @@ class App extends Component {
return true; return true;
} }
}))); })));
}
if (Setting.isLocalAdminUser(this.state.account)) { res.push(Setting.getItem(<Link style={{color: "black"}} to="/sessions">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone />, [
res.push(Setting.getItem(<Link style={{color: "black"}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone />, [
Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>, "/applications"),
Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>, "/providers"),
Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, "/resources"),
Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>, "/certs"),
]));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/records">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone />, [
Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>, "/records"),
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>, "/sessions"), Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>, "/sessions"),
Setting.getItem(<a target="_blank" rel="noreferrer" href={"http://localhost:18001/records"}>{i18next.t("general:Records")}</a>, "/records"),
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
])); ]));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone />, [ res.push(Setting.getItem(<Link style={{color: "black"}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone />, [
@@ -479,7 +484,9 @@ class App extends Component {
renderRouter() { renderRouter() {
return ( return (
<Switch> <Switch>
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} /> <Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<Dashboard account={this.state.account} {...props} />)} />
<Route exact path="/apps" render={(props) => this.renderLoginIfNotLoggedIn(<AppListPage account={this.state.account} {...props} />)} />
<Route exact path="/shortcuts" render={(props) => this.renderLoginIfNotLoggedIn(<ShortcutsPage account={this.state.account} {...props} />)} />
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} /> <Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} onChangeTheme={this.setTheme} {...props} />)} /> <Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} onChangeTheme={this.setTheme} {...props} />)} />
@@ -529,7 +536,6 @@ class App extends Component {
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} /> <Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:organizationName/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} /> <Route exact path="/payments/:organizationName/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:organizationName/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} /> <Route exact path="/payments/:organizationName/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
<Route exact path="/mfa/setup" render={(props) => this.renderLoginIfNotLoggedIn(<MfaSetupPage account={this.state.account} onfinish={() => this.setState({requiredEnableMfa: false})} {...props} />)} /> <Route exact path="/mfa/setup" render={(props) => this.renderLoginIfNotLoggedIn(<MfaSetupPage account={this.state.account} onfinish={() => this.setState({requiredEnableMfa: false})} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} /> <Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} /> <Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />

View File

@@ -13,11 +13,12 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from "antd"; import {Button, Card, Col, Input, Row, Select} from "antd";
import * as AdapterBackend from "./backend/AdapterBackend"; import * as AdapterBackend from "./backend/AdapterBackend";
import * as EnforcerBackend from "./backend/EnforcerBackend"; import * as EnforcerBackend from "./backend/EnforcerBackend";
import * as ModelBackend from "./backend/ModelBackend"; import * as ModelBackend from "./backend/ModelBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import PolicyTable from "./table/PolicyTable";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -42,7 +43,7 @@ class EnforcerEditPage extends React.Component {
} }
getEnforcer() { getEnforcer() {
EnforcerBackend.getEnforcer(this.state.organizationName, this.state.enforcerName) EnforcerBackend.getEnforcer(this.state.organizationName, this.state.enforcerName, true)
.then((res) => { .then((res) => {
if (res.data === null) { if (res.data === null) {
this.props.history.push("/404"); this.props.history.push("/404");
@@ -169,7 +170,7 @@ class EnforcerEditPage extends React.Component {
<Select virtual={false} disabled={Setting.builtInObject(this.state.enforcer)} style={{width: "100%"}} value={this.state.enforcer.model} onChange={(model => { <Select virtual={false} disabled={Setting.builtInObject(this.state.enforcer)} style={{width: "100%"}} value={this.state.enforcer.model} onChange={(model => {
this.updateEnforcerField("model", model); this.updateEnforcerField("model", model);
})} })}
options={this.state.models.map((model) => Setting.getOption(model.displayName, `${model.owner}/${model.name}`)) options={this.state.models.map((model) => Setting.getOption(`${model.owner}/${model.name}`, `${model.owner}/${model.name}`))
} /> } />
</Col> </Col>
</Row> </Row>
@@ -181,18 +182,16 @@ class EnforcerEditPage extends React.Component {
<Select virtual={false} disabled={Setting.builtInObject(this.state.enforcer)} style={{width: "100%"}} value={this.state.enforcer.adapter} onChange={(adapter => { <Select virtual={false} disabled={Setting.builtInObject(this.state.enforcer)} style={{width: "100%"}} value={this.state.enforcer.adapter} onChange={(adapter => {
this.updateEnforcerField("adapter", adapter); this.updateEnforcerField("adapter", adapter);
})} })}
options={this.state.adapters.map((adapter) => Setting.getOption(adapter.name, `${adapter.owner}/${adapter.name}`)) options={this.state.adapters.map((adapter) => Setting.getOption(`${adapter.owner}/${adapter.name}`, `${adapter.owner}/${adapter.name}`))
} /> } />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} : {Setting.getLabel(i18next.t("adapter:Policies"), i18next.t("adapter:Policies - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={22}>
<Switch checked={this.state.enforcer.isEnabled} onChange={checked => { <PolicyTable enforcer={this.state.enforcer} modelCfg={this.state.enforcer?.modelCfg} mode={this.state.mode} />
this.updateEnforcerField("isEnabled", checked);
}} />
</Col> </Col>
</Row> </Row>
</Card> </Card>

View File

@@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Switch, Table} from "antd"; import {Button, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as EnforcerBackend from "./backend/EnforcerBackend"; import * as EnforcerBackend from "./backend/EnforcerBackend";
@@ -31,7 +31,6 @@ class EnforcerListPage extends BaseListPage {
name: `enforcer_${randomName}`, name: `enforcer_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
displayName: `New Enforcer - ${randomName}`, displayName: `New Enforcer - ${randomName}`,
isEnabled: true,
}; };
} }
@@ -75,7 +74,7 @@ class EnforcerListPage extends BaseListPage {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
key: "name", key: "name",
width: "150px", width: "200px",
fixed: "left", fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps("name"), ...this.getColumnSearchProps("name"),
@@ -116,19 +115,39 @@ class EnforcerListPage extends BaseListPage {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: "displayName", dataIndex: "displayName",
key: "displayName", key: "displayName",
width: "200px", // width: "200px",
sorter: true, sorter: true,
...this.getColumnSearchProps("displayName"), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("general:Is enabled"), title: i18next.t("general:Model"),
dataIndex: "isEnabled", dataIndex: "model",
key: "isEnabled", key: "model",
width: "120px", width: "250px",
fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Link to={`/models/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Adapter"),
dataIndex: "adapter",
key: "adapter",
width: "250px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/adapters/${text}`}>
{text}
</Link>
); );
}, },
}, },
@@ -136,7 +155,7 @@ class EnforcerListPage extends BaseListPage {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: "", dataIndex: "",
key: "op", key: "op",
width: "170px", width: "180px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (

View File

@@ -103,7 +103,7 @@ class GroupListPage extends BaseListPage {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
key: "name", key: "name",
width: "120px", width: "150px",
fixed: "left", fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps("name"), ...this.getColumnSearchProps("name"),
@@ -119,7 +119,7 @@ class GroupListPage extends BaseListPage {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: "owner", dataIndex: "owner",
key: "owner", key: "owner",
width: "120px", width: "140px",
sorter: true, sorter: true,
...this.getColumnSearchProps("owner"), ...this.getColumnSearchProps("owner"),
render: (text, record, index) => { render: (text, record, index) => {
@@ -134,7 +134,7 @@ class GroupListPage extends BaseListPage {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: "createdTime", dataIndex: "createdTime",
key: "createdTime", key: "createdTime",
width: "150px", width: "180px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -144,7 +144,7 @@ class GroupListPage extends BaseListPage {
title: i18next.t("general:Updated time"), title: i18next.t("general:Updated time"),
dataIndex: "updatedTime", dataIndex: "updatedTime",
key: "updatedTime", key: "updatedTime",
width: "150px", width: "180px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -154,7 +154,7 @@ class GroupListPage extends BaseListPage {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: "displayName", dataIndex: "displayName",
key: "displayName", key: "displayName",
width: "100px", // width: "200px",
sorter: true, sorter: true,
...this.getColumnSearchProps("displayName"), ...this.getColumnSearchProps("displayName"),
}, },
@@ -162,7 +162,7 @@ class GroupListPage extends BaseListPage {
title: i18next.t("general:Type"), title: i18next.t("general:Type"),
dataIndex: "type", dataIndex: "type",
key: "type", key: "type",
width: "110px", width: "140px",
sorter: true, sorter: true,
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
@@ -177,7 +177,7 @@ class GroupListPage extends BaseListPage {
title: i18next.t("group:Parent group"), title: i18next.t("group:Parent group"),
dataIndex: "parentId", dataIndex: "parentId",
key: "parentId", key: "parentId",
width: "110px", width: "220px",
sorter: true, sorter: true,
...this.getColumnSearchProps("parentId"), ...this.getColumnSearchProps("parentId"),
render: (text, record, index) => { render: (text, record, index) => {
@@ -199,7 +199,7 @@ class GroupListPage extends BaseListPage {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: "", dataIndex: "",
key: "op", key: "op",
width: "170px", width: "180px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
const haveChildren = this.state.groups.find((group) => group.parentId === record.id) !== undefined; const haveChildren = this.state.groups.find((group) => group.parentId === record.id) !== undefined;

View File

@@ -221,6 +221,7 @@ class GroupTreePage extends React.Component {
onChange={(value) => { onChange={(value) => {
this.setState({ this.setState({
organizationName: value, organizationName: value,
groupName: "",
}); });
this.props.history.push(`/trees/${value}`); this.props.history.push(`/trees/${value}`);
}} }}

View File

@@ -191,8 +191,8 @@ class LdapSyncPage extends React.Component {
}, },
{ {
title: i18next.t("general:Phone"), title: i18next.t("general:Phone"),
dataIndex: "phone", dataIndex: "mobile",
key: "phone", key: "mobile",
sorter: (a, b) => a.phone.localeCompare(b.phone), sorter: (a, b) => a.phone.localeCompare(b.phone),
}, },
{ {

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from "antd"; import {Button, Card, Col, Input, Row, Select} from "antd";
import * as ModelBackend from "./backend/ModelBackend"; import * as ModelBackend from "./backend/ModelBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@@ -161,16 +161,6 @@ class ModelEditPage extends React.Component {
</div> </div>
</Col> </Col>
</Row> </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.model.isEnabled} onChange={checked => {
this.updateModelField("isEnabled", checked);
}} />
</Col>
</Row>
</Card> </Card>
); );
} }

View File

@@ -13,8 +13,9 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Controlled as CodeMirror} from "react-codemirror2";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Switch, Table} from "antd"; import {Button, Popover, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ModelBackend from "./backend/ModelBackend"; import * as ModelBackend from "./backend/ModelBackend";
@@ -47,7 +48,6 @@ class ModelListPage extends BaseListPage {
createdTime: moment().format(), createdTime: moment().format(),
displayName: `New Model - ${randomName}`, displayName: `New Model - ${randomName}`,
modelText: rbacModel, modelText: rbacModel,
isEnabled: true,
}; };
} }
@@ -91,7 +91,7 @@ class ModelListPage extends BaseListPage {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
key: "name", key: "name",
width: "150px", width: "180px",
fixed: "left", fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps("name"), ...this.getColumnSearchProps("name"),
@@ -107,7 +107,7 @@ class ModelListPage extends BaseListPage {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: "owner", dataIndex: "owner",
key: "owner", key: "owner",
width: "120px", width: "180px",
sorter: true, sorter: true,
...this.getColumnSearchProps("owner"), ...this.getColumnSearchProps("owner"),
render: (text, record, index) => { render: (text, record, index) => {
@@ -122,7 +122,7 @@ class ModelListPage extends BaseListPage {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: "createdTime", dataIndex: "createdTime",
key: "createdTime", key: "createdTime",
width: "160px", width: "180px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -137,14 +137,26 @@ class ModelListPage extends BaseListPage {
...this.getColumnSearchProps("displayName"), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("general:Is enabled"), title: i18next.t("model:Model text"),
dataIndex: "isEnabled", dataIndex: "modelText",
key: "isEnabled", key: "modelText",
width: "120px", // width: "180px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Popover placement="topRight" content={() => {
return (
<CodeMirror
value={text}
options={{mode: "properties", theme: "default"}}
onBeforeChange={(editor, data, value) => {}}
/>
);
}} title="" trigger="hover">
{
Setting.getShortText(text, 100)
}
</Popover>
); );
}, },
}, },
@@ -152,7 +164,7 @@ class ModelListPage extends BaseListPage {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: "", dataIndex: "",
key: "op", key: "op",
width: "170px", width: "180px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (

View File

@@ -81,7 +81,6 @@ class OrganizationListPage extends BaseListPage {
{name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"}, {name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is online", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is online", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is global admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
@@ -182,7 +181,7 @@ class OrganizationListPage extends BaseListPage {
title: i18next.t("organization:Website URL"), title: i18next.t("organization:Website URL"),
dataIndex: "websiteUrl", dataIndex: "websiteUrl",
key: "websiteUrl", key: "websiteUrl",
width: "300px", width: "200px",
sorter: true, sorter: true,
...this.getColumnSearchProps("websiteUrl"), ...this.getColumnSearchProps("websiteUrl"),
render: (text, record, index) => { render: (text, record, index) => {
@@ -243,7 +242,7 @@ class OrganizationListPage extends BaseListPage {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: "", dataIndex: "",
key: "op", key: "op",
width: "320px", width: "350px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (

View File

@@ -48,7 +48,7 @@ class PaymentResultPage extends React.Component {
}); });
// window.console.log("payment=", res.data); // window.console.log("payment=", res.data);
if (res.data.state === "Created") { if (res.data.state === "Created") {
if (res.data.type === "PayPal") { if (["PayPal", "Stripe"].includes(res.data.type)) {
this.setState({ this.setState({
timeout: setTimeout(() => { timeout: setTimeout(() => {
PaymentBackend.notifyPayment(this.state.organizationName, this.state.paymentName) PaymentBackend.notifyPayment(this.state.organizationName, this.state.paymentName)
@@ -135,6 +135,26 @@ class PaymentResultPage extends React.Component {
/> />
</div> </div>
); );
} else if (payment.state === "Timeout") {
return (
<div>
{
Setting.renderHelmet(payment)
}
<Result
status="warning"
title={`${i18next.t("payment:The payment has time out")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}`}
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
extra={[
<Button type="primary" key="returnUrl" onClick={() => {
this.goToPaymentUrl(payment);
}}>
{i18next.t("payment:Return to Website")}
</Button>,
]}
/>
</div>
);
} else { } else {
return ( return (
<div> <div>

View File

@@ -163,6 +163,8 @@ class ProductBuyPage extends React.Component {
text = i18next.t("product:WeChat Pay"); text = i18next.t("product:WeChat Pay");
} else if (provider.type === "PayPal") { } else if (provider.type === "PayPal") {
text = i18next.t("product:PayPal"); text = i18next.t("product:PayPal");
} else if (provider.type === "Stripe") {
text = i18next.t("product:Stripe");
} }
return ( return (

Some files were not shown because too many files have changed in this diff Show More