Add http endpoint to list permissions (#5571)

* Add http endpoint to list permissions

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* extract handler registration

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* use generated protobuf

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* update permissions mock in graph service

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* add unit test

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* return correct userid

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* assert error message type in tests

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

---------

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2023-02-15 14:24:19 +01:00
committed by GitHub
parent cde615886e
commit 92923f6801
9 changed files with 744 additions and 266 deletions
+30
View File
@@ -47,6 +47,36 @@ func (_m *Permissions) GetPermissionByID(ctx context.Context, request *v0.GetPer
return r0, r1
}
// ListPermissions provides a mock function with given fields: ctx, req, opts
func (_m *Permissions) ListPermissions(ctx context.Context, req *v0.ListPermissionsRequest, opts ...client.CallOption) (*v0.ListPermissionsResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, req)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *v0.ListPermissionsResponse
if rf, ok := ret.Get(0).(func(context.Context, *v0.ListPermissionsRequest, ...client.CallOption) *v0.ListPermissionsResponse); ok {
r0 = rf(ctx, req, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v0.ListPermissionsResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v0.ListPermissionsRequest, ...client.CallOption) error); ok {
r1 = rf(ctx, req, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListPermissionsByResource provides a mock function with given fields: ctx, in, opts
func (_m *Permissions) ListPermissionsByResource(ctx context.Context, in *v0.ListPermissionsByResourceRequest, opts ...client.CallOption) (*v0.ListPermissionsByResourceResponse, error) {
_va := make([]interface{}, len(opts))
+1
View File
@@ -71,6 +71,7 @@ type Publisher interface {
// Permissions is the interface used to access the permissions service
type Permissions interface {
ListPermissions(ctx context.Context, req *settingssvc.ListPermissionsRequest, opts ...client.CallOption) (*settingssvc.ListPermissionsResponse, error)
GetPermissionByID(ctx context.Context, request *settingssvc.GetPermissionByIDRequest, opts ...client.CallOption) (*settingssvc.GetPermissionByIDResponse, error)
ListPermissionsByResource(ctx context.Context, in *settingssvc.ListPermissionsByResourceRequest, opts ...client.CallOption) (*settingssvc.ListPermissionsByResourceResponse, error)
}
+57 -8
View File
@@ -5,7 +5,7 @@ import (
"errors"
"fmt"
permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
@@ -54,20 +54,20 @@ func NewService(cfg *config.Config, logger log.Logger) Service {
// CheckPermission implements the CS3 API Permssions service.
// It's used to check if a subject (user or group) has a permission.
func (g Service) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest) (*permissions.CheckPermissionResponse, error) {
func (g Service) CheckPermission(ctx context.Context, req *cs3permissions.CheckPermissionRequest) (*cs3permissions.CheckPermissionResponse, error) {
spec := req.SubjectRef.Spec
var accountID string
switch ref := spec.(type) {
case *permissions.SubjectReference_UserId:
case *cs3permissions.SubjectReference_UserId:
accountID = ref.UserId.OpaqueId
case *permissions.SubjectReference_GroupId:
case *cs3permissions.SubjectReference_GroupId:
accountID = ref.GroupId.OpaqueId
}
assignments, err := g.manager.ListRoleAssignments(accountID)
if err != nil {
return &permissions.CheckPermissionResponse{
return &cs3permissions.CheckPermissionResponse{
Status: status.NewInternal(ctx, err.Error()),
}, nil
}
@@ -80,21 +80,21 @@ func (g Service) CheckPermission(ctx context.Context, req *permissions.CheckPerm
permission, err := g.manager.ReadPermissionByName(req.Permission, roleIDs)
if err != nil {
if !errors.Is(err, settings.ErrNotFound) {
return &permissions.CheckPermissionResponse{
return &cs3permissions.CheckPermissionResponse{
Status: status.NewInternal(ctx, err.Error()),
}, nil
}
}
if permission == nil {
return &permissions.CheckPermissionResponse{
return &cs3permissions.CheckPermissionResponse{
Status: &rpcv1beta1.Status{
Code: rpcv1beta1.Code_CODE_PERMISSION_DENIED,
},
}, nil
}
return &permissions.CheckPermissionResponse{
return &cs3permissions.CheckPermissionResponse{
Status: status.NewOK(ctx),
}, nil
}
@@ -463,6 +463,55 @@ func (g Service) RemoveRoleFromUser(ctx context.Context, req *settingssvc.Remove
return nil
}
// ListPermissions implements the PermissionServiceHandler interface
func (g Service) ListPermissions(ctx context.Context, req *settingssvc.ListPermissionsRequest, res *settingssvc.ListPermissionsResponse) error {
ownAccountUUID, ok := metadata.Get(ctx, middleware.AccountID)
if !ok {
g.logger.Debug().Str("id", g.id).Msg("user not in context")
return merrors.InternalServerError(g.id, "user not in context")
}
if ownAccountUUID != req.AccountUuid {
return merrors.NotFound(g.id, "user not found: %s", req.AccountUuid)
}
assignments, err := g.manager.ListRoleAssignments(req.AccountUuid)
if err != nil {
return err
}
// deduplicate role ids
roleIDs := map[string]struct{}{}
for _, a := range assignments {
roleIDs[a.RoleId] = struct{}{}
}
// deduplicate permission names
permissionNames := map[string]struct{}{}
for roleID := range roleIDs {
bundle, err := g.manager.ReadBundle(roleID)
if err != nil {
if !errors.Is(err, settings.ErrNotFound) {
return err
}
continue
}
if bundle != nil {
for _, setting := range bundle.GetSettings() {
permissionNames[setting.Name] = struct{}{}
}
}
}
res.Permissions = make([]string, 0, len(permissionNames))
for p := range permissionNames {
res.Permissions = append(res.Permissions, p)
}
return nil
}
// ListPermissionsByResource implements the PermissionServiceHandler interface
func (g Service) ListPermissionsByResource(ctx context.Context, req *settingssvc.ListPermissionsByResourceRequest, res *settingssvc.ListPermissionsByResourceResponse) error {
if validationError := validateListPermissionsByResource(req); validationError != nil {
@@ -2,6 +2,7 @@ package svc
import (
"context"
"net/http"
"testing"
"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
@@ -11,6 +12,7 @@ import (
"github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults"
"github.com/stretchr/testify/assert"
"github.com/test-go/testify/mock"
merrors "go-micro.dev/v4/errors"
"go-micro.dev/v4/metadata"
)
@@ -169,3 +171,66 @@ func TestRemoveOwnRoleAssignment(t *testing.T) {
err = svc.RemoveRoleFromUser(ctxWithUUID, &req, nil)
assert.Nil(t, err)
}
func TestListPermissionsOfCurrentUser(t *testing.T) {
manager := &mocks.Manager{}
a := []*settingsmsg.UserRoleAssignment{
{
Id: "00000000-0000-0000-0000-000000000001",
AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7",
RoleId: "aceb15b8-7486-479f-ae32-c91118e07a39",
},
}
manager.On("ListRoleAssignments", mock.Anything).Return(a, nil)
b := &settingsmsg.Bundle{
Id: "aceb15b8-7486-479f-ae32-c91118e07a39",
Settings: []*settingsmsg.Setting{
{
Name: "some-permission",
},
{
Name: "other-permission",
},
{
Name: "duplicate-permission",
},
{
Name: "duplicate-permission",
},
},
}
manager.On("ReadBundle", mock.Anything).Return(b, nil)
svc := Service{
manager: manager,
}
// Listing permissions for yourself
req := v0.ListPermissionsRequest{
AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7",
}
res := v0.ListPermissionsResponse{}
err := svc.ListPermissions(ctxWithUUID, &req, &res)
assert.NoError(t, err)
assert.Len(t, res.Permissions, 3)
}
func TestListPermissionsOfOtherUser(t *testing.T) {
manager := &mocks.Manager{}
svc := Service{
manager: manager,
}
// Listing permissions for another user produces a not found error
req := v0.ListPermissionsRequest{
AccountUuid: "66666666-4444-4444-8888-88ab47aceba7",
}
res := v0.ListPermissionsResponse{}
err := svc.ListPermissions(ctxWithUUID, &req, &res)
assert.Error(t, err)
// assert the requested account uuid was not found
merr, ok := merrors.As(err)
assert.True(t, ok)
assert.Equal(t, int32(http.StatusNotFound), merr.Code)
assert.Contains(t, err.Error(), req.AccountUuid)
}