mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-26 06:50:36 -05:00
Add endpoint for listing permissions for a resource
This commit is contained in:
1
go.mod
1
go.mod
@@ -34,6 +34,7 @@ require (
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884
|
||||
google.golang.org/protobuf v1.23.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
)
|
||||
|
||||
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
|
||||
|
||||
1
go.sum
1
go.sum
@@ -1352,6 +1352,7 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -567,3 +567,77 @@ func (h *roleServiceHandler) AssignRoleToUser(ctx context.Context, in *AssignRol
|
||||
func (h *roleServiceHandler) RemoveRoleFromUser(ctx context.Context, in *RemoveRoleFromUserRequest, out *empty.Empty) error {
|
||||
return h.RoleServiceHandler.RemoveRoleFromUser(ctx, in, out)
|
||||
}
|
||||
|
||||
// Api Endpoints for PermissionService service
|
||||
|
||||
func NewPermissionServiceEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{
|
||||
&api.Endpoint{
|
||||
Name: "PermissionService.ListPermissionsByResource",
|
||||
Path: []string{"/api/v0/settings/permissions-list-by-resource"},
|
||||
Method: []string{"POST"},
|
||||
Body: "*",
|
||||
Handler: "rpc",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Client API for PermissionService service
|
||||
|
||||
type PermissionService interface {
|
||||
ListPermissionsByResource(ctx context.Context, in *ListPermissionsByResourceRequest, opts ...client.CallOption) (*ListPermissionsByResourceResponse, error)
|
||||
}
|
||||
|
||||
type permissionService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewPermissionService(name string, c client.Client) PermissionService {
|
||||
return &permissionService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *permissionService) ListPermissionsByResource(ctx context.Context, in *ListPermissionsByResourceRequest, opts ...client.CallOption) (*ListPermissionsByResourceResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "PermissionService.ListPermissionsByResource", in)
|
||||
out := new(ListPermissionsByResourceResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for PermissionService service
|
||||
|
||||
type PermissionServiceHandler interface {
|
||||
ListPermissionsByResource(context.Context, *ListPermissionsByResourceRequest, *ListPermissionsByResourceResponse) error
|
||||
}
|
||||
|
||||
func RegisterPermissionServiceHandler(s server.Server, hdlr PermissionServiceHandler, opts ...server.HandlerOption) error {
|
||||
type permissionService interface {
|
||||
ListPermissionsByResource(ctx context.Context, in *ListPermissionsByResourceRequest, out *ListPermissionsByResourceResponse) error
|
||||
}
|
||||
type PermissionService struct {
|
||||
permissionService
|
||||
}
|
||||
h := &permissionServiceHandler{hdlr}
|
||||
opts = append(opts, api.WithEndpoint(&api.Endpoint{
|
||||
Name: "PermissionService.ListPermissionsByResource",
|
||||
Path: []string{"/api/v0/settings/permissions-list-by-resource"},
|
||||
Method: []string{"POST"},
|
||||
Body: "*",
|
||||
Handler: "rpc",
|
||||
}))
|
||||
return s.Handle(s.NewHandler(&PermissionService{h}, opts...))
|
||||
}
|
||||
|
||||
type permissionServiceHandler struct {
|
||||
PermissionServiceHandler
|
||||
}
|
||||
|
||||
func (h *permissionServiceHandler) ListPermissionsByResource(ctx context.Context, in *ListPermissionsByResourceRequest, out *ListPermissionsByResourceResponse) error {
|
||||
return h.PermissionServiceHandler.ListPermissionsByResource(ctx, in, out)
|
||||
}
|
||||
|
||||
@@ -389,6 +389,48 @@ func RegisterRoleServiceWeb(r chi.Router, i RoleServiceHandler, middlewares ...f
|
||||
r.MethodFunc("POST", "/api/v0/settings/assignments-remove", handler.RemoveRoleFromUser)
|
||||
}
|
||||
|
||||
type webPermissionServiceHandler struct {
|
||||
r chi.Router
|
||||
h PermissionServiceHandler
|
||||
}
|
||||
|
||||
func (h *webPermissionServiceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.r.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *webPermissionServiceHandler) ListPermissionsByResource(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
req := &ListPermissionsByResourceRequest{}
|
||||
|
||||
resp := &ListPermissionsByResourceResponse{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusPreconditionFailed)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.h.ListPermissionsByResource(
|
||||
r.Context(),
|
||||
req,
|
||||
resp,
|
||||
); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusCreated)
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
|
||||
func RegisterPermissionServiceWeb(r chi.Router, i PermissionServiceHandler, middlewares ...func(http.Handler) http.Handler) {
|
||||
handler := &webPermissionServiceHandler{
|
||||
r: r,
|
||||
h: i,
|
||||
}
|
||||
|
||||
r.MethodFunc("POST", "/api/v0/settings/permissions-list-by-resource", handler.ListPermissionsByResource)
|
||||
}
|
||||
|
||||
// SaveBundleRequestJSONMarshaler describes the default jsonpb.Marshaler used by all
|
||||
// instances of SaveBundleRequest. This struct is safe to replace or modify but
|
||||
// should not be done so concurrently.
|
||||
@@ -1253,6 +1295,78 @@ func (m *UserRoleAssignment) UnmarshalJSON(b []byte) error {
|
||||
|
||||
var _ json.Unmarshaler = (*UserRoleAssignment)(nil)
|
||||
|
||||
// ListPermissionsByResourceRequestJSONMarshaler describes the default jsonpb.Marshaler used by all
|
||||
// instances of ListPermissionsByResourceRequest. This struct is safe to replace or modify but
|
||||
// should not be done so concurrently.
|
||||
var ListPermissionsByResourceRequestJSONMarshaler = new(jsonpb.Marshaler)
|
||||
|
||||
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
|
||||
// uses the more correct jsonpb package to correctly marshal the message.
|
||||
func (m *ListPermissionsByResourceRequest) MarshalJSON() ([]byte, error) {
|
||||
if m == nil {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
if err := ListPermissionsByResourceRequestJSONMarshaler.Marshal(buf, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
var _ json.Marshaler = (*ListPermissionsByResourceRequest)(nil)
|
||||
|
||||
// ListPermissionsByResourceRequestJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
|
||||
// instances of ListPermissionsByResourceRequest. This struct is safe to replace or modify but
|
||||
// should not be done so concurrently.
|
||||
var ListPermissionsByResourceRequestJSONUnmarshaler = new(jsonpb.Unmarshaler)
|
||||
|
||||
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
|
||||
// uses the more correct jsonpb package to correctly unmarshal the message.
|
||||
func (m *ListPermissionsByResourceRequest) UnmarshalJSON(b []byte) error {
|
||||
return ListPermissionsByResourceRequestJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
|
||||
}
|
||||
|
||||
var _ json.Unmarshaler = (*ListPermissionsByResourceRequest)(nil)
|
||||
|
||||
// ListPermissionsByResourceResponseJSONMarshaler describes the default jsonpb.Marshaler used by all
|
||||
// instances of ListPermissionsByResourceResponse. This struct is safe to replace or modify but
|
||||
// should not be done so concurrently.
|
||||
var ListPermissionsByResourceResponseJSONMarshaler = new(jsonpb.Marshaler)
|
||||
|
||||
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
|
||||
// uses the more correct jsonpb package to correctly marshal the message.
|
||||
func (m *ListPermissionsByResourceResponse) MarshalJSON() ([]byte, error) {
|
||||
if m == nil {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
if err := ListPermissionsByResourceResponseJSONMarshaler.Marshal(buf, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
var _ json.Marshaler = (*ListPermissionsByResourceResponse)(nil)
|
||||
|
||||
// ListPermissionsByResourceResponseJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
|
||||
// instances of ListPermissionsByResourceResponse. This struct is safe to replace or modify but
|
||||
// should not be done so concurrently.
|
||||
var ListPermissionsByResourceResponseJSONUnmarshaler = new(jsonpb.Unmarshaler)
|
||||
|
||||
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
|
||||
// uses the more correct jsonpb package to correctly unmarshal the message.
|
||||
func (m *ListPermissionsByResourceResponse) UnmarshalJSON(b []byte) error {
|
||||
return ListPermissionsByResourceResponseJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
|
||||
}
|
||||
|
||||
var _ json.Unmarshaler = (*ListPermissionsByResourceResponse)(nil)
|
||||
|
||||
// ResourceJSONMarshaler describes the default jsonpb.Marshaler used by all
|
||||
// instances of Resource. This struct is safe to replace or modify but
|
||||
// should not be done so concurrently.
|
||||
|
||||
@@ -118,6 +118,15 @@ service RoleService {
|
||||
}
|
||||
}
|
||||
|
||||
service PermissionService {
|
||||
rpc ListPermissionsByResource(ListPermissionsByResourceRequest) returns (ListPermissionsByResourceResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/api/v0/settings/permissions-list-by-resource",
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
// requests and responses for settings bundles
|
||||
// ---
|
||||
@@ -238,6 +247,19 @@ message UserRoleAssignment {
|
||||
string role_id = 3;
|
||||
}
|
||||
|
||||
// --
|
||||
// requests and responses for permissions
|
||||
// ---
|
||||
|
||||
message ListPermissionsByResourceRequest {
|
||||
Resource resource = 1;
|
||||
repeated string role_ids = 2;
|
||||
}
|
||||
|
||||
message ListPermissionsByResourceResponse {
|
||||
repeated Permission permissions = 1;
|
||||
}
|
||||
|
||||
// ---
|
||||
// resource payloads
|
||||
// ---
|
||||
|
||||
@@ -280,6 +280,38 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v0/settings/permissions-list-by-resource": {
|
||||
"post": {
|
||||
"operationId": "PermissionService_ListPermissionsByResource",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/protoListPermissionsByResourceResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/protoListPermissionsByResourceRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"PermissionService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v0/settings/roles-list": {
|
||||
"post": {
|
||||
"operationId": "RoleService_ListRoles",
|
||||
@@ -668,6 +700,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"protoListPermissionsByResourceRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"resource": {
|
||||
"$ref": "#/definitions/protoResource"
|
||||
},
|
||||
"role_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"protoListPermissionsByResourceResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protoPermission"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"protoListRoleAssignmentsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -3,50 +3,53 @@ package svc
|
||||
import "github.com/owncloud/ocis-settings/pkg/proto/v0"
|
||||
|
||||
func (g Service) hasPermission(
|
||||
assignments []*proto.UserRoleAssignment,
|
||||
roleIDs []string,
|
||||
resource *proto.Resource,
|
||||
operation proto.Permission_Operation,
|
||||
operations []proto.Permission_Operation,
|
||||
constraint proto.Permission_Constraint,
|
||||
) bool {
|
||||
for index := range assignments {
|
||||
if g.isAllowedByRole(assignments[index], resource, operation, constraint) {
|
||||
permissions, err := g.manager.ListPermissionsByResource(resource, roleIDs)
|
||||
if err != nil {
|
||||
g.logger.Debug().Err(err).
|
||||
Str("resource-type", resource.Type.String()).
|
||||
Str("resource-id", resource.Id).
|
||||
Msg("permissions could not be loaded for resource")
|
||||
return false
|
||||
}
|
||||
permissions = getFilteredPermissionsByOperations(permissions, operations)
|
||||
return isConstraintFulfilled(permissions, constraint)
|
||||
}
|
||||
|
||||
// filterPermissionsByOperations returns the subset of the given permissions, where at least one of the given operations is fulfilled.
|
||||
func getFilteredPermissionsByOperations(permissions []*proto.Permission, operations []proto.Permission_Operation) []*proto.Permission {
|
||||
var filteredPermissions []*proto.Permission
|
||||
for _, permission := range permissions {
|
||||
if isAnyOperationFulfilled(permission, operations) {
|
||||
filteredPermissions = append(filteredPermissions, permission)
|
||||
}
|
||||
}
|
||||
return filteredPermissions
|
||||
}
|
||||
|
||||
// isAnyOperationFulfilled checks if the permissions is about any of the operations
|
||||
func isAnyOperationFulfilled(permission *proto.Permission, operations []proto.Permission_Operation) bool {
|
||||
for _, operation := range operations {
|
||||
if operation == permission.Operation {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g Service) isAllowedByRole(
|
||||
assignment *proto.UserRoleAssignment,
|
||||
resource *proto.Resource,
|
||||
operation proto.Permission_Operation,
|
||||
constraint proto.Permission_Constraint,
|
||||
) bool {
|
||||
role, err := g.manager.ReadBundle(assignment.RoleId)
|
||||
if err != nil {
|
||||
g.logger.Err(err).Str("bundle", assignment.RoleId).Msg("Failed to fetch role")
|
||||
return false
|
||||
}
|
||||
for _, setting := range role.Settings {
|
||||
if _, ok := setting.Value.(*proto.Setting_PermissionValue); ok {
|
||||
value := setting.Value.(*proto.Setting_PermissionValue).PermissionValue
|
||||
if resource.Type == setting.Resource.Type &&
|
||||
resource.Id == setting.Resource.Id &&
|
||||
operation == value.Operation &&
|
||||
isConstraintMatch(constraint, value.Constraint) {
|
||||
return true
|
||||
}
|
||||
// isConstraintFulfilled checks if one of the permissions has the same or a parent of the constraint.
|
||||
// this is only a comparison on ENUM level. More sophisticated checks cannot happen here...
|
||||
func isConstraintFulfilled(permissions []*proto.Permission, constraint proto.Permission_Constraint) bool {
|
||||
for _, permission := range permissions {
|
||||
// comparing enum by order is not a feasible solution, because `SHARED` is not a superset of `OWN`.
|
||||
if permission.Constraint == proto.Permission_CONSTRAINT_ALL {
|
||||
return true
|
||||
}
|
||||
return permission.Constraint != proto.Permission_CONSTRAINT_UNKNOWN && permission.Constraint == constraint
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isConstraintMatch checks if the `given` constraint is the same or a superset of the `required` constraint.
|
||||
// this is only a comparison on ENUM level. this is not a check about the appropriate constraint for a resource.
|
||||
func isConstraintMatch(given, required proto.Permission_Constraint) bool {
|
||||
// comparing enum by order is not a feasible solution, because `SHARED` is not a superset of `OWN`.
|
||||
if given == proto.Permission_CONSTRAINT_ALL {
|
||||
return true
|
||||
}
|
||||
return given != proto.Permission_CONSTRAINT_UNKNOWN && given == required
|
||||
}
|
||||
|
||||
@@ -86,13 +86,7 @@ func (g Service) ListBundles(c context.Context, req *proto.ListBundlesRequest, r
|
||||
if err != nil {
|
||||
return merrors.NotFound("ocis-settings", "%s", err)
|
||||
}
|
||||
|
||||
// fetch roles of the user
|
||||
rolesResponse := &proto.ListRoleAssignmentsResponse{}
|
||||
err = g.ListRoleAssignments(c, &proto.ListRoleAssignmentsRequest{AccountUuid: req.AccountUuid}, rolesResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
roleIDs := g.getRoleIDs(c, req.AccountUuid)
|
||||
|
||||
// filter settings in bundles that are allowed according to roles
|
||||
var filteredBundles []*proto.Bundle
|
||||
@@ -104,9 +98,9 @@ func (g Service) ListBundles(c context.Context, req *proto.ListBundlesRequest, r
|
||||
Id: setting.Id,
|
||||
}
|
||||
if g.hasPermission(
|
||||
rolesResponse.Assignments,
|
||||
roleIDs,
|
||||
settingResource,
|
||||
proto.Permission_OPERATION_UPDATE,
|
||||
[]proto.Permission_Operation{proto.Permission_OPERATION_READ},
|
||||
proto.Permission_CONSTRAINT_OWN,
|
||||
) {
|
||||
filteredSettings = append(filteredSettings, setting)
|
||||
@@ -277,6 +271,19 @@ func (g Service) RemoveRoleFromUser(c context.Context, req *proto.RemoveRoleFrom
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListPermissionsByResource implements the PermissionServiceHandler interface
|
||||
func (g Service) ListPermissionsByResource(c context.Context, req *proto.ListPermissionsByResourceRequest, res *proto.ListPermissionsByResourceResponse) error {
|
||||
if validationError := validateListPermissionsByResource(req); validationError != nil {
|
||||
return merrors.BadRequest("ocis-settings", "%s", validationError)
|
||||
}
|
||||
permissions, err := g.manager.ListPermissionsByResource(req.Resource, req.RoleIds)
|
||||
if err != nil {
|
||||
return merrors.BadRequest("ocis-settings", "%s", err)
|
||||
}
|
||||
res.Permissions = permissions
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanUpResource makes sure that the account uuid of the authenticated user is injected if needed.
|
||||
func cleanUpResource(c context.Context, resource *proto.Resource) {
|
||||
if resource != nil && resource.Type == proto.Resource_TYPE_USER {
|
||||
@@ -295,6 +302,24 @@ func getValidatedAccountUUID(c context.Context, accountUUID string) string {
|
||||
return accountUUID
|
||||
}
|
||||
|
||||
// getRoleIDs loads the role assignments for the given accountUUID
|
||||
// TODO: this should work on the context in the future, as roles are supposed to be sent within the context.
|
||||
func (g Service) getRoleIDs(c context.Context, accountUUID string) []string {
|
||||
// TODO: replace this with role ids from the context
|
||||
// WIP PR: https://github.com/owncloud/ocis-proxy/pull/70
|
||||
rolesResponse := &proto.ListRoleAssignmentsResponse{}
|
||||
err := g.ListRoleAssignments(c, &proto.ListRoleAssignmentsRequest{AccountUuid: accountUUID}, rolesResponse)
|
||||
if err != nil {
|
||||
g.logger.Err(err).Str("accountUUID", accountUUID).Msg("failed to list role assignments")
|
||||
return []string{}
|
||||
}
|
||||
var roleIDs []string
|
||||
for _, assignment := range rolesResponse.Assignments {
|
||||
roleIDs = append(roleIDs, assignment.RoleId)
|
||||
}
|
||||
return roleIDs
|
||||
}
|
||||
|
||||
func (g Service) getValueWithIdentifier(value *proto.Value) (*proto.ValueWithIdentifier, error) {
|
||||
bundle, err := g.manager.ReadBundle(value.BundleId)
|
||||
if err != nil {
|
||||
|
||||
@@ -122,6 +122,16 @@ func validateRemoveRoleFromUser(req *proto.RemoveRoleFromUserRequest) error {
|
||||
)
|
||||
}
|
||||
|
||||
func validateListPermissionsByResource(req *proto.ListPermissionsByResourceRequest) error {
|
||||
if err := validateResource(req.Resource); err != nil {
|
||||
return err
|
||||
}
|
||||
return validation.ValidateStruct(
|
||||
req,
|
||||
validation.Field(&req.RoleIds, validation.Each(requireAlphanumeric...)),
|
||||
)
|
||||
}
|
||||
|
||||
// validateResource is an internal helper for validating the content of a resource.
|
||||
func validateResource(resource *proto.Resource) error {
|
||||
if err := validation.Validate(&resource, validation.Required); err != nil {
|
||||
|
||||
@@ -18,6 +18,7 @@ type Manager interface {
|
||||
BundleManager
|
||||
ValueManager
|
||||
RoleAssignmentManager
|
||||
PermissionManager
|
||||
}
|
||||
|
||||
// BundleManager is a bundle service interface for abstraction of storage implementations
|
||||
@@ -44,3 +45,8 @@ type RoleAssignmentManager interface {
|
||||
WriteRoleAssignment(accountUUID, roleID string) (*proto.UserRoleAssignment, error)
|
||||
RemoveRoleAssignment(assignmentID string) error
|
||||
}
|
||||
|
||||
// PermissionManager is a permissions service interface for abstraction of storage implementations
|
||||
type PermissionManager interface {
|
||||
ListPermissionsByResource(resource *proto.Resource, roleIDs []string) ([]*proto.Permission, error)
|
||||
}
|
||||
|
||||
34
pkg/store/filesystem/permissions.go
Normal file
34
pkg/store/filesystem/permissions.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis-settings/pkg/proto/v0"
|
||||
"github.com/owncloud/ocis-settings/pkg/util"
|
||||
)
|
||||
|
||||
// ListPermissionsByResource collects all permissions from the provided roleIDs that match the requested resource
|
||||
func (s Store) ListPermissionsByResource(resource *proto.Resource, roleIDs []string) ([]*proto.Permission, error) {
|
||||
var records []*proto.Permission
|
||||
for _, roleID := range roleIDs {
|
||||
role, err := s.ReadBundle(roleID)
|
||||
if err != nil {
|
||||
s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping")
|
||||
continue
|
||||
}
|
||||
records = append(records, extractPermissionsByResource(resource, role)...)
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// extractPermissionsByResource collects all permissions from the provided role that match the requested resource
|
||||
func extractPermissionsByResource(resource *proto.Resource, role *proto.Bundle) []*proto.Permission {
|
||||
var permissions []*proto.Permission
|
||||
for _, setting := range role.Settings {
|
||||
if _, ok := setting.Value.(*proto.Setting_PermissionValue); ok {
|
||||
value := setting.Value.(*proto.Setting_PermissionValue).PermissionValue
|
||||
if util.IsResourceMatched(setting.Resource, resource) {
|
||||
permissions = append(permissions, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return permissions
|
||||
}
|
||||
15
pkg/util/resource_helper.go
Normal file
15
pkg/util/resource_helper.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package util
|
||||
|
||||
import "github.com/owncloud/ocis-settings/pkg/proto/v0"
|
||||
|
||||
const (
|
||||
ResourceIdAll = "all"
|
||||
)
|
||||
|
||||
// IsResourceMatched checks if the `example` resource is an exact match or a subset of `definition`
|
||||
func IsResourceMatched(definition, example *proto.Resource) bool {
|
||||
if definition.Type != example.Type {
|
||||
return false
|
||||
}
|
||||
return definition.Id == ResourceIdAll || definition.Id == example.Id
|
||||
}
|
||||
90
pkg/util/resource_helper_test.go
Normal file
90
pkg/util/resource_helper_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis-settings/pkg/proto/v0"
|
||||
"gotest.tools/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsResourceMatched(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
definition *proto.Resource
|
||||
example *proto.Resource
|
||||
matched bool
|
||||
}{
|
||||
{
|
||||
"same resource types without ids match",
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"different resource types without ids don't match",
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_USER,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"same resource types with different ids don't match",
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_USER,
|
||||
Id: "einstein",
|
||||
},
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_USER,
|
||||
Id: "marie",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"same resource types with same ids match",
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_USER,
|
||||
Id: "einstein",
|
||||
},
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_USER,
|
||||
Id: "einstein",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"same resource types with definition = ALL and without id in example is a match",
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_USER,
|
||||
Id: ResourceIdAll,
|
||||
},
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_USER,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"same resource types with definition.id = ALL and with some id in example is a match",
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_USER,
|
||||
Id: ResourceIdAll,
|
||||
},
|
||||
&proto.Resource{
|
||||
Type: proto.Resource_TYPE_USER,
|
||||
Id: "einstein",
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
assert.Equal(t, scenario.matched, IsResourceMatched(scenario.definition, scenario.example))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user