176 lines
4.0 KiB
Go
176 lines
4.0 KiB
Go
package rules
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/titpetric/factory"
|
|
|
|
"github.com/crusttech/crust/internal/auth"
|
|
)
|
|
|
|
const (
|
|
delimiter = ":"
|
|
everyoneRoleId uint64 = 1
|
|
defaultAccess Access = Deny
|
|
)
|
|
|
|
type (
|
|
resources struct {
|
|
ctx context.Context
|
|
db *factory.DB
|
|
}
|
|
|
|
// CheckAccessFunc function.
|
|
CheckAccessFunc func() Access
|
|
)
|
|
|
|
func NewResources(ctx context.Context, db *factory.DB) ResourcesInterface {
|
|
return (&resources{}).With(ctx, db)
|
|
}
|
|
|
|
func (r *resources) With(ctx context.Context, db *factory.DB) ResourcesInterface {
|
|
return &resources{
|
|
ctx: ctx,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
func (r *resources) identity() uint64 {
|
|
return auth.GetIdentityFromContext(r.ctx).Identity()
|
|
}
|
|
|
|
// IsAllowed function checks granted permission for specific resource and operation. Permission checks on
|
|
// global level are not allowed and will always return Deny.
|
|
func (r *resources) Check(resource string, operation string, fallbacks ...CheckAccessFunc) Access {
|
|
// Number one check, do we have a valid identity?
|
|
if !auth.GetIdentityFromContext(r.ctx).Valid() {
|
|
return Deny
|
|
}
|
|
|
|
parts := strings.Split(resource, delimiter)
|
|
|
|
// Permission check on global level is not allowed.
|
|
if parts[len(parts)-1] == "*" || parts[len(parts)-1] == "" {
|
|
return Deny
|
|
}
|
|
|
|
// Resource-specific check
|
|
checks := []CheckAccessFunc{
|
|
func() Access { return r.checkAccess(resource, operation) },
|
|
func() Access { return r.checkAccessEveryone(resource, operation) },
|
|
}
|
|
|
|
if len(parts) > 1 {
|
|
// If this is a non-service resource (so, not system, messaging),
|
|
// add checks for any-resource (ending with `*`)
|
|
parts[len(parts)-1] = "*"
|
|
anyResource := strings.Join(parts, delimiter)
|
|
|
|
checks = append(
|
|
checks,
|
|
func() Access { return r.checkAccess(anyResource, operation) },
|
|
func() Access { return r.checkAccessEveryone(anyResource, operation) },
|
|
)
|
|
}
|
|
|
|
checks = append(checks, fallbacks...)
|
|
|
|
for _, check := range checks {
|
|
if access := check(); access != Inherit {
|
|
return access
|
|
}
|
|
}
|
|
return defaultAccess
|
|
}
|
|
|
|
func (r *resources) checkAccess(resource string, operation string) Access {
|
|
user := r.identity()
|
|
result := []Access{}
|
|
query := []string{
|
|
// select rules
|
|
"select r.value from sys_rules r",
|
|
// join members
|
|
"inner join sys_role_member m on (m.rel_role = r.rel_role and m.rel_user=?)",
|
|
// add conditions
|
|
"where r.resource=? and r.operation=?",
|
|
}
|
|
queryString := strings.Join(query, " ")
|
|
if err := r.db.Select(&result, queryString, user, resource, operation); err != nil {
|
|
// @todo: log error
|
|
return Deny
|
|
}
|
|
|
|
// order by deny, allow
|
|
for _, val := range result {
|
|
if val == Deny {
|
|
return Deny
|
|
}
|
|
}
|
|
for _, val := range result {
|
|
if val == Allow {
|
|
return Allow
|
|
}
|
|
}
|
|
return Inherit
|
|
}
|
|
|
|
func (r *resources) checkAccessEveryone(resource string, operation string) Access {
|
|
result := []Access{}
|
|
query := []string{
|
|
// select rules
|
|
"select r.value from sys_rules r",
|
|
// add conditions
|
|
"where r.rel_role = ? and r.resource=? and r.operation=?",
|
|
}
|
|
queryString := strings.Join(query, " ")
|
|
if err := r.db.Select(&result, queryString, everyoneRoleId, resource, operation); err != nil {
|
|
// @todo: log error
|
|
return Deny
|
|
}
|
|
|
|
if len(result) > 0 {
|
|
return result[0]
|
|
}
|
|
|
|
return Inherit
|
|
}
|
|
|
|
func (r *resources) Grant(roleID uint64, rules []Rule) error {
|
|
return r.db.Transaction(func() error {
|
|
var err error
|
|
for _, rule := range rules {
|
|
rule.RoleID = roleID
|
|
|
|
switch rule.Value {
|
|
case Inherit:
|
|
_, err = r.db.NamedExec("delete from sys_rules where rel_role=:rel_role and resource=:resource and operation=:operation", rule)
|
|
default:
|
|
err = r.db.Replace("sys_rules", rule)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (r *resources) Read(roleID uint64) ([]Rule, error) {
|
|
result := []Rule{}
|
|
|
|
query := "select * from sys_rules where rel_role = ?"
|
|
if err := r.db.Select(&result, query, roleID); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (r *resources) Delete(roleID uint64) error {
|
|
query := "delete from sys_rules where rel_role = ?"
|
|
if _, err := r.db.Exec(query, roleID); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|