Add endpoint for listing permissions for a resource
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user