Compare commits

...

15 Commits

Author SHA1 Message Date
e266696b32 feat: add default permission to built-in group (#1087)
* fix: add default permission

* fix: add default permission

* fix: add default permission
2022-09-02 12:03:13 +08:00
e108d26ec7 fix: recover header logo && add styleint check (#1084)
* fix: fix header logo not show

* feat: update lint-staged

feat: add stylelint
2022-08-31 23:26:58 +08:00
349ce7f1d4 fix: refactor build.sh #1081 (#1082)
* fix: Add default access permission for new built-in group users

* fix: Add default access permission for new built-in group users

* fix: File is not `gofumpt`-ed (gofumpt)

* fix: refactor build.sh #1081

* fix: rollback

* fix: newline

* fix: refactor build.sh rename var #1081
2022-08-31 16:08:10 +08:00
8da50b7893 feat: extend managed accounts for get-account api (#1068)
* feat: add get-extend-account api

* feat: extend managed accounts for get-account api

* fix go-linter err

* Use GetApplicationsByOrganizationName
2022-08-30 00:57:27 +08:00
2394c8e2b4 Make sure newStaticBaseUrl is not empty 2022-08-29 21:27:47 +08:00
c62983d734 Use conf.GetConfigString() 2022-08-29 21:26:00 +08:00
5948782cdd fix: fix eslint error in webstorm (#1073) 2022-08-29 15:23:51 +08:00
674d1619dd fix: fix hot update error #1071 (#1072) 2022-08-29 13:45:31 +08:00
11b8b65ca0 feat: update antd and react to latest (#1069) 2022-08-28 23:14:04 +08:00
411d76798d fix: fix upload file security issue (#1063)
* fix: fix upload file security issue

* fix: fix
2022-08-25 11:34:09 +08:00
7b0b426a76 feat: check model grammar when saving and provide a ACL model as init data (#1062)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-08-24 17:21:05 +08:00
a383af0ebc feat: fix token info not contains roles and permissions (#1060)
* fix: fix token info not contains roles and permissions

feat: remove repeated code for obtaining roles and permissions in user controller

* Update user.go

* Update user.go

* Update token.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-08-24 01:41:26 +08:00
f02875e1b1 fix: enable captcha in the application which is not built-in (#1061) 2022-08-23 23:30:45 +08:00
e2921419b9 Add TestDeployStaticFiles() 2022-08-23 21:17:58 +08:00
42864700ec chore: update badges (#1058) 2022-08-23 13:11:42 +08:00
35 changed files with 4307 additions and 5513 deletions

View File

@ -8,7 +8,7 @@
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg"> <img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
</a> </a>
<a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml"> <a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml">
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casbin/jcasbin/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/casbin/casdoor.svg">
@ -42,65 +42,48 @@
</a> </a>
</p> </p>
## Online demo ## Online demo
- International: https://door.casdoor.org (read-only) - International: https://door.casdoor.org (read-only)
- Asian mirror: https://door.casdoor.com (read-only) - Asian mirror: https://door.casdoor.com (read-only)
- Asian mirror: https://demo.casdoor.com (read-write, will restore for every 5 minutes) - Asian mirror: https://demo.casdoor.com (read-write, will restore for every 5 minutes)
## Documentation ## Documentation
- International: https://casdoor.org - International: https://casdoor.org
- Asian mirror: https://docs.casdoor.cn - Asian mirror: https://docs.casdoor.cn
## Install ## Install
- By source code: https://casdoor.org/docs/basic/server-installation - By source code: https://casdoor.org/docs/basic/server-installation
- By Docker: https://casdoor.org/docs/basic/try-with-docker - By Docker: https://casdoor.org/docs/basic/try-with-docker
## How to connect to Casdoor? ## How to connect to Casdoor?
https://casdoor.org/docs/how-to-connect/overview https://casdoor.org/docs/how-to-connect/overview
## Casdoor Public API ## Casdoor Public API
- Docs: https://casdoor.org/docs/basic/public-api - Docs: https://casdoor.org/docs/basic/public-api
- Swagger: https://door.casdoor.com/swagger - Swagger: https://door.casdoor.com/swagger
## Integrations ## Integrations
https://casdoor.org/docs/integration/apisix https://casdoor.org/docs/integration/apisix
## How to contact? ## How to contact?
- Gitter: https://gitter.im/casbin/casdoor - Gitter: https://gitter.im/casbin/casdoor
- Forum: https://forum.casbin.com - Forum: https://forum.casbin.com
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e - Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e
## Contribute ## Contribute
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community). For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
### I18n translation ### I18n translation
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file. If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the `web/` directory, please remember to add what you have added to the `web/src/locales/en/data.json` file.
## License ## License

View File

@ -4,8 +4,8 @@ curl www.google.com -o /dev/null --connect-timeout 5 2 > /dev/null
if [ $? == 0 ] if [ $? == 0 ]
then then
echo "Successfully connected to Google, no need to use Go proxy" echo "Successfully connected to Google, no need to use Go proxy"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
else else
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct" echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server . GO_PROXY_SETTING=GOPROXY=https://goproxy.cn,direct
fi fi
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $GO_PROXY_SETTING go build -ldflags="-w -s" -o server .

View File

@ -28,7 +28,15 @@ func GetConfigString(key string) string {
if value, ok := os.LookupEnv(key); ok { if value, ok := os.LookupEnv(key); ok {
return value return value
} }
return beego.AppConfig.String(key)
res := beego.AppConfig.String(key)
if res == "" {
if key == "staticBaseUrl" {
res = "https://cdn.casbin.org"
}
}
return res
} }
func GetConfigBool(key string) (bool, error) { func GetConfigBool(key string) (bool, error) {

View File

@ -269,6 +269,11 @@ func (c *ApiController) GetAccount() {
return return
} }
managedAccounts := c.Input().Get("managedAccounts")
if managedAccounts == "1" {
user = object.ExtendManagedAccountsWithUser(user)
}
organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user)) organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
resp := Response{ resp := Response{
Status: "ok", Status: "ok",

View File

@ -119,12 +119,7 @@ func (c *ApiController) GetUser() {
user = object.GetUser(id) user = object.GetUser(id)
} }
if user != nil { object.ExtendUserWithRolesAndPermissions(user)
roles := object.GetRolesByUser(user.GetId())
user.Roles = roles
permissions := object.GetPermissionsByUser(user.GetId())
user.Permissions = permissions
}
c.Data["json"] = object.GetMaskedUser(user) c.Data["json"] = object.GetMaskedUser(user)
c.ServeJSON() c.ServeJSON()

70
deployment/deploy.go Normal file
View File

@ -0,0 +1,70 @@
// 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 deployment
import (
"fmt"
"os"
"strings"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/storage"
"github.com/casdoor/casdoor/util"
"github.com/casdoor/oss"
)
func deployStaticFiles(provider *object.Provider) {
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
if storageProvider == nil {
panic(fmt.Sprintf("the provider type: %s is not supported", provider.Type))
}
uploadFolder(storageProvider, "js")
uploadFolder(storageProvider, "css")
updateHtml(provider.Domain)
}
func uploadFolder(storageProvider oss.StorageInterface, folder string) {
path := fmt.Sprintf("../web/build/static/%s/", folder)
filenames := util.ListFiles(path)
for _, filename := range filenames {
if !strings.HasSuffix(filename, folder) {
continue
}
file, err := os.Open(path + filename)
if err != nil {
panic(err)
}
objectKey := fmt.Sprintf("static/%s/%s", folder, filename)
_, err = storageProvider.Put(objectKey, file)
if err != nil {
panic(err)
}
fmt.Printf("Uploaded [%s] to [%s]\n", path, objectKey)
}
}
func updateHtml(domainPath string) {
htmlPath := "../web/build/index.html"
html := util.ReadStringFromPath(htmlPath)
html = strings.Replace(html, "\"/static/", fmt.Sprintf("\"%s", domainPath), -1)
util.WriteStringToPath(html, htmlPath)
fmt.Printf("Updated HTML to [%s]\n", html)
}

29
deployment/deploy_test.go Normal file
View File

@ -0,0 +1,29 @@
// 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.
//go:build !skipCi
// +build !skipCi
package deployment
import (
"testing"
"github.com/casdoor/casdoor/object"
)
func TestDeployStaticFiles(t *testing.T) {
provider := object.GetProvider("admin/provider_storage_aliyun_oss")
deployStaticFiles(provider)
}

View File

@ -362,3 +362,34 @@ func IsAllowOrigin(origin string) bool {
return allowOrigin return allowOrigin
} }
func getApplicationMap(organization string) map[string]*Application {
applications := GetApplicationsByOrganizationName("admin", organization)
applicationMap := make(map[string]*Application)
for _, application := range applications {
applicationMap[application.Name] = application
}
return applicationMap
}
func ExtendManagedAccountsWithUser(user *User) *User {
if user.ManagedAccounts == nil || len(user.ManagedAccounts) == 0 {
return user
}
applicationMap := getApplicationMap(user.Owner)
var managedAccounts []ManagedAccount
for _, managedAccount := range user.ManagedAccounts {
application := applicationMap[managedAccount.Application]
if application != nil {
managedAccount.SigninUrl = application.SigninUrl
managedAccounts = append(managedAccounts, managedAccount)
}
}
user.ManagedAccounts = managedAccounts
return user
}

View File

@ -302,6 +302,10 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
} }
if isHit { if isHit {
containsAsterisk := ContainsAsterisk(userId, permission.Users)
if containsAsterisk {
return true, err
}
enforcer := getEnforcer(permission) enforcer := getEnforcer(permission)
allowed, err = enforcer.Enforce(userId, application.Name, "read") allowed, err = enforcer.Enforce(userId, application.Name, "read")
break break

View File

@ -19,7 +19,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/webauthn" "github.com/duo-labs/webauthn/webauthn"
) )
@ -27,6 +27,7 @@ import (
func InitDb() { func InitDb() {
existed := initBuiltInOrganization() existed := initBuiltInOrganization()
if !existed { if !existed {
initBuiltInModel()
initBuiltInPermission() initBuiltInPermission()
initBuiltInProvider() initBuiltInProvider()
initBuiltInUser() initBuiltInUser()
@ -38,8 +39,6 @@ func InitDb() {
initWebAuthn() initWebAuthn()
} }
var staticBaseUrl = beego.AppConfig.String("staticBaseUrl")
func initBuiltInOrganization() bool { func initBuiltInOrganization() bool {
organization := getOrganization("admin", "built-in") organization := getOrganization("admin", "built-in")
if organization != nil { if organization != nil {
@ -52,10 +51,10 @@ func initBuiltInOrganization() bool {
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Organization", DisplayName: "Built-in Organization",
WebsiteUrl: "https://example.com", WebsiteUrl: "https://example.com",
Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", staticBaseUrl), Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")),
PasswordType: "plain", PasswordType: "plain",
PhonePrefix: "86", PhonePrefix: "86",
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", staticBaseUrl), DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{}, Tags: []string{},
AccountItems: []*AccountItem{ AccountItems: []*AccountItem{
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
@ -105,7 +104,7 @@ func initBuiltInUser() {
Type: "normal-user", Type: "normal-user",
Password: "123", Password: "123",
DisplayName: "Admin", DisplayName: "Admin",
Avatar: fmt.Sprintf("%s/img/casbin.svg", staticBaseUrl), Avatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Email: "admin@example.com", Email: "admin@example.com",
Phone: "12345678910", Phone: "12345678910",
Address: []string{}, Address: []string{},
@ -135,7 +134,7 @@ func initBuiltInApplication() {
Name: "app-built-in", Name: "app-built-in",
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
DisplayName: "Casdoor", DisplayName: "Casdoor",
Logo: fmt.Sprintf("%s/img/casdoor-logo_1185x256.png", staticBaseUrl), Logo: fmt.Sprintf("%s/img/casdoor-logo_1185x256.png", conf.GetConfigString("staticBaseUrl")),
HomepageUrl: "https://casdoor.org", HomepageUrl: "https://casdoor.org",
Organization: "built-in", Organization: "built-in",
Cert: "cert-built-in", Cert: "cert-built-in",
@ -239,6 +238,33 @@ func initWebAuthn() {
gob.Register(webauthn.SessionData{}) gob.Register(webauthn.SessionData{})
} }
func initBuiltInModel() {
model := GetModel("built-in/model-built-in")
if model != nil {
return
}
model = &Model{
Owner: "built-in",
Name: "model-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Model",
IsEnabled: true,
ModelText: `[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act`,
}
AddModel(model)
}
func initBuiltInPermission() { func initBuiltInPermission() {
permission := GetPermission("built-in/permission-built-in") permission := GetPermission("built-in/permission-built-in")
if permission != nil { if permission != nil {
@ -250,9 +276,10 @@ func initBuiltInPermission() {
Name: "permission-built-in", Name: "permission-built-in",
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Permission", DisplayName: "Built-in Permission",
Users: []string{"built-in/admin"}, Users: []string{"built-in/*"},
Roles: []string{}, Roles: []string{},
Domains: []string{}, Domains: []string{},
Model: "model-built-in",
ResourceType: "Application", ResourceType: "Application",
Resources: []string{"app-built-in"}, Resources: []string{"app-built-in"},
Actions: []string{"Read", "Write", "Admin"}, Actions: []string{"Read", "Write", "Admin"},

View File

@ -17,6 +17,7 @@ package object
import ( import (
"fmt" "fmt"
"github.com/casbin/casbin/v2/model"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
) )
@ -85,13 +86,19 @@ func GetModel(id string) *Model {
return getModel(owner, name) return getModel(owner, name)
} }
func UpdateModel(id string, model *Model) bool { func UpdateModel(id string, modelObj *Model) bool {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
if getModel(owner, name) == nil { if getModel(owner, name) == nil {
return false return false
} }
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(model) // check model grammar
_, err := model.NewModelFromString(modelObj.ModelText)
if err != nil {
panic(err)
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(modelObj)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -207,3 +207,17 @@ func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
return permissions return permissions
} }
func ContainsAsterisk(userId string, users []string) bool {
containsAsterisk := false
group, _ := util.GetOwnerAndNameFromId(userId)
for _, user := range users {
permissionGroup, permissionUserName := util.GetOwnerAndNameFromId(user)
if permissionGroup == group && permissionUserName == "*" {
containsAsterisk = true
break
}
}
return containsAsterisk
}

View File

@ -28,8 +28,8 @@ import (
"time" "time"
"github.com/RobotsAndPencils/go-saml" "github.com/RobotsAndPencils/go-saml"
"github.com/astaxie/beego"
"github.com/beevik/etree" "github.com/beevik/etree"
"github.com/casdoor/casdoor/conf"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
dsig "github.com/russellhaering/goxmldsig" dsig "github.com/russellhaering/goxmldsig"
uuid "github.com/satori/go.uuid" uuid "github.com/satori/go.uuid"
@ -181,7 +181,7 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
block, _ := pem.Decode([]byte(cert.Certificate)) block, _ := pem.Decode([]byte(cert.Certificate))
certificate := base64.StdEncoding.EncodeToString(block.Bytes) certificate := base64.StdEncoding.EncodeToString(block.Bytes)
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
originFrontend, originBackend := getOriginFromHost(host) originFrontend, originBackend := getOriginFromHost(host)
if origin != "" { if origin != "" {
originBackend = origin originBackend = origin

View File

@ -103,6 +103,11 @@ func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
} }
func UploadFileSafe(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) { func UploadFileSafe(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
// check fullFilePath is there security issue
if strings.Contains(fullFilePath, "..") {
return "", "", fmt.Errorf("the fullFilePath: %s is not allowed", fullFilePath)
}
var fileUrl string var fileUrl string
var objectKey string var objectKey string
var err error var err error

View File

@ -287,6 +287,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
} }
} }
ExtendUserWithRolesAndPermissions(user)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host) accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
if err != nil { if err != nil {
panic(err) panic(err)
@ -421,6 +422,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
} }
} }
ExtendUserWithRolesAndPermissions(user)
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host) newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil { if err != nil {
return &TokenError{ return &TokenError{
@ -571,6 +573,7 @@ func GetPasswordToken(application *Application, username string, password string
} }
} }
ExtendUserWithRolesAndPermissions(user)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host) accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil { if err != nil {
return nil, &TokenError{ return nil, &TokenError{
@ -640,6 +643,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
// GetTokenByUser // GetTokenByUser
// Implicit flow // Implicit flow
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) { func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
ExtendUserWithRolesAndPermissions(user)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host) accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil { if err != nil {
return nil, err return nil, err
@ -726,6 +730,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
AddUser(user) AddUser(user)
} }
ExtendUserWithRolesAndPermissions(user)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", host) accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", host)
if err != nil { if err != nil {
return nil, &TokenError{ return nil, &TokenError{

View File

@ -566,3 +566,12 @@ func (user *User) GetId() string {
func isUserIdGlobalAdmin(userId string) bool { func isUserIdGlobalAdmin(userId string) bool {
return strings.HasPrefix(userId, "built-in/") return strings.HasPrefix(userId, "built-in/")
} }
func ExtendUserWithRolesAndPermissions(user *User) {
if user == nil {
return
}
user.Roles = GetRolesByUser(user.GetId())
user.Permissions = GetPermissionsByUser(user.GetId())
}

View File

@ -19,7 +19,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/duo-labs/webauthn/protocol" "github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn" "github.com/duo-labs/webauthn/webauthn"
) )
@ -27,7 +27,7 @@ import (
func GetWebAuthnObject(host string) *webauthn.WebAuthn { func GetWebAuthnObject(host string) *webauthn.WebAuthn {
var err error var err error
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
if origin == "" { if origin == "" {
_, origin = getOriginFromHost(host) _, origin = getOriginFromHost(host)
} }
@ -38,7 +38,7 @@ func GetWebAuthnObject(host string) *webauthn.WebAuthn {
} }
webAuthn, err := webauthn.New(&webauthn.Config{ webAuthn, err := webauthn.New(&webauthn.Config{
RPDisplayName: beego.AppConfig.String("appname"), // Display Name for your site RPDisplayName: conf.GetConfigString("appname"), // Display Name for your site
RPID: strings.Split(localUrl.Host, ":")[0], // Generally the domain name for your site, it's ok because splits cannot return empty array RPID: strings.Split(localUrl.Host, ":")[0], // Generally the domain name for your site, it's ok because splits cannot return empty array
RPOrigin: origin, // The origin URL for WebAuthn requests RPOrigin: origin, // The origin URL for WebAuthn requests
// RPIcon: "https://duo.com/logo.png", // Optional icon URL for your site // RPIcon: "https://duo.com/logo.png", // Optional icon URL for your site

View File

@ -19,14 +19,14 @@ import (
"os" "os"
"strings" "strings"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
var ( var (
oldStaticBaseUrl = "https://cdn.casbin.org" oldStaticBaseUrl = "https://cdn.casbin.org"
newStaticBaseUrl = beego.AppConfig.String("staticBaseUrl") newStaticBaseUrl = conf.GetConfigString("staticBaseUrl")
) )
func StaticFilter(ctx *context.Context) { func StaticFilter(ctx *context.Context) {

View File

@ -16,6 +16,7 @@ package util
import ( import (
"fmt" "fmt"
"io/ioutil"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@ -43,6 +44,23 @@ func EnsureFileFolderExists(path string) {
} }
} }
func ListFiles(path string) []string {
res := []string{}
files, err := ioutil.ReadDir(path)
if err != nil {
panic(err)
}
for _, f := range files {
if !f.IsDir() {
res = append(res, f.Name())
}
}
return res
}
func RemoveExt(filename string) string { func RemoveExt(filename string) string {
return filename[:len(filename)-len(filepath.Ext(filename))] return filename[:len(filename)-len(filepath.Ext(filename))]
} }

View File

@ -4,12 +4,18 @@
"es6": true, "es6": true,
"node": true "node": true
}, },
"parser": "babel-eslint", "parser": "@babel/eslint-parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": 12, "ecmaVersion": 12,
"sourceType": "module", "sourceType": "module",
"ecmaFeatures": { "ecmaFeatures": {
"jsx": true "jsx": true
},
"requireConfigFile": false,
"babelOptions": {
"babelrc": false,
"configFile": false,
"presets": ["@babel/preset-react"]
} }
}, },
"settings": { "settings": {

6
web/.stylelintrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-recommended-less"
]
}

17
web/babel.config.json Normal file
View File

@ -0,0 +1,17 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
]
}

View File

@ -3,35 +3,35 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.6.2", "@ant-design/icons": "^4.7.0",
"@craco/craco": "^6.1.1", "@craco/craco": "^6.4.5",
"@crowdin/cli": "^3.6.4", "@crowdin/cli": "^3.7.10",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"antd": "^4.15.5", "antd": "^4.22.8",
"codemirror": "^5.61.1", "codemirror": "^5.61.1",
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",
"core-js": "^3.21.1", "core-js": "^3.25.0",
"craco-less": "^1.17.1", "craco-less": "^2.0.0",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^2.0.0",
"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",
"moment": "^2.29.1", "moment": "^2.29.1",
"qs": "^6.10.2", "qs": "^6.10.2",
"react": "^17.0.2", "react": "^18.2.0",
"react-app-polyfill": "^3.0.0", "react-app-polyfill": "^3.0.0",
"react-codemirror2": "^7.2.1", "react-codemirror2": "^7.2.1",
"react-cropper": "^2.1.7", "react-cropper": "^2.1.7",
"react-device-detect": "^1.14.0", "react-device-detect": "^2.2.2",
"react-dom": "^17.0.2", "react-dom": "^18.2.0",
"react-github-corner": "^2.5.0", "react-github-corner": "^2.5.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-highlight-words": "^0.17.0", "react-highlight-words": "^0.18.0",
"react-i18next": "^11.8.7", "react-i18next": "^11.8.7",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.3.3",
"react-scripts": "4.0.3", "react-scripts": "5.0.1",
"react-social-login-buttons": "^3.4.0" "react-social-login-buttons": "^3.4.0"
}, },
"scripts": { "scripts": {
@ -41,7 +41,8 @@
"eject": "craco eject", "eject": "craco eject",
"crowdin:sync": "crowdin upload && crowdin download", "crowdin:sync": "crowdin upload && crowdin download",
"preinstall": "node -e \"if (process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('Use yarn for installing: https://yarnpkg.com/en/docs/install')\"", "preinstall": "node -e \"if (process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('Use yarn for installing: https://yarnpkg.com/en/docs/install')\"",
"fix": "eslint --fix ." "fix": "eslint --fix src/**/*.{js,jsx,ts,tsx}",
"lint:css": "stylelint src/**/*.{css,less} --fix"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
@ -61,15 +62,24 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.13",
"@babel/eslint-parser": "^7.18.9",
"@babel/preset-react": "^7.18.6",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^7.11.0", "eslint": "8.22.0",
"eslint-plugin-react": "^7.30.1", "eslint-plugin-react": "^7.31.1",
"husky": "^4.3.8", "husky": "^4.3.8",
"lint-staged": "^13.0.3" "lint-staged": "^13.0.3",
"stylelint": "^14.11.0",
"stylelint-config-recommended-less": "^1.0.4",
"stylelint-config-standard": "^28.0.0"
}, },
"lint-staged": { "lint-staged": {
"src/**/*.{js,jsx,css,sass,ts,tsx}": [ "src/**/*.{css,less}": [
"yarn fix" "stylelint --fix"
],
"src/**/*.{js,jsx,ts,tsx}": [
"eslint --fix"
] ]
}, },
"husky": { "husky": {

View File

@ -595,7 +595,7 @@ class App extends Component {
// theme="dark" // theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"} mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]} selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px", width: "80%", position: "absolute"}} style={{lineHeight: "64px", width: "80%", position: "absolute", left: "145px"}}
> >
{ {
this.renderMenu() this.renderMenu()

View File

@ -1,6 +1,8 @@
@import '~antd/dist/antd.less'; /* stylelint-disable at-rule-name-case */
/* stylelint-disable selector-class-pattern */
@import "~antd/dist/antd.less";
@StaticBaseUrl:"https://cdn.casbin.org"; @StaticBaseUrl: "https://cdn.casbin.org";
.App { .App {
text-align: center; text-align: center;
@ -69,8 +71,8 @@
} }
.content-warp-card { .content-warp-card {
box-shadow: 0 1px 5px 0 rgba(51, 51, 51, 0.14); box-shadow: 0 1px 5px 0 rgb(51 51 51 / 14%);
margin: 5px 5px 5px 5px; margin: 5px;
flex: 1; flex: 1;
align-items: stretch; align-items: stretch;
} }

View File

@ -52,6 +52,7 @@ class BaseListPage extends React.Component {
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)} onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{marginBottom: 8, display: "block"}} style={{marginBottom: 8, display: "block"}}
/> />
<Space> <Space>
<Button <Button
type="primary" type="primary"

View File

@ -38,7 +38,7 @@ class ManagedAccountTable extends React.Component {
} }
addRow(table) { addRow(table) {
const row = {application: "", username: "", password: "", signinUrl: ""}; const row = {application: "", username: "", password: ""};
if (table === undefined || table === null) { if (table === undefined || table === null) {
table = []; table = [];
} }
@ -69,16 +69,11 @@ class ManagedAccountTable extends React.Component {
key: "application", key: "application",
render: (text, record, index) => { render: (text, record, index) => {
const items = this.props.applications; const items = this.props.applications;
const signinUrlMap = new Map();
for (const application of items) {
signinUrlMap.set(application.name, application.signinUrl);
}
return ( return (
<Select virtual={false} style={{width: "100%"}} <Select virtual={false} style={{width: "100%"}}
value={text} value={text}
onChange={value => { onChange={value => {
this.updateField(table, index, "application", value); this.updateField(table, index, "application", value);
this.updateField(table, index, "signinUrl", signinUrlMap.get(value));
}} > }} >
{ {
items.map((item, index) => <Option key={index} value={item.name}>{item.name}</Option>) items.map((item, index) => <Option key={index} value={item.name}>{item.name}</Option>)

View File

@ -89,6 +89,7 @@ export const ResetModal = (props) => {
textBefore={i18next.t("code:Code You Received")} textBefore={i18next.t("code:Code You Received")}
onChange={setCode} onChange={setCode}
onButtonClickArgs={[dest, destType, Setting.getApplicationName(application)]} onButtonClickArgs={[dest, destType, Setting.getApplicationName(application)]}
application={application}
/> />
</Row> </Row>
</Col> </Col>

View File

@ -353,11 +353,13 @@ class ForgetPage extends React.Component {
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} disabled={this.state.username === "" || this.state.verifyType === ""}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.state.application), this.state.name]} onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.state.application), this.state.name]}
application={application}
/> />
) : ( ) : (
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} disabled={this.state.username === "" || this.state.verifyType === ""}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.state.application), this.state.name]} onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.state.application), this.state.name]}
application={application}
/> />
)} )}
</Form.Item> </Form.Item>

View File

@ -625,6 +625,7 @@ class LoginPage extends React.Component {
<CountDownInput <CountDownInput
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone} disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]} onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
application={application}
/> />
</Form.Item> </Form.Item>
) : ( ) : (

View File

@ -360,6 +360,7 @@ class SignupPage extends React.Component {
<CountDownInput <CountDownInput
disabled={!this.state.validEmail} disabled={!this.state.validEmail}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(application)]} onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(application)]}
application={application}
/> />
</Form.Item> </Form.Item>
} }
@ -412,6 +413,7 @@ class SignupPage extends React.Component {
<CountDownInput <CountDownInput
disabled={!this.state.validPhone} disabled={!this.state.validPhone}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]} onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
application={application}
/> />
</Form.Item> </Form.Item>
</React.Fragment> </React.Fragment>

View File

@ -17,13 +17,12 @@ import React from "react";
import i18next from "i18next"; import i18next from "i18next";
import * as UserBackend from "../backend/UserBackend"; import * as UserBackend from "../backend/UserBackend";
import {SafetyOutlined} from "@ant-design/icons"; import {SafetyOutlined} from "@ant-design/icons";
import {authConfig} from "../auth/Auth";
import {CaptchaWidget} from "./CaptchaWidget"; import {CaptchaWidget} from "./CaptchaWidget";
const {Search} = Input; const {Search} = Input;
export const CountDownInput = (props) => { export const CountDownInput = (props) => {
const {disabled, textBefore, onChange, onButtonClickArgs} = props; const {disabled, textBefore, onChange, onButtonClickArgs, application} = props;
const [visible, setVisible] = React.useState(false); const [visible, setVisible] = React.useState(false);
const [key, setKey] = React.useState(""); const [key, setKey] = React.useState("");
const [captchaImg, setCaptchaImg] = React.useState(""); const [captchaImg, setCaptchaImg] = React.useState("");
@ -69,7 +68,7 @@ export const CountDownInput = (props) => {
}; };
const loadCaptcha = () => { const loadCaptcha = () => {
UserBackend.getCaptcha("admin", authConfig.appName, false).then(res => { UserBackend.getCaptcha(application.owner, application.name, false).then(res => {
if (res.type === "none") { if (res.type === "none") {
UserBackend.sendCode("none", "", "", ...onButtonClickArgs).then(res => { UserBackend.sendCode("none", "", "", ...onButtonClickArgs).then(res => {
if (res) { if (res) {

View File

@ -1,14 +1,28 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family:
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', -apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
"Fira Sans",
"Droid Sans",
"Helvetica Neue",
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', font-family:
source-code-pro,
Menlo,
Monaco,
Consolas,
"Courier New",
monospace; monospace;
} }
@ -17,12 +31,14 @@ code {
background-size: 130px, 27px; background-size: 130px, 27px;
width: 130px; width: 130px;
height: 27px; height: 27px;
/*background: rgba(0, 0, 0, 0.2);*/ margin: 17px 0 16px 15px;
margin: 17px 10px 16px 20px;
float: left; float: left;
} }
.ant-table.ant-table-middle .ant-table-title, .ant-table.ant-table-middle .ant-table-footer, .ant-table.ant-table-middle thead > tr > th, .ant-table.ant-table-middle tbody > tr > td { .ant-table.ant-table-middle .ant-table-title,
.ant-table.ant-table-middle .ant-table-footer,
.ant-table.ant-table-middle thead > tr > th,
.ant-table.ant-table-middle tbody > tr > td {
padding: 1px 8px !important; padding: 1px 8px !important;
} }

View File

@ -16,18 +16,19 @@ import "core-js/es";
import "react-app-polyfill/ie9"; import "react-app-polyfill/ie9";
import "react-app-polyfill/stable"; import "react-app-polyfill/stable";
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import {createRoot} from "react-dom/client";
import "./index.css"; import "./index.css";
import App from "./App"; import App from "./App";
import * as serviceWorker from "./serviceWorker"; import * as serviceWorker from "./serviceWorker";
import {BrowserRouter} from "react-router-dom"; import {BrowserRouter} from "react-router-dom";
ReactDOM.render( const container = document.getElementById("root");
<BrowserRouter>
const app = createRoot(container);
app.render(<BrowserRouter>
<App /> <App />
</BrowserRouter>, </BrowserRouter>);
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls. // unregister() to register() below. Note this comes with some pitfalls.

File diff suppressed because it is too large Load Diff