Compare commits

..

18 Commits

Author SHA1 Message Date
Chao
a147fa3e0b feat: fix bug that tableNamePrefix caused getRolesByUserInternal() to fail (#2450)
If set tableNamePrefix in app.conf, while cause sql error
2023-10-28 09:45:54 +08:00
Yang Luo
9d03665523 Fix FromProviderToIdpInfo() bug 2023-10-27 18:10:22 +08:00
Yang Luo
0106c7f7fa Fix GetIdProvider() bug 2023-10-27 17:03:37 +08:00
Yang Luo
6713dad0af Fix this.props.account null issue 2023-10-27 02:13:23 +08:00
Yang Luo
6ef2b51782 Support fastAutoSignin by backend redirection 2023-10-27 00:44:50 +08:00
Yang Luo
1732cd8538 Fix the bug that sometimes cannot auto login with enableAutoSignin = true 2023-10-27 00:06:17 +08:00
Yang Luo
a10548fe73 Fix org admin's enforcer policy APIs 2023-10-26 23:31:36 +08:00
Yang Luo
f6a7888f83 Deleted user cannot perform actions 2023-10-26 10:41:38 +08:00
Yang Luo
93efaa5459 Fix FileExist() error handling 2023-10-26 10:40:28 +08:00
jump2cn
0bfe683108 feat: change canonicalizer algorithm to xml-exc-c14n# (#2440) 2023-10-24 14:13:09 +08:00
Yang Luo
8a4758c22d Update sync code 2023-10-22 11:56:56 +08:00
Yang Luo
ee3b46e91c Allow permission.Model to be empty 2023-10-22 02:35:51 +08:00
Yang Luo
37744d6cd7 Improve permission error handling 2023-10-22 02:30:29 +08:00
Yang Luo
98defe617b Add providerItem.SignupGroup 2023-10-20 23:10:43 +08:00
Yang Luo
96cbf51ca0 Remove useless alertType field 2023-10-20 23:01:11 +08:00
Yang Luo
22b57fdd23 Add application.EnableSamlC14n10 2023-10-20 22:37:23 +08:00
haiwu
b68e291f37 feat: support SAML Custom provider (#2430)
* 111

* feat: support custom saml provider

* feat: gofumpt code

* feat: gofumpt code

* feat: remove comment

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-10-20 21:11:36 +08:00
aiden
9960b4933b feat: respect isReadOnly in the syncer (#2427)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-19 18:57:12 +08:00
28 changed files with 471 additions and 166 deletions

View File

@@ -127,8 +127,14 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
return true
}
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
return true
if user != nil {
if user.IsDeleted {
return false
}
if user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
return true
}
}
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)

View File

@@ -477,11 +477,10 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
return
}
userInfo := &idp.UserInfo{}
if provider.Category == "SAML" {
// SAML
userInfo.Id, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
userInfo, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
@@ -524,7 +523,8 @@ func (c *ApiController) Login() {
if authForm.Method == "signup" {
user := &object.User{}
if provider.Category == "SAML" {
user, err = object.GetUser(util.GetId(application.Organization, userInfo.Id))
// The userInfo.Id is the NameID in SAML response, it could be name / email / phone
user, err = object.GetUserByFields(application.Organization, userInfo.Id)
if err != nil {
c.ResponseError(err.Error())
return
@@ -651,6 +651,15 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to create user, user information is invalid: %s"), util.StructToJson(user)))
return
}
if providerItem.SignupGroup != "" {
user.Groups = []string{providerItem.SignupGroup}
_, err = object.UpdateUser(user.GetId(), user, []string{"groups"}, false)
if err != nil {
c.ResponseError(err.Error())
return
}
}
}
// sync info from 3rd-party if possible
@@ -679,6 +688,7 @@ func (c *ApiController) Login() {
record2.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record2) })
} else if provider.Category == "SAML" {
// TODO: since we get the user info from SAML response, we can try to create the user
resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))}
}
// resp = &Response{Status: "ok", Msg: "", Data: res}

View File

@@ -85,7 +85,7 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
case "GitLab":
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
case "Adfs":
case "ADFS":
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
case "Baidu":
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)

View File

@@ -57,6 +57,7 @@ type Application struct {
EnableAutoSignin bool `json:"enableAutoSignin"`
EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"`
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
EnableWebAuthn bool `json:"enableWebAuthn"`
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
OrgChoiceMode string `json:"orgChoiceMode"`

View File

@@ -379,7 +379,10 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
continue
}
enforcer := getPermissionEnforcer(permission)
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return false, err
}
var isAllowed bool
isAllowed, err = enforcer.Enforce(userId, application.Name, "Read")

View File

@@ -178,7 +178,7 @@ func initBuiltInApplication() {
EnablePassword: true,
EnableSignUp: true,
Providers: []*ProviderItem{
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Rule: "None", Provider: nil},
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, SignupGroup: "", Rule: "None", Provider: nil},
},
SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},

View File

@@ -113,11 +113,15 @@ func GetPermission(id string) (*Permission, error) {
// checkPermissionValid verifies if the permission is valid
func checkPermissionValid(permission *Permission) error {
enforcer := getPermissionEnforcer(permission)
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return err
}
enforcer.EnableAutoSave(false)
policies := getPolicies(permission)
_, err := enforcer.AddPolicies(policies)
_, err = enforcer.AddPolicies(policies)
if err != nil {
return err
}
@@ -129,7 +133,7 @@ func checkPermissionValid(permission *Permission) error {
groupingPolicies := getGroupingPolicies(permission)
if len(groupingPolicies) > 0 {
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
if err != nil {
return err
}
@@ -150,7 +154,7 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
return false, nil
}
if permission.ResourceType == "Application" {
if permission.ResourceType == "Application" && permission.Model != "" {
model, err := GetModelEx(util.GetId(owner, permission.Model))
if err != nil {
return false, err
@@ -174,8 +178,16 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
}
if affected != 0 {
removeGroupingPolicies(oldPermission)
removePolicies(oldPermission)
err = removeGroupingPolicies(oldPermission)
if err != nil {
return false, err
}
err = removePolicies(oldPermission)
if err != nil {
return false, err
}
if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
if isEmpty {
@@ -185,8 +197,16 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
}
}
}
addGroupingPolicies(permission)
addPolicies(permission)
err = addGroupingPolicies(permission)
if err != nil {
return false, err
}
err = addPolicies(permission)
if err != nil {
return false, err
}
}
return affected != 0, nil
@@ -199,40 +219,54 @@ func AddPermission(permission *Permission) (bool, error) {
}
if affected != 0 {
addGroupingPolicies(permission)
addPolicies(permission)
err = addGroupingPolicies(permission)
if err != nil {
return false, err
}
err = addPolicies(permission)
if err != nil {
return false, err
}
}
return affected != 0, nil
}
func AddPermissions(permissions []*Permission) bool {
func AddPermissions(permissions []*Permission) (bool, error) {
if len(permissions) == 0 {
return false
return false, nil
}
affected, err := ormer.Engine.Insert(permissions)
if err != nil {
if !strings.Contains(err.Error(), "Duplicate entry") {
panic(err)
return false, err
}
}
for _, permission := range permissions {
// add using for loop
if affected != 0 {
addGroupingPolicies(permission)
addPolicies(permission)
err = addGroupingPolicies(permission)
if err != nil {
return false, err
}
err = addPolicies(permission)
if err != nil {
return false, err
}
}
}
return affected != 0
return affected != 0, nil
}
func AddPermissionsInBatch(permissions []*Permission) bool {
func AddPermissionsInBatch(permissions []*Permission) (bool, error) {
batchSize := conf.GetConfigBatchSize()
if len(permissions) == 0 {
return false
return false, nil
}
affected := false
@@ -245,12 +279,18 @@ func AddPermissionsInBatch(permissions []*Permission) bool {
tmp := permissions[start:end]
fmt.Printf("The syncer adds permissions: [%d - %d]\n", start, end)
if AddPermissions(tmp) {
b, err := AddPermissions(tmp)
if err != nil {
return false, err
}
if b {
affected = true
}
}
return affected
return affected, nil
}
func DeletePermission(permission *Permission) (bool, error) {
@@ -260,8 +300,16 @@ func DeletePermission(permission *Permission) (bool, error) {
}
if affected != 0 {
removeGroupingPolicies(permission)
removePolicies(permission)
err = removeGroupingPolicies(permission)
if err != nil {
return false, err
}
err = removePolicies(permission)
if err != nil {
return false, err
}
if permission.Adapter != "" && permission.Adapter != "permission_rule" {
isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
if isEmpty {

View File

@@ -26,23 +26,23 @@ import (
xormadapter "github.com/casdoor/xorm-adapter/v3"
)
func getPermissionEnforcer(p *Permission, permissionIDs ...string) *casbin.Enforcer {
func getPermissionEnforcer(p *Permission, permissionIDs ...string) (*casbin.Enforcer, error) {
// Init an enforcer instance without specifying a model or adapter.
// If you specify an adapter, it will load all policies, which is a
// heavy process that can slow down the application.
enforcer, err := casbin.NewEnforcer(&log.DefaultLogger{}, false)
if err != nil {
panic(err)
return nil, err
}
err = p.setEnforcerModel(enforcer)
if err != nil {
panic(err)
return nil, err
}
err = p.setEnforcerAdapter(enforcer)
if err != nil {
panic(err)
return nil, err
}
policyFilterV5 := []string{p.GetId()}
@@ -60,10 +60,10 @@ func getPermissionEnforcer(p *Permission, permissionIDs ...string) *casbin.Enfor
err = enforcer.LoadFilteredPolicy(policyFilter)
if err != nil {
panic(err)
return nil, err
}
return enforcer
return enforcer, nil
}
func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
@@ -201,72 +201,96 @@ func getGroupingPolicies(permission *Permission) [][]string {
return groupingPolicies
}
func addPolicies(permission *Permission) {
enforcer := getPermissionEnforcer(permission)
func addPolicies(permission *Permission) error {
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return err
}
policies := getPolicies(permission)
_, err := enforcer.AddPolicies(policies)
if err != nil {
panic(err)
}
_, err = enforcer.AddPolicies(policies)
return err
}
func addGroupingPolicies(permission *Permission) {
enforcer := getPermissionEnforcer(permission)
func removePolicies(permission *Permission) error {
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return err
}
policies := getPolicies(permission)
_, err = enforcer.RemovePolicies(policies)
return err
}
func addGroupingPolicies(permission *Permission) error {
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return err
}
groupingPolicies := getGroupingPolicies(permission)
if len(groupingPolicies) > 0 {
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
if err != nil {
panic(err)
return err
}
}
return nil
}
func removeGroupingPolicies(permission *Permission) {
enforcer := getPermissionEnforcer(permission)
func removeGroupingPolicies(permission *Permission) error {
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return err
}
groupingPolicies := getGroupingPolicies(permission)
if len(groupingPolicies) > 0 {
_, err := enforcer.RemoveGroupingPolicies(groupingPolicies)
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
if err != nil {
panic(err)
return err
}
}
}
func removePolicies(permission *Permission) {
enforcer := getPermissionEnforcer(permission)
policies := getPolicies(permission)
_, err := enforcer.RemovePolicies(policies)
if err != nil {
panic(err)
}
return nil
}
type CasbinRequest = []interface{}
func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
enforcer := getPermissionEnforcer(permission, permissionIds...)
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
if err != nil {
return false, err
}
return enforcer.Enforce(*request...)
}
func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
enforcer := getPermissionEnforcer(permission, permissionIds...)
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
if err != nil {
return nil, err
}
return enforcer.BatchEnforce(*requests)
}
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([]string, error) {
permissions, _, err := getPermissionsAndRolesByUser(userId)
if err != nil {
panic(err)
return nil, err
}
for _, role := range GetAllRoles(userId) {
permissionsByRole, err := GetPermissionsByRole(role)
if err != nil {
panic(err)
return nil, err
}
permissions = append(permissions, permissionsByRole...)
@@ -274,19 +298,24 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []
var values []string
for _, permission := range permissions {
enforcer := getPermissionEnforcer(permission)
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return nil, err
}
values = append(values, fn(enforcer)...)
}
return values
return values, nil
}
func GetAllObjects(userId string) []string {
func GetAllObjects(userId string) ([]string, error) {
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
return enforcer.GetAllObjects()
})
}
func GetAllActions(userId string) []string {
func GetAllActions(userId string) ([]string, error) {
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
return enforcer.GetAllActions()
})
@@ -330,17 +359,23 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
// load [policy_definition]
policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",")
fieldsNum := len(policyDefinition)
if fieldsNum > builtInAvailableField {
panic(fmt.Errorf("the maximum policy_definition field number cannot exceed %d, got %d", builtInAvailableField, fieldsNum))
return nil, fmt.Errorf("the maximum policy_definition field number cannot exceed %d, got %d", builtInAvailableField, fieldsNum)
}
// filled empty field with "" and V5 with "permissionId"
for i := builtInAvailableField - fieldsNum; i > 0; i-- {
policyDefinition = append(policyDefinition, "")
}
policyDefinition = append(policyDefinition, "permissionId")
m, _ := model.NewModelFromString(modelText)
m, err := model.NewModelFromString(modelText)
if err != nil {
return nil, err
}
m.AddDef("p", "p", strings.Join(policyDefinition, ","))
return m, err

View File

@@ -83,5 +83,10 @@ func UploadPermissions(owner string, path string) (bool, error) {
return false, nil
}
return AddPermissionsInBatch(newPermissions), nil
affected, err := AddPermissionsInBatch(newPermissions)
if err != nil {
return false, err
}
return affected, nil
}

View File

@@ -415,7 +415,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
providerInfo.ClientId = provider.ClientId2
providerInfo.ClientSecret = provider.ClientSecret2
}
} else if provider.Type == "AzureAD" {
} else if provider.Type == "AzureAD" || provider.Type == "ADFS" {
providerInfo.HostUrl = provider.Domain
}

View File

@@ -18,13 +18,13 @@ type ProviderItem struct {
Owner string `json:"owner"`
Name string `json:"name"`
CanSignUp bool `json:"canSignUp"`
CanSignIn bool `json:"canSignIn"`
CanUnlink bool `json:"canUnlink"`
Prompted bool `json:"prompted"`
AlertType string `json:"alertType"`
Rule string `json:"rule"`
Provider *Provider `json:"provider"`
CanSignUp bool `json:"canSignUp"`
CanSignIn bool `json:"canSignIn"`
CanUnlink bool `json:"canUnlink"`
Prompted bool `json:"prompted"`
SignupGroup string `json:"signupGroup"`
Rule string `json:"rule"`
Provider *Provider `json:"provider"`
}
func (application *Application) GetProviderItem(providerName string) *ProviderItem {

View File

@@ -151,8 +151,16 @@ func UpdateRole(id string, role *Role) (bool, error) {
}
for _, permission := range permissions {
addGroupingPolicies(permission)
addPolicies(permission)
err = addGroupingPolicies(permission)
if err != nil {
return false, err
}
err = addPolicies(permission)
if err != nil {
return false, err
}
visited[permission.GetId()] = struct{}{}
}
@@ -166,10 +174,15 @@ func UpdateRole(id string, role *Role) (bool, error) {
if err != nil {
return false, err
}
for _, permission := range permissions {
permissionId := permission.GetId()
if _, ok := visited[permissionId]; !ok {
addGroupingPolicies(permission)
err = addGroupingPolicies(permission)
if err != nil {
return false, err
}
visited[permissionId] = struct{}{}
}
}
@@ -259,9 +272,9 @@ func getRolesByUserInternal(userId string) ([]*Role, error) {
return roles, err
}
query := ormer.Engine.Where("role.users like ?", fmt.Sprintf("%%%s%%", userId))
query := ormer.Engine.Where("users like ?", fmt.Sprintf("%%%s%%", userId))
for _, group := range user.Groups {
query = query.Or("role.groups like ?", fmt.Sprintf("%%%s%%", group))
query = query.Or("groups like ?", fmt.Sprintf("%%%s%%", group))
}
err = query.Find(&roles)

View File

@@ -324,6 +324,11 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
}
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
ctx.Hash = crypto.SHA1
if application.EnableSamlC14n10 {
ctx.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("")
}
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
//if err != nil {
// return "", "", fmt.Errorf("err: %s", err.Error())

View File

@@ -23,23 +23,49 @@ import (
"regexp"
"strings"
"github.com/casdoor/casdoor/idp"
"github.com/mitchellh/mapstructure"
"github.com/casdoor/casdoor/i18n"
saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig"
)
func ParseSamlResponse(samlResponse string, provider *Provider, host string) (string, error) {
func ParseSamlResponse(samlResponse string, provider *Provider, host string) (*idp.UserInfo, error) {
samlResponse, _ = url.QueryUnescape(samlResponse)
sp, err := buildSp(provider, samlResponse, host)
if err != nil {
return "", err
return nil, err
}
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
if err != nil {
return "", err
return nil, err
}
return assertionInfo.NameID, err
userInfoMap := make(map[string]string)
for spAttr, idpAttr := range provider.UserMapping {
for _, attr := range assertionInfo.Values {
if attr.Name == idpAttr {
userInfoMap[spAttr] = attr.Values[0].Value
}
}
}
userInfoMap["id"] = assertionInfo.NameID
customUserInfo := &idp.CustomUserInfo{}
err = mapstructure.Decode(userInfoMap, customUserInfo)
if err != nil {
return nil, err
}
userInfo := &idp.UserInfo{
Id: customUserInfo.Id,
Username: customUserInfo.Username,
DisplayName: customUserInfo.DisplayName,
Email: customUserInfo.Email,
AvatarUrl: customUserInfo.AvatarUrl,
}
return userInfo, err
}
func GenerateSamlRequest(id, relayState, host, lang string) (auth string, method string, err error) {
@@ -146,14 +172,24 @@ func getCertificateFromSamlResponse(samlResponse string, providerType string) (s
if err != nil {
return "", err
}
deStr := strings.Replace(string(de), "\n", "", -1)
tagMap := map[string]string{
"Aliyun IDaaS": "ds",
"Keycloak": "dsig",
}
var (
expression string
deStr = strings.Replace(string(de), "\n", "", -1)
tagMap = map[string]string{
"Aliyun IDaaS": "ds",
"Keycloak": "dsig",
}
)
tag := tagMap[providerType]
expression := fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag)
if tag == "" {
// <ds:X509Certificate>...</ds:X509Certificate>
// <dsig:X509Certificate>...</dsig:X509Certificate>
// <X509Certificate>...</X509Certificate>
// ...
expression = "<[^>]*:?X509Certificate>([\\s\\S]*?)<[^>]*:?X509Certificate>"
} else {
expression = fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag)
}
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
return res[1], nil
}

View File

@@ -59,6 +59,10 @@ func AddUserToOriginalDatabase(user *User) error {
return nil
}
if syncer.IsReadOnly {
return nil
}
updatedOUser := syncer.createOriginalUserFromUser(user)
_, err = syncer.addUser(updatedOUser)
if err != nil {
@@ -78,6 +82,10 @@ func UpdateUserToOriginalDatabase(user *User) error {
return nil
}
if syncer.IsReadOnly {
return nil
}
newUser, err := GetUser(user.GetId())
if err != nil {
return err

View File

@@ -66,6 +66,13 @@ func getObject(ctx *context.Context) (string, string) {
path := ctx.Request.URL.Path
if method == http.MethodGet {
if ctx.Request.URL.Path == "/api/get-policies" && ctx.Input.Query("id") == "/" {
adapterId := ctx.Input.Query("adapterId")
if adapterId != "" {
return util.GetOwnerAndNameFromIdNoCheck(adapterId)
}
}
// query == "?id=built-in/admin"
id := ctx.Input.Query("id")
if id != "" {
@@ -79,8 +86,14 @@ func getObject(ctx *context.Context) (string, string) {
return "", ""
} else {
body := ctx.Input.RequestBody
if path == "/api/add-policy" || path == "/api/remove-policy" || path == "/api/update-policy" {
id := ctx.Input.Query("id")
if id != "" {
return util.GetOwnerAndNameFromIdNoCheck(id)
}
}
body := ctx.Input.RequestBody
if len(body) == 0 {
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name")
}

View File

@@ -26,6 +26,7 @@ import (
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@@ -46,6 +47,46 @@ func getWebBuildFolder() string {
return path
}
func fastAutoSignin(ctx *context.Context) (string, error) {
userId := getSessionUser(ctx)
if userId == "" {
return "", nil
}
clientId := ctx.Input.Query("client_id")
responseType := ctx.Input.Query("response_type")
redirectUri := ctx.Input.Query("redirect_uri")
scope := ctx.Input.Query("scope")
state := ctx.Input.Query("state")
nonce := ""
codeChallenge := ""
if clientId == "" || responseType != "code" || redirectUri == "" {
return "", nil
}
application, err := object.GetApplicationByClientId(clientId)
if err != nil {
return "", err
}
if application == nil {
return "", nil
}
if !application.EnableAutoSignin {
return "", nil
}
code, err := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, ctx.Request.Host, getAcceptLanguage(ctx))
if err != nil {
return "", err
} else if code.Message != "" {
return "", fmt.Errorf(code.Message)
}
res := fmt.Sprintf("%s?code=%s&state=%s", redirectUri, code.Code, state)
return res, nil
}
func StaticFilter(ctx *context.Context) {
urlPath := ctx.Request.URL.Path
@@ -63,6 +104,19 @@ func StaticFilter(ctx *context.Context) {
return
}
if urlPath == "/login/oauth/authorize" {
redirectUrl, err := fastAutoSignin(ctx)
if err != nil {
responseError(ctx, err.Error())
return
}
if redirectUrl != "" {
http.Redirect(ctx.ResponseWriter, ctx.Request, redirectUrl, http.StatusFound)
return
}
}
webBuildFolder := getWebBuildFolder()
path := webBuildFolder
if urlPath == "/" {

View File

@@ -36,7 +36,12 @@ func (db *Database) onDDL(header *replication.EventHeader, nextPos mysql.Positio
}
func (db *Database) OnRow(e *canal.RowsEvent) error {
log.Info("serverId: ", e.Header.ServerID)
if e.Header != nil {
log.Info("serverId: ", e.Header.ServerID)
} else {
log.Info("serverId: e.Header == nil")
}
if strings.Contains(db.Gtid, db.serverUuid) {
return nil
}
@@ -87,11 +92,13 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
updateSql, args, err := getUpdateSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue, pkColumnNames, pkColumnValue)
if err != nil {
log.Error(err)
return err
}
res, err := db.engine.DB().Exec(updateSql, args...)
if err != nil {
log.Error(err)
return err
}
log.Info(updateSql, args, res)
@@ -113,11 +120,13 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
deleteSql, args, err := getDeleteSql(e.Table.Schema, e.Table.Name, pkColumnNames, pkColumnValue)
if err != nil {
log.Error(err)
return err
}
res, err := db.engine.DB().Exec(deleteSql, args...)
if err != nil {
log.Error(err)
return err
}
log.Info(deleteSql, args, res)
@@ -141,11 +150,13 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
insertSql, args, err := getInsertSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue)
if err != nil {
log.Error(err)
return err
}
res, err := db.engine.DB().Exec(insertSql, args...)
if err != nil {
log.Error(err)
return err
}
log.Info(insertSql, args, res)

View File

@@ -20,11 +20,21 @@ func startSyncJob(db1 *Database, db2 *Database) error {
var wg sync.WaitGroup
// start canal1 replication
go db1.startCanal(db2)
go func(db1 *Database, db2 *Database) {
err := db1.startCanal(db2)
if err != nil {
panic(err)
}
}(db1, db2)
wg.Add(1)
// start canal2 replication
go db2.startCanal(db1)
go func(db1 *Database, db2 *Database) {
err := db2.startCanal(db1)
if err != nil {
panic(err)
}
}(db1, db2)
wg.Add(1)
wg.Wait()

View File

@@ -24,7 +24,10 @@ import (
)
func TestStartSyncJob(t *testing.T) {
db1 := newDatabase("127.0.0.1", 3306, "casdoor", "root", "123456")
db2 := newDatabase("127.0.0.1", 3306, "casdoor2", "root", "123456")
startSyncJob(db1, db2)
db1 := newDatabase("localhost", 3306, "casdoor", "root", "123456")
db2 := newDatabase("localhost", 3306, "casdoor2", "root", "123456")
err := startSyncJob(db1, db2)
if err != nil {
panic(err)
}
}

View File

@@ -15,9 +15,7 @@
package sync
import (
"fmt"
"log"
"strconv"
"github.com/Masterminds/squirrel"
"github.com/xorm-io/xorm"
@@ -74,21 +72,23 @@ func createEngine(dataSourceName string) (*xorm.Engine, error) {
}
func getServerId(engin *xorm.Engine) (uint32, error) {
res, err := engin.QueryInterface("SELECT @@server_id")
record, err := engin.QueryInterface("SELECT @@server_id")
if err != nil {
return 0, err
}
serverId, _ := strconv.ParseUint(fmt.Sprintf("%s", res[0]["@@server_id"]), 10, 32)
return uint32(serverId), nil
res := uint32(record[0]["@@server_id"].(int64))
return res, nil
}
func getServerUuid(engin *xorm.Engine) (string, error) {
res, err := engin.QueryString("show variables like 'server_uuid'")
record, err := engin.QueryString("show variables like 'server_uuid'")
if err != nil {
return "", err
}
serverUuid := fmt.Sprintf("%s", res[0]["Value"])
return serverUuid, err
res := record[0]["Value"]
return res, err
}
func getPkColumnNames(columnNames []string, PKColumns []int) []string {

View File

@@ -24,7 +24,10 @@ import (
)
func FileExist(path string) bool {
if _, err := os.Stat(path); os.IsNotExist(err) {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return false
} else if err != nil {
return false
}
return true

View File

@@ -640,13 +640,23 @@ class ApplicationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable SAML C14N10"), i18next.t("application:Enable SAML C14N10 - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSamlC14n10} onChange={checked => {
this.updateApplicationField("enableSamlC14n10", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:SAML Attribute"), i18next.t("general:SAML Attribute - Tooltip"))} :
{Setting.getLabel(i18next.t("general:SAML attributes"), i18next.t("general:SAML attributes - Tooltip"))} :
</Col>
<Col span={22} >
<SamlAttributeTable
title={i18next.t("general:SAML Attribute")}
title={i18next.t("general:SAML attributes")}
table={this.state.application.samlAttributes}
application={this.state.application}
onUpdateTable={(value) => {this.updateApplicationField("samlAttributes", value);}}

View File

@@ -44,7 +44,7 @@ class ApplicationListPage extends BaseListPage {
enableCodeSignin: false,
enableSamlCompress: false,
providers: [
{name: "provider_captcha_default", canSignUp: false, canSignIn: false, canUnlink: false, prompted: false, alertType: "None"},
{name: "provider_captcha_default", canSignUp: false, canSignIn: false, canUnlink: false, prompted: false, signupGroup: "", rule: ""},
],
signupItems: [
{name: "ID", visible: false, required: true, rule: "Random"},

View File

@@ -379,10 +379,11 @@ class ProviderEditPage extends React.Component {
loadSamlConfiguration() {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(this.state.provider.metadata, "text/xml");
const cert = xmlDoc.getElementsByTagName("ds:X509Certificate")[0].childNodes[0].nodeValue;
const endpoint = xmlDoc.getElementsByTagName("md:SingleSignOnService")[0].getAttribute("Location");
const issuerUrl = xmlDoc.getElementsByTagName("md:EntityDescriptor")[0].getAttribute("entityID");
const rawXml = this.state.provider.metadata.replace("\n", "");
const xmlDoc = parser.parseFromString(rawXml, "text/xml");
const cert = xmlDoc.querySelector("X509Certificate").childNodes[0].nodeValue.replace(" ", "");
const endpoint = xmlDoc.querySelector("SingleSignOnService").getAttribute("Location");
const issuerUrl = xmlDoc.querySelector("EntityDescriptor").getAttribute("entityID");
this.updateProviderField("idP", cert);
this.updateProviderField("endpoint", endpoint);
this.updateProviderField("issuerUrl", issuerUrl);
@@ -491,7 +492,7 @@ class ProviderEditPage extends React.Component {
this.updateProviderField("type", value);
if (value === "Local File System") {
this.updateProviderField("domain", Setting.getFullServerUrl());
} else if (value === "Custom") {
} else if (value === "Custom" && this.state.provider.category === "OAuth") {
this.updateProviderField("customAuthUrl", "https://door.casdoor.com/login/oauth/authorize");
this.updateProviderField("scopes", "openid profile email");
this.updateProviderField("customTokenUrl", "https://door.casdoor.com/api/login/oauth/access_token");
@@ -553,48 +554,54 @@ class ProviderEditPage extends React.Component {
)
}
{
this.state.provider.type !== "Custom" ? null : (
this.state.provider.type === "Custom" ? (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customAuthUrl} onChange={e => {
this.updateProviderField("customAuthUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customTokenUrl} onChange={e => {
this.updateProviderField("customTokenUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.scopes} onChange={e => {
this.updateProviderField("scopes", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customUserInfoUrl} onChange={e => {
this.updateProviderField("customUserInfoUrl", e.target.value);
}} />
</Col>
</Row>
{
this.state.provider.category === "OAuth" ? (
<Col>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customAuthUrl} onChange={e => {
this.updateProviderField("customAuthUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customTokenUrl} onChange={e => {
this.updateProviderField("customTokenUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.scopes} onChange={e => {
this.updateProviderField("scopes", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customUserInfoUrl} onChange={e => {
this.updateProviderField("customUserInfoUrl", e.target.value);
}} />
</Col>
</Row>
</Col>
) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:User mapping"), i18next.t("provider:User mapping - Tooltip"))} :
@@ -631,7 +638,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
</React.Fragment>
)
) : null
}
{
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||

View File

@@ -209,6 +209,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_keycloak.png`,
url: "https://www.keycloak.org/",
},
"Custom": {
logo: `${StaticBaseUrl}/img/social_custom.png`,
url: "https://door.casdoor.com/",
},
},
Payment: {
"Dummy": {
@@ -866,10 +870,10 @@ export function getClickable(text) {
}
export function getProviderLogoURL(provider) {
if (provider.type === "Custom" && provider.customLogo) {
return provider.customLogo;
}
if (provider.category === "OAuth") {
if (provider.type === "Custom" && provider.customLogo) {
return provider.customLogo;
}
return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
} else {
const info = OtherProviderInfo[provider.category][provider.type];
@@ -1014,6 +1018,7 @@ export function getProviderTypeOptions(category) {
return ([
{id: "Aliyun IDaaS", name: "Aliyun IDaaS"},
{id: "Keycloak", name: "Keycloak"},
{id: "Custom", name: "Custom"},
]);
} else if (category === "Payment") {
return ([

View File

@@ -99,7 +99,9 @@ class LoginPage extends React.Component {
this.setState({enableCaptchaModal: CaptchaRule.Never});
}
}
}
if (prevProps.account !== this.props.account && this.props.account !== undefined) {
if (this.props.account && this.props.account.owner === this.props.application?.organization) {
const params = new URLSearchParams(this.props.location.search);
const silentSignin = params.get("silentSignin");

View File

@@ -14,7 +14,7 @@
import React from "react";
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
import * as Provider from "../auth/Provider";
@@ -39,7 +39,7 @@ class ProviderTable extends React.Component {
}
addRow(table) {
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, alertType: "None", rule: "None"};
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, prompted: false, signupGroup: "", rule: "None"};
if (table === undefined) {
table = [];
}
@@ -172,6 +172,23 @@ class ProviderTable extends React.Component {
);
},
},
{
title: i18next.t("provider:Signup group"),
dataIndex: "signupGroup",
key: "signupGroup",
width: "120px",
render: (text, record, index) => {
if (!["OAuth", "Web3"].includes(record.provider?.category)) {
return null;
}
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "signupGroup", e.target.value);
}} />
);
},
},
{
title: i18next.t("application:Rule"),
dataIndex: "rule",