mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-04-29 03:33:10 -04:00
Merge pull request #48 from owncloud/filter-settings-by-permissions
Filter settings by permissions
This commit is contained in:
@@ -8,6 +8,10 @@ issues:
|
||||
text: "SA1019:"
|
||||
linters:
|
||||
- staticcheck
|
||||
# Exclude scopelint for tests files because of https://github.com/kyoh86/scopelint/issues/4
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- scopelint
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
|
||||
3
go.mod
3
go.mod
@@ -22,7 +22,7 @@ require (
|
||||
github.com/mitchellh/reflectwalk v1.0.1 // indirect
|
||||
github.com/oklog/run v1.0.0
|
||||
github.com/openzipkin/zipkin-go v0.2.2
|
||||
github.com/owncloud/ocis-pkg/v2 v2.2.2-0.20200812103920-db41b5a3d14d
|
||||
github.com/owncloud/ocis-pkg/v2 v2.4.0
|
||||
github.com/restic/calens v0.2.0
|
||||
github.com/spf13/viper v1.6.3
|
||||
github.com/stretchr/testify v1.6.1
|
||||
@@ -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
|
||||
|
||||
6
go.sum
6
go.sum
@@ -534,6 +534,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/haya14busa/goverage v0.0.0-20180129164344-eec3514a20b5 h1:FdBGmSkD2QpQzRWup//SGObvWf2nq89zj9+ta9OvI3A=
|
||||
github.com/haya14busa/goverage v0.0.0-20180129164344-eec3514a20b5/go.mod h1:0YZ2wQSuwviXXXGUiK6zXzskyBLAbLXhamxzcFHSLoM=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
@@ -760,6 +761,10 @@ github.com/ory/x v0.0.85/go.mod h1:s44V8t3xyjWZREcU+mWlp4h302rTuM4aLXcW+y5FbQ8=
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
|
||||
github.com/owncloud/ocis-pkg/v2 v2.2.2-0.20200812103920-db41b5a3d14d h1:eruHqxLfS3fiPO1ylg60T++wShVqtayI4LxUhwOEuN4=
|
||||
github.com/owncloud/ocis-pkg/v2 v2.2.2-0.20200812103920-db41b5a3d14d/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=
|
||||
github.com/owncloud/ocis-pkg/v2 v2.3.1-0.20200825114153-bc31e3e4b1e0 h1:dz/I+K7+1t3RKrNafDtb6EWJCQe7+z6c4V9UF0bGxCI=
|
||||
github.com/owncloud/ocis-pkg/v2 v2.3.1-0.20200825114153-bc31e3e4b1e0/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=
|
||||
github.com/owncloud/ocis-pkg/v2 v2.4.0 h1:/3ZOd4txtwjiNKJA9iLT9BjrJw5YgHSX13fQR4BYfGY=
|
||||
github.com/owncloud/ocis-pkg/v2 v2.4.0/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
|
||||
@@ -1352,6 +1357,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)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
// ---
|
||||
@@ -138,7 +147,6 @@ message GetBundleResponse {
|
||||
}
|
||||
|
||||
message ListBundlesRequest {
|
||||
string account_uuid = 1;
|
||||
}
|
||||
|
||||
message ListBundlesResponse {
|
||||
@@ -238,6 +246,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
|
||||
// ---
|
||||
@@ -334,6 +355,8 @@ message Permission {
|
||||
OPERATION_READ = 2;
|
||||
OPERATION_UPDATE = 3;
|
||||
OPERATION_DELETE = 4;
|
||||
OPERATION_WRITE = 5;// WRITE is a combination of CREATE and UPDATE
|
||||
OPERATION_READWRITE = 6;// READWRITE is a combination of READ and WRITE
|
||||
}
|
||||
Operation operation = 1;
|
||||
enum Constraint {
|
||||
|
||||
@@ -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",
|
||||
@@ -623,12 +655,7 @@
|
||||
}
|
||||
},
|
||||
"protoListBundlesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"account_uuid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
"type": "object"
|
||||
},
|
||||
"protoListBundlesResponse": {
|
||||
"type": "object",
|
||||
@@ -668,6 +695,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": {
|
||||
@@ -749,7 +801,9 @@
|
||||
"OPERATION_CREATE",
|
||||
"OPERATION_READ",
|
||||
"OPERATION_UPDATE",
|
||||
"OPERATION_DELETE"
|
||||
"OPERATION_DELETE",
|
||||
"OPERATION_WRITE",
|
||||
"OPERATION_READWRITE"
|
||||
],
|
||||
"default": "OPERATION_UNKNOWN"
|
||||
},
|
||||
|
||||
57
pkg/service/v0/permissions.go
Normal file
57
pkg/service/v0/permissions.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package svc
|
||||
|
||||
import "github.com/owncloud/ocis-settings/pkg/proto/v0"
|
||||
|
||||
func (g Service) hasPermission(
|
||||
roleIDs []string,
|
||||
resource *proto.Resource,
|
||||
operations []proto.Permission_Operation,
|
||||
constraint proto.Permission_Constraint,
|
||||
) bool {
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if permission.Constraint != proto.Permission_CONSTRAINT_UNKNOWN && permission.Constraint == constraint {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -2,9 +2,11 @@ package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
merrors "github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
"github.com/owncloud/ocis-pkg/v2/log"
|
||||
"github.com/owncloud/ocis-pkg/v2/middleware"
|
||||
"github.com/owncloud/ocis-settings/pkg/config"
|
||||
@@ -27,23 +29,28 @@ func NewService(cfg *config.Config, logger log.Logger) Service {
|
||||
logger: logger,
|
||||
manager: store.New(cfg),
|
||||
}
|
||||
// FIXME: we're writing default roles per service start (i.e. twice at the moment, for http and grpc server).
|
||||
service.RegisterDefaultRoles()
|
||||
return service
|
||||
}
|
||||
|
||||
// RegisterDefaultRoles composes default roles and saves them. Skipped if the roles already exist.
|
||||
func (g Service) RegisterDefaultRoles() {
|
||||
// FIXME: we're writing default roles per service start (i.e. twice at the moment, for http and grpc server). has to happen only once.
|
||||
for _, role := range generateBundlesDefaultRoles() {
|
||||
bundleID := role.Extension + "." + role.Id
|
||||
// check if the role already exists
|
||||
bundle, _ := service.manager.ReadBundle(role.Id)
|
||||
bundle, _ := g.manager.ReadBundle(role.Id)
|
||||
if bundle != nil {
|
||||
logger.Debug().Msgf("Settings bundle %v already exists. Skipping.", bundleID)
|
||||
g.logger.Debug().Str("bundleID", bundleID).Msg("bundle already exists. skipping.")
|
||||
continue
|
||||
}
|
||||
// create the role
|
||||
_, err := service.manager.WriteBundle(role)
|
||||
_, err := g.manager.WriteBundle(role)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msgf("Failed to register settings bundle %v", bundleID)
|
||||
g.logger.Error().Err(err).Str("bundleID", bundleID).Msg("failed to register bundle")
|
||||
}
|
||||
logger.Debug().Msgf("Successfully registered settings bundle %v", bundleID)
|
||||
g.logger.Debug().Str("bundleID", bundleID).Msg("successfully registered bundle")
|
||||
}
|
||||
return service
|
||||
}
|
||||
|
||||
// TODO: check permissions on every request
|
||||
@@ -64,21 +71,28 @@ func (g Service) SaveBundle(c context.Context, req *proto.SaveBundleRequest, res
|
||||
|
||||
// GetBundle implements the BundleServiceHandler interface
|
||||
func (g Service) GetBundle(c context.Context, req *proto.GetBundleRequest, res *proto.GetBundleResponse) error {
|
||||
accountUUID := getValidatedAccountUUID(c, "me")
|
||||
if validationError := validateGetBundle(req); validationError != nil {
|
||||
return merrors.BadRequest("ocis-settings", "%s", validationError)
|
||||
}
|
||||
r, err := g.manager.ReadBundle(req.BundleId)
|
||||
bundle, err := g.manager.ReadBundle(req.BundleId)
|
||||
if err != nil {
|
||||
return merrors.NotFound("ocis-settings", "%s", err)
|
||||
}
|
||||
res.Bundle = r
|
||||
roleIDs := g.getRoleIDs(c, accountUUID)
|
||||
filteredBundle := g.getFilteredBundle(roleIDs, bundle)
|
||||
if len(filteredBundle.Settings) == 0 {
|
||||
err = fmt.Errorf("could not read bundle: %s", req.BundleId)
|
||||
return merrors.NotFound("ocis-settings", "%s", err)
|
||||
}
|
||||
res.Bundle = filteredBundle
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListBundles implements the BundleServiceHandler interface
|
||||
func (g Service) ListBundles(c context.Context, req *proto.ListBundlesRequest, res *proto.ListBundlesResponse) error {
|
||||
// fetch all bundles
|
||||
req.AccountUuid = getValidatedAccountUUID(c, req.AccountUuid)
|
||||
accountUUID := getValidatedAccountUUID(c, "me")
|
||||
if validationError := validateListBundles(req); validationError != nil {
|
||||
return merrors.BadRequest("ocis-settings", "%s", validationError)
|
||||
}
|
||||
@@ -86,10 +100,56 @@ func (g Service) ListBundles(c context.Context, req *proto.ListBundlesRequest, r
|
||||
if err != nil {
|
||||
return merrors.NotFound("ocis-settings", "%s", err)
|
||||
}
|
||||
res.Bundles = bundles
|
||||
roleIDs := g.getRoleIDs(c, accountUUID)
|
||||
|
||||
// filter settings in bundles that are allowed according to roles
|
||||
var filteredBundles []*proto.Bundle
|
||||
for _, bundle := range bundles {
|
||||
filteredBundle := g.getFilteredBundle(roleIDs, bundle)
|
||||
if len(filteredBundle.Settings) > 0 {
|
||||
filteredBundles = append(filteredBundles, filteredBundle)
|
||||
}
|
||||
}
|
||||
|
||||
res.Bundles = filteredBundles
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g Service) getFilteredBundle(roleIDs []string, bundle *proto.Bundle) *proto.Bundle {
|
||||
// check if full bundle is whitelisted
|
||||
bundleResource := &proto.Resource{
|
||||
Type: proto.Resource_TYPE_BUNDLE,
|
||||
Id: bundle.Id,
|
||||
}
|
||||
if g.hasPermission(
|
||||
roleIDs,
|
||||
bundleResource,
|
||||
[]proto.Permission_Operation{proto.Permission_OPERATION_READ, proto.Permission_OPERATION_READWRITE},
|
||||
proto.Permission_CONSTRAINT_OWN,
|
||||
) {
|
||||
return bundle
|
||||
}
|
||||
|
||||
// filter settings based on permissions
|
||||
var filteredSettings []*proto.Setting
|
||||
for _, setting := range bundle.Settings {
|
||||
settingResource := &proto.Resource{
|
||||
Type: proto.Resource_TYPE_SETTING,
|
||||
Id: setting.Id,
|
||||
}
|
||||
if g.hasPermission(
|
||||
roleIDs,
|
||||
settingResource,
|
||||
[]proto.Permission_Operation{proto.Permission_OPERATION_READ, proto.Permission_OPERATION_READWRITE},
|
||||
proto.Permission_CONSTRAINT_OWN,
|
||||
) {
|
||||
filteredSettings = append(filteredSettings, setting)
|
||||
}
|
||||
}
|
||||
bundle.Settings = filteredSettings
|
||||
return bundle
|
||||
}
|
||||
|
||||
// AddSettingToBundle implements the BundleServiceHandler interface
|
||||
func (g Service) AddSettingToBundle(c context.Context, req *proto.AddSettingToBundleRequest, res *proto.AddSettingToBundleResponse) error {
|
||||
cleanUpResource(c, req.Setting.Resource)
|
||||
@@ -194,7 +254,7 @@ func (g Service) ListValues(c context.Context, req *proto.ListValuesRequest, res
|
||||
|
||||
// ListRoles implements the RoleServiceHandler interface
|
||||
func (g Service) ListRoles(c context.Context, req *proto.ListBundlesRequest, res *proto.ListBundlesResponse) error {
|
||||
req.AccountUuid = getValidatedAccountUUID(c, req.AccountUuid)
|
||||
//accountUUID := getValidatedAccountUUID(c, "me")
|
||||
if validationError := validateListRoles(req); validationError != nil {
|
||||
return merrors.BadRequest("ocis-settings", "%s", validationError)
|
||||
}
|
||||
@@ -202,6 +262,7 @@ func (g Service) ListRoles(c context.Context, req *proto.ListBundlesRequest, res
|
||||
if err != nil {
|
||||
return merrors.NotFound("ocis-settings", "%s", err)
|
||||
}
|
||||
// TODO: only allow to list roles when user has account management permissions
|
||||
res.Bundles = r
|
||||
return nil
|
||||
}
|
||||
@@ -245,6 +306,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 {
|
||||
@@ -256,13 +330,35 @@ func cleanUpResource(c context.Context, resource *proto.Resource) {
|
||||
// the result of this function will always be a valid lower-case UUID or an empty string.
|
||||
func getValidatedAccountUUID(c context.Context, accountUUID string) string {
|
||||
if accountUUID == "me" {
|
||||
if ownAccountUUID, ok := c.Value(middleware.UUIDKey).(string); ok {
|
||||
if ownAccountUUID, ok := metadata.Get(c, middleware.AccountID); ok {
|
||||
accountUUID = ownAccountUUID
|
||||
}
|
||||
}
|
||||
if accountUUID == "me" {
|
||||
// no matter what happens above, an accountUUID of `me` must not be passed on. Clear it instead.
|
||||
accountUUID = ""
|
||||
}
|
||||
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{}
|
||||
}
|
||||
roleIDs := make([]string, 0)
|
||||
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 {
|
||||
|
||||
@@ -4,13 +4,14 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
"github.com/owncloud/ocis-pkg/v2/middleware"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
ctxWithUUID = context.WithValue(context.Background(), middleware.UUIDKey, "61445573-4dbe-4d56-88dc-88ab47aceba7")
|
||||
ctxWithEmptyUUID = context.WithValue(context.Background(), middleware.UUIDKey, "")
|
||||
ctxWithUUID = metadata.Set(context.Background(), middleware.AccountID, "61445573-4dbe-4d56-88dc-88ab47aceba7")
|
||||
ctxWithEmptyUUID = metadata.Set(context.Background(), middleware.AccountID, "")
|
||||
emptyCtx = context.Background()
|
||||
|
||||
scenarios = []struct {
|
||||
@@ -23,11 +24,17 @@ var (
|
||||
name: "context with UUID; identifier = 'me'",
|
||||
ctx: ctxWithUUID,
|
||||
accountUUID: "me",
|
||||
expect: ctxWithUUID.Value(middleware.UUIDKey).(string),
|
||||
expect: "61445573-4dbe-4d56-88dc-88ab47aceba7",
|
||||
},
|
||||
{
|
||||
name: "context with empty UUID; identifier = 'me'",
|
||||
ctx: ctxWithEmptyUUID,
|
||||
accountUUID: "me",
|
||||
expect: "",
|
||||
},
|
||||
{
|
||||
name: "context without UUID; identifier = 'me'",
|
||||
ctx: ctxWithEmptyUUID,
|
||||
ctx: emptyCtx,
|
||||
accountUUID: "me",
|
||||
expect: "",
|
||||
},
|
||||
|
||||
@@ -46,11 +46,11 @@ func validateSaveBundle(req *proto.SaveBundleRequest) error {
|
||||
}
|
||||
|
||||
func validateGetBundle(req *proto.GetBundleRequest) error {
|
||||
return validation.Validate(&req.BundleId, requireAccountID...)
|
||||
return validation.Validate(&req.BundleId, is.UUID)
|
||||
}
|
||||
|
||||
func validateListBundles(req *proto.ListBundlesRequest) error {
|
||||
return validation.Validate(&req.AccountUuid, requireAccountID...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAddSettingToBundle(req *proto.AddSettingToBundleRequest) error {
|
||||
@@ -100,7 +100,7 @@ func validateListValues(req *proto.ListValuesRequest) error {
|
||||
}
|
||||
|
||||
func validateListRoles(req *proto.ListBundlesRequest) error {
|
||||
return validation.Validate(&req.AccountUuid, requireAccountID...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateListRoleAssignments(req *proto.ListRoleAssignmentsRequest) error {
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ func (s Store) ListRoleAssignments(accountUUID string) ([]*proto.UserRoleAssignm
|
||||
assignmentsFolder := s.buildFolderPathForRoleAssignments(false)
|
||||
assignmentFiles, err := ioutil.ReadDir(assignmentsFolder)
|
||||
if err != nil {
|
||||
s.Logger.Error().Err(err).Str("assignmentFiles", assignmentsFolder).Msg("error reading assignment file")
|
||||
return records, nil
|
||||
}
|
||||
|
||||
@@ -35,7 +34,7 @@ func (s Store) ListRoleAssignments(accountUUID string) ([]*proto.UserRoleAssignm
|
||||
|
||||
// WriteRoleAssignment appends the given role assignment to the existing assignments of the respective account.
|
||||
func (s Store) WriteRoleAssignment(accountUUID, roleID string) (*proto.UserRoleAssignment, error) {
|
||||
// as per https://jira.owncloud.com/browse/OCIS-117 "Each user can have exactly one role"
|
||||
// as per https://github.com/owncloud/product/issues/103 "Each user can have exactly one role"
|
||||
list, err := s.ListRoleAssignments(accountUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -73,7 +73,7 @@ func (s Store) ReadSetting(settingID string) (*proto.Setting, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf(settingID, fmt.Sprintf("could not read setting: %v", settingID))
|
||||
return nil, fmt.Errorf("could not read setting: %v", settingID)
|
||||
}
|
||||
|
||||
// WriteBundle writes the given record into a file within the dataPath.
|
||||
|
||||
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) {
|
||||
records := make([]*proto.Permission, 0)
|
||||
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 {
|
||||
permissions := make([]*proto.Permission, 0)
|
||||
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
|
||||
}
|
||||
16
pkg/util/resource_helper.go
Normal file
16
pkg/util/resource_helper.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package util
|
||||
|
||||
import "github.com/owncloud/ocis-settings/pkg/proto/v0"
|
||||
|
||||
const (
|
||||
// ResourceIDAll declares on a resource that it matches any id
|
||||
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
|
||||
}
|
||||
91
pkg/util/resource_helper_test.go
Normal file
91
pkg/util/resource_helper_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/owncloud/ocis-settings/pkg/proto/v0"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
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