From c6170a209c49003dbf19ff277a0d2b3e1b268a53 Mon Sep 17 00:00:00 2001 From: Nikos Date: Mon, 27 Nov 2023 10:44:44 +0200 Subject: [PATCH] feat: Add authorizer struct --- internal/authorization/auth_model.go | 5 ++ internal/authorization/authorization.go | 93 +++++++++++++++++++++++++ internal/authorization/interfaces.go | 21 ++++++ internal/authorization/schema.openfga | 14 ++++ 4 files changed, 133 insertions(+) create mode 100644 internal/authorization/auth_model.go create mode 100644 internal/authorization/authorization.go create mode 100644 internal/authorization/interfaces.go create mode 100644 internal/authorization/schema.openfga diff --git a/internal/authorization/auth_model.go b/internal/authorization/auth_model.go new file mode 100644 index 000000000..18a112909 --- /dev/null +++ b/internal/authorization/auth_model.go @@ -0,0 +1,5 @@ +package authorization + +// Code generated by Makefile; DO NOT EDIT. + +var authModel = `{"schema_version":"1.1","type_definitions":[{"type":"user"},{"type":"app"},{"metadata":{"relations":{"member":{"directly_related_user_types":[{"type":"user"},{"relation":"member","type":"group"}]}}},"relations":{"member":{"this":{}}},"type":"group"},{"metadata":{"relations":{"member":{"directly_related_user_types":[{"type":"app"},{"relation":"member","type":"app_group"}]}}},"relations":{"member":{"this":{}}},"type":"app_group"},{"metadata":{"relations":{"allowed_access":{"directly_related_user_types":[{"type":"app"},{"relation":"member","type":"app_group"}]},"member":{"directly_related_user_types":[{"type":"user"}]}}},"relations":{"allowed_access":{"this":{}},"member":{"this":{}}},"type":"provider"}]}` diff --git a/internal/authorization/authorization.go b/internal/authorization/authorization.go new file mode 100644 index 000000000..9f490fd22 --- /dev/null +++ b/internal/authorization/authorization.go @@ -0,0 +1,93 @@ +package authorization + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/canonical/identity-platform-login-ui/internal/logging" + "github.com/canonical/identity-platform-login-ui/internal/monitoring" + fga "github.com/openfga/go-sdk" + "go.opentelemetry.io/otel/trace" +) + +var ErrInvalidAuthModel = fmt.Errorf("Invalid authorization model schema") + +type Authorizer struct { + Client AuthzClientInterface + + tracer trace.Tracer + monitor monitoring.MonitorInterface + logger logging.LoggerInterface +} + +func (a *Authorizer) Check(ctx context.Context, user string, relation string, object string) (bool, error) { + ctx, span := a.tracer.Start(ctx, "authorization.Authorizer.Check") + defer span.End() + + return a.Client.Check(ctx, user, relation, object) +} + +func (a *Authorizer) ListObjects(ctx context.Context, user string, relation string, objectType string) ([]string, error) { + ctx, span := a.tracer.Start(ctx, "authorization.Authorizer.ListObjects") + defer span.End() + + return a.Client.ListObjects(ctx, user, relation, objectType) +} + +func (a *Authorizer) FilterObjects(ctx context.Context, user string, relation string, objectType string, objs []string) ([]string, error) { + ctx, span := a.tracer.Start(ctx, "authorization.Authorizer.FilterObjects") + defer span.End() + + allowedObjs, err := a.ListObjects(ctx, user, relation, objectType) + if err != nil { + return nil, err + } + + var ret []string + for _, obj := range allowedObjs { + if contains(objs, obj) { + ret = append(ret, obj) + } + } + return ret, nil +} + +func (a *Authorizer) ValidateModel(ctx context.Context) error { + ctx, span := a.tracer.Start(ctx, "authorization.Authorizer.ValidateModel") + defer span.End() + + var builtinAuthorizationModel fga.AuthorizationModel + err := json.Unmarshal([]byte(authModel), &builtinAuthorizationModel) + if err != nil { + return err + } + + eq, err := a.Client.CompareModel(ctx, builtinAuthorizationModel) + if err != nil { + return err + } + if !eq { + return ErrInvalidAuthModel + } + return nil +} + +func NewAuthorizer(client AuthzClientInterface, tracer trace.Tracer, monitor monitoring.MonitorInterface, logger logging.LoggerInterface) *Authorizer { + authorizer := new(Authorizer) + authorizer.Client = client + authorizer.tracer = tracer + authorizer.monitor = monitor + authorizer.logger = logger + + return authorizer +} + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/internal/authorization/interfaces.go b/internal/authorization/interfaces.go new file mode 100644 index 000000000..ef67824da --- /dev/null +++ b/internal/authorization/interfaces.go @@ -0,0 +1,21 @@ +package authorization + +import ( + "context" + + fga "github.com/openfga/go-sdk" +) + +type AuthorizerInterface interface { + ListObjects(context.Context, string, string, string) ([]string, error) + Check(context.Context, string, string, string) (bool, error) + FilterObjects(context.Context, string, string, string, []string) ([]string, error) + ValidateModel(context.Context) error +} + +type AuthzClientInterface interface { + ListObjects(context.Context, string, string, string) ([]string, error) + Check(context.Context, string, string, string) (bool, error) + ReadModel(context.Context) (*fga.AuthorizationModel, error) + CompareModel(context.Context, fga.AuthorizationModel) (bool, error) +} diff --git a/internal/authorization/schema.openfga b/internal/authorization/schema.openfga new file mode 100644 index 000000000..61b3c1bf3 --- /dev/null +++ b/internal/authorization/schema.openfga @@ -0,0 +1,14 @@ +model + schema 1.1 +type user +type app +type group + relations + define member: [user, group#member] +type app_group + relations + define member: [app, app_group#member] +type provider + relations + define member: [user] + define allowed_access: [app, app_group#member]