Files
paralus/pkg/service/authz.go
Abin Simon 83621acd1a Disable caching in casbin
Until we implement
https://casbin.org/docs/en/dispatchers#distributedenforcer it is
better to disable caching as it might cause issues otherwise when
running multiple instances of the base.

Also, now that we do not have caching, we don't have the need to
invalidate the cache after update operations.
2022-06-14 10:49:16 +05:30

344 lines
10 KiB
Go

package service
import (
"context"
"fmt"
"github.com/casbin/casbin/v2"
"github.com/paralus/paralus/internal/dao"
"github.com/paralus/paralus/internal/models"
authzpbv1 "github.com/paralus/paralus/proto/types/authz"
"github.com/uptrace/bun"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type AuthzService interface {
Enforce(context.Context, *authzpbv1.EnforceRequest) (*authzpbv1.BoolReply, error)
ListPolicies(context.Context, *authzpbv1.Policy) (*authzpbv1.Policies, error)
CreatePolicies(context.Context, *authzpbv1.Policies) (*authzpbv1.BoolReply, error)
DeletePolicies(context.Context, *authzpbv1.Policy) (*authzpbv1.BoolReply, error)
ListUserGroups(context.Context, *authzpbv1.UserGroup) (*authzpbv1.UserGroups, error)
CreateUserGroups(ctx context.Context, p *authzpbv1.UserGroups) (*authzpbv1.BoolReply, error)
DeleteUserGroups(ctx context.Context, p *authzpbv1.UserGroup) (*authzpbv1.BoolReply, error)
ListRolePermissionMappings(ctx context.Context, p *authzpbv1.FilteredRolePermissionMapping) (*authzpbv1.RolePermissionMappingList, error)
CreateRolePermissionMappings(ctx context.Context, p *authzpbv1.RolePermissionMappingList) (*authzpbv1.BoolReply, error)
DeleteRolePermissionMappings(ctx context.Context, p *authzpbv1.FilteredRolePermissionMapping) (*authzpbv1.BoolReply, error)
}
type authzService struct {
db *bun.DB
enforcer *casbin.CachedEnforcer
mappingCache map[string][]rpmUrlAction
}
func NewAuthzService(db *bun.DB, en *casbin.CachedEnforcer) AuthzService {
en.EnableCache(false) // disables caching in casbin
return &authzService{
db: db,
enforcer: en,
mappingCache: make(map[string][]rpmUrlAction),
}
}
const (
groupGtype = "g2"
roleGtype = "g"
)
type rpmUrlAction struct {
url string
methods []string
}
func processRpms(rpm models.ResourcePermission) []rpmUrlAction {
urls := []rpmUrlAction{}
for _, rurl := range rpm.ResourceUrls {
tmp := rpmUrlAction{
url: rpm.BaseUrl,
methods: make([]string, 0),
}
if url, ok := rurl["url"].(string); ok {
tmp.url = tmp.url + url
}
if methods, ok := rurl["methods"].([]interface{}); ok {
for _, method := range methods {
if met, ok := method.(string); ok {
tmp.methods = append(tmp.methods, met)
}
}
}
urls = append(urls, tmp)
}
for _, raurl := range rpm.ResourceActionUrls {
tmp := rpmUrlAction{
url: rpm.BaseUrl,
methods: make([]string, 0),
}
if url, ok := raurl["url"].(string); ok {
tmp.url = tmp.url + url
}
if methods, ok := raurl["methods"].([]interface{}); ok {
for _, method := range methods {
if met, ok := method.(string); ok {
tmp.methods = append(tmp.methods, met)
}
}
}
urls = append(urls, tmp)
}
return urls
}
func (s *authzService) cacheResourceRolePermissions(ctx context.Context) error {
var items []models.ResourcePermission
entities, err := dao.ListAll(ctx, s.db, &items)
if err != nil {
return err
}
if rpms, ok := entities.(*[]models.ResourcePermission); ok {
for _, rpm := range *rpms {
if perm, ok := s.mappingCache[rpm.Name]; ok {
s.mappingCache[rpm.Name] = append(perm, processRpms(rpm)...)
} else {
s.mappingCache[rpm.Name] = processRpms(rpm)
}
}
}
return nil
}
func (s *authzService) toPolicies(policies [][]string) *authzpbv1.Policies {
if len(policies) == 0 {
return &authzpbv1.Policies{}
}
res := &authzpbv1.Policies{}
res.Policies = make([]*authzpbv1.Policy, len(policies))
for i := range policies {
res.Policies[i] = &authzpbv1.Policy{
Sub: policies[i][0],
Ns: policies[i][1],
Proj: policies[i][2],
Org: policies[i][3],
Obj: policies[i][4],
}
}
return res
}
func (s *authzService) fromPolicies(policies *authzpbv1.Policies) ([][]string, error) {
res := [][]string{}
for i, p := range policies.GetPolicies() {
rule := []string{p.GetSub(), p.GetNs(), p.GetProj(), p.GetOrg(), p.GetObj()}
for _, field := range rule {
if field == "" {
return res, fmt.Errorf(fmt.Sprintf("index %d: policy elements do not meet definition", i))
}
}
res = append(res, rule)
}
return res, nil
}
func (s *authzService) toUserGroups(ug [][]string) *authzpbv1.UserGroups {
if len(ug) == 0 {
return &authzpbv1.UserGroups{}
}
res := &authzpbv1.UserGroups{}
res.UserGroups = make([]*authzpbv1.UserGroup, len(ug))
for i := range ug {
res.UserGroups[i] = &authzpbv1.UserGroup{
User: ug[i][0],
Grp: ug[i][1],
}
}
return res
}
func (s *authzService) fromUserGroups(ugs *authzpbv1.UserGroups) ([][]string, error) {
res := [][]string{}
for i, p := range ugs.GetUserGroups() {
rule := []string{p.GetUser(), p.GetGrp()}
for _, field := range rule {
if field == "" {
return res, fmt.Errorf(fmt.Sprintf("index %d: request elements do not meet definition", i))
}
}
res = append(res, rule)
}
return res, nil
}
func (s *authzService) toRolePermissionMappingList(r [][]string) *authzpbv1.RolePermissionMappingList {
if len(r) == 0 {
return &authzpbv1.RolePermissionMappingList{}
}
rpms := make(map[string][]string)
for _, rpm := range r {
rpms[rpm[1]] = append(rpms[rpm[1]], rpm[0])
}
res := &authzpbv1.RolePermissionMappingList{}
res.RolePermissionMappingList = make([]*authzpbv1.RolePermissionMapping, len(rpms))
var i int
for k, v := range rpms {
i++
res.RolePermissionMappingList[i] = &authzpbv1.RolePermissionMapping{
Role: k,
Permission: v,
}
}
return res
}
func (s *authzService) fromRolePermissionMappingList(ctx context.Context, r *authzpbv1.RolePermissionMappingList) ([][]string, error) {
if len(s.mappingCache) == 0 {
s.cacheResourceRolePermissions(ctx)
}
res := [][]string{}
for i, mapping := range r.GetRolePermissionMappingList() {
for _, permission := range mapping.GetPermission() {
rules := [][]string{}
for _, rpm := range s.mappingCache[permission] {
for _, method := range rpm.methods {
rule := []string{rpm.url, mapping.GetRole(), method}
for _, field := range rule {
if field == "" {
return res, fmt.Errorf(fmt.Sprintf("index %d: mapping elements do not meet definition", i))
}
}
rules = append(rules, rule)
}
}
res = append(res, rules...)
}
}
return res, nil
}
func (s *authzService) Enforce(ctx context.Context, req *authzpbv1.EnforceRequest) (*authzpbv1.BoolReply, error) {
var param interface{}
params := make([]interface{}, 0, len(req.Params))
for index := range req.Params {
param = req.Params[index]
params = append(params, param)
}
res, err := s.enforcer.Enforce(params...)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, err.Error())
}
return &authzpbv1.BoolReply{Res: res}, nil
}
func (s *authzService) ListPolicies(ctx context.Context, p *authzpbv1.Policy) (*authzpbv1.Policies, error) {
return s.toPolicies(s.enforcer.GetFilteredPolicy(0, p.GetSub(), p.GetNs(), p.GetProj(), p.GetOrg(), p.GetObj())), nil
}
func (s *authzService) CreatePolicies(ctx context.Context, p *authzpbv1.Policies) (*authzpbv1.BoolReply, error) {
if len(p.GetPolicies()) == 0 {
return &authzpbv1.BoolReply{Res: false}, nil
}
policies, err := s.fromPolicies(p)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, err.Error())
}
// err could be from db, policy assertions; dispatcher, watcher updates (not pertinent)
res, err := s.enforcer.AddPolicies(policies)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
// s.enforcer.InvalidateCache()
return &authzpbv1.BoolReply{Res: res}, nil
}
func (s *authzService) DeletePolicies(ctx context.Context, p *authzpbv1.Policy) (*authzpbv1.BoolReply, error) {
// err could be from db, policy assertions, cache; dispatcher, watcher updates (not pertinent)
res, err := s.enforcer.RemoveFilteredPolicy(0, p.GetSub(), p.GetNs(), p.GetProj(), p.GetOrg(), p.GetObj())
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
// s.enforcer.InvalidateCache()
return &authzpbv1.BoolReply{Res: res}, nil
}
func (s *authzService) ListUserGroups(ctx context.Context, p *authzpbv1.UserGroup) (*authzpbv1.UserGroups, error) {
return s.toUserGroups(s.enforcer.GetFilteredNamedGroupingPolicy(groupGtype, 0, p.GetUser(), p.GetGrp())), nil
}
func (s *authzService) CreateUserGroups(ctx context.Context, p *authzpbv1.UserGroups) (*authzpbv1.BoolReply, error) {
if len(p.GetUserGroups()) == 0 {
return &authzpbv1.BoolReply{Res: false}, nil
}
ugs, err := s.fromUserGroups(p)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, err.Error())
}
// err could be from db, policy assertions; dispatcher, watcher updates (not pertinent)
res, err := s.enforcer.AddNamedGroupingPolicies(groupGtype, ugs)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
// s.enforcer.InvalidateCache()
return &authzpbv1.BoolReply{Res: res}, nil
}
func (s *authzService) DeleteUserGroups(ctx context.Context, p *authzpbv1.UserGroup) (*authzpbv1.BoolReply, error) {
// err could be from db, policy assertions, cache; dispatcher, watcher updates (not pertinent)
res, err := s.enforcer.RemoveFilteredNamedGroupingPolicy(groupGtype, 0, p.GetUser(), p.GetGrp())
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
// s.enforcer.InvalidateCache()
return &authzpbv1.BoolReply{Res: res}, nil
}
// NOTE: might need identifier per permission in list if inheritance is needed
func (s *authzService) ListRolePermissionMappings(ctx context.Context, p *authzpbv1.FilteredRolePermissionMapping) (*authzpbv1.RolePermissionMappingList, error) {
// TODO: Change list of urls to permissions (many to one)
return s.toRolePermissionMappingList(s.enforcer.GetFilteredNamedGroupingPolicy(roleGtype, 0, p.GetRole())), nil
}
func (s *authzService) CreateRolePermissionMappings(ctx context.Context, p *authzpbv1.RolePermissionMappingList) (*authzpbv1.BoolReply, error) {
if len(p.GetRolePermissionMappingList()) == 0 {
return &authzpbv1.BoolReply{Res: false}, nil
}
rpms, err := s.fromRolePermissionMappingList(ctx, p)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
res, err := s.enforcer.AddNamedGroupingPolicies(roleGtype, rpms)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
// s.enforcer.InvalidateCache()
return &authzpbv1.BoolReply{Res: res}, nil
}
func (s *authzService) DeleteRolePermissionMappings(ctx context.Context, p *authzpbv1.FilteredRolePermissionMapping) (*authzpbv1.BoolReply, error) {
res, err := s.enforcer.RemoveFilteredNamedGroupingPolicy(roleGtype, 1, p.GetRole())
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
// s.enforcer.InvalidateCache()
return &authzpbv1.BoolReply{Res: res}, nil
}