mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
feat(authz): build authz service (#9064)
* feat(authz): define the domain layer * feat(authz): added openfga schema and split the enterprise code * feat(authz): revert http handler * feat(authz): address comments * feat(authz): address comments * feat(authz): typo comments * feat(authz): address review comments * feat(authz): address review comments * feat(authz): update the oss model * feat(authz): update the sequential check
This commit is contained in:
parent
24307b48ff
commit
0c25de9560
44
ee/authz/openfgaschema/base.fga
Normal file
44
ee/authz/openfgaschema/base.fga
Normal file
@ -0,0 +1,44 @@
|
||||
module base
|
||||
|
||||
type organisation
|
||||
relations
|
||||
define read: [user, role#assignee]
|
||||
define update: [user, role#assignee]
|
||||
|
||||
type user
|
||||
relations
|
||||
define read: [user, role#assignee]
|
||||
define update: [user, role#assignee]
|
||||
define delete: [user, role#assignee]
|
||||
|
||||
type anonymous
|
||||
|
||||
type role
|
||||
relations
|
||||
define assignee: [user]
|
||||
|
||||
define read: [user, role#assignee]
|
||||
define update: [user, role#assignee]
|
||||
define delete: [user, role#assignee]
|
||||
|
||||
type resources
|
||||
relations
|
||||
define create: [user, role#assignee]
|
||||
define list: [user, role#assignee]
|
||||
|
||||
define read: [user, role#assignee]
|
||||
define update: [user, role#assignee]
|
||||
define delete: [user, role#assignee]
|
||||
|
||||
type resource
|
||||
relations
|
||||
define read: [user, anonymous, role#assignee]
|
||||
define update: [user, role#assignee]
|
||||
define delete: [user, role#assignee]
|
||||
|
||||
define block: [user, role#assignee]
|
||||
|
||||
|
||||
type telemetry
|
||||
relations
|
||||
define read: [user, anonymous, role#assignee]
|
||||
29
ee/authz/openfgaschema/schema.go
Normal file
29
ee/authz/openfgaschema/schema.go
Normal file
@ -0,0 +1,29 @@
|
||||
package openfgaschema
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed base.fga
|
||||
baseDSL string
|
||||
)
|
||||
|
||||
type schema struct{}
|
||||
|
||||
func NewSchema() authz.Schema {
|
||||
return &schema{}
|
||||
}
|
||||
|
||||
func (schema *schema) Get(ctx context.Context) []openfgapkgtransformer.ModuleFile {
|
||||
return []openfgapkgtransformer.ModuleFile{
|
||||
{
|
||||
Name: "base.fga",
|
||||
Contents: baseDSL,
|
||||
},
|
||||
}
|
||||
}
|
||||
132
ee/http/middleware/authz.go
Normal file
132
ee/http/middleware/authz.go
Normal file
@ -0,0 +1,132 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
authzDeniedMessage string = "::AUTHZ-DENIED::"
|
||||
)
|
||||
|
||||
type AuthZ struct {
|
||||
logger *slog.Logger
|
||||
authzService authz.AuthZ
|
||||
}
|
||||
|
||||
func NewAuthZ(logger *slog.Logger) *AuthZ {
|
||||
if logger == nil {
|
||||
panic("cannot build authz middleware, logger is empty")
|
||||
}
|
||||
|
||||
return &AuthZ{logger: logger}
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := claims.IsViewer(); err != nil {
|
||||
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) EditAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := claims.IsEditor(); err != nil {
|
||||
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) AdminAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := claims.IsAdmin(); err != nil {
|
||||
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) SelfAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
id := mux.Vars(req)["id"]
|
||||
if err := claims.IsSelfAccess(id); err != nil {
|
||||
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
// Check middleware accepts the relation, typeable, parentTypeable (for direct access + group relations) and a callback function to derive selector and parentSelectors on per request basis.
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, translation authtypes.Relation, typeable authtypes.Typeable, parentTypeable authtypes.Typeable, cb authtypes.SelectorCallbackFn) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
selector, parentSelectors, err := cb(req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(req.Context(), claims, relation, typeable, selector, parentTypeable, parentSelectors...)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
@ -12,4 +13,7 @@ type AuthZ interface {
|
||||
|
||||
// Check returns error when the upstream authorization server is unavailable or the subject (s) doesn't have relation (r) on object (o).
|
||||
Check(context.Context, *openfgav1.CheckRequestTupleKey) error
|
||||
|
||||
// CheckWithTupleCreation takes upon the responsibility for generating the tuples alongside everything Check does.
|
||||
CheckWithTupleCreation(context.Context, authtypes.Claims, authtypes.Relation, authtypes.Typeable, authtypes.Selector, authtypes.Typeable, ...authtypes.Selector) error
|
||||
}
|
||||
|
||||
@ -194,3 +194,40 @@ func (provider *provider) Check(ctx context.Context, tupleReq *openfgav1.CheckRe
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, relation authtypes.Relation, typeable authtypes.Typeable, selector authtypes.Selector, parentTypeable authtypes.Typeable, parentSelectors ...authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeUser, claims.UserID, authtypes.Relation{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := typeable.Tuples(subject, relation, selector, parentTypeable, parentSelectors...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
check, err := provider.sequentialCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !check {
|
||||
return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subject %s cannot %s object %s", subject, relation.StringValue(), typeable.Type().StringValue())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) sequentialCheck(ctx context.Context, tuplesReq []*openfgav1.CheckRequestTupleKey) (bool, error) {
|
||||
for _, tupleReq := range tuplesReq {
|
||||
err := provider.Check(ctx, tupleReq)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if errors.Ast(err, errors.TypeInternal) {
|
||||
// return at the first internal error as the evaluation will be incorrect
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ type role
|
||||
|
||||
type organisation
|
||||
relations
|
||||
define admin: [role#assignee]
|
||||
define editor: [role#assignee] or admin
|
||||
define viewer: [role#assignee] or editor
|
||||
define create: [role#assignee]
|
||||
define read: [role#assignee]
|
||||
define update: [role#assignee]
|
||||
define delete: [role#assignee]
|
||||
@ -106,11 +106,15 @@ func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
})
|
||||
}
|
||||
|
||||
// each individual APIs should be responsible for defining the relation and the object being accessed, subject will be derived from the request
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation string) http.HandlerFunc {
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, _ authtypes.Relation, translation authtypes.Relation, _ authtypes.Typeable, _ authtypes.Typeable, _ authtypes.SelectorCallbackFn) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
checkRequestTupleKey := authtypes.NewTuple("", "", "")
|
||||
err := middleware.authzService.Check(req.Context(), checkRequestTupleKey)
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(req.Context(), claims, translation, authtypes.TypeableOrganization, authtypes.MustNewSelector(authtypes.TypeOrganization, claims.OrgID), nil)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
27
pkg/types/authtypes/name.go
Normal file
27
pkg/types/authtypes/name.go
Normal file
@ -0,0 +1,27 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
nameRegex = regexp.MustCompile("^[a-z]{1,35}$")
|
||||
)
|
||||
|
||||
type Name struct {
|
||||
val string
|
||||
}
|
||||
|
||||
func MustNewName(name string) Name {
|
||||
if !nameRegex.MatchString(name) {
|
||||
panic(errors.NewInternalf(errors.CodeInternal, "name must conform to regex %s", nameRegex.String()))
|
||||
}
|
||||
|
||||
return Name{val: name}
|
||||
}
|
||||
|
||||
func (name Name) String() string {
|
||||
return name.val
|
||||
}
|
||||
23
pkg/types/authtypes/organization.go
Normal file
23
pkg/types/authtypes/organization.go
Normal file
@ -0,0 +1,23 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(organization)
|
||||
|
||||
type organization struct{}
|
||||
|
||||
func (organization *organization) Tuples(subject string, relation Relation, selector Selector, parentTypeable Typeable, parentSelectors ...Selector) ([]*openfgav1.CheckRequestTupleKey, error) {
|
||||
tuples := make([]*openfgav1.CheckRequestTupleKey, 0)
|
||||
object := strings.Join([]string{TypeRole.StringValue(), selector.String()}, ":")
|
||||
tuples = append(tuples, &openfgav1.CheckRequestTupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (organization *organization) Type() Type {
|
||||
return TypeOrganization
|
||||
}
|
||||
23
pkg/types/authtypes/relation.go
Normal file
23
pkg/types/authtypes/relation.go
Normal file
@ -0,0 +1,23 @@
|
||||
package authtypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
var (
|
||||
RelationCreate = Relation{valuer.NewString("create")}
|
||||
RelationRead = Relation{valuer.NewString("read")}
|
||||
RelationUpdate = Relation{valuer.NewString("update")}
|
||||
RelationDelete = Relation{valuer.NewString("delete")}
|
||||
RelationList = Relation{valuer.NewString("list")}
|
||||
RelationBlock = Relation{valuer.NewString("block")}
|
||||
RelationAssignee = Relation{valuer.NewString("assignee")}
|
||||
)
|
||||
|
||||
var (
|
||||
TypeUserSupportedRelations = []Relation{RelationRead, RelationUpdate, RelationDelete}
|
||||
TypeRoleSupportedRelations = []Relation{RelationAssignee, RelationRead, RelationUpdate, RelationDelete}
|
||||
TypeOrganizationSupportedRelations = []Relation{RelationCreate, RelationRead, RelationUpdate, RelationDelete, RelationList}
|
||||
TypeResourceSupportedRelations = []Relation{RelationRead, RelationUpdate, RelationDelete, RelationBlock}
|
||||
TypeResourcesSupportedRelations = []Relation{RelationCreate, RelationRead, RelationUpdate, RelationDelete, RelationList}
|
||||
)
|
||||
|
||||
type Relation struct{ valuer.String }
|
||||
37
pkg/types/authtypes/resource.go
Normal file
37
pkg/types/authtypes/resource.go
Normal file
@ -0,0 +1,37 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(resource)
|
||||
|
||||
type resource struct {
|
||||
name Name
|
||||
}
|
||||
|
||||
func MustNewResource(name string) Typeable {
|
||||
return &resource{name: MustNewName(name)}
|
||||
}
|
||||
|
||||
func (resource *resource) Tuples(subject string, relation Relation, selector Selector, parentTypeable Typeable, parentSelectors ...Selector) ([]*openfgav1.CheckRequestTupleKey, error) {
|
||||
tuples := make([]*openfgav1.CheckRequestTupleKey, 0)
|
||||
for _, selector := range parentSelectors {
|
||||
resourcesTuples, err := parentTypeable.Tuples(subject, relation, selector, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tuples = append(tuples, resourcesTuples...)
|
||||
}
|
||||
|
||||
object := strings.Join([]string{TypeResource.StringValue(), resource.name.String(), selector.String()}, ":")
|
||||
tuples = append(tuples, &openfgav1.CheckRequestTupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (resource *resource) Type() Type {
|
||||
return TypeResource
|
||||
}
|
||||
26
pkg/types/authtypes/resources.go
Normal file
26
pkg/types/authtypes/resources.go
Normal file
@ -0,0 +1,26 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(resources)
|
||||
|
||||
type resources struct {
|
||||
name Name
|
||||
}
|
||||
|
||||
func MustNewResources(name string) Typeable {
|
||||
return &resources{name: MustNewName(name)}
|
||||
}
|
||||
|
||||
func (resources *resources) Tuples(subject string, relation Relation, selector Selector, _ Typeable, _ ...Selector) ([]*openfgav1.CheckRequestTupleKey, error) {
|
||||
object := strings.Join([]string{TypeResources.StringValue(), resources.name.String(), selector.String()}, ":")
|
||||
return []*openfgav1.CheckRequestTupleKey{{User: subject, Relation: relation.StringValue(), Object: object}}, nil
|
||||
}
|
||||
|
||||
func (resources *resources) Type() Type {
|
||||
return TypeResources
|
||||
}
|
||||
31
pkg/types/authtypes/role.go
Normal file
31
pkg/types/authtypes/role.go
Normal file
@ -0,0 +1,31 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(role)
|
||||
|
||||
type role struct{}
|
||||
|
||||
func (role *role) Tuples(subject string, relation Relation, selector Selector, parentTypeable Typeable, parentSelectors ...Selector) ([]*openfgav1.CheckRequestTupleKey, error) {
|
||||
tuples := make([]*openfgav1.CheckRequestTupleKey, 0)
|
||||
for _, selector := range parentSelectors {
|
||||
resourcesTuples, err := parentTypeable.Tuples(subject, relation, selector, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tuples = append(tuples, resourcesTuples...)
|
||||
}
|
||||
|
||||
object := strings.Join([]string{TypeRole.StringValue(), selector.String()}, ":")
|
||||
tuples = append(tuples, &openfgav1.CheckRequestTupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (role *role) Type() Type {
|
||||
return TypeRole
|
||||
}
|
||||
62
pkg/types/authtypes/selector.go
Normal file
62
pkg/types/authtypes/selector.go
Normal file
@ -0,0 +1,62 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
typeUserSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeRoleSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeOrganizationSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeResourceSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeResourcesSelectorRegex = regexp.MustCompile(`^org:[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
)
|
||||
|
||||
type SelectorCallbackFn func(*http.Request) (Selector, []Selector, error)
|
||||
|
||||
type Selector struct {
|
||||
val string
|
||||
}
|
||||
|
||||
func NewSelector(typed Type, selector string) (Selector, error) {
|
||||
switch typed {
|
||||
case TypeUser:
|
||||
if !typeUserSelectorRegex.MatchString(selector) {
|
||||
return Selector{}, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeUserSelectorRegex.String())
|
||||
}
|
||||
case TypeRole:
|
||||
if !typeRoleSelectorRegex.MatchString(selector) {
|
||||
return Selector{}, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeRoleSelectorRegex.String())
|
||||
}
|
||||
case TypeOrganization:
|
||||
if !typeOrganizationSelectorRegex.MatchString(selector) {
|
||||
return Selector{}, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeOrganizationSelectorRegex.String())
|
||||
}
|
||||
case TypeResource:
|
||||
if !typeResourceSelectorRegex.MatchString(selector) {
|
||||
return Selector{}, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeResourceSelectorRegex.String())
|
||||
}
|
||||
case TypeResources:
|
||||
if !typeResourcesSelectorRegex.MatchString(selector) {
|
||||
return Selector{}, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeResourcesSelectorRegex.String())
|
||||
}
|
||||
}
|
||||
|
||||
return Selector{val: selector}, nil
|
||||
}
|
||||
|
||||
func MustNewSelector(typed Type, input string) Selector {
|
||||
selector, err := NewSelector(typed, input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return selector
|
||||
}
|
||||
|
||||
func (selector Selector) String() string {
|
||||
return selector.val
|
||||
}
|
||||
18
pkg/types/authtypes/subject.go
Normal file
18
pkg/types/authtypes/subject.go
Normal file
@ -0,0 +1,18 @@
|
||||
package authtypes
|
||||
|
||||
func NewSubject(subjectType Type, selector string, relation Relation) (string, error) {
|
||||
if relation.IsZero() {
|
||||
return subjectType.StringValue() + ":" + selector, nil
|
||||
}
|
||||
|
||||
return subjectType.StringValue() + ":" + selector + "#" + relation.StringValue(), nil
|
||||
}
|
||||
|
||||
func MustNewSubject(subjectType Type, selector string, relation Relation) string {
|
||||
subject, err := NewSubject(subjectType, selector, relation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return subject
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeAuthZUnavailable = errors.MustNewCode("authz_unavailable")
|
||||
ErrCodeAuthZForbidden = errors.MustNewCode("authz_forbidden")
|
||||
)
|
||||
|
||||
func NewTuple(subject string, relation string, object string) *openfgav1.CheckRequestTupleKey {
|
||||
return &openfgav1.CheckRequestTupleKey{User: subject, Relation: relation, Object: object}
|
||||
}
|
||||
36
pkg/types/authtypes/typeable.go
Normal file
36
pkg/types/authtypes/typeable.go
Normal file
@ -0,0 +1,36 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeAuthZUnavailable = errors.MustNewCode("authz_unavailable")
|
||||
ErrCodeAuthZForbidden = errors.MustNewCode("authz_forbidden")
|
||||
ErrCodeAuthZInvalidSelectorRegex = errors.MustNewCode("authz_invalid_selector_regex")
|
||||
ErrCodeAuthZUnsupportedRelation = errors.MustNewCode("authz_unsupported_relation")
|
||||
ErrCodeAuthZInvalidSubject = errors.MustNewCode("authz_invalid_subject")
|
||||
)
|
||||
|
||||
var (
|
||||
TypeUser = Type{valuer.NewString("user")}
|
||||
TypeRole = Type{valuer.NewString("role")}
|
||||
TypeOrganization = Type{valuer.NewString("organization")}
|
||||
TypeResource = Type{valuer.NewString("resource")}
|
||||
TypeResources = Type{valuer.NewString("resources")}
|
||||
)
|
||||
|
||||
var (
|
||||
TypeableUser = &user{}
|
||||
TypeableRole = &role{}
|
||||
TypeableOrganization = &organization{}
|
||||
)
|
||||
|
||||
type Typeable interface {
|
||||
Type() Type
|
||||
Tuples(subject string, relation Relation, selector Selector, parentType Typeable, parentSelectors ...Selector) ([]*openfgav1.CheckRequestTupleKey, error)
|
||||
}
|
||||
|
||||
type Type struct{ valuer.String }
|
||||
31
pkg/types/authtypes/user.go
Normal file
31
pkg/types/authtypes/user.go
Normal file
@ -0,0 +1,31 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(user)
|
||||
|
||||
type user struct{}
|
||||
|
||||
func (user *user) Tuples(subject string, relation Relation, selector Selector, parentTypeable Typeable, parentSelectors ...Selector) ([]*openfgav1.CheckRequestTupleKey, error) {
|
||||
tuples := make([]*openfgav1.CheckRequestTupleKey, 0)
|
||||
for _, selector := range parentSelectors {
|
||||
resourcesTuples, err := parentTypeable.Tuples(subject, relation, selector, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tuples = append(tuples, resourcesTuples...)
|
||||
}
|
||||
|
||||
object := strings.Join([]string{TypeUser.StringValue(), selector.String()}, ":")
|
||||
tuples = append(tuples, &openfgav1.CheckRequestTupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (user *user) Type() Type {
|
||||
return TypeUser
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user