+1
@@ -27,6 +27,7 @@ import (
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/ldap"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/machine"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/nextcloud"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/ocmshares"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/oidc"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/owncloudsql"
|
||||
_ "github.com/cs3org/reva/v2/pkg/auth/manager/publicshares"
|
||||
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
// Copyright 2018-2023 CERN
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// In applying this license, CERN does not waive the privileges and immunities
|
||||
// granted to it by virtue of its status as an Intergovernmental Organization
|
||||
// or submit itself to any jurisdiction.
|
||||
|
||||
package ocmshares
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
ocminvite "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/appctx"
|
||||
"github.com/cs3org/reva/v2/pkg/auth"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/manager/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/scope"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/sharedconf"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/cs3org/reva/v2/pkg/utils/cfg"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register("ocmshares", New)
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
c *config
|
||||
gw gateway.GatewayAPIClient
|
||||
}
|
||||
|
||||
type config struct {
|
||||
GatewayAddr string `mapstructure:"gatewaysvc"`
|
||||
}
|
||||
|
||||
func (c *config) ApplyDefaults() {
|
||||
c.GatewayAddr = sharedconf.GetGatewaySVC(c.GatewayAddr)
|
||||
}
|
||||
|
||||
// New creates a new ocmshares authentication manager.
|
||||
func New(m map[string]interface{}) (auth.Manager, error) {
|
||||
var mgr manager
|
||||
if err := mgr.Configure(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gw, err := pool.GetGatewayServiceClient(mgr.c.GatewayAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mgr.gw = gw
|
||||
|
||||
return &mgr, nil
|
||||
}
|
||||
|
||||
func (m *manager) Configure(ml map[string]interface{}) error {
|
||||
var c config
|
||||
if err := cfg.Decode(ml, &c); err != nil {
|
||||
return errors.Wrap(err, "ocmshares: error decoding config")
|
||||
}
|
||||
m.c = &c
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) Authenticate(ctx context.Context, token, _ string) (*userpb.User, map[string]*authpb.Scope, error) {
|
||||
log := appctx.GetLogger(ctx).With().Str("token", token).Logger()
|
||||
shareRes, err := m.gw.GetOCMShareByToken(ctx, &ocm.GetOCMShareByTokenRequest{
|
||||
Token: token,
|
||||
})
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Error().Err(err).Msg("error getting ocm share by token")
|
||||
return nil, nil, err
|
||||
case shareRes.Status.Code == rpc.Code_CODE_NOT_FOUND:
|
||||
log.Debug().Msg("ocm share not found")
|
||||
return nil, nil, errtypes.NotFound(shareRes.Status.Message)
|
||||
case shareRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED:
|
||||
log.Debug().Msg("permission denied")
|
||||
return nil, nil, errtypes.InvalidCredentials(shareRes.Status.Message)
|
||||
case shareRes.Status.Code != rpc.Code_CODE_OK:
|
||||
log.Error().Interface("status", shareRes.Status).Msg("got unexpected error in the grpc call to GetOCMShare")
|
||||
return nil, nil, errtypes.InternalError(shareRes.Status.Message)
|
||||
}
|
||||
|
||||
// the user authenticated using the ocmshares authentication method
|
||||
// is the recipient of the share
|
||||
u := shareRes.Share.Grantee.GetUserId()
|
||||
|
||||
d, err := utils.MarshalProtoV1ToJSON(shareRes.GetShare().Creator)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
o := &types.Opaque{
|
||||
Map: map[string]*types.OpaqueEntry{
|
||||
"user-filter": {
|
||||
Decoder: "json",
|
||||
Value: d,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
userRes, err := m.gw.GetAcceptedUser(ctx, &ocminvite.GetAcceptedUserRequest{
|
||||
RemoteUserId: u,
|
||||
Opaque: o,
|
||||
})
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, nil, err
|
||||
case userRes.Status.Code == rpc.Code_CODE_NOT_FOUND:
|
||||
return nil, nil, errtypes.NotFound(shareRes.Status.Message)
|
||||
case userRes.Status.Code != rpc.Code_CODE_OK:
|
||||
return nil, nil, errtypes.InternalError(userRes.Status.Message)
|
||||
}
|
||||
|
||||
role, roleStr := getRole(shareRes.Share)
|
||||
|
||||
scope, err := scope.AddOCMShareScope(shareRes.Share, role, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
user := userRes.RemoteUser
|
||||
user.Opaque = &types.Opaque{
|
||||
Map: map[string]*types.OpaqueEntry{
|
||||
"ocm-share-role": {
|
||||
Decoder: "plain",
|
||||
Value: []byte(roleStr),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return user, scope, nil
|
||||
}
|
||||
|
||||
func getRole(s *ocm.Share) (authpb.Role, string) {
|
||||
// TODO: consider to somehow merge the permissions from all the access methods?
|
||||
// it's not clear infact which should be the role when webdav is editor role while
|
||||
// webapp is only view mode for example
|
||||
// this implementation considers only the simple case in which when a client creates
|
||||
// a share with multiple access methods, the permissions are matching in all of them.
|
||||
for _, m := range s.AccessMethods {
|
||||
switch v := m.Term.(type) {
|
||||
case *ocm.AccessMethod_WebdavOptions:
|
||||
p := v.WebdavOptions.Permissions
|
||||
if p.InitiateFileUpload {
|
||||
return authpb.Role_ROLE_EDITOR, "editor"
|
||||
}
|
||||
if p.InitiateFileDownload {
|
||||
return authpb.Role_ROLE_VIEWER, "viewer"
|
||||
}
|
||||
case *ocm.AccessMethod_WebappOptions:
|
||||
viewMode := v.WebappOptions.ViewMode
|
||||
if viewMode == provider.ViewMode_VIEW_MODE_VIEW_ONLY ||
|
||||
viewMode == provider.ViewMode_VIEW_MODE_READ_ONLY ||
|
||||
viewMode == provider.ViewMode_VIEW_MODE_PREVIEW {
|
||||
return authpb.Role_ROLE_VIEWER, "viewer"
|
||||
}
|
||||
if viewMode == provider.ViewMode_VIEW_MODE_READ_WRITE {
|
||||
return authpb.Role_ROLE_EDITOR, "editor"
|
||||
}
|
||||
}
|
||||
}
|
||||
return authpb.Role_ROLE_INVALID, "invalid"
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
// Copyright 2018-2023 CERN
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// In applying this license, CERN does not waive the privileges and immunities
|
||||
// granted to it by virtue of its status as an Intergovernmental Organization
|
||||
// or submit itself to any jurisdiction.
|
||||
|
||||
package scope
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||
appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// FIXME: the namespace here is hardcoded
|
||||
// find a way to pass it from the config.
|
||||
const ocmNamespace = "/ocm"
|
||||
|
||||
func ocmShareScope(_ context.Context, scope *authpb.Scope, resource interface{}, _ *zerolog.Logger) (bool, error) {
|
||||
var share ocmv1beta1.Share
|
||||
if err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch v := resource.(type) {
|
||||
// viewer role
|
||||
case *registry.ListStorageProvidersRequest:
|
||||
ref := &provider.Reference{}
|
||||
if v.Opaque != nil && v.Opaque.Map != nil {
|
||||
if e, ok := v.Opaque.Map["storage_id"]; ok {
|
||||
if ref.ResourceId == nil {
|
||||
ref.ResourceId = &provider.ResourceId{}
|
||||
}
|
||||
ref.ResourceId.StorageId = string(e.Value)
|
||||
}
|
||||
if e, ok := v.Opaque.Map["space_id"]; ok {
|
||||
if ref.ResourceId == nil {
|
||||
ref.ResourceId = &provider.ResourceId{}
|
||||
}
|
||||
ref.ResourceId.SpaceId = string(e.Value)
|
||||
}
|
||||
if e, ok := v.Opaque.Map["opaque_id"]; ok {
|
||||
if ref.ResourceId == nil {
|
||||
ref.ResourceId = &provider.ResourceId{}
|
||||
}
|
||||
ref.ResourceId.OpaqueId = string(e.Value)
|
||||
}
|
||||
if e, ok := v.Opaque.Map["path"]; ok {
|
||||
ref.Path = string(e.Value)
|
||||
}
|
||||
}
|
||||
return checkStorageRefForOCMShare(&share, ref, ocmNamespace), nil
|
||||
case *registry.GetStorageProvidersRequest:
|
||||
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.StatRequest:
|
||||
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.ListContainerRequest:
|
||||
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.InitiateFileDownloadRequest:
|
||||
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *appprovider.OpenInAppRequest:
|
||||
return checkStorageRefForOCMShare(&share, &provider.Reference{ResourceId: v.ResourceInfo.Id}, ocmNamespace), nil
|
||||
case *gateway.OpenInAppRequest:
|
||||
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.GetLockRequest:
|
||||
return checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
|
||||
// editor role
|
||||
case *provider.CreateContainerRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.TouchFileRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.DeleteRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.MoveRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetSource(), ocmNamespace) && checkStorageRefForOCMShare(&share, v.GetDestination(), ocmNamespace), nil
|
||||
case *provider.InitiateFileUploadRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.SetArbitraryMetadataRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.UnsetArbitraryMetadataRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.SetLockRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.RefreshLockRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
case *provider.UnlockRequest:
|
||||
return hasRoleEditor(*scope) && checkStorageRefForOCMShare(&share, v.GetRef(), ocmNamespace), nil
|
||||
|
||||
// App provider requests
|
||||
case *appregistry.GetDefaultAppProviderForMimeTypeRequest:
|
||||
return true, nil
|
||||
case *appregistry.GetAppProvidersRequest:
|
||||
return true, nil
|
||||
case *userv1beta1.GetUserByClaimRequest:
|
||||
return true, nil
|
||||
case *userv1beta1.GetUserRequest:
|
||||
return true, nil
|
||||
case *provider.ListStorageSpacesRequest:
|
||||
return true, nil
|
||||
|
||||
case *ocmv1beta1.GetOCMShareRequest:
|
||||
return checkOCMShareRef(&share, v.GetRef()), nil
|
||||
case *ocmv1beta1.GetOCMShareByTokenRequest:
|
||||
return share.Token == v.GetToken(), nil
|
||||
case string:
|
||||
return checkResourcePath(v), nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func checkStorageRefForOCMShare(s *ocmv1beta1.Share, r *provider.Reference, ns string) bool {
|
||||
if r.ResourceId != nil {
|
||||
return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId()) || strings.HasPrefix(r.ResourceId.OpaqueId, s.Token)
|
||||
}
|
||||
|
||||
// FIXME: the paths here are hardcoded
|
||||
if strings.HasPrefix(r.GetPath(), "/public/"+s.Token) {
|
||||
return true
|
||||
}
|
||||
return strings.HasPrefix(r.GetPath(), filepath.Join(ns, s.Token))
|
||||
}
|
||||
|
||||
func checkOCMShareRef(s *ocmv1beta1.Share, ref *ocmv1beta1.ShareReference) bool {
|
||||
return ref.GetToken() == s.Token
|
||||
}
|
||||
|
||||
// AddOCMShareScope adds the scope to allow access to an OCM share and the share resource.
|
||||
func AddOCMShareScope(share *ocmv1beta1.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
|
||||
// Create a new "scope share" to only expose the required fields `ResourceId` and `Token` to the scope.
|
||||
scopeShare := ocmv1beta1.Share{ResourceId: share.ResourceId, Token: share.Token}
|
||||
val, err := utils.MarshalProtoV1ToJSON(&scopeShare)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if scopes == nil {
|
||||
scopes = make(map[string]*authpb.Scope)
|
||||
}
|
||||
|
||||
scopes["ocmshare:"+share.Id.OpaqueId] = &authpb.Scope{
|
||||
Resource: &types.OpaqueEntry{
|
||||
Decoder: "json",
|
||||
Value: val,
|
||||
},
|
||||
Role: role,
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
// GetOCMSharesFromScopes returns all OCM shares in the given scope.
|
||||
func GetOCMSharesFromScopes(scopes map[string]*authpb.Scope) ([]*ocmv1beta1.Share, error) {
|
||||
var shares []*ocmv1beta1.Share
|
||||
for k, s := range scopes {
|
||||
if strings.HasPrefix(k, "ocmshare:") {
|
||||
res := s.Resource
|
||||
if res.Decoder != "json" {
|
||||
return nil, errtypes.InternalError("resource should be json encoded")
|
||||
}
|
||||
var share ocmv1beta1.Share
|
||||
err := utils.UnmarshalJSONToProtoV1(res.Value, &share)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shares = append(shares, &share)
|
||||
}
|
||||
}
|
||||
return shares, nil
|
||||
}
|
||||
+1
@@ -37,6 +37,7 @@ var supportedScopes = map[string]Verifier{
|
||||
"share": shareScope,
|
||||
"receivedshare": receivedShareScope,
|
||||
"lightweight": lightweightAccountScope,
|
||||
"ocmshare": ocmShareScope,
|
||||
}
|
||||
|
||||
// VerifyScope is the function to be called when dismantling tokens to check if
|
||||
|
||||
Reference in New Issue
Block a user