Skip to content

Commit

Permalink
Port ocm related auth changes
Browse files Browse the repository at this point in the history
  • Loading branch information
aduffeck committed Oct 9, 2023
1 parent 0fceffe commit e751971
Show file tree
Hide file tree
Showing 7 changed files with 413 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ import (
// Load core authentication strategies.
_ "github.com/cs3org/reva/v2/internal/http/interceptors/auth/credential/strategy/basic"
_ "github.com/cs3org/reva/v2/internal/http/interceptors/auth/credential/strategy/bearer"
_ "github.com/cs3org/reva/v2/internal/http/interceptors/auth/credential/strategy/ocmshares"
// Add your own here.
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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 (
"fmt"
"net/http"

"github.com/cs3org/reva/v2/internal/http/interceptors/auth/credential/registry"
"github.com/cs3org/reva/v2/pkg/auth"
)

func init() {
registry.Register("ocmshares", New)
}

const (
headerShareToken = "ocm-token"
)

type strategy struct{}

// New returns a new auth strategy that handles public share verification.
func New(m map[string]interface{}) (auth.CredentialStrategy, error) {
return &strategy{}, nil
}

func (s *strategy) GetCredentials(w http.ResponseWriter, r *http.Request) (*auth.Credentials, error) {
token := r.Header.Get(headerShareToken)
if token == "" {
token = r.URL.Query().Get(headerShareToken)
}
if token == "" {
return nil, fmt.Errorf("no ocm token provided")
}

return &auth.Credentials{Type: "ocmshares", ClientID: token}, nil
}

func (s *strategy) AddWWWAuthenticate(w http.ResponseWriter, r *http.Request, realm string) {
// TODO read realm from forwarded header?
}
1 change: 1 addition & 0 deletions pkg/auth/manager/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
187 changes: 187 additions & 0 deletions pkg/auth/manager/ocmshares/ocmshares.go
Original file line number Diff line number Diff line change
@@ -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"
}
Loading

0 comments on commit e751971

Please sign in to comment.