Compare commits

..

9 Commits

Author SHA1 Message Date
Yang Luo
1c296e9b6f feat: activate enableGzip by default in app.conf 2023-10-15 01:27:42 +08:00
haiwu
3d80ec721f fix: use user.UpdatedTime as scim.Meta.Version instead of user.Id (#2406)
* 111

* fix: use user.UpdatedTime as scim.Meta.Version instead of user.Id

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-10-14 11:03:58 +08:00
Yang Luo
43d849086f Fix 127.0.0.1 bug in isHostIntranet() 2023-10-13 23:29:37 +08:00
Yang Luo
69b144d80f feat: change back to running RecordMessage() filter before API handling, because the logged-out user info is missing after session is cleared. Revert: https://github.com/casdoor/casdoor/pull/2369 2023-10-13 16:53:30 +08:00
Yang Luo
52a66ef044 Fix webhook not triggered issue in SendWebhooks() 2023-10-13 16:47:09 +08:00
Yang Luo
ec0a8e16f7 feat: fix CheckLoginPermission() logic 2023-10-13 15:41:23 +08:00
Yang Luo
80a8000057 Add GetModelEx() 2023-10-13 13:45:13 +08:00
Yang Luo
77091a3ae5 Fix null model issue in UpdatePermission() 2023-10-13 12:55:11 +08:00
Pedro Padron
983da685a2 feat: support calling get-user API by only email, phone or userId without owner (#2398) 2023-10-13 02:48:55 +08:00
15 changed files with 195 additions and 61 deletions

View File

@@ -19,6 +19,7 @@ origin =
staticBaseUrl = "https://cdn.casbin.org"
isDemoMode = false
batchSize = 100
enableGzip = true
ldapServerPort = 389
radiusServerPort = 1812
radiusSecret = "secret"

View File

@@ -37,6 +37,11 @@ func (c *ApiController) Enforce() {
resourceId := c.Input().Get("resourceId")
enforcerId := c.Input().Get("enforcerId")
if len(c.Ctx.Input.RequestBody) == 0 {
c.ResponseError("The request body should not be empty")
return
}
var request object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
if err != nil {

View File

@@ -160,35 +160,47 @@ func (c *ApiController) GetUser() {
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
}
if owner == "" {
owner = util.GetOwnerFromId(id)
}
var user *object.User
organization, err := object.GetOrganization(util.GetId("admin", owner))
if err != nil {
c.ResponseError(err.Error())
return
}
if id == "" && owner == "" {
switch {
case email != "":
user, err = object.GetUserByEmailOnly(email)
case phone != "":
user, err = object.GetUserByPhoneOnly(phone)
case userId != "":
user, err = object.GetUserByUserIdOnly(userId)
}
} else {
if owner == "" {
owner = util.GetOwnerFromId(id)
}
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
if !hasPermission {
organization, err := object.GetOrganization(util.GetId("admin", owner))
if err != nil {
c.ResponseError(err.Error())
return
}
}
var user *object.User
switch {
case email != "":
user, err = object.GetUserByEmail(owner, email)
case phone != "":
user, err = object.GetUserByPhone(owner, phone)
case userId != "":
user = userFromUserId
default:
user, err = object.GetUser(id)
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
if !hasPermission {
c.ResponseError(err.Error())
return
}
}
switch {
case email != "":
user, err = object.GetUserByEmail(owner, email)
case phone != "":
user, err = object.GetUserByPhone(owner, phone)
case userId != "":
user = userFromUserId
default:
user, err = object.GetUser(id)
}
}
if err != nil {

View File

@@ -59,7 +59,7 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
beego.InsertFilter("*", beego.AfterExec, routers.RecordMessage, false)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionOn = true
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"

View File

@@ -22,14 +22,15 @@ config: |
dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1"
dbName = casdoor
redisEndpoint =
defaultStorageProvider =
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
socks5Proxy = ""
verificationCodeTimeout = 10
initScore = 2000
initScore = 0
logPostOnly = true
origin = "https://door.casbin.com"
origin =
enableGzip = true
imagePullSecrets: []
nameOverride: ""

View File

@@ -361,6 +361,8 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
return false, err
}
allowPermissionCount := 0
denyPermissionCount := 0
allowCount := 0
denyCount := 0
for _, permission := range permissions {
@@ -368,8 +370,13 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
continue
}
if permission.isUserHit(userId) {
allowCount += 1
if !permission.isUserHit(userId) {
if permission.Effect == "Allow" {
allowPermissionCount += 1
} else {
denyPermissionCount += 1
}
continue
}
enforcer := getPermissionEnforcer(permission)
@@ -391,8 +398,18 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
}
}
// Deny-override, if one deny is found, then deny
if denyCount > 0 {
return false, nil
} else if allowCount > 0 {
return true, nil
}
// For no-allow and no-deny condition
// If only allow permissions exist, we suppose it's Deny-by-default, aka no-allow means deny
// Otherwise, it's Allow-by-default, aka no-deny means allow
if allowPermissionCount > 0 && denyPermissionCount == 0 {
return false, nil
}
return true, nil
}

View File

@@ -246,7 +246,7 @@ func (enforcer *Enforcer) LoadModelCfg() error {
return nil
}
model, err := GetModel(enforcer.Model)
model, err := GetModelEx(enforcer.Model)
if err != nil {
return err
} else if model == nil {

View File

@@ -84,6 +84,19 @@ func GetModel(id string) (*Model, error) {
return getModel(owner, name)
}
func GetModelEx(id string) (*Model, error) {
owner, name := util.GetOwnerAndNameFromId(id)
model, err := getModel(owner, name)
if err != nil {
return nil, err
}
if model != nil {
return model, nil
}
return getModel("built-in", name)
}
func UpdateModelWithCheck(id string, modelObj *Model) error {
// check model grammar
_, err := model.NewModelFromString(modelObj.ModelText)

View File

@@ -151,17 +151,20 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
}
if permission.ResourceType == "Application" {
model, err := GetModel(util.GetId(owner, permission.Model))
model, err := GetModelEx(util.GetId(owner, permission.Model))
if err != nil {
return false, err
} else if model == nil {
return false, fmt.Errorf("the model: %s for permission: %s is not found", permission.Model, permission.GetId())
}
modelCfg, err := getModelCfg(model)
if err != nil {
return false, err
}
if len(strings.Split(modelCfg["p"], ",")) != 3 {
return false, fmt.Errorf("the model: %s for permission: %s is not valid, application type resources need 3 size [policy_defination] model", permission.Model, permission.GetId())
return false, fmt.Errorf("the model: %s for permission: %s is not valid, Casbin model's [policy_defination] section should have 3 elements", permission.Model, permission.GetId())
}
}
@@ -421,10 +424,10 @@ func (p *Permission) GetId() string {
}
func (p *Permission) isUserHit(name string) bool {
targetOrg, _ := util.GetOwnerAndNameFromId(name)
targetOrg, targetName := util.GetOwnerAndNameFromId(name)
for _, user := range p.Users {
userOrg, userName := util.GetOwnerAndNameFromId(user)
if userOrg == targetOrg && userName == "*" {
if userOrg == targetOrg && (userName == "*" || userName == targetName) {
return true
}
}
@@ -433,7 +436,7 @@ func (p *Permission) isUserHit(name string) bool {
func (p *Permission) isResourceHit(name string) bool {
for _, resource := range p.Resources {
if name == resource {
if resource == "*" || resource == name {
return true
}
}

View File

@@ -93,12 +93,8 @@ func AddRecord(record *casvisorsdk.Record) bool {
return affected
}
func SendWebhooks(record *casvisorsdk.Record) error {
webhooks, err := getWebhooksByOrganization(record.Organization)
if err != nil {
return err
}
func getFilteredWebhooks(webhooks []*Webhook, action string) []*Webhook {
res := []*Webhook{}
for _, webhook := range webhooks {
if !webhook.IsEnabled {
continue
@@ -106,28 +102,56 @@ func SendWebhooks(record *casvisorsdk.Record) error {
matched := false
for _, event := range webhook.Events {
if record.Action == event {
if action == event {
matched = true
break
}
}
if matched {
var user *User
if webhook.IsUserExtended {
user, err = getUser(record.Organization, record.User)
user, err = GetMaskedUser(user, false, err)
if err != nil {
return err
}
res = append(res, webhook)
}
}
return res
}
func SendWebhooks(record *casvisorsdk.Record) error {
webhooks, err := getWebhooksByOrganization(record.Organization)
if err != nil {
return err
}
errs := []error{}
webhooks = getFilteredWebhooks(webhooks, record.Action)
for _, webhook := range webhooks {
var user *User
if webhook.IsUserExtended {
user, err = getUser(record.Organization, record.User)
if err != nil {
errs = append(errs, err)
continue
}
err = sendWebhook(webhook, record, user)
user, err = GetMaskedUser(user, false, err)
if err != nil {
return err
errs = append(errs, err)
continue
}
}
err = sendWebhook(webhook, record, user)
if err != nil {
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
errStrings := []string{}
for _, err := range errs {
errStrings = append(errStrings, err.Error())
}
return fmt.Errorf(strings.Join(errStrings, " | "))
}
return nil
}

View File

@@ -372,6 +372,24 @@ func GetUserByEmail(owner string, email string) (*User, error) {
}
}
func GetUserByEmailOnly(email string) (*User, error) {
if email == "" {
return nil, nil
}
user := User{Email: email}
existed, err := ormer.Engine.Get(&user)
if err != nil {
return nil, err
}
if existed {
return &user, nil
} else {
return nil, nil
}
}
func GetUserByPhone(owner string, phone string) (*User, error) {
if owner == "" || phone == "" {
return nil, nil
@@ -390,6 +408,24 @@ func GetUserByPhone(owner string, phone string) (*User, error) {
}
}
func GetUserByPhoneOnly(phone string) (*User, error) {
if phone == "" {
return nil, nil
}
user := User{Phone: phone}
existed, err := ormer.Engine.Get(&user)
if err != nil {
return nil, err
}
if existed {
return &user, nil
} else {
return nil, nil
}
}
func GetUserByUserId(owner string, userId string) (*User, error) {
if owner == "" || userId == "" {
return nil, nil

View File

@@ -190,5 +190,5 @@ func isHostIntranet(s string) bool {
return false
}
return ip.IsPrivate()
return ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()
}

View File

@@ -96,10 +96,13 @@ func buildExternalId(user *object.User) optional.String {
func buildMeta(user *object.User) scim.Meta {
createdTime := util.String2Time(user.CreatedTime)
updatedTime := util.String2Time(user.UpdatedTime)
if user.UpdatedTime == "" {
updatedTime = createdTime
}
return scim.Meta{
Created: &createdTime,
LastModified: &updatedTime,
Version: user.Id,
Version: util.Time2String(updatedTime),
}
}

View File

@@ -54,6 +54,10 @@ func String2Time(timestamp string) time.Time {
return parseTime
}
func Time2String(timestamp time.Time) string {
return timestamp.Format(time.RFC3339)
}
func IsTokenExpired(createdTime string, expiresIn int) bool {
createdTimeObj, _ := time.Parse(time.RFC3339, createdTime)
expiresAtObj := createdTimeObj.Add(time.Duration(expiresIn) * time.Second)

View File

@@ -277,7 +277,10 @@ class PermissionEditPage extends React.Component {
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.users}
onChange={(value => {this.updatePermissionField("users", value);})}
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
options={[
Setting.getOption(i18next.t("organization:All"), "*"),
...this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`)),
]}
/>
</Col>
</Row>
@@ -288,7 +291,10 @@ class PermissionEditPage extends React.Component {
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.groups}
onChange={(value => {this.updatePermissionField("groups", value);})}
options={this.state.groups.map((group) => Setting.getOption(`${group.owner}/${group.name}`, `${group.owner}/${group.name}`))}
options={[
Setting.getOption(i18next.t("organization:All"), "*"),
...this.state.groups.map((group) => Setting.getOption(`${group.owner}/${group.name}`, `${group.owner}/${group.name}`)),
]}
/>
</Col>
</Row>
@@ -299,8 +305,11 @@ class PermissionEditPage extends React.Component {
<Col span={22} >
<Select disabled={!this.hasRoleDefinition(this.state.model)} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
onChange={(value => {this.updatePermissionField("roles", value);})}
options={this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission) => Setting.getOption(`${permission.owner}/${permission.name}`, `${permission.owner}/${permission.name}`))
} />
options={[
Setting.getOption(i18next.t("organization:All"), "*"),
...this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission) => Setting.getOption(`${permission.owner}/${permission.name}`, `${permission.owner}/${permission.name}`)),
]}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
@@ -312,8 +321,11 @@ class PermissionEditPage extends React.Component {
onChange={(value => {
this.updatePermissionField("domains", value);
})}
options={this.state.permission.domains.map((domain) => Setting.getOption(domain, domain))
} />
options={[
Setting.getOption(i18next.t("organization:All"), "*"),
...this.state.permission.domains.map((domain) => Setting.getOption(domain, domain)),
]}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
@@ -340,8 +352,11 @@ class PermissionEditPage extends React.Component {
<Col span={22} >
<Select virtual={false} mode={(this.state.permission.resourceType === "Custom") ? "tags" : "multiple"} style={{width: "100%"}} value={this.state.permission.resources}
onChange={(value => {this.updatePermissionField("resources", value);})}
options={this.state.resources.map((resource) => Setting.getOption(`${resource.name}`, `${resource.name}`))
} />
options={[
Setting.getOption(i18next.t("organization:All"), "*"),
...this.state.resources.map((resource) => Setting.getOption(`${resource.name}`, `${resource.name}`)),
]}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >