mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-09 01:13:41 +08:00
Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
a399184cfc | |||
2f9f946c87 | |||
d8b60f838e | |||
7599e2715a | |||
35676455bc | |||
8128671c8c | |||
ee54dec3b3 | |||
d278bc9651 | |||
b23bd0b189 | |||
409be85264 | |||
0395b7e1a9 | |||
4536fd0636 | |||
af9ae7dbb7 | |||
e266696b32 | |||
e108d26ec7 | |||
349ce7f1d4 | |||
8da50b7893 | |||
2394c8e2b4 | |||
c62983d734 | |||
5948782cdd | |||
674d1619dd | |||
11b8b65ca0 | |||
411d76798d | |||
7b0b426a76 | |||
a383af0ebc | |||
f02875e1b1 | |||
e2921419b9 | |||
42864700ec |
21
README.md
21
README.md
@ -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
|
||||||
|
|
||||||
|
@ -128,6 +128,12 @@ p, *, *, GET, /api/get-release, *, *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsAllowed(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
func IsAllowed(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||||
|
if conf.IsDemoMode() {
|
||||||
|
if !isAllowedInDemoMode(subOwner, subName, method, urlPath, objOwner, objName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -135,3 +141,22 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||||
|
if method == "POST" {
|
||||||
|
if urlPath == "/api/login" || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" {
|
||||||
|
return true
|
||||||
|
} else if urlPath == "/api/update-user" {
|
||||||
|
// Allow ordinary users to update their own information
|
||||||
|
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If method equals GET
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
4
build.sh
4
build.sh
@ -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 .
|
export GOPROXY="https://goproxy.cn,direct"
|
||||||
fi
|
fi
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
|
||||||
|
@ -16,4 +16,5 @@ verificationCodeTimeout = 10
|
|||||||
initScore = 2000
|
initScore = 2000
|
||||||
logPostOnly = true
|
logPostOnly = true
|
||||||
origin =
|
origin =
|
||||||
staticBaseUrl = "https://cdn.casbin.org"
|
staticBaseUrl = "https://cdn.casbin.org"
|
||||||
|
isDemoMode = false
|
||||||
|
14
conf/conf.go
14
conf/conf.go
@ -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) {
|
||||||
@ -72,3 +80,7 @@ func GetBeegoConfDataSourceName() string {
|
|||||||
|
|
||||||
return dataSourceName
|
return dataSourceName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsDemoMode() bool {
|
||||||
|
return strings.ToLower(GetConfigString("isDemoMode")) == "true"
|
||||||
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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
70
deployment/deploy.go
Normal 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
29
deployment/deploy_test.go
Normal 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)
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -16,10 +16,28 @@
|
|||||||
|
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import "github.com/go-gomail/gomail"
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
|
"github.com/go-gomail/gomail"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDialer(provider *Provider) *gomail.Dialer {
|
||||||
|
dialer := &gomail.Dialer{}
|
||||||
|
if provider.Type == "SUBMAIL" {
|
||||||
|
dialer = gomail.NewDialer(provider.Host, provider.Port, provider.AppId, provider.ClientSecret)
|
||||||
|
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
} else {
|
||||||
|
dialer = gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.SSL = !provider.DisableSsl
|
||||||
|
|
||||||
|
return dialer
|
||||||
|
}
|
||||||
|
|
||||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||||
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
dialer := getDialer(provider)
|
||||||
|
|
||||||
message := gomail.NewMessage()
|
message := gomail.NewMessage()
|
||||||
message.SetAddressHeader("From", provider.ClientId, sender)
|
message.SetAddressHeader("From", provider.ClientId, sender)
|
||||||
@ -32,8 +50,7 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
|||||||
|
|
||||||
// DailSmtpServer Dail Smtp server
|
// DailSmtpServer Dail Smtp server
|
||||||
func DailSmtpServer(provider *Provider) error {
|
func DailSmtpServer(provider *Provider) error {
|
||||||
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
dialer := getDialer(provider)
|
||||||
dialer.SSL = !provider.DisableSsl
|
|
||||||
|
|
||||||
sender, err := dialer.Dial()
|
sender, err := dialer.Dial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -19,14 +19,17 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitDb() {
|
func InitDb() {
|
||||||
|
MigratePermissionRule()
|
||||||
|
|
||||||
existed := initBuiltInOrganization()
|
existed := initBuiltInOrganization()
|
||||||
if !existed {
|
if !existed {
|
||||||
|
initBuiltInModel()
|
||||||
initBuiltInPermission()
|
initBuiltInPermission()
|
||||||
initBuiltInProvider()
|
initBuiltInProvider()
|
||||||
initBuiltInUser()
|
initBuiltInUser()
|
||||||
@ -38,8 +41,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 +53,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 +106,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 +136,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 +240,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 +278,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"},
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,11 @@ type OidcDiscovery struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getOriginFromHost(host string) (string, string) {
|
func getOriginFromHost(host string) (string, string) {
|
||||||
|
origin := conf.GetConfigString("origin")
|
||||||
|
if origin != "" {
|
||||||
|
return origin, origin
|
||||||
|
}
|
||||||
|
|
||||||
protocol := "https://"
|
protocol := "https://"
|
||||||
if strings.HasPrefix(host, "localhost") {
|
if strings.HasPrefix(host, "localhost") {
|
||||||
protocol = "http://"
|
protocol = "http://"
|
||||||
@ -58,12 +63,6 @@ func getOriginFromHost(host string) (string, string) {
|
|||||||
func GetOidcDiscovery(host string) OidcDiscovery {
|
func GetOidcDiscovery(host string) OidcDiscovery {
|
||||||
originFrontend, originBackend := getOriginFromHost(host)
|
originFrontend, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
origin := conf.GetConfigString("origin")
|
|
||||||
if origin != "" {
|
|
||||||
originFrontend = origin
|
|
||||||
originBackend = origin
|
|
||||||
}
|
|
||||||
|
|
||||||
// Examples:
|
// Examples:
|
||||||
// https://login.okta.com/.well-known/openid-configuration
|
// https://login.okta.com/.well-known/openid-configuration
|
||||||
// https://auth0.auth0.com/.well-known/openid-configuration
|
// https://auth0.auth0.com/.well-known/openid-configuration
|
||||||
|
@ -16,6 +16,7 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
@ -207,3 +208,44 @@ func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
|
|||||||
|
|
||||||
return permissions
|
return permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MigratePermissionRule() {
|
||||||
|
models := []*Model{}
|
||||||
|
err := adapter.Engine.Find(&models, &Model{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
isHit := false
|
||||||
|
for _, model := range models {
|
||||||
|
if strings.Contains(model.ModelText, "permission") {
|
||||||
|
// update model table
|
||||||
|
model.ModelText = strings.Replace(model.ModelText, "permission,", "", -1)
|
||||||
|
UpdateModel(model.GetId(), model)
|
||||||
|
isHit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isHit {
|
||||||
|
// update permission_rule table
|
||||||
|
sql := "UPDATE `permission_rule`SET V0 = V1, V1 = V2, V2 = V3, V3 = V4, V4 = V5 WHERE V0 IN (SELECT CONCAT(owner, '/', name) AS permission_id FROM `permission`)"
|
||||||
|
_, err = adapter.Engine.Exec(sql)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -48,6 +48,7 @@ type Provider struct {
|
|||||||
DisableSsl bool `json:"disableSsl"`
|
DisableSsl bool `json:"disableSsl"`
|
||||||
Title string `xorm:"varchar(100)" json:"title"`
|
Title string `xorm:"varchar(100)" json:"title"`
|
||||||
Content string `xorm:"varchar(1000)" json:"content"`
|
Content string `xorm:"varchar(1000)" json:"content"`
|
||||||
|
Receiver string `xorm:"varchar(100)" json:"receiver"`
|
||||||
|
|
||||||
RegionId string `xorm:"varchar(100)" json:"regionId"`
|
RegionId string `xorm:"varchar(100)" json:"regionId"`
|
||||||
SignName string `xorm:"varchar(100)" json:"signName"`
|
SignName string `xorm:"varchar(100)" json:"signName"`
|
||||||
|
@ -28,7 +28,6 @@ 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/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
dsig "github.com/russellhaering/goxmldsig"
|
dsig "github.com/russellhaering/goxmldsig"
|
||||||
@ -176,16 +175,12 @@ type Attribute struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
|
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
|
||||||
//_, originBackend := getOriginFromHost(host)
|
|
||||||
cert := getCertByApplication(application)
|
cert := getCertByApplication(application)
|
||||||
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")
|
|
||||||
originFrontend, originBackend := getOriginFromHost(host)
|
originFrontend, originBackend := getOriginFromHost(host)
|
||||||
if origin != "" {
|
|
||||||
originBackend = origin
|
|
||||||
}
|
|
||||||
d := IdpEntityDescriptor{
|
d := IdpEntityDescriptor{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Local: "md:EntityDescriptor",
|
Local: "md:EntityDescriptor",
|
||||||
|
@ -70,10 +70,12 @@ func GenerateSamlLoginUrl(id, relayState string) (string, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
|
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
|
||||||
|
origin := conf.GetConfigString("origin")
|
||||||
|
|
||||||
certStore := dsig.MemoryX509CertificateStore{
|
certStore := dsig.MemoryX509CertificateStore{
|
||||||
Roots: []*x509.Certificate{},
|
Roots: []*x509.Certificate{},
|
||||||
}
|
}
|
||||||
origin := conf.GetConfigString("origin")
|
|
||||||
certEncodedData := ""
|
certEncodedData := ""
|
||||||
if samlResponse != "" {
|
if samlResponse != "" {
|
||||||
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
||||||
|
@ -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
|
||||||
|
@ -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{
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
)
|
)
|
||||||
@ -67,11 +66,7 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||||
|
|
||||||
user.Password = ""
|
user.Password = ""
|
||||||
origin := conf.GetConfigString("origin")
|
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
if origin != "" {
|
|
||||||
originBackend = origin
|
|
||||||
}
|
|
||||||
|
|
||||||
name := util.GenerateId()
|
name := util.GenerateId()
|
||||||
jti := fmt.Sprintf("%s/%s", application.Owner, name)
|
jti := fmt.Sprintf("%s/%s", application.Owner, name)
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/duo-labs/webauthn/webauthn"
|
"github.com/duo-labs/webauthn/webauthn"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
@ -527,11 +526,8 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
|
|||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
|
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
|
||||||
}
|
}
|
||||||
origin := conf.GetConfigString("origin")
|
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
if origin != "" {
|
|
||||||
originBackend = origin
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := Userinfo{
|
resp := Userinfo{
|
||||||
Sub: user.Id,
|
Sub: user.Id,
|
||||||
@ -566,3 +562,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())
|
||||||
|
}
|
||||||
|
@ -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,20 +27,17 @@ import (
|
|||||||
func GetWebAuthnObject(host string) *webauthn.WebAuthn {
|
func GetWebAuthnObject(host string) *webauthn.WebAuthn {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
origin := beego.AppConfig.String("origin")
|
_, originBackend := getOriginFromHost(host)
|
||||||
if origin == "" {
|
|
||||||
_, origin = getOriginFromHost(host)
|
|
||||||
}
|
|
||||||
|
|
||||||
localUrl, err := url.Parse(origin)
|
localUrl, err := url.Parse(originBackend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("error when parsing origin:" + err.Error())
|
panic("error when parsing origin:" + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
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: originBackend, // 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
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -47,7 +47,7 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
|
|||||||
|
|
||||||
sender := organization.DisplayName
|
sender := organization.DisplayName
|
||||||
title := provider.Title
|
title := provider.Title
|
||||||
code := getRandomCode(5)
|
code := getRandomCode(6)
|
||||||
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||||
content := fmt.Sprintf(provider.Content, code)
|
content := fmt.Sprintf(provider.Content, code)
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ func SendVerificationCodeToPhone(organization *Organization, user *User, provide
|
|||||||
return errors.New("please set a SMS provider first")
|
return errors.New("please set a SMS provider first")
|
||||||
}
|
}
|
||||||
|
|
||||||
code := getRandomCode(5)
|
code := getRandomCode(6)
|
||||||
if err := SendSms(provider, code, dest); err != nil {
|
if err := SendSms(provider, code, dest); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
18
util/path.go
18
util/path.go
@ -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))]
|
||||||
}
|
}
|
||||||
|
@ -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": {
|
||||||
@ -91,6 +97,7 @@
|
|||||||
"react/jsx-key": "error",
|
"react/jsx-key": "error",
|
||||||
"no-console": "error",
|
"no-console": "error",
|
||||||
"eqeqeq": "error",
|
"eqeqeq": "error",
|
||||||
|
"keyword-spacing": "error",
|
||||||
|
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"react/display-name": "off",
|
"react/display-name": "off",
|
||||||
|
6
web/.stylelintrc.json
Normal file
6
web/.stylelintrc.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"stylelint-config-standard",
|
||||||
|
"stylelint-config-recommended-less"
|
||||||
|
]
|
||||||
|
}
|
17
web/babel.config.json
Normal file
17
web/babel.config.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
@ -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": {
|
||||||
|
@ -527,7 +527,7 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRouter() {
|
renderRouter() {
|
||||||
return(
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||||
@ -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()
|
||||||
@ -618,7 +618,7 @@ class App extends Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return(
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Header style={{padding: "0", marginBottom: "3px"}}>
|
<Header style={{padding: "0", marginBottom: "3px"}}>
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -39,101 +39,102 @@ class BaseListPage extends React.Component {
|
|||||||
this.fetch({pagination});
|
this.fetch({pagination});
|
||||||
}
|
}
|
||||||
|
|
||||||
getColumnSearchProps = dataIndex => ({
|
getColumnSearchProps = dataIndex => ({
|
||||||
filterDropdown: ({setSelectedKeys, selectedKeys, confirm, clearFilters}) => (
|
filterDropdown: ({setSelectedKeys, selectedKeys, confirm, clearFilters}) => (
|
||||||
<div style={{padding: 8}}>
|
<div style={{padding: 8}}>
|
||||||
<Input
|
<Input
|
||||||
ref={node => {
|
ref={node => {
|
||||||
this.searchInput = node;
|
this.searchInput = node;
|
||||||
}}
|
}}
|
||||||
placeholder={`Search ${dataIndex}`}
|
placeholder={`Search ${dataIndex}`}
|
||||||
value={selectedKeys[0]}
|
value={selectedKeys[0]}
|
||||||
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||||
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
||||||
style={{marginBottom: 8, display: "block"}}
|
style={{marginBottom: 8, display: "block"}}
|
||||||
/>
|
/>
|
||||||
<Space>
|
|
||||||
<Button
|
<Space>
|
||||||
type="primary"
|
<Button
|
||||||
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
type="primary"
|
||||||
icon={<SearchOutlined />}
|
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
||||||
size="small"
|
icon={<SearchOutlined />}
|
||||||
style={{width: 90}}
|
size="small"
|
||||||
>
|
style={{width: 90}}
|
||||||
|
>
|
||||||
Search
|
Search
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{width: 90}}>
|
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{width: 90}}>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
confirm({closeDropdown: false});
|
confirm({closeDropdown: false});
|
||||||
this.setState({
|
this.setState({
|
||||||
searchText: selectedKeys[0],
|
searchText: selectedKeys[0],
|
||||||
searchedColumn: dataIndex,
|
searchedColumn: dataIndex,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Filter
|
Filter
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
),
|
||||||
|
filterIcon: filtered => <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}} />,
|
||||||
|
onFilter: (value, record) =>
|
||||||
|
record[dataIndex]
|
||||||
|
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
|
||||||
|
: "",
|
||||||
|
onFilterDropdownVisibleChange: visible => {
|
||||||
|
if (visible) {
|
||||||
|
setTimeout(() => this.searchInput.select(), 100);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: text =>
|
||||||
|
this.state.searchedColumn === dataIndex ? (
|
||||||
|
<Highlighter
|
||||||
|
highlightStyle={{backgroundColor: "#ffc069", padding: 0}}
|
||||||
|
searchWords={[this.state.searchText]}
|
||||||
|
autoEscape
|
||||||
|
textToHighlight={text ? text.toString() : ""}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
text
|
||||||
),
|
),
|
||||||
filterIcon: filtered => <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}} />,
|
});
|
||||||
onFilter: (value, record) =>
|
|
||||||
record[dataIndex]
|
handleSearch = (selectedKeys, confirm, dataIndex) => {
|
||||||
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
|
this.fetch({searchText: selectedKeys[0], searchedColumn: dataIndex, pagination: this.state.pagination});
|
||||||
: "",
|
};
|
||||||
onFilterDropdownVisibleChange: visible => {
|
|
||||||
if (visible) {
|
handleReset = clearFilters => {
|
||||||
setTimeout(() => this.searchInput.select(), 100);
|
clearFilters();
|
||||||
}
|
const {pagination} = this.state;
|
||||||
},
|
this.fetch({pagination});
|
||||||
render: text =>
|
};
|
||||||
this.state.searchedColumn === dataIndex ? (
|
|
||||||
<Highlighter
|
handleTableChange = (pagination, filters, sorter) => {
|
||||||
highlightStyle={{backgroundColor: "#ffc069", padding: 0}}
|
this.fetch({
|
||||||
searchWords={[this.state.searchText]}
|
sortField: sorter.field,
|
||||||
autoEscape
|
sortOrder: sorter.order,
|
||||||
textToHighlight={text ? text.toString() : ""}
|
pagination,
|
||||||
/>
|
...filters,
|
||||||
) : (
|
searchText: this.state.searchText,
|
||||||
text
|
searchedColumn: this.state.searchedColumn,
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
handleSearch = (selectedKeys, confirm, dataIndex) => {
|
render() {
|
||||||
this.fetch({searchText: selectedKeys[0], searchedColumn: dataIndex, pagination: this.state.pagination});
|
return (
|
||||||
};
|
<div>
|
||||||
|
{
|
||||||
handleReset = clearFilters => {
|
this.renderTable(this.state.data)
|
||||||
clearFilters();
|
}
|
||||||
const {pagination} = this.state;
|
</div>
|
||||||
this.fetch({pagination});
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
handleTableChange = (pagination, filters, sorter) => {
|
|
||||||
this.fetch({
|
|
||||||
sortField: sorter.field,
|
|
||||||
sortOrder: sorter.order,
|
|
||||||
pagination,
|
|
||||||
...filters,
|
|
||||||
searchText: this.state.searchText,
|
|
||||||
searchedColumn: this.state.searchedColumn,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{
|
|
||||||
this.renderTable(this.state.data)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BaseListPage;
|
export default BaseListPage;
|
||||||
|
@ -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>)
|
||||||
|
@ -88,7 +88,7 @@ class ModelListPage extends BaseListPage {
|
|||||||
...this.getColumnSearchProps("name"),
|
...this.getColumnSearchProps("name"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/models/${text}`}>
|
<Link to={`/models/${record.owner}/${text}`}>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -34,7 +34,6 @@ class ProviderEditPage extends React.Component {
|
|||||||
providerName: props.match.params.providerName,
|
providerName: props.match.params.providerName,
|
||||||
provider: null,
|
provider: null,
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
testEmail: this.props.account["email"] !== undefined ? this.props.account["email"] : "",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,6 +130,9 @@ class ProviderEditPage extends React.Component {
|
|||||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Huawei Cloud SMS") {
|
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Huawei Cloud SMS") {
|
||||||
text = i18next.t("provider:Channel No.");
|
text = i18next.t("provider:Channel No.");
|
||||||
tooltip = i18next.t("provider:Channel No. - Tooltip");
|
tooltip = i18next.t("provider:Channel No. - Tooltip");
|
||||||
|
} else if (this.state.provider.category === "Email" && this.state.provider.type === "SUBMAIL") {
|
||||||
|
text = i18next.t("provider:App ID");
|
||||||
|
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -199,9 +201,12 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField("type", "GitHub");
|
this.updateProviderField("type", "GitHub");
|
||||||
} else if (value === "Email") {
|
} else if (value === "Email") {
|
||||||
this.updateProviderField("type", "Default");
|
this.updateProviderField("type", "Default");
|
||||||
|
this.updateProviderField("host", "smtp.example.com");
|
||||||
|
this.updateProviderField("port", 465);
|
||||||
this.updateProviderField("disableSsl", false);
|
this.updateProviderField("disableSsl", false);
|
||||||
this.updateProviderField("title", "Casdoor Verification Code");
|
this.updateProviderField("title", "Casdoor Verification Code");
|
||||||
this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.");
|
this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.");
|
||||||
|
this.updateProviderField("receiver", this.props.account.email);
|
||||||
} else if (value === "SMS") {
|
} else if (value === "SMS") {
|
||||||
this.updateProviderField("type", "Aliyun SMS");
|
this.updateProviderField("type", "Aliyun SMS");
|
||||||
} else if (value === "Storage") {
|
} else if (value === "Storage") {
|
||||||
@ -536,7 +541,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("provider:Email Content"), i18next.t("provider:Email Content - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Email Content"), i18next.t("provider:Email Content - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<TextArea autoSize={{minRows: 1, maxRows: 100}} value={this.state.provider.content} onChange={e => {
|
<TextArea autoSize={{minRows: 3, maxRows: 100}} value={this.state.provider.content} onChange={e => {
|
||||||
this.updateProviderField("content", e.target.value);
|
this.updateProviderField("content", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@ -546,19 +551,16 @@ class ProviderEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4} >
|
<Col span={4} >
|
||||||
<Input value={this.state.testEmail}
|
<Input value={this.state.provider.receiver} placeholder = {i18next.t("user:Input your email")} onChange={e => {
|
||||||
placeHolder = {i18next.t("user:Input your email")}
|
this.updateProviderField("receiver", e.target.value);
|
||||||
onChange={e => {
|
}} />
|
||||||
this.setState({testEmail: e.target.value});
|
|
||||||
}} />
|
|
||||||
</Col>
|
</Col>
|
||||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||||
onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
|
||||||
{i18next.t("provider:Test Connection")}
|
{i18next.t("provider:Test Connection")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||||
disabled={!Setting.isValidEmail(this.state.testEmail)}
|
disabled={!Setting.isValidEmail(this.state.provider.receiver)}
|
||||||
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.testEmail)} >
|
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.provider.receiver)} >
|
||||||
{i18next.t("provider:Send Test Email")}
|
{i18next.t("provider:Send Test Email")}
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -56,8 +56,12 @@ export const ResetModal = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let placeHolder = "";
|
let placeholder = "";
|
||||||
if (destType === "email") {placeHolder = i18next.t("user:Input your email");} else if (destType === "phone") {placeHolder = i18next.t("user:Input your phone number");}
|
if (destType === "email") {
|
||||||
|
placeholder = i18next.t("user:Input your email");
|
||||||
|
} else if (destType === "phone") {
|
||||||
|
placeholder = i18next.t("user:Input your phone number");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
@ -80,7 +84,7 @@ export const ResetModal = (props) => {
|
|||||||
<Input
|
<Input
|
||||||
addonBefore={destType === "email" ? i18next.t("user:New Email") : i18next.t("user:New phone")}
|
addonBefore={destType === "email" ? i18next.t("user:New Email") : i18next.t("user:New phone")}
|
||||||
prefix={destType === "email" ? <MailOutlined /> : <PhoneOutlined />}
|
prefix={destType === "email" ? <MailOutlined /> : <PhoneOutlined />}
|
||||||
placeholder={placeHolder}
|
placeholder={placeholder}
|
||||||
onChange={e => setDest(e.target.value)}
|
onChange={e => setDest(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
@ -89,6 +93,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>
|
||||||
|
@ -450,9 +450,9 @@ export function trim(str, ch) {
|
|||||||
let start = 0;
|
let start = 0;
|
||||||
let end = str.length;
|
let end = str.length;
|
||||||
|
|
||||||
while(start < end && str[start] === ch) {++start;}
|
while (start < end && str[start] === ch) {++start;}
|
||||||
|
|
||||||
while(end > start && str[end - 1] === ch) {--end;}
|
while (end > start && str[end - 1] === ch) {--end;}
|
||||||
|
|
||||||
return (start > 0 || end < str.length) ? str.substring(start, end) : str;
|
return (start > 0 || end < str.length) ? str.substring(start, end) : str;
|
||||||
}
|
}
|
||||||
@ -633,6 +633,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
{id: "Default", name: "Default"},
|
{id: "Default", name: "Default"},
|
||||||
|
{id: "SUBMAIL", name: "SUBMAIL"},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (category === "SMS") {
|
} else if (category === "SMS") {
|
||||||
|
@ -50,8 +50,12 @@ class AuthCallback extends React.Component {
|
|||||||
// Casdoor's own login page, so "code" is not necessary
|
// Casdoor's own login page, so "code" is not necessary
|
||||||
if (realRedirectUri === null) {
|
if (realRedirectUri === null) {
|
||||||
const samlRequest = innerParams.get("SAMLRequest");
|
const samlRequest = innerParams.get("SAMLRequest");
|
||||||
|
// cas don't use 'redirect_url', it is called 'service'
|
||||||
|
const casService = innerParams.get("service");
|
||||||
if (samlRequest !== null && samlRequest !== undefined && samlRequest !== "") {
|
if (samlRequest !== null && samlRequest !== undefined && samlRequest !== "") {
|
||||||
return "saml";
|
return "saml";
|
||||||
|
} else if (casService !== null && casService !== undefined && casService !== "") {
|
||||||
|
return "cas";
|
||||||
}
|
}
|
||||||
return "login";
|
return "login";
|
||||||
}
|
}
|
||||||
@ -97,6 +101,7 @@ class AuthCallback extends React.Component {
|
|||||||
const providerName = innerParams.get("provider");
|
const providerName = innerParams.get("provider");
|
||||||
const method = innerParams.get("method");
|
const method = innerParams.get("method");
|
||||||
const samlRequest = innerParams.get("SAMLRequest");
|
const samlRequest = innerParams.get("SAMLRequest");
|
||||||
|
const casService = innerParams.get("service");
|
||||||
|
|
||||||
const redirectUri = `${window.location.origin}/callback`;
|
const redirectUri = `${window.location.origin}/callback`;
|
||||||
|
|
||||||
@ -111,6 +116,31 @@ class AuthCallback extends React.Component {
|
|||||||
redirectUri: redirectUri,
|
redirectUri: redirectUri,
|
||||||
method: method,
|
method: method,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.getResponseType() === "cas") {
|
||||||
|
// user is using casdoor as cas sso server, and wants the ticket to be acquired
|
||||||
|
AuthBackend.loginCas(body, {"service": casService}).then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
let msg = "Logged in successfully.";
|
||||||
|
if (casService === "") {
|
||||||
|
// If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
|
||||||
|
msg += "Now you can visit apps protected by Casdoor.";
|
||||||
|
}
|
||||||
|
Util.showMessage("success", msg);
|
||||||
|
|
||||||
|
if (casService !== "") {
|
||||||
|
const st = res.data;
|
||||||
|
const newUrl = new URL(casService);
|
||||||
|
newUrl.searchParams.append("ticket", st);
|
||||||
|
window.location.href = newUrl.toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// OAuth
|
||||||
const oAuthParams = Util.getOAuthGetParameters(innerParams);
|
const oAuthParams = Util.getOAuthGetParameters(innerParams);
|
||||||
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
|
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
|
||||||
AuthBackend.login(body, oAuthParams)
|
AuthBackend.login(body, oAuthParams)
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
) : (
|
) : (
|
||||||
|
@ -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>
|
||||||
@ -582,9 +584,9 @@ class SignupPage extends React.Component {
|
|||||||
{i18next.t("signup:Have account?")}
|
{i18next.t("signup:Have account?")}
|
||||||
<a onClick={() => {
|
<a onClick={() => {
|
||||||
const linkInStorage = sessionStorage.getItem("signinUrl");
|
const linkInStorage = sessionStorage.getItem("signinUrl");
|
||||||
if(linkInStorage !== null && linkInStorage !== "") {
|
if (linkInStorage !== null && linkInStorage !== "") {
|
||||||
Setting.goToLink(linkInStorage);
|
Setting.goToLink(linkInStorage);
|
||||||
}else{
|
} else {
|
||||||
Setting.goToLogin(this, application);
|
Setting.goToLogin(this, application);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
|
||||||
<App />
|
const app = createRoot(container);
|
||||||
</BrowserRouter>,
|
|
||||||
document.getElementById("root")
|
app.render(<BrowserRouter>
|
||||||
);
|
<App />
|
||||||
|
</BrowserRouter>);
|
||||||
|
|
||||||
// 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.
|
||||||
|
9191
web/yarn.lock
9191
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user