github.com/cs3org/reva/v2@v2.27.7/pkg/auth/manager/ocmshares/ocmshares.go (about) 1 // Copyright 2018-2023 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package ocmshares 20 21 import ( 22 "context" 23 24 provider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" 25 authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" 26 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 27 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 28 ocminvite "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" 29 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 30 ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" 31 types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 32 "github.com/cs3org/reva/v2/pkg/appctx" 33 "github.com/cs3org/reva/v2/pkg/auth" 34 "github.com/cs3org/reva/v2/pkg/auth/manager/registry" 35 "github.com/cs3org/reva/v2/pkg/auth/scope" 36 "github.com/cs3org/reva/v2/pkg/errtypes" 37 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 38 "github.com/cs3org/reva/v2/pkg/sharedconf" 39 "github.com/cs3org/reva/v2/pkg/utils" 40 "github.com/cs3org/reva/v2/pkg/utils/cfg" 41 "github.com/pkg/errors" 42 ) 43 44 func init() { 45 registry.Register("ocmshares", New) 46 } 47 48 type manager struct { 49 c *config 50 gateway *pool.Selector[gateway.GatewayAPIClient] 51 } 52 53 type config struct { 54 GatewayAddr string `mapstructure:"gatewaysvc"` 55 } 56 57 func (c *config) ApplyDefaults() { 58 c.GatewayAddr = sharedconf.GetGatewaySVC(c.GatewayAddr) 59 } 60 61 // New creates a new ocmshares authentication manager. 62 func New(m map[string]interface{}) (auth.Manager, error) { 63 var mgr manager 64 if err := mgr.Configure(m); err != nil { 65 return nil, err 66 } 67 gw, err := pool.GatewaySelector(mgr.c.GatewayAddr) 68 if err != nil { 69 return nil, err 70 } 71 mgr.gateway = gw 72 73 return &mgr, nil 74 } 75 76 func (m *manager) Configure(ml map[string]interface{}) error { 77 var c config 78 if err := cfg.Decode(ml, &c); err != nil { 79 return errors.Wrap(err, "ocmshares: error decoding config") 80 } 81 m.c = &c 82 return nil 83 } 84 85 func (m *manager) Authenticate(ctx context.Context, ocmshare, sharedSecret string) (*userpb.User, map[string]*authpb.Scope, error) { 86 log := appctx.GetLogger(ctx).With().Str("ocmshare", ocmshare).Logger() 87 gwc, err := m.gateway.Next() 88 if err != nil { 89 return nil, nil, err 90 } 91 92 // We need to use GetOCMShareByToken, as GetOCMShare would require a user in the context 93 shareRes, err := gwc.GetOCMShareByToken(ctx, &ocm.GetOCMShareByTokenRequest{ 94 Token: sharedSecret, 95 }) 96 97 switch { 98 case err != nil: 99 log.Error().Err(err).Msg("error getting ocm share by token") 100 return nil, nil, err 101 case shareRes.Status.Code == rpc.Code_CODE_NOT_FOUND: 102 log.Debug().Msg("ocm share not found") 103 return nil, nil, errtypes.NotFound(shareRes.Status.Message) 104 case shareRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED: 105 log.Debug().Msg("permission denied") 106 return nil, nil, errtypes.InvalidCredentials(shareRes.Status.Message) 107 case shareRes.Status.Code != rpc.Code_CODE_OK: 108 log.Error().Interface("status", shareRes.Status).Msg("got unexpected error in the grpc call to GetOCMShare") 109 return nil, nil, errtypes.InternalError(shareRes.Status.Message) 110 } 111 112 // compare ocm share id 113 if shareRes.GetShare().GetId().GetOpaqueId() != ocmshare { 114 log.Error().Str("persisted", ocmshare).Str("requested", shareRes.GetShare().GetId().GetOpaqueId()).Msg("mismatching ocm share id for existing secret") 115 return nil, nil, errtypes.InvalidCredentials("invalid shared secret") 116 } 117 118 // the user authenticated using the ocmshares authentication method 119 // is the recipient of the share 120 u := shareRes.Share.Grantee.GetUserId() 121 122 d, err := utils.MarshalProtoV1ToJSON(shareRes.GetShare().Creator) 123 if err != nil { 124 return nil, nil, err 125 } 126 127 o := &types.Opaque{ 128 Map: map[string]*types.OpaqueEntry{ 129 "user-filter": { 130 Decoder: "json", 131 Value: d, 132 }, 133 }, 134 } 135 136 userRes, err := gwc.GetAcceptedUser(ctx, &ocminvite.GetAcceptedUserRequest{ 137 RemoteUserId: u, 138 Opaque: o, 139 }) 140 141 switch { 142 case err != nil: 143 return nil, nil, err 144 case userRes.Status.Code == rpc.Code_CODE_NOT_FOUND: 145 return nil, nil, errtypes.NotFound(shareRes.Status.Message) 146 case userRes.Status.Code != rpc.Code_CODE_OK: 147 return nil, nil, errtypes.InternalError(userRes.Status.Message) 148 } 149 150 role, roleStr := getRole(shareRes.Share) 151 152 scope, err := scope.AddOCMShareScope(shareRes.Share, role, nil) 153 if err != nil { 154 return nil, nil, err 155 } 156 157 user := userRes.RemoteUser 158 user.Opaque = &types.Opaque{ 159 Map: map[string]*types.OpaqueEntry{ 160 "ocm-share-role": { 161 Decoder: "plain", 162 Value: []byte(roleStr), 163 }, 164 }, 165 } 166 167 user.Opaque = utils.AppendJSONToOpaque(user.Opaque, "impersonating-user", userRes.RemoteUser) 168 169 return user, scope, nil 170 } 171 172 func getRole(s *ocm.Share) (authpb.Role, string) { 173 // TODO: consider to somehow merge the permissions from all the access methods? 174 // it's not clear infact which should be the role when webdav is editor role while 175 // webapp is only view mode for example 176 // this implementation considers only the simple case in which when a client creates 177 // a share with multiple access methods, the permissions are matching in all of them. 178 for _, m := range s.AccessMethods { 179 switch v := m.Term.(type) { 180 case *ocm.AccessMethod_WebdavOptions: 181 p := v.WebdavOptions.Permissions 182 if p.InitiateFileUpload { 183 return authpb.Role_ROLE_EDITOR, "editor" 184 } 185 if p.InitiateFileDownload { 186 return authpb.Role_ROLE_VIEWER, "viewer" 187 } 188 case *ocm.AccessMethod_WebappOptions: 189 viewMode := v.WebappOptions.ViewMode 190 if viewMode == provider.ViewMode_VIEW_MODE_VIEW_ONLY || 191 viewMode == provider.ViewMode_VIEW_MODE_READ_ONLY || 192 viewMode == provider.ViewMode_VIEW_MODE_PREVIEW { 193 return authpb.Role_ROLE_VIEWER, "viewer" 194 } 195 if viewMode == provider.ViewMode_VIEW_MODE_READ_WRITE { 196 return authpb.Role_ROLE_EDITOR, "editor" 197 } 198 } 199 } 200 return authpb.Role_ROLE_INVALID, "invalid" 201 }