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  }