diff --git a/changelog/unreleased/enhancement-sharing-ng-list-permissions.md b/changelog/unreleased/enhancement-sharing-ng-list-permissions.md new file mode 100644 index 0000000000..e822832939 --- /dev/null +++ b/changelog/unreleased/enhancement-sharing-ng-list-permissions.md @@ -0,0 +1,6 @@ +Enhancement: Add Sharing NG list permissions endpoint + +We've added a new sharing ng endpoint which lists all permissions for a given item. + +https://github.com/owncloud/ocis/pull/7805 +https://github.com/owncloud/ocis/issues/6993 diff --git a/ocis-pkg/conversions/ptr.go b/ocis-pkg/conversions/ptr.go new file mode 100644 index 0000000000..04431dcc70 --- /dev/null +++ b/ocis-pkg/conversions/ptr.go @@ -0,0 +1,38 @@ +package conversions + +// ToPointer converts a value to a pointer +func ToPointer[T any](val T) *T { + return &val +} + +// ToValue converts a pointer to a value +func ToValue[T any](ptr *T) T { + if ptr == nil { + var t T + return t + } + + return *ptr +} + +// ToPointerSlice converts a slice of values to a slice of pointers +func ToPointerSlice[E any](s []E) []*E { + rs := make([]*E, len(s)) + + for i, v := range s { + rs[i] = ToPointer(v) + } + + return rs +} + +// ToValueSlice converts a slice of pointers to a slice of values +func ToValueSlice[E any](s []*E) []E { + rs := make([]E, len(s)) + + for i, v := range s { + rs[i] = ToValue(v) + } + + return rs +} diff --git a/ocis-pkg/conversions/ptr_test.go b/ocis-pkg/conversions/ptr_test.go new file mode 100644 index 0000000000..d29b356bf3 --- /dev/null +++ b/ocis-pkg/conversions/ptr_test.go @@ -0,0 +1,113 @@ +package conversions_test + +import ( + "fmt" + "testing" + + libregraph "github.com/owncloud/libre-graph-api-go" + + "github.com/owncloud/ocis/v2/ocis-pkg/conversions" +) + +func checkIdentical[T any](t *testing.T, p T, want string) { + t.Helper() + got := fmt.Sprintf("%T", p) + if got != want { + t.Errorf("want:%q got:%q", want, got) + } +} + +func TestToPointer2(t *testing.T) { + checkIdentical(t, conversions.ToPointer("a"), "*string") + checkIdentical(t, conversions.ToPointer(1), "*int") + checkIdentical(t, conversions.ToPointer(-1), "*int") + checkIdentical(t, conversions.ToPointer(float64(1)), "*float64") + checkIdentical(t, conversions.ToPointer(float64(-1)), "*float64") + checkIdentical(t, conversions.ToPointer(libregraph.UnifiedRoleDefinition{}), "*libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToPointer([]string{"a"}), "*[]string") + checkIdentical(t, conversions.ToPointer([]int{1}), "*[]int") + checkIdentical(t, conversions.ToPointer([]float64{1}), "*[]float64") + checkIdentical(t, conversions.ToPointer([]libregraph.UnifiedRoleDefinition{{}}), "*[]libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToPointer(conversions.ToPointer("a")), "**string") + checkIdentical(t, conversions.ToPointer(conversions.ToPointer(1)), "**int") + checkIdentical(t, conversions.ToPointer(conversions.ToPointer(-1)), "**int") + checkIdentical(t, conversions.ToPointer(conversions.ToPointer(float64(1))), "**float64") + checkIdentical(t, conversions.ToPointer(conversions.ToPointer(float64(-1))), "**float64") + checkIdentical(t, conversions.ToPointer(conversions.ToPointer(libregraph.UnifiedRoleDefinition{})), "**libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToPointer(conversions.ToPointer([]string{"a"})), "**[]string") + checkIdentical(t, conversions.ToPointer(conversions.ToPointer([]int{1})), "**[]int") + checkIdentical(t, conversions.ToPointer(conversions.ToPointer([]float64{1})), "**[]float64") + checkIdentical(t, conversions.ToPointer(conversions.ToPointer([]libregraph.UnifiedRoleDefinition{{}})), "**[]libregraph.UnifiedRoleDefinition") +} + +func TestToValue(t *testing.T) { + checkIdentical(t, conversions.ToValue((*int)(nil)), "int") + checkIdentical(t, conversions.ToValue((*string)(nil)), "string") + checkIdentical(t, conversions.ToValue((*float64)(nil)), "float64") + checkIdentical(t, conversions.ToValue((*libregraph.UnifiedRoleDefinition)(nil)), "libregraph.UnifiedRoleDefinition") + checkIdentical(t, conversions.ToValue((*[]string)(nil)), "[]string") + checkIdentical(t, conversions.ToValue((*[]libregraph.UnifiedRoleDefinition)(nil)), "[]libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToValue(conversions.ToPointer("a")), "string") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(1)), "int") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(-1)), "int") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(float64(1))), "float64") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(float64(-1))), "float64") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(libregraph.UnifiedRoleDefinition{})), "libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToValue(conversions.ToPointer([]string{"a"})), "[]string") + checkIdentical(t, conversions.ToValue(conversions.ToPointer([]int{1})), "[]int") + checkIdentical(t, conversions.ToValue(conversions.ToPointer([]float64{1})), "[]float64") + checkIdentical(t, conversions.ToValue(conversions.ToPointer([]libregraph.UnifiedRoleDefinition{{}})), "[]libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer("a"))), "*string") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer(1))), "*int") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer(-1))), "*int") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer(float64(1)))), "*float64") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer(float64(-1)))), "*float64") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer(libregraph.UnifiedRoleDefinition{}))), "*libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer([]string{"a"}))), "*[]string") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer([]int{1}))), "*[]int") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer([]float64{1}))), "*[]float64") + checkIdentical(t, conversions.ToValue(conversions.ToPointer(conversions.ToPointer([]libregraph.UnifiedRoleDefinition{{}}))), "*[]libregraph.UnifiedRoleDefinition") +} + +func TestToPointerSlice(t *testing.T) { + checkIdentical(t, conversions.ToPointerSlice([]string{"a"}), "[]*string") + checkIdentical(t, conversions.ToPointerSlice([]int{1}), "[]*int") + checkIdentical(t, conversions.ToPointerSlice([]libregraph.UnifiedRoleDefinition{{}}), "[]*libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToPointerSlice(([]string)(nil)), "[]*string") + checkIdentical(t, conversions.ToPointerSlice(([]int)(nil)), "[]*int") + checkIdentical(t, conversions.ToPointerSlice(([]libregraph.UnifiedRoleDefinition)(nil)), "[]*libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToPointerSlice([]*string{conversions.ToPointer("a")}), "[]**string") + checkIdentical(t, conversions.ToPointerSlice([]*int{conversions.ToPointer(1)}), "[]**int") + checkIdentical(t, conversions.ToPointerSlice(([]*libregraph.UnifiedRoleDefinition)(nil)), "[]**libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToPointerSlice(([]*string)(nil)), "[]**string") + checkIdentical(t, conversions.ToPointerSlice(([]*int)(nil)), "[]**int") + checkIdentical(t, conversions.ToPointerSlice(([]*libregraph.UnifiedRoleDefinition)(nil)), "[]**libregraph.UnifiedRoleDefinition") +} + +func TestToValueSlice(t *testing.T) { + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice([]string{"a"})), "[]string") + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice([]int{1})), "[]int") + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice([]libregraph.UnifiedRoleDefinition{{}})), "[]libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice(([]string)(nil))), "[]string") + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice(([]int)(nil))), "[]int") + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice(([]libregraph.UnifiedRoleDefinition)(nil))), "[]libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice([]*string{conversions.ToPointer("a")})), "[]*string") + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice([]*int{conversions.ToPointer(1)})), "[]*int") + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice([]*libregraph.UnifiedRoleDefinition{conversions.ToPointer(libregraph.UnifiedRoleDefinition{})})), "[]*libregraph.UnifiedRoleDefinition") + + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice(([]*string)(nil))), "[]*string") + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice(([]*int)(nil))), "[]*int") + checkIdentical(t, conversions.ToValueSlice(conversions.ToPointerSlice(([]*libregraph.UnifiedRoleDefinition)(nil))), "[]*libregraph.UnifiedRoleDefinition") +} diff --git a/ocis-pkg/conversions/strings.go b/ocis-pkg/conversions/strings.go index 69f3603635..63cc68f2a7 100644 --- a/ocis-pkg/conversions/strings.go +++ b/ocis-pkg/conversions/strings.go @@ -2,7 +2,6 @@ package conversions import ( "strings" - "unicode/utf8" ) // StringToSliceString splits a string into a slice string according to separator @@ -15,15 +14,3 @@ func StringToSliceString(src string, sep string) []string { return parts } - -// Reverse reverses a string -func Reverse(s string) string { - size := len(s) - buf := make([]byte, size) - for start := 0; start < size; { - r, n := utf8.DecodeRuneInString(s[start:]) - start += n - utf8.EncodeRune(buf[size-start:], r) - } - return string(buf) -} diff --git a/services/graph/pkg/errorcode/cs3.go b/services/graph/pkg/errorcode/cs3.go new file mode 100644 index 0000000000..a8956cb79b --- /dev/null +++ b/services/graph/pkg/errorcode/cs3.go @@ -0,0 +1,62 @@ +package errorcode + +import ( + "slices" + + cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// FromCS3Status converts a CS3 status code into a corresponding local Error representation. +// +// It evaluates the provided CS3 status code and returns an equivalent graph Error. +// If the CS3 status code does not have a direct equivalent within the app, +// or is ignored, a general purpose Error is returned. +// +// This function is particularly useful when dealing with CS3 responses, +// and a unified error handling within the application is necessary. +func FromCS3Status(status *cs3rpc.Status, ignore ...cs3rpc.Code) *Error { + err := &Error{errorCode: GeneralException, msg: "unspecified error has occurred"} + + if status != nil { + err.msg = status.GetMessage() + } + + code := status.GetCode() + switch { + case slices.Contains(ignore, status.GetCode()): + fallthrough + case code == cs3rpc.Code_CODE_OK: + err = nil + case code == cs3rpc.Code_CODE_NOT_FOUND: + err.errorCode = ItemNotFound + case code == cs3rpc.Code_CODE_PERMISSION_DENIED: + err.errorCode = AccessDenied + case code == cs3rpc.Code_CODE_UNAUTHENTICATED: + err.errorCode = Unauthenticated + case code == cs3rpc.Code_CODE_INVALID_ARGUMENT: + err.errorCode = InvalidRequest + case code == cs3rpc.Code_CODE_ALREADY_EXISTS: + err.errorCode = NameAlreadyExists + case code == cs3rpc.Code_CODE_FAILED_PRECONDITION: + err.errorCode = PreconditionFailed + case code == cs3rpc.Code_CODE_UNIMPLEMENTED: + err.errorCode = NotSupported + } + + return err +} + +// FromStat transforms a *provider.StatResponse object and an error into an *Error. +// +// It takes a stat of type *provider.StatResponse, an error, and a variadic parameter of type cs3rpc.Code. +// If the error is not nil, it creates an Error object with the error message and a GeneralException code. +// If the error is nil, it invokes the FromCS3Status function with the StatResponse Status and the ignore codes. +func FromStat(stat *provider.StatResponse, err error, ignore ...cs3rpc.Code) *Error { + switch { + case err != nil: + return &Error{msg: err.Error(), errorCode: GeneralException} + default: + return FromCS3Status(stat.GetStatus(), ignore...) + } +} diff --git a/services/graph/pkg/errorcode/cs3_test.go b/services/graph/pkg/errorcode/cs3_test.go new file mode 100644 index 0000000000..b53fe37116 --- /dev/null +++ b/services/graph/pkg/errorcode/cs3_test.go @@ -0,0 +1,67 @@ +package errorcode_test + +import ( + "errors" + "reflect" + "testing" + + cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + + "github.com/owncloud/ocis/v2/ocis-pkg/conversions" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" +) + +func TestFromCS3Status(t *testing.T) { + var tests = []struct { + status *cs3rpc.Status + ignore []cs3rpc.Code + result *errorcode.Error + }{ + {nil, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "unspecified error has occurred"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_OK}, nil, nil}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_NOT_FOUND}, []cs3rpc.Code{cs3rpc.Code_CODE_NOT_FOUND}, nil}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_PERMISSION_DENIED}, []cs3rpc.Code{cs3rpc.Code_CODE_NOT_FOUND, cs3rpc.Code_CODE_PERMISSION_DENIED}, nil}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_NOT_FOUND, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.ItemNotFound, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_PERMISSION_DENIED, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.AccessDenied, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNAUTHENTICATED, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.Unauthenticated, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_INVALID_ARGUMENT, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRequest, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_ALREADY_EXISTS, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.NameAlreadyExists, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_FAILED_PRECONDITION, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.PreconditionFailed, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNIMPLEMENTED, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.NotSupported, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_INVALID, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_CANCELLED, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNKNOWN, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_RESOURCE_EXHAUSTED, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_ABORTED, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_OUT_OF_RANGE, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_INTERNAL, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNAVAILABLE, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_REDIRECTION, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_INSUFFICIENT_STORAGE, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + {&cs3rpc.Status{Code: cs3rpc.Code_CODE_LOCKED, Message: "msg"}, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))}, + } + + for _, test := range tests { + if output := errorcode.FromCS3Status(test.status, test.ignore...); !reflect.DeepEqual(output, test.result) { + t.Error("Test Failed: {} expected, recieved: {}", test.result, output) + } + } +} + +func TestFromStat(t *testing.T) { + var tests = []struct { + stat *provider.StatResponse + err error + result *errorcode.Error + }{ + {nil, errors.New("some error"), conversions.ToPointer(errorcode.New(errorcode.GeneralException, "some error"))}, + {&provider.StatResponse{Status: &cs3rpc.Status{Code: cs3rpc.Code_CODE_OK}}, nil, nil}, + } + + for _, test := range tests { + if output := errorcode.FromStat(test.stat, test.err); !reflect.DeepEqual(output, test.result) { + t.Error("Test Failed: {} expected, recieved: {}", test.result, output) + } + } +} diff --git a/services/graph/pkg/service/v0/errorcode/errorcode.go b/services/graph/pkg/errorcode/errorcode.go similarity index 100% rename from services/graph/pkg/service/v0/errorcode/errorcode.go rename to services/graph/pkg/errorcode/errorcode.go diff --git a/services/graph/pkg/identity/backend.go b/services/graph/pkg/identity/backend.go index 4079b15a4f..cc94141d3a 100644 --- a/services/graph/pkg/identity/backend.go +++ b/services/graph/pkg/identity/backend.go @@ -8,7 +8,7 @@ import ( cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) // Errors used by the interfaces diff --git a/services/graph/pkg/identity/cache.go b/services/graph/pkg/identity/cache.go index a0bdf65d86..f394b040ca 100644 --- a/services/graph/pkg/identity/cache.go +++ b/services/graph/pkg/identity/cache.go @@ -12,7 +12,7 @@ import ( revautils "github.com/cs3org/reva/v2/pkg/utils" "github.com/jellydator/ttlcache/v3" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) // IdentityCache implements a simple ttl based cache for looking up users and groups by ID diff --git a/services/graph/pkg/identity/cs3.go b/services/graph/pkg/identity/cs3.go index 730e661086..7a305c22fb 100644 --- a/services/graph/pkg/identity/cs3.go +++ b/services/graph/pkg/identity/cs3.go @@ -13,7 +13,7 @@ import ( libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/shared" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) var ( diff --git a/services/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go index 42ad441df7..d2f11432a0 100644 --- a/services/graph/pkg/identity/ldap.go +++ b/services/graph/pkg/identity/ldap.go @@ -17,7 +17,7 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/graph/pkg/config" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) const ( diff --git a/services/graph/pkg/identity/ldap_education_class.go b/services/graph/pkg/identity/ldap_education_class.go index 283506e39a..daba2f9f17 100644 --- a/services/graph/pkg/identity/ldap_education_class.go +++ b/services/graph/pkg/identity/ldap_education_class.go @@ -8,7 +8,7 @@ import ( "github.com/go-ldap/ldap/v3" "github.com/libregraph/idm/pkg/ldapdn" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) type educationClassAttributeMap struct { diff --git a/services/graph/pkg/identity/ldap_education_school.go b/services/graph/pkg/identity/ldap_education_school.go index fe02f4b262..a412d77a00 100644 --- a/services/graph/pkg/identity/ldap_education_school.go +++ b/services/graph/pkg/identity/ldap_education_school.go @@ -11,7 +11,7 @@ import ( libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/graph/pkg/config" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) type educationConfig struct { diff --git a/services/graph/pkg/identity/ldap_education_school_test.go b/services/graph/pkg/identity/ldap_education_school_test.go index 520cdc5c90..336b310ad8 100644 --- a/services/graph/pkg/identity/ldap_education_school_test.go +++ b/services/graph/pkg/identity/ldap_education_school_test.go @@ -10,7 +10,7 @@ import ( libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/services/graph/mocks" "github.com/owncloud/ocis/v2/services/graph/pkg/config" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/services/graph/pkg/identity/ldap_education_user.go b/services/graph/pkg/identity/ldap_education_user.go index 8755ec7e15..4d0b2750be 100644 --- a/services/graph/pkg/identity/ldap_education_user.go +++ b/services/graph/pkg/identity/ldap_education_user.go @@ -8,7 +8,7 @@ import ( "github.com/go-ldap/ldap/v3" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) type educationUserAttributeMap struct { diff --git a/services/graph/pkg/identity/ldap_group.go b/services/graph/pkg/identity/ldap_group.go index e994d9c9de..ab02f5327e 100644 --- a/services/graph/pkg/identity/ldap_group.go +++ b/services/graph/pkg/identity/ldap_group.go @@ -14,7 +14,7 @@ import ( "github.com/libregraph/idm/pkg/ldapdn" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) type groupAttributeMap struct { diff --git a/services/graph/pkg/middleware/auth.go b/services/graph/pkg/middleware/auth.go index 6becccf902..6b06da06ea 100644 --- a/services/graph/pkg/middleware/auth.go +++ b/services/graph/pkg/middleware/auth.go @@ -3,15 +3,16 @@ package middleware import ( "net/http" + gmmetadata "go-micro.dev/v4/metadata" + "google.golang.org/grpc/metadata" + "github.com/cs3org/reva/v2/pkg/auth/scope" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/token/manager/jwt" "github.com/owncloud/ocis/v2/ocis-pkg/account" "github.com/owncloud/ocis/v2/ocis-pkg/log" opkgm "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" - gmmetadata "go-micro.dev/v4/metadata" - "google.golang.org/grpc/metadata" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) // authOptions initializes the available default options. diff --git a/services/graph/pkg/middleware/requireadmin.go b/services/graph/pkg/middleware/requireadmin.go index ef3dd64fe4..39bc0102d3 100644 --- a/services/graph/pkg/middleware/requireadmin.go +++ b/services/graph/pkg/middleware/requireadmin.go @@ -6,7 +6,7 @@ import ( revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/roles" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" settings "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" ) diff --git a/services/graph/pkg/middleware/token.go b/services/graph/pkg/middleware/token.go index b75f4be849..2765961806 100644 --- a/services/graph/pkg/middleware/token.go +++ b/services/graph/pkg/middleware/token.go @@ -5,7 +5,7 @@ import ( "crypto/subtle" "net/http" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) var ( diff --git a/services/graph/pkg/service/v0/application.go b/services/graph/pkg/service/v0/application.go index c7c7c7a51c..0c9cb0dae4 100644 --- a/services/graph/pkg/service/v0/application.go +++ b/services/graph/pkg/service/v0/application.go @@ -8,7 +8,7 @@ import ( "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) // ListApplications implements the Service interface. diff --git a/services/graph/pkg/service/v0/approleassignments.go b/services/graph/pkg/service/v0/approleassignments.go index fd64f60111..ce70a8b133 100644 --- a/services/graph/pkg/service/v0/approleassignments.go +++ b/services/graph/pkg/service/v0/approleassignments.go @@ -12,7 +12,7 @@ import ( libregraph "github.com/owncloud/libre-graph-api-go" settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" merrors "go-micro.dev/v4/errors" ) diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go index dad7029d02..7124c3995a 100644 --- a/services/graph/pkg/service/v0/driveitems.go +++ b/services/graph/pkg/service/v0/driveitems.go @@ -17,6 +17,7 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/go-chi/chi/v5" @@ -25,12 +26,16 @@ import ( "golang.org/x/crypto/sha3" "golang.org/x/sync/errgroup" + "github.com/cs3org/reva/v2/pkg/publicshare" + "github.com/cs3org/reva/v2/pkg/share" + revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/ocis-pkg/conversions" "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" "github.com/owncloud/ocis/v2/services/graph/pkg/validate" ) @@ -244,16 +249,82 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, &ListResponse{Value: files}) } -// Invite invites a user to a storage drive (space). -func (g Graph) Invite(w http.ResponseWriter, r *http.Request) { - gatewayClient, err := g.gatewaySelector.Next() - if err != nil { - g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) +// ListPermissions lists the permissions of a driveItem +func (g Graph) ListPermissions(w http.ResponseWriter, r *http.Request) { + gatewayClient, ok := g.GetGatewayClient(w, r) + if !ok { return } - _, itemID, err := g.extractDriveAndDriveItem(r) + _, itemID, err := g.GetDriveAndItemIDParam(r) + if err != nil { + errorcode.RenderError(w, r, err) + return + } + + ctx := r.Context() + + statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}}) + if errCode := errorcode.FromStat(statResponse, err); errCode != nil { + g.logger.Warn().Err(errCode).Interface("stat.res", statResponse) + errCode.Render(w, r) + return + } + + permissionSet := *statResponse.GetInfo().GetPermissionSet() + allowedActions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(permissionSet) + + collectionOfPermissions := libregraph.CollectionOfPermissionsWithAllowedValues{ + LibreGraphPermissionsActionsAllowedValues: allowedActions, + LibreGraphPermissionsRolesAllowedValues: conversions.ToValueSlice( + unifiedrole.GetApplicableRoleDefinitionsForActions( + allowedActions, + unifiedrole.UnifiedRoleConditionGrantee, + g.config.FilesSharing.EnableResharing, + false, + ), + ), + } + + for i, definition := range collectionOfPermissions.LibreGraphPermissionsRolesAllowedValues { + // the openapi spec defines that the rolePermissions should not be part of the response + definition.RolePermissions = nil + collectionOfPermissions.LibreGraphPermissionsRolesAllowedValues[i] = definition + } + + driveItems := make(driveItemsByResourceID) + driveItems, err = g.listUserShares(ctx, []*collaboration.Filter{ + share.ResourceIDFilter(conversions.ToPointer(itemID)), + }, driveItems) + if err != nil { + errorcode.RenderError(w, r, err) + return + } + + driveItems, err = g.listPublicShares(ctx, []*link.ListPublicSharesRequest_Filter{ + publicshare.ResourceIDFilter(conversions.ToPointer(itemID)), + }, driveItems) + if err != nil { + errorcode.RenderError(w, r, err) + return + } + + for _, driveItem := range driveItems { + collectionOfPermissions.Value = append(collectionOfPermissions.Value, driveItem.Permissions...) + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, collectionOfPermissions) +} + +// Invite invites a user to a storage drive (space). +func (g Graph) Invite(w http.ResponseWriter, r *http.Request) { + gatewayClient, ok := g.GetGatewayClient(w, r) + if !ok { + return + } + + _, itemID, err := g.GetDriveAndItemIDParam(r) if err != nil { errorcode.RenderError(w, r, err) return @@ -268,23 +339,13 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - if err = validate.StructCtx(ctx, driveItemInvite); err != nil { + if err := validate.StructCtx(ctx, driveItemInvite); err != nil { g.logger.Debug().Err(err).Interface("Body", r.Body).Msg("invalid request body") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) return } - statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}}) - switch { - case err != nil: - fallthrough - case statResponse.GetStatus().GetCode() != cs3rpc.Code_CODE_OK: - g.logger.Debug().Err(err).Interface("itemID", itemID).Interface("Stat", statResponse).Msg("stat failed") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) - return - } - - unifiedRolePermissions := []libregraph.UnifiedRolePermission{{AllowedResourceActions: driveItemInvite.LibreGraphPermissionsActions}} + unifiedRolePermissions := []*libregraph.UnifiedRolePermission{{AllowedResourceActions: driveItemInvite.LibreGraphPermissionsActions}} for _, roleId := range driveItemInvite.GetRoles() { role, err := unifiedrole.NewUnifiedRoleFromID(roleId, g.config.FilesSharing.EnableResharing) if err != nil { @@ -293,7 +354,14 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) { return } - unifiedRolePermissions = append(unifiedRolePermissions, role.GetRolePermissions()...) + unifiedRolePermissions = append(unifiedRolePermissions, conversions.ToPointerSlice(role.GetRolePermissions())...) + } + + statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}}) + if errCode := errorcode.FromStat(statResponse, err); errCode != nil { + g.logger.Warn().Err(errCode).Interface("stat.res", statResponse) + errCode.Render(w, r) + return } createShareErrors := sync.Map{} @@ -349,7 +417,7 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) { permission.GrantedToV2 = &libregraph.SharePointIdentitySet{ Group: &libregraph.Identity{ DisplayName: group.GetDisplayName(), - Id: libregraph.PtrString(group.GetId()), + Id: conversions.ToPointer(group.GetId()), }, } default: @@ -368,7 +436,7 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) { permission.GrantedToV2 = &libregraph.SharePointIdentitySet{ User: &libregraph.Identity{ DisplayName: user.GetDisplayName(), - Id: libregraph.PtrString(user.GetId()), + Id: conversions.ToPointer(user.GetId()), }, } } @@ -388,7 +456,7 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) { } if id := createShareResponse.GetShare().GetId().GetOpaqueId(); id != "" { - permission.Id = libregraph.PtrString(id) + permission.Id = conversions.ToPointer(id) } if expiration := createShareResponse.GetShare().GetExpiration(); expiration != nil { diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go index b5441ed610..a3ef19371f 100644 --- a/services/graph/pkg/service/v0/driveitems_test.go +++ b/services/graph/pkg/service/v0/driveitems_test.go @@ -14,6 +14,7 @@ import ( grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" @@ -23,12 +24,15 @@ import ( "github.com/tidwall/gjson" "google.golang.org/grpc" + "github.com/cs3org/reva/v2/pkg/storagespace" + revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" + "github.com/owncloud/ocis/v2/ocis-pkg/conversions" "github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/services/graph/mocks" "github.com/owncloud/ocis/v2/services/graph/pkg/config" @@ -233,10 +237,7 @@ var _ = Describe("Driveitems", func() { Describe("Invite", func() { var ( - itemID string driveItemInvite *libregraph.DriveItemInvite - listSharesMock *mock.Call - listSharesResponse *collaboration.ListSharesResponse statMock *mock.Call statResponse *provider.StatResponse getUserResponse *userpb.GetUserResponse @@ -248,10 +249,9 @@ var _ = Describe("Driveitems", func() { ) BeforeEach(func() { - itemID = "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!1177add3-b4eb-434e-a2e8-1859b31b17bf" rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!cd621428-dfbe-44c1-9393-65bf0dd440a6") - rctx.URLParams.Add("itemID", itemID) + rctx.URLParams.Add("driveID", "1$2") + rctx.URLParams.Add("itemID", "1$2!3") ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) ctx = revactx.ContextSetUser(ctx, currentUser) @@ -289,12 +289,6 @@ var _ = Describe("Driveitems", func() { } getGroupMock.Return(getGroupResponse, nil) - listSharesMock = gatewayClient.On("ListShares", mock.Anything, mock.Anything) - listSharesResponse = &collaboration.ListSharesResponse{ - Status: status.NewOK(ctx), - } - listSharesMock.Return(listSharesResponse, nil) - createShareMock = gatewayClient.On("CreateShare", mock.Anything, mock.Anything) createShareResponse = &collaboration.CreateShareResponse{ Status: status.NewOK(ctx), @@ -378,53 +372,6 @@ var _ = Describe("Driveitems", func() { Expect(jsonData.Get(`0.@libre\.graph\.permissions\.actions.0`).String()).To(Equal(unifiedrole.DriveItemContentRead)) }) - It("validates the driveID", func() { - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "") - - ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx) - - svc.Invite( - rr, - httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)). - WithContext(ctx), - ) - - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - }) - - It("validates the itemID", func() { - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!cd621428-dfbe-44c1-9393-65bf0dd440a6") - rctx.URLParams.Add("itemID", "") - - ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx) - - svc.Invite( - rr, - httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)). - WithContext(ctx), - ) - - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - }) - - It("checks if the itemID and driveID is compatible to each other", func() { - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "1$2!3") - rctx.URLParams.Add("itemID", "4$5!6") - - ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx) - - svc.Invite( - rr, - httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)). - WithContext(ctx), - ) - - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - }) - It("fails if the request body is empty", func() { svc.Invite( rr, @@ -450,26 +397,6 @@ var _ = Describe("Driveitems", func() { }, http.StatusBadRequest), ) - DescribeTable("Stat", - func(prep func(), code int) { - prep() - svc.Invite( - rr, - httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)). - WithContext(ctx), - ) - - Expect(rr.Code).To(Equal(code)) - statMock.Parent.AssertNumberOfCalls(GinkgoT(), "Stat", 1) - }, - Entry("fails if not ok", func() { - statResponse.Status = status.NewNotFound(context.Background(), "") - }, http.StatusInternalServerError), - Entry("fails if errors", func() { - statMock.Return(nil, errors.New("error")) - }, http.StatusInternalServerError), - ) - DescribeTable("GetGroup", func(prep func(), code int) { driveItemInvite.Recipients = []libregraph.DriveRecipient{ @@ -538,6 +465,90 @@ var _ = Describe("Driveitems", func() { ) }) + Describe("ListPermissions", func() { + var ( + statMock *mock.Call + statResponse *provider.StatResponse + listSharesMock *mock.Call + listSharesResponse *collaboration.ListSharesResponse + listPublicSharesMock *mock.Call + listPublicSharesResponse *link.ListPublicSharesResponse + ) + + toResourceID := func(in string) *provider.ResourceId { + out, err := storagespace.ParseID(in) + Expect(err).To(BeNil()) + + return &out + } + + BeforeEach(func() { + rctx := chi.NewRouteContext() + rctx.URLParams.Add("driveID", "1$2") + rctx.URLParams.Add("itemID", "1$2!3") + + ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) + ctx = revactx.ContextSetUser(ctx, currentUser) + + statMock = gatewayClient.On("Stat", mock.Anything, mock.Anything) + statResponse = &provider.StatResponse{ + Status: status.NewOK(ctx), + Info: &provider.ResourceInfo{ + Id: toResourceID("1$2!3"), + PermissionSet: unifiedrole.PermissionsToCS3ResourcePermissions( + conversions.ToPointerSlice(unifiedrole.NewViewerUnifiedRole(true).GetRolePermissions()), + ), + Owner: &userpb.UserId{}, + }, + } + statMock.Return(statResponse, nil) + + listSharesMock = gatewayClient.On("ListShares", mock.Anything, mock.Anything) + listSharesResponse = &collaboration.ListSharesResponse{ + Status: status.NewOK(ctx), + Shares: []*collaboration.Share{{ + Id: &collaboration.ShareId{OpaqueId: "123"}, + ResourceId: toResourceID("1$2!3"), + Grantee: &provider.Grantee{}, + Permissions: &collaboration.SharePermissions{ + Permissions: unifiedrole.PermissionsToCS3ResourcePermissions( + conversions.ToPointerSlice(unifiedrole.NewViewerUnifiedRole(true).GetRolePermissions()), + ), + }, + }}, + } + listSharesMock.Return(listSharesResponse, nil) + + listPublicSharesMock = gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything) + listPublicSharesResponse = &link.ListPublicSharesResponse{ + Status: status.NewOK(ctx), + } + listPublicSharesMock.Return(listPublicSharesResponse, nil) + }) + + It("lists permissions", func() { + svc.ListPermissions( + rr, + httptest.NewRequest(http.MethodGet, "/", nil). + WithContext(ctx), + ) + + Expect(rr.Code).To(Equal(http.StatusOK)) + + actions := gjson.Get(rr.Body.String(), `@libre\.graph\.permissions\.actions\.allowedValues`) + Expect(actions.Get("#").Num).To(Equal(float64(7))) + + roles := gjson.Get(rr.Body.String(), `@libre\.graph\.permissions\.roles\.allowedValues`) + Expect(roles.Get("#").Num).To(Equal(float64(1))) + Expect(roles.Get("0.id").Str).To(Equal("b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5")) + Expect(roles.Get("0.rolePermissions").Exists()).To(BeFalse()) + + value := gjson.Get(rr.Body.String(), "value") + Expect(value.Get("#").Num).To(Equal(float64(1))) + Expect(value.Get("0.id").Str).To(Equal("123")) + }) + }) + Describe("GetRootDriveChildren", func() { It("handles ListStorageSpaces not found", func() { gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{ diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go index 9f8f785377..9cfcb12dbe 100644 --- a/services/graph/pkg/service/v0/drives.go +++ b/services/graph/pkg/service/v0/drives.go @@ -19,19 +19,20 @@ import ( cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/storagespace" - "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" - settingsServiceExt "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" "github.com/pkg/errors" merrors "go-micro.dev/v4/errors" "golang.org/x/sync/errgroup" + + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" + settingsServiceExt "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" ) const ( diff --git a/services/graph/pkg/service/v0/educationclasses.go b/services/graph/pkg/service/v0/educationclasses.go index 942e9fa98b..061b2638a6 100644 --- a/services/graph/pkg/service/v0/educationclasses.go +++ b/services/graph/pkg/service/v0/educationclasses.go @@ -11,7 +11,7 @@ import ( revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/events" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/go-chi/chi/v5" "github.com/go-chi/render" diff --git a/services/graph/pkg/service/v0/educationclasses_test.go b/services/graph/pkg/service/v0/educationclasses_test.go index 765b19e801..a140bad02e 100644 --- a/services/graph/pkg/service/v0/educationclasses_test.go +++ b/services/graph/pkg/service/v0/educationclasses_test.go @@ -22,9 +22,9 @@ import ( "github.com/owncloud/ocis/v2/services/graph/mocks" "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" "github.com/stretchr/testify/mock" "google.golang.org/grpc" ) diff --git a/services/graph/pkg/service/v0/educationschools.go b/services/graph/pkg/service/v0/educationschools.go index 8a176ef205..dce131fdde 100644 --- a/services/graph/pkg/service/v0/educationschools.go +++ b/services/graph/pkg/service/v0/educationschools.go @@ -11,8 +11,8 @@ import ( "github.com/CiscoM31/godata" libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" "github.com/go-chi/chi/v5" "github.com/go-chi/render" diff --git a/services/graph/pkg/service/v0/educationschools_test.go b/services/graph/pkg/service/v0/educationschools_test.go index 9b9e4e57a5..6f49ab0963 100644 --- a/services/graph/pkg/service/v0/educationschools_test.go +++ b/services/graph/pkg/service/v0/educationschools_test.go @@ -25,9 +25,9 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" ) type schoolList struct { diff --git a/services/graph/pkg/service/v0/educationuser.go b/services/graph/pkg/service/v0/educationuser.go index 8ae884ee8c..d636973121 100644 --- a/services/graph/pkg/service/v0/educationuser.go +++ b/services/graph/pkg/service/v0/educationuser.go @@ -15,7 +15,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) // GetEducationUsers implements the Service interface. diff --git a/services/graph/pkg/service/v0/graph.go b/services/graph/pkg/service/v0/graph.go index 36e3c3b7b4..4b5c60f541 100644 --- a/services/graph/pkg/service/v0/graph.go +++ b/services/graph/pkg/service/v0/graph.go @@ -26,8 +26,8 @@ import ( searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" ) //go:generate make -C ../../.. generate diff --git a/services/graph/pkg/service/v0/graph_test.go b/services/graph/pkg/service/v0/graph_test.go index e7b6acdaa0..970dd35b66 100644 --- a/services/graph/pkg/service/v0/graph_test.go +++ b/services/graph/pkg/service/v0/graph_test.go @@ -29,8 +29,8 @@ import ( "github.com/owncloud/ocis/v2/services/graph/mocks" "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" "github.com/pkg/errors" "github.com/stretchr/testify/mock" "google.golang.org/grpc" diff --git a/services/graph/pkg/service/v0/groups.go b/services/graph/pkg/service/v0/groups.go index dfd1424deb..434ebef4b1 100644 --- a/services/graph/pkg/service/v0/groups.go +++ b/services/graph/pkg/service/v0/groups.go @@ -10,7 +10,7 @@ import ( "github.com/CiscoM31/godata" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/events" diff --git a/services/graph/pkg/service/v0/groups_test.go b/services/graph/pkg/service/v0/groups_test.go index e0f2c84634..d492798e23 100644 --- a/services/graph/pkg/service/v0/groups_test.go +++ b/services/graph/pkg/service/v0/groups_test.go @@ -22,9 +22,9 @@ import ( "github.com/owncloud/ocis/v2/services/graph/mocks" "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" "github.com/stretchr/testify/mock" "google.golang.org/grpc" ) diff --git a/services/graph/pkg/service/v0/links.go b/services/graph/pkg/service/v0/links.go index 5e814920a7..5efb1bdfe8 100644 --- a/services/graph/pkg/service/v0/links.go +++ b/services/graph/pkg/service/v0/links.go @@ -12,11 +12,12 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" + + "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/linktype" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" ) // CreateLink creates a public link on the cs3 api @@ -24,8 +25,9 @@ func (g Graph) CreateLink(w http.ResponseWriter, r *http.Request) { logger := g.logger.SubloggerWithRequestID(r.Context()) logger.Info().Msg("calling create link") - _, driveItemID, ok := g.GetDriveAndItemIDParam(w, r) - if !ok { + _, driveItemID, err := g.GetDriveAndItemIDParam(r) + if err != nil { + errorcode.RenderError(w, r, err) return } diff --git a/services/graph/pkg/service/v0/net/headers.go b/services/graph/pkg/service/v0/net/headers.go deleted file mode 100644 index 84316eb157..0000000000 --- a/services/graph/pkg/service/v0/net/headers.go +++ /dev/null @@ -1,9 +0,0 @@ -package net - -const ( - // "github.com/cs3org/reva/v2/internal/http/services/datagateway" is internal so we redeclare it here - // HeaderTokenTransport holds the header key for the reva transfer token - HeaderTokenTransport = "X-Reva-Transfer" - // HeaderIfModifiedSince is used to mimic/pass on caching headers when using grpc - HeaderIfModifiedSince = "If-Modified-Since" -) diff --git a/services/graph/pkg/service/v0/password.go b/services/graph/pkg/service/v0/password.go index f272feda57..30dfb2a3e8 100644 --- a/services/graph/pkg/service/v0/password.go +++ b/services/graph/pkg/service/v0/password.go @@ -7,11 +7,12 @@ import ( "github.com/CiscoM31/godata" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/events" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) // ChangeOwnPassword implements the Service interface. It allows the user to change diff --git a/services/graph/pkg/service/v0/personaldata.go b/services/graph/pkg/service/v0/personaldata.go index f5df48f2b6..09d90e8f2e 100644 --- a/services/graph/pkg/service/v0/personaldata.go +++ b/services/graph/pkg/service/v0/personaldata.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strconv" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" diff --git a/services/graph/pkg/service/v0/rolemanagement.go b/services/graph/pkg/service/v0/rolemanagement.go index a7b479c926..549fbbab8e 100644 --- a/services/graph/pkg/service/v0/rolemanagement.go +++ b/services/graph/pkg/service/v0/rolemanagement.go @@ -8,7 +8,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" ) diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index 33e3559dc7..7286e7c12b 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -110,6 +110,7 @@ type Service interface { CreateLink(w http.ResponseWriter, r *http.Request) Invite(w http.ResponseWriter, r *http.Request) + ListPermissions(w http.ResponseWriter, r *http.Request) DeletePermission(w http.ResponseWriter, r *http.Request) GetTags(w http.ResponseWriter, r *http.Request) @@ -202,8 +203,9 @@ func NewService(opts ...Option) (Graph, error) { r.Get("/me/drive/sharedWithMe", svc.ListSharedWithMe) r.Route("/drives/{driveID}/items/{itemID}", func(r chi.Router) { r.Post("/invite", svc.Invite) - r.Post("/createLink", svc.CreateLink) + r.Get("/permissions", svc.ListPermissions) r.Delete("/permissions/{permissionID}", svc.DeletePermission) + r.Post("/createLink", svc.CreateLink) }) r.Route("/roleManagement/permissions/roleDefinitions", func(r chi.Router) { r.Get("/", svc.GetRoleDefinitions) @@ -222,13 +224,13 @@ func NewService(opts ...Option) (Graph, error) { }) r.Route("/me", func(r chi.Router) { r.Get("/", svc.GetMe) - r.Get("/drive", svc.GetUserDrive) - r.Route("/drives", func(r chi.Router) { - r.Get("/", svc.GetDrives) - }) - r.Get("/drive/root/children", svc.GetRootDriveChildren) - r.Post("/changePassword", svc.ChangeOwnPassword) r.Patch("/", svc.PatchMe) + r.Route("/drive", func(r chi.Router) { + r.Get("/", svc.GetUserDrive) + r.Get("/root/children", svc.GetRootDriveChildren) + }) + r.Get("/drives", svc.GetDrives) + r.Post("/changePassword", svc.ChangeOwnPassword) }) r.Route("/users", func(r chi.Router) { r.With(requireAdmin).Get("/", svc.GetUsers) diff --git a/services/graph/pkg/service/v0/sharedbyme.go b/services/graph/pkg/service/v0/sharedbyme.go index 79cab7c38c..3c45c8c390 100644 --- a/services/graph/pkg/service/v0/sharedbyme.go +++ b/services/graph/pkg/service/v0/sharedbyme.go @@ -9,12 +9,14 @@ import ( collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/share" - "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" + + "github.com/cs3org/reva/v2/pkg/share" + "github.com/cs3org/reva/v2/pkg/storagespace" + + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" ) @@ -27,13 +29,13 @@ func (g Graph) GetSharedByMe(w http.ResponseWriter, r *http.Request) { driveItems := make(driveItemsByResourceID) var err error - driveItems, err = g.listUserShares(ctx, driveItems) + driveItems, err = g.listUserShares(ctx, nil, driveItems) if err != nil { errorcode.RenderError(w, r, err) return } - driveItems, err = g.listPublicShares(ctx, driveItems) + driveItems, err = g.listPublicShares(ctx, nil, driveItems) if err != nil { errorcode.RenderError(w, r, err) return @@ -48,19 +50,21 @@ func (g Graph) GetSharedByMe(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, &ListResponse{Value: res}) } -func (g Graph) listUserShares(ctx context.Context, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { +func (g Graph) listUserShares(ctx context.Context, filters []*collaboration.Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { g.logger.Error().Err(err).Msg("could not select next gateway client") return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) } - filters := []*collaboration.Filter{ + concreteFilters := []*collaboration.Filter{ share.UserGranteeFilter(), share.GroupGranteeFilter(), } + concreteFilters = append(concreteFilters, filters...) + lsUserSharesRequest := collaboration.ListSharesRequest{ - Filters: filters, + Filters: concreteFilters, } lsUserSharesResponse, err := gatewayClient.ListShares(ctx, &lsUserSharesRequest) @@ -77,7 +81,7 @@ func (g Graph) listUserShares(ctx context.Context, driveItems driveItemsByResour return driveItems, nil } -func (g Graph) listPublicShares(ctx context.Context, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { +func (g Graph) listPublicShares(ctx context.Context, filters []*link.ListPublicSharesRequest_Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { @@ -85,10 +89,11 @@ func (g Graph) listPublicShares(ctx context.Context, driveItems driveItemsByReso return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) } - filters := []*link.ListPublicSharesRequest_Filter{} + var concreteFilters []*link.ListPublicSharesRequest_Filter + concreteFilters = append(concreteFilters, filters...) req := link.ListPublicSharesRequest{ - Filters: filters, + Filters: concreteFilters, } lsPublicSharesResponse, err := gatewayClient.ListPublicShares(ctx, &req) diff --git a/services/graph/pkg/service/v0/sharedwithme.go b/services/graph/pkg/service/v0/sharedwithme.go index 755a7ac9db..56457e3a71 100644 --- a/services/graph/pkg/service/v0/sharedwithme.go +++ b/services/graph/pkg/service/v0/sharedwithme.go @@ -12,8 +12,8 @@ import ( "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" ) // ListSharedWithMe lists the files shared with the current user. diff --git a/services/graph/pkg/service/v0/sharedwithme_test.go b/services/graph/pkg/service/v0/sharedwithme_test.go index 3b44f99518..5e7cfeec94 100644 --- a/services/graph/pkg/service/v0/sharedwithme_test.go +++ b/services/graph/pkg/service/v0/sharedwithme_test.go @@ -25,9 +25,9 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" ) var _ = Describe("SharedWithMe", func() { diff --git a/services/graph/pkg/service/v0/tags.go b/services/graph/pkg/service/v0/tags.go index 594daaf649..22a9720d95 100644 --- a/services/graph/pkg/service/v0/tags.go +++ b/services/graph/pkg/service/v0/tags.go @@ -13,7 +13,7 @@ import ( "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "go-micro.dev/v4/metadata" ) diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index 870d047f63..04e4a2c23e 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -31,8 +31,8 @@ import ( settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" ocissettingssvc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" ) diff --git a/services/graph/pkg/service/v0/utils.go b/services/graph/pkg/service/v0/utils.go index 7aa0ea1ee0..d566448bf5 100644 --- a/services/graph/pkg/service/v0/utils.go +++ b/services/graph/pkg/service/v0/utils.go @@ -5,8 +5,10 @@ import ( "io" "net/http" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) // StrictJSONUnmarshal is a wrapper around json.Unmarshal that returns an error if the json contains unknown fields. @@ -16,36 +18,56 @@ func StrictJSONUnmarshal(r io.Reader, v interface{}) error { return dec.Decode(v) } +// IsSpaceRoot returns true if the resourceID is a space root. +func IsSpaceRoot(rid *storageprovider.ResourceId) bool { + if rid == nil { + return false + } + if rid.GetSpaceId() == "" || rid.GetOpaqueId() == "" { + return false + } + + return rid.GetSpaceId() == rid.GetOpaqueId() +} + // GetDriveAndItemIDParam parses the driveID and itemID from the request, // validates the common fields and returns the parsed IDs if ok. -func (g Graph) GetDriveAndItemIDParam(w http.ResponseWriter, r *http.Request) (storageprovider.ResourceId, storageprovider.ResourceId, bool) { +func (g Graph) GetDriveAndItemIDParam(r *http.Request) (storageprovider.ResourceId, storageprovider.ResourceId, error) { empty := storageprovider.ResourceId{} driveID, err := parseIDParam(r, "driveID") if err != nil { g.logger.Debug().Err(err).Msg("could not parse driveID") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid driveID") - return empty, empty, false + return empty, empty, errorcode.New(errorcode.InvalidRequest, "invalid driveID") } itemID, err := parseIDParam(r, "itemID") if err != nil { g.logger.Debug().Err(err).Msg("could not parse itemID") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid itemID") - return empty, empty, false + return empty, empty, errorcode.New(errorcode.InvalidRequest, "invalid itemID") } if itemID.GetOpaqueId() == "" { g.logger.Debug().Interface("driveID", driveID).Interface("itemID", itemID).Msg("empty item opaqueID") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid itemID") - return empty, empty, false + return empty, empty, errorcode.New(errorcode.InvalidRequest, "invalid itemID") } if driveID.GetStorageId() != itemID.GetStorageId() || driveID.GetSpaceId() != itemID.GetSpaceId() { g.logger.Debug().Interface("driveID", driveID).Interface("itemID", itemID).Msg("driveID and itemID do not match") - errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "driveID and itemID do not match") - return empty, empty, false + return empty, empty, errorcode.New(errorcode.ItemNotFound, "driveID and itemID do not match") } - return driveID, itemID, true + return driveID, itemID, nil +} + +// GetGatewayClient returns a gateway client from the gatewaySelector. +func (g Graph) GetGatewayClient(w http.ResponseWriter, r *http.Request) (gateway.GatewayAPIClient, bool) { + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + return nil, false + } + + return gatewayClient, true } diff --git a/services/graph/pkg/service/v0/utils_test.go b/services/graph/pkg/service/v0/utils_test.go new file mode 100644 index 0000000000..cff11c2bbf --- /dev/null +++ b/services/graph/pkg/service/v0/utils_test.go @@ -0,0 +1,89 @@ +package svc_test + +import ( + "context" + "net/http" + "net/http/httptest" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/go-chi/chi/v5" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" +) + +var _ = Describe("Utils", func() { + var ( + svc service.Graph + ) + + BeforeEach(func() { + cfg := defaults.FullDefaultConfig() + cfg.GRPCClientTLS = &shared.GRPCClientTLS{} + + svc, _ = service.NewService( + service.Config(cfg), + ) + }) + + DescribeTable("GetDriveAndItemIDParam", + func(driveID, itemID string, shouldPass bool) { + rctx := chi.NewRouteContext() + rctx.URLParams.Add("driveID", driveID) + rctx.URLParams.Add("itemID", itemID) + + extractedDriveID, extractedItemID, err := svc.GetDriveAndItemIDParam( + httptest.NewRequest(http.MethodGet, "/", nil). + WithContext( + context.WithValue(context.Background(), chi.RouteCtxKey, rctx), + ), + ) + + switch shouldPass { + case true: + Expect(err).To(BeNil()) + parsedItemID, _ := storagespace.ParseID(itemID) + Expect(extractedItemID).To(Equal(parsedItemID)) + + parsedDriveID, _ := storagespace.ParseID(driveID) + Expect(extractedDriveID).To(Equal(parsedDriveID)) + default: + Expect(err).ToNot(BeNil()) + } + }, + Entry("fails: invalid driveID", "", "1$2!3", false), + Entry("fails: invalid itemID", "1$2", "", false), + Entry("fails: incompatible driveID and itemID", "1$2", "3$4!5", false), + Entry("fails: no itemID opaqueId", "1$2", "3$4", false), + Entry("pass: valid driveID and itemID", "1$2", "1$2!5", true), + ) + + DescribeTable("IsSpaceRoot", + func(resourceID *provider.ResourceId, isRoot bool) { + Expect(service.IsSpaceRoot(resourceID)).To(Equal(isRoot)) + }, + Entry("spaceId and opaqueID equal", &provider.ResourceId{ + StorageId: "1", + OpaqueId: "2", + SpaceId: "2", + }, true), + Entry("nil", nil, false), + Entry("spaceID empty", &provider.ResourceId{ + StorageId: "1", + OpaqueId: "2", + }, false), + Entry("opaqueID empty", &provider.ResourceId{ + StorageId: "1", + SpaceId: "3", + }, false), + Entry("spaceID and opaqueID unequal", &provider.ResourceId{ + OpaqueId: "2", + SpaceId: "3", + }, false), + ) +}) diff --git a/services/graph/pkg/unifiedrole/unifiedrole.go b/services/graph/pkg/unifiedrole/unifiedrole.go index 7c59192b94..a2d0948fb0 100644 --- a/services/graph/pkg/unifiedrole/unifiedrole.go +++ b/services/graph/pkg/unifiedrole/unifiedrole.go @@ -1,7 +1,9 @@ package unifiedrole import ( + "cmp" "errors" + "slices" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/conversions" @@ -217,8 +219,72 @@ func GetBuiltinRoleDefinitionList(resharing bool) []*libregraph.UnifiedRoleDefin } } +// GetApplicableRoleDefinitionsForActions returns a list of role definitions +// that match the provided actions and constraints +func GetApplicableRoleDefinitionsForActions(actions []string, constraints string, resharing, descending bool) []*libregraph.UnifiedRoleDefinition { + var definitions []*libregraph.UnifiedRoleDefinition + + for _, definition := range GetBuiltinRoleDefinitionList(resharing) { + match := true + + for _, permission := range definition.GetRolePermissions() { + if permission.GetCondition() != constraints { + match = false + break + } + + for _, action := range permission.GetAllowedResourceActions() { + if !slices.Contains(actions, action) { + match = false + break + } + } + } + + if !match { + continue + } + + definitions = append(definitions, definition) + } + + return WeightRoleDefinitions(definitions, descending) +} + +// WeightRoleDefinitions sorts the provided role definitions by the number of permissions[n].actions they grant, +// the implementation is optimistic and assumes that the weight relies on the number of available actions. +// descending - false - sorts the roles from least to most permissions +// descending - true - sorts the roles from most to least permissions +func WeightRoleDefinitions(roleDefinitions []*libregraph.UnifiedRoleDefinition, descending bool) []*libregraph.UnifiedRoleDefinition { + slices.SortFunc(roleDefinitions, func(i, j *libregraph.UnifiedRoleDefinition) int { + var ia []string + for _, rp := range i.GetRolePermissions() { + ia = append(ia, rp.GetAllowedResourceActions()...) + } + + var ja []string + for _, rp := range j.GetRolePermissions() { + ja = append(ja, rp.GetAllowedResourceActions()...) + } + + switch descending { + case true: + return cmp.Compare(len(ja), len(ia)) + default: + return cmp.Compare(len(ia), len(ja)) + } + }) + + for i, definition := range roleDefinitions { + definition.LibreGraphWeight = libregraph.PtrInt32(int32(i) + 1) + } + + // return for the sage of consistency, optional because the slice is modified in place + return roleDefinitions +} + // PermissionsToCS3ResourcePermissions converts the provided libregraph UnifiedRolePermissions to a cs3 ResourcePermissions -func PermissionsToCS3ResourcePermissions(unifiedRolePermissions []libregraph.UnifiedRolePermission) *provider.ResourcePermissions { +func PermissionsToCS3ResourcePermissions(unifiedRolePermissions []*libregraph.UnifiedRolePermission) *provider.ResourcePermissions { p := &provider.ResourcePermissions{} for _, permission := range unifiedRolePermissions { diff --git a/services/graph/pkg/unifiedrole/unifiedrole_test.go b/services/graph/pkg/unifiedrole/unifiedrole_test.go index 721cf17671..2d35901f37 100644 --- a/services/graph/pkg/unifiedrole/unifiedrole_test.go +++ b/services/graph/pkg/unifiedrole/unifiedrole_test.go @@ -2,8 +2,11 @@ package unifiedrole_test import ( "fmt" + "slices" + + rConversions "github.com/cs3org/reva/v2/pkg/conversions" + "github.com/owncloud/ocis/v2/ocis-pkg/conversions" - "github.com/cs3org/reva/v2/pkg/conversions" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/types" @@ -14,24 +17,26 @@ import ( var _ = Describe("unifiedroles", func() { DescribeTable("CS3ResourcePermissionsToUnifiedRole", - func(legacyRole *conversions.Role, unifiedRole *libregraph.UnifiedRoleDefinition) { + func(legacyRole *rConversions.Role, unifiedRole *libregraph.UnifiedRoleDefinition) { cs3perm := legacyRole.CS3ResourcePermissions() r := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3perm, unifiedrole.UnifiedRoleConditionGrantee, true) Expect(r.GetId()).To(Equal(unifiedRole.GetId())) }, - Entry(conversions.RoleViewer, conversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true)), - Entry(conversions.RoleEditor, conversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true)), - Entry(conversions.RoleFileEditor, conversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true)), - Entry(conversions.RoleCoowner, conversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole()), - Entry(conversions.RoleManager, conversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole()), + Entry(rConversions.RoleViewer, rConversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true)), + Entry(rConversions.RoleEditor, rConversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true)), + Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true)), + Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole()), + Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole()), ) DescribeTable("UnifiedRolePermissionsToCS3ResourcePermissions", - func(cs3Role *conversions.Role, libregraphRole *libregraph.UnifiedRoleDefinition, match bool) { + func(cs3Role *rConversions.Role, libregraphRole *libregraph.UnifiedRoleDefinition, match bool) { permsFromCS3 := cs3Role.CS3ResourcePermissions() - permsFromUnifiedRole := unifiedrole.PermissionsToCS3ResourcePermissions(libregraphRole.RolePermissions) + permsFromUnifiedRole := unifiedrole.PermissionsToCS3ResourcePermissions( + conversions.ToPointerSlice(libregraphRole.RolePermissions), + ) var matcher types.GomegaMatcher @@ -43,14 +48,203 @@ var _ = Describe("unifiedroles", func() { Expect(permsFromCS3).To(matcher) }, - Entry(conversions.RoleViewer, conversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true), true), - Entry(conversions.RoleEditor, conversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true), true), - Entry(conversions.RoleFileEditor, conversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true), true), - Entry(conversions.RoleCoowner, conversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole(), true), - Entry(conversions.RoleManager, conversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), true), - Entry("no match", conversions.NewFileEditorRole(true), unifiedrole.NewManagerUnifiedRole(), false), + Entry(rConversions.RoleViewer, rConversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true), true), + Entry(rConversions.RoleEditor, rConversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true), true), + Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true), true), + Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole(), true), + Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), true), + Entry("no match", rConversions.NewFileEditorRole(true), unifiedrole.NewManagerUnifiedRole(), false), ) + DescribeTable("WeightRoleDefinitions", + func(roleDefinitions []*libregraph.UnifiedRoleDefinition, descending bool, expectedDefinitions []*libregraph.UnifiedRoleDefinition) { + + for i, generatedDefinition := range unifiedrole.WeightRoleDefinitions(roleDefinitions, descending) { + Expect(generatedDefinition.Id).To(Equal(expectedDefinitions[i].Id)) + } + }, + + Entry("ascending", + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewViewerUnifiedRole(false), + unifiedrole.NewFileEditorUnifiedRole(false), + }, + false, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewViewerUnifiedRole(false), + unifiedrole.NewFileEditorUnifiedRole(false), + }, + ), + + Entry("descending", + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewViewerUnifiedRole(false), + unifiedrole.NewFileEditorUnifiedRole(false), + }, + true, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewFileEditorUnifiedRole(false), + unifiedrole.NewViewerUnifiedRole(false), + }, + ), + ) + + { + rolesToAction := func(definitions ...*libregraph.UnifiedRoleDefinition) []string { + var actions []string + + for _, definition := range definitions { + for _, permission := range definition.GetRolePermissions() { + for _, action := range permission.GetAllowedResourceActions() { + if slices.Contains(actions, action) { + continue + } + actions = append(actions, action) + } + } + } + + return actions + } + + DescribeTable("GetApplicableRoleDefinitionsForActions", + func(givenActions []string, constraints string, resharing bool, expectedDefinitions []*libregraph.UnifiedRoleDefinition) { + + generatedDefinitions := unifiedrole.GetApplicableRoleDefinitionsForActions(givenActions, constraints, resharing, false) + + Expect(len(generatedDefinitions)).To(Equal(len(expectedDefinitions))) + + for i, generatedDefinition := range generatedDefinitions { + Expect(generatedDefinition.Id).To(Equal(expectedDefinitions[i].Id)) + Expect(*generatedDefinition.LibreGraphWeight).To(Equal(int32(i + 1))) + } + + generatedActions := rolesToAction(generatedDefinitions...) + Expect(len(givenActions) >= len(generatedActions)).To(BeTrue()) + + for _, generatedAction := range generatedActions { + Expect(slices.Contains(givenActions, generatedAction)).To(BeTrue()) + } + }, + + Entry( + "ViewerUnifiedRole", + rolesToAction(unifiedrole.NewViewerUnifiedRole(false)), + unifiedrole.UnifiedRoleConditionGrantee, + false, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewViewerUnifiedRole(false), + }, + ), + + Entry( + "ViewerUnifiedRole | share", + rolesToAction(unifiedrole.NewViewerUnifiedRole(true)), + unifiedrole.UnifiedRoleConditionGrantee, + true, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewViewerUnifiedRole(true), + }, + ), + + Entry( + "NewFileEditorUnifiedRole", + rolesToAction(unifiedrole.NewFileEditorUnifiedRole(false)), + unifiedrole.UnifiedRoleConditionGrantee, + false, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewViewerUnifiedRole(false), + unifiedrole.NewFileEditorUnifiedRole(false), + }, + ), + + Entry( + "NewFileEditorUnifiedRole - share", + rolesToAction(unifiedrole.NewFileEditorUnifiedRole(true)), + unifiedrole.UnifiedRoleConditionGrantee, + true, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewViewerUnifiedRole(true), + unifiedrole.NewFileEditorUnifiedRole(true), + }, + ), + + Entry( + "NewEditorUnifiedRole", + rolesToAction(unifiedrole.NewEditorUnifiedRole(false)), + unifiedrole.UnifiedRoleConditionGrantee, + false, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewUploaderUnifiedRole(), + unifiedrole.NewViewerUnifiedRole(false), + unifiedrole.NewFileEditorUnifiedRole(false), + unifiedrole.NewEditorUnifiedRole(false), + }, + ), + + Entry( + "NewEditorUnifiedRole - share", + rolesToAction(unifiedrole.NewEditorUnifiedRole(true)), + unifiedrole.UnifiedRoleConditionGrantee, + true, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewUploaderUnifiedRole(), + unifiedrole.NewViewerUnifiedRole(true), + unifiedrole.NewFileEditorUnifiedRole(true), + unifiedrole.NewEditorUnifiedRole(true), + }, + ), + + Entry( + "GetBuiltinRoleDefinitionList", + rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList(false)...), + unifiedrole.UnifiedRoleConditionGrantee, + false, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewUploaderUnifiedRole(), + unifiedrole.NewViewerUnifiedRole(false), + unifiedrole.NewFileEditorUnifiedRole(false), + unifiedrole.NewEditorUnifiedRole(false), + unifiedrole.NewCoownerUnifiedRole(), + unifiedrole.NewManagerUnifiedRole(), + }, + ), + + Entry( + "GetBuiltinRoleDefinitionList - share", + rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList(true)...), + unifiedrole.UnifiedRoleConditionGrantee, + true, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewUploaderUnifiedRole(), + unifiedrole.NewViewerUnifiedRole(true), + unifiedrole.NewFileEditorUnifiedRole(true), + unifiedrole.NewEditorUnifiedRole(true), + unifiedrole.NewCoownerUnifiedRole(), + unifiedrole.NewManagerUnifiedRole(), + }, + ), + + Entry( + "single", + []string{unifiedrole.DriveItemQuotaRead}, + unifiedrole.UnifiedRoleConditionGrantee, + true, + []*libregraph.UnifiedRoleDefinition{}, + ), + + Entry( + "mixed", + append(rolesToAction(unifiedrole.NewUploaderUnifiedRole()), unifiedrole.DriveItemQuotaRead), + unifiedrole.UnifiedRoleConditionGrantee, + true, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.NewUploaderUnifiedRole(), + }, + ), + ) + } + { var newUnifiedRoleFromIDEntries []TableEntry for _, resharing := range []bool{true, false} { diff --git a/services/invitations/pkg/server/http/server.go b/services/invitations/pkg/server/http/server.go index 5c04542f44..744b1fb262 100644 --- a/services/invitations/pkg/server/http/server.go +++ b/services/invitations/pkg/server/http/server.go @@ -13,7 +13,7 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/middleware" ohttp "github.com/owncloud/ocis/v2/ocis-pkg/service/http" "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/invitations/pkg/invitations" svc "github.com/owncloud/ocis/v2/services/invitations/pkg/service/v0" "go-micro.dev/v4" diff --git a/services/proxy/pkg/middleware/create_home.go b/services/proxy/pkg/middleware/create_home.go index f7da59a0e9..203f4d84ac 100644 --- a/services/proxy/pkg/middleware/create_home.go +++ b/services/proxy/pkg/middleware/create_home.go @@ -13,7 +13,7 @@ import ( "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "google.golang.org/grpc/metadata" ) diff --git a/services/proxy/pkg/user/backend/cs3.go b/services/proxy/pkg/user/backend/cs3.go index df0031c219..c27183aeef 100644 --- a/services/proxy/pkg/user/backend/cs3.go +++ b/services/proxy/pkg/user/backend/cs3.go @@ -18,7 +18,7 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/proxy/pkg/config" ) diff --git a/services/userlog/pkg/service/http.go b/services/userlog/pkg/service/http.go index 05dca5050f..74a8db8d30 100644 --- a/services/userlog/pkg/service/http.go +++ b/services/userlog/pkg/service/http.go @@ -10,7 +10,7 @@ import ( revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/utils" "github.com/owncloud/ocis/v2/ocis-pkg/roles" - "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" settings "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" "go.opentelemetry.io/otel/attribute" )