Add endpoint for listing permissions for a resource

This commit is contained in:
Benedikt Kulmann
2020-08-24 16:50:28 +02:00
parent e016ebdec8
commit 5fb1a8a2bb
14 changed files with 1060 additions and 452 deletions
+37 -34
View File
@@ -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
}
+34 -9
View File
@@ -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 {
+10
View File
@@ -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 {