Compare commits

...

26 Commits

Author SHA1 Message Date
e158b58ffa fix: add hidden signal to support chrome extension to auto-signin (#1109)
* feat: add hiden applicationName(support chrome extension to auto recognize applicationName)

* feat: add hiden applicationName for all page

* fix typo

* delete unuseful code

* remove hidden applicationName from login page

* prevent crash if signupApplication is null

* Update App.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-09-07 17:02:28 +08:00
a399184cfc fix: correct edit URL in model list (#1108)
Co-authored-by: Mario Fischer <mario.fischer@inmanet.de>
2022-09-07 00:54:27 +08:00
2f9f946c87 feat: fix GOPROXY bug by exporting environment variable (#1106) 2022-09-05 23:17:39 +08:00
d8b60f838e fix: fix bugs about 3rd-party login in cas flow (#1096) 2022-09-05 23:02:25 +08:00
7599e2715a feat: add demo mode (#1097)
* feat: add demo mode

* feat: add demo mode

* Update app.conf

* Update authz.go

* Update authz.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-09-04 21:20:19 +08:00
35676455bc chore(style): add keyword spacing rule (#1098) 2022-09-04 19:40:30 +08:00
8128671c8c Improve email code 2022-09-04 12:15:07 +08:00
ee54dec3b3 feat: add support for mysubmail (#1095)
* feat: add support for mysubmail

* Update email.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-09-04 12:09:50 +08:00
d278bc9651 Add receiver for Email provider 2022-09-04 11:37:36 +08:00
b23bd0b189 Support SUBMAIL email provider 2022-09-04 11:21:20 +08:00
409be85264 Fix placeholder typo 2022-09-03 18:52:35 +08:00
0395b7e1a9 feat: migrate permission data (#1083)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-09-03 16:45:58 +08:00
4536fd0636 Use app.conf value in getOriginFromHost() 2022-09-03 15:12:34 +08:00
af9ae7dbb7 feat: buildx failed with: EROR: failed to solve: executor failed running [/bin/sh -c ./build.sh]: exit code: 127 (#1089) 2022-09-02 14:50:27 +08:00
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
40 changed files with 4335 additions and 5545 deletions

View File

@ -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
}

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 . export GOPROXY="https://goproxy.cn,direct"
fi fi
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .

View File

@ -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

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) {
@ -72,3 +80,7 @@ func GetBeegoConfDataSourceName() string {
return dataSourceName return dataSourceName
} }
func IsDemoMode() bool {
return strings.ToLower(GetConfigString("isDemoMode")) == "true"
}

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()

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

@ -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 {

View File

@ -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"},

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

@ -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

View File

@ -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
}

View File

@ -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"`

View File

@ -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",

View File

@ -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)

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

@ -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)

View File

@ -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())
}

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,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 {

View File

@ -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
} }

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

@ -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
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

@ -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"}}>
{ {
@ -746,6 +746,7 @@ class App extends Component {
const organization = this.state.account.organization; const organization = this.state.account.organization;
return ( return (
<React.Fragment> <React.Fragment>
<div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />
<Helmet> <Helmet>
<title>{organization.displayName}</title> <title>{organization.displayName}</title>
<link rel="icon" href={organization.favicon} /> <link rel="icon" href={organization.favicon} />

View File

@ -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

@ -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;

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

@ -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>
); );

View File

@ -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>

View File

@ -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>

View File

@ -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") {

View File

@ -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)

View File

@ -584,9 +584,9 @@ class SignupPage extends React.Component {
&nbsp;&nbsp;{i18next.t("signup:Have account?")}&nbsp; &nbsp;&nbsp;{i18next.t("signup:Have account?")}&nbsp;
<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);
} }
}}> }}>

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>
<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.

File diff suppressed because it is too large Load Diff