From 5ab53b2474f1c3be1ec45cef2826e868fa1709a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 4 Sep 2024 17:38:54 +0200 Subject: [PATCH] bump reva to 9878984ce702 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- changelog/unreleased/bump-reva.md | 1 + go.mod | 2 +- go.sum | 4 +- services/graph/pkg/identity/backend.go | 20 +-- tests/acceptance/TestHelpers/GraphHelper.php | 9 ++ tests/acceptance/bootstrap/FeatureContext.php | 8 ++ .../apiOcm/searchFederationUsers.feature | 22 ++-- .../grpc/services/gateway/ocmshareprovider.go | 4 + .../ocminvitemanager/ocminvitemanager.go | 10 +- .../ocmshareprovider/ocmshareprovider.go | 40 +++--- .../http/services/sciencemesh/email.go | 119 ------------------ .../http/services/sciencemesh/sciencemesh.go | 31 +++-- .../http/services/sciencemesh/token.go | 103 +++++++++------ .../cs3org/reva/v2/pkg/events/sciencemesh.go | 26 ++++ .../pkg/ocm/provider/authorizer/json/json.go | 11 +- .../cs3org/reva/v2/pkg/ocm/user/user.go | 55 ++++++++ vendor/modules.txt | 3 +- 17 files changed, 260 insertions(+), 208 deletions(-) delete mode 100644 vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/email.go create mode 100644 vendor/github.com/cs3org/reva/v2/pkg/events/sciencemesh.go create mode 100644 vendor/github.com/cs3org/reva/v2/pkg/ocm/user/user.go diff --git a/changelog/unreleased/bump-reva.md b/changelog/unreleased/bump-reva.md index 70745801d..093e0b5f4 100644 --- a/changelog/unreleased/bump-reva.md +++ b/changelog/unreleased/bump-reva.md @@ -2,6 +2,7 @@ Enhancement: Bump reva Bumps reva version +https://github.com/owncloud/ocis/pull/9981 https://github.com/owncloud/ocis/pull/9920 https://github.com/owncloud/ocis/pull/9879 https://github.com/owncloud/ocis/pull/9860 diff --git a/go.mod b/go.mod index 94fb4ad57..e19c73e8e 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/coreos/go-oidc/v3 v3.11.0 github.com/cs3org/go-cs3apis v0.0.0-20240724121416-062c4e3046cb - github.com/cs3org/reva/v2 v2.23.1-0.20240829154445-c991ee0e085f + github.com/cs3org/reva/v2 v2.23.1-0.20240904150329-9878984ce702 github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e github.com/egirna/icap-client v0.1.1 diff --git a/go.sum b/go.sum index 5b21def3f..6f7853171 100644 --- a/go.sum +++ b/go.sum @@ -255,8 +255,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/cs3org/go-cs3apis v0.0.0-20240724121416-062c4e3046cb h1:KmYZDReplv/yfwc1LNYpDcVhVujC3Pasv6WjXx1haSU= github.com/cs3org/go-cs3apis v0.0.0-20240724121416-062c4e3046cb/go.mod h1:yyP8PRo0EZou3nSH7H4qjlzQwaydPeIRNgX50npQHpE= -github.com/cs3org/reva/v2 v2.23.1-0.20240829154445-c991ee0e085f h1:YHqyK+VZthBijeul54z16Kw1q6rn412jbRMUMp20h1k= -github.com/cs3org/reva/v2 v2.23.1-0.20240829154445-c991ee0e085f/go.mod h1:p7CHBXcg6sSqB+0JMNDfC1S7TSh9FghXkw1kTV3KcJI= +github.com/cs3org/reva/v2 v2.23.1-0.20240904150329-9878984ce702 h1:iJRlH13kZfImkuH7/BLG6+BkNqZs4s5a+81B+4r6xbQ= +github.com/cs3org/reva/v2 v2.23.1-0.20240904150329-9878984ce702/go.mod h1:p7CHBXcg6sSqB+0JMNDfC1S7TSh9FghXkw1kTV3KcJI= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= diff --git a/services/graph/pkg/identity/backend.go b/services/graph/pkg/identity/backend.go index 53cbd85ef..adfc6185b 100644 --- a/services/graph/pkg/identity/backend.go +++ b/services/graph/pkg/identity/backend.go @@ -7,6 +7,7 @@ import ( "github.com/CiscoM31/godata" cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + ocmuser "github.com/cs3org/reva/v2/pkg/ocm/user" libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" ) @@ -116,19 +117,24 @@ func CreateUserModelFromCS3(u *cs3user.User) *libregraph.User { u.Id = &cs3user.UserId{} } userType := cs3UserTypeToGraph(u.GetId().GetType()) - return &libregraph.User{ - Identities: []libregraph.ObjectIdentity{ - { - Issuer: &u.GetId().Idp, - IssuerAssignedId: &u.GetId().OpaqueId, - }, - }, + user := &libregraph.User{ + Identities: []libregraph.ObjectIdentity{{ + Issuer: &u.GetId().Idp, + IssuerAssignedId: &u.GetId().OpaqueId, + }}, UserType: &userType, DisplayName: u.DisplayName, Mail: &u.Mail, OnPremisesSamAccountName: u.Username, Id: &u.Id.OpaqueId, } + // decode the remote id if the user is federated + if u.GetId().GetType() == cs3user.UserType_USER_TYPE_FEDERATED { + remoteID := ocmuser.RemoteID(u.GetId()) + user.Identities[0].Issuer = &remoteID.Idp + user.Identities[0].IssuerAssignedId = &remoteID.OpaqueId + } + return user } func cs3UserTypeToGraph(cs3type cs3user.UserType) string { diff --git a/tests/acceptance/TestHelpers/GraphHelper.php b/tests/acceptance/TestHelpers/GraphHelper.php index 99cad0d8c..ec05c2046 100644 --- a/tests/acceptance/TestHelpers/GraphHelper.php +++ b/tests/acceptance/TestHelpers/GraphHelper.php @@ -87,6 +87,15 @@ class GraphHelper { public static function getEtagRegex(): string { return "^\\\"[a-f0-9:.]{1,32}\\\"$"; } + /** + * Federated users have a base64 encoded string of {remoteid}@{provider} as their id + * This regex matches only non empty base64 encoded strings + * + * @return string + */ + public static function getFederatedUserRegex(): string { + return '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$'; + } /** * Key name can consist of @@@ diff --git a/tests/acceptance/bootstrap/FeatureContext.php b/tests/acceptance/bootstrap/FeatureContext.php index 090951cbe..a2e4da8f6 100644 --- a/tests/acceptance/bootstrap/FeatureContext.php +++ b/tests/acceptance/bootstrap/FeatureContext.php @@ -2266,6 +2266,14 @@ class FeatureContext extends BehatVariablesContext { ], "parameter" => [] ], + [ + "code" => "%federated_user_id_pattern%", + "function" => [ + __NAMESPACE__ . '\TestHelpers\GraphHelper', + "getFederatedUserRegex" + ], + "parameter" => [] + ], [ "code" => "%group_id_pattern%", "function" => [ diff --git a/tests/acceptance/features/apiOcm/searchFederationUsers.feature b/tests/acceptance/features/apiOcm/searchFederationUsers.feature index 9eceb5d86..23e94ea84 100755 --- a/tests/acceptance/features/apiOcm/searchFederationUsers.feature +++ b/tests/acceptance/features/apiOcm/searchFederationUsers.feature @@ -46,7 +46,7 @@ Feature: search federation users }, "id": { "type": "string", - "pattern": "^%user_id_pattern%$" + "pattern": "^%federated_user_id_pattern%$" }, "userType": { "type": "string", @@ -108,7 +108,7 @@ Feature: search federation users }, "id": { "type": "string", - "pattern": "^%user_id_pattern%$" + "pattern": "^%federated_user_id_pattern%$" }, "userType": { "type": "string", @@ -126,7 +126,7 @@ Feature: search federation users ], "properties": { "issuer": { - "const": "https://federation-ocis-server:10200" + "const": "federation-ocis-server:10200" }, "issuerAssignedId": { "type": "string", @@ -176,7 +176,7 @@ Feature: search federation users }, "id": { "type": "string", - "pattern": "^%user_id_pattern%$" + "pattern": "^%federated_user_id_pattern%$" }, "userType": { "type": "string", @@ -238,7 +238,7 @@ Feature: search federation users }, "id": { "type": "string", - "pattern": "^%user_id_pattern%$" + "pattern": "^%federated_user_id_pattern%$" }, "userType": { "type": "string", @@ -256,7 +256,7 @@ Feature: search federation users ], "properties": { "issuer": { - "const": "https://federation-ocis-server:10200" + "const": "federation-ocis-server:10200" }, "issuerAssignedId": { "type": "string", @@ -460,7 +460,7 @@ Feature: search federation users }, "id": { "type": "string", - "pattern": "^%user_id_pattern%$" + "pattern": "^%federated_user_id_pattern%$" }, "mail": { "const": "alice@example.org" @@ -525,7 +525,7 @@ Feature: search federation users }, "id": { "type": "string", - "pattern": "^%user_id_pattern%$" + "pattern": "^%federated_user_id_pattern%$" }, "mail": { "const": "brian@example.org" @@ -545,7 +545,7 @@ Feature: search federation users ], "properties": { "issuer": { - "const": "https://federation-ocis-server:10200" + "const": "federation-ocis-server:10200" }, "issuerAssignedId": { "type": "string", @@ -672,7 +672,7 @@ Feature: search federation users }, "id": { "type": "string", - "pattern": "^%user_id_pattern%$" + "pattern": "^%federated_user_id_pattern%$" }, "mail": { "const": "brian@example.org" @@ -692,7 +692,7 @@ Feature: search federation users ], "properties": { "issuer": { - "const": "https://federation-ocis-server:10200" + "const": "federation-ocis-server:10200" }, "issuerAssignedId": { "type": "string", diff --git a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/gateway/ocmshareprovider.go b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/gateway/ocmshareprovider.go index 63e2a39ee..d5e0f4991 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/gateway/ocmshareprovider.go +++ b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/gateway/ocmshareprovider.go @@ -51,11 +51,15 @@ func (s *svc) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareRequest }, nil } + // persist the OCM share in the ocm share provider res, err := c.CreateOCMShare(ctx, req) if err != nil { return nil, errors.Wrap(err, "gateway: error calling CreateOCMShare") } + // add a grant to the storage provider so the share can efficiently be listed + // the grant does not grant any permissions. access is granted by the OCM link token + // that is used by the public storage provider to impersonate the resource owner status, err := s.addGrant(ctx, req.ResourceId, req.Grantee, req.AccessMethods[0].GetWebdavOptions().Permissions, req.Expiration, nil) switch { case err != nil: diff --git a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/ocminvitemanager/ocminvitemanager.go b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/ocminvitemanager/ocminvitemanager.go index 671f86409..9a7b3a129 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/ocminvitemanager/ocminvitemanager.go +++ b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/ocminvitemanager/ocminvitemanager.go @@ -32,6 +32,7 @@ import ( "github.com/cs3org/reva/v2/pkg/ocm/client" "github.com/cs3org/reva/v2/pkg/ocm/invite" "github.com/cs3org/reva/v2/pkg/ocm/invite/repository/registry" + ocmuser "github.com/cs3org/reva/v2/pkg/ocm/user" "github.com/cs3org/reva/v2/pkg/rgrpc" "github.com/cs3org/reva/v2/pkg/rgrpc/status" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" @@ -210,6 +211,9 @@ func (s *service) ForwardInvite(ctx context.Context, req *invitepb.ForwardInvite OpaqueId: remoteUser.UserID, } + // we need to use a unique identifier for federated users + remoteUserID = ocmuser.FederatedID(remoteUserID) + if err := s.repo.AddRemoteUser(ctx, user.Id, &userpb.User{ Id: remoteUserID, Mail: remoteUser.Email, @@ -266,7 +270,11 @@ func (s *service) AcceptInvite(ctx context.Context, req *invitepb.AcceptInviteRe }, nil } - if err := s.repo.AddRemoteUser(ctx, token.GetUserId(), req.GetRemoteUser()); err != nil { + remoteUser := req.GetRemoteUser() + // we need to use a unique identifier for federated users + remoteUser.Id = ocmuser.FederatedID(remoteUser.Id) + + if err := s.repo.AddRemoteUser(ctx, token.GetUserId(), remoteUser); err != nil { if errors.Is(err, invite.ErrUserAlreadyAccepted) { return &invitepb.AcceptInviteResponse{ Status: status.NewAlreadyExists(ctx, err, err.Error()), diff --git a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/ocmshareprovider/ocmshareprovider.go b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/ocmshareprovider/ocmshareprovider.go index b42ce7fa4..b35eca3db 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/grpc/services/ocmshareprovider/ocmshareprovider.go +++ b/vendor/github.com/cs3org/reva/v2/internal/grpc/services/ocmshareprovider/ocmshareprovider.go @@ -20,7 +20,6 @@ package ocmshareprovider import ( "context" - "fmt" "net/url" "path/filepath" "strings" @@ -35,11 +34,13 @@ import ( providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/ocmd" + "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/ocm/client" "github.com/cs3org/reva/v2/pkg/ocm/share" "github.com/cs3org/reva/v2/pkg/ocm/share/repository/registry" + ocmuser "github.com/cs3org/reva/v2/pkg/ocm/user" "github.com/cs3org/reva/v2/pkg/rgrpc" "github.com/cs3org/reva/v2/pkg/rgrpc/status" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" @@ -157,10 +158,6 @@ func getOCMEndpoint(originProvider *ocmprovider.ProviderInfo) (string, error) { return "", errors.New("ocm endpoint not specified for mesh provider") } -func formatOCMUser(u *userpb.UserId) string { - return fmt.Sprintf("%s@%s", u.OpaqueId, u.Idp) -} - func getResourceType(info *providerpb.ResourceInfo) string { switch info.Type { case providerpb.ResourceType_RESOURCE_TYPE_FILE: @@ -288,6 +285,7 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq Nanos: uint32(now % 1000000000), } + // 1. persist the share in the repository ocmshare := &ocm.Share{ Token: tkn, Name: filepath.Base(info.Path), @@ -314,6 +312,8 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq }, nil } + // 2. create the share on the remote provider + // 2.a get the ocm endpoint of the remote provider ocmEndpoint, err := getOCMEndpoint(req.RecipientMeshProvider) if err != nil { return &ocm.CreateOCMShareResponse{ @@ -321,18 +321,20 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq }, nil } + // 2.b replace outgoing user ids with ocm user ids + // unpack the federated user id + shareWith := ocmuser.FormatOCMUser(ocmuser.RemoteID(req.GetGrantee().GetUserId())) + + // wrap the local user id in a federated user id + owner := ocmuser.FormatOCMUser(ocmuser.FederatedID(info.Owner)) + sender := ocmuser.FormatOCMUser(ocmuser.FederatedID(user.Id)) + newShareReq := &client.NewShareRequest{ - ShareWith: formatOCMUser(req.Grantee.GetUserId()), - Name: ocmshare.Name, - ProviderID: ocmshare.Id.OpaqueId, - Owner: formatOCMUser(&userpb.UserId{ - OpaqueId: info.Owner.OpaqueId, - Idp: s.conf.ProviderDomain, - }), - Sender: formatOCMUser(&userpb.UserId{ - OpaqueId: user.Id.OpaqueId, - Idp: s.conf.ProviderDomain, - }), + ShareWith: shareWith, + Name: ocmshare.Name, + ProviderID: ocmshare.Id.OpaqueId, + Owner: owner, + Sender: sender, SenderDisplayName: user.DisplayName, ShareType: "user", ResourceType: getResourceType(info), @@ -343,8 +345,14 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq newShareReq.Expiration = req.Expiration.Seconds } + // 2.c make POST /shares request newShareRes, err := s.client.NewShare(ctx, ocmEndpoint, newShareReq) if err != nil { + err2 := s.repo.DeleteShare(ctx, user, &ocm.ShareReference{Spec: &ocm.ShareReference_Id{Id: ocmshare.Id}}) + if err2 != nil { + appctx.GetLogger(ctx).Error().Err(err2).Str("shareid", ocmshare.GetId().GetOpaqueId()).Msg("could not delete local ocm share") + } + // TODO remove the share from the local storage switch { case errors.Is(err, client.ErrInvalidParameters): return &ocm.CreateOCMShareResponse{ diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/email.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/email.go deleted file mode 100644 index 239c89ba5..000000000 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/email.go +++ /dev/null @@ -1,119 +0,0 @@ -// 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 sciencemesh - -import ( - "bytes" - "html/template" - "io" - "os" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" -) - -type emailParams struct { - User *userpb.User - Token string - MeshDirectoryURL string - InviteLink string -} - -const defaultSubject = `ScienceMesh: {{.User.DisplayName}} wants to collaborate with you` - -const defaultBody = `Hi - -{{.User.DisplayName}} ({{.User.Mail}}) wants to start sharing OCM resources with you. -To accept the invite, please visit the following URL: -{{.InviteLink}} - -Alternatively, you can visit your mesh provider and use the following details: -Token: {{.Token}} -ProviderDomain: {{.User.Id.Idp}} - -Best, -The ScienceMesh team` - -func (h *tokenHandler) sendEmail(recipient string, obj *emailParams) error { - subj, err := h.generateEmailSubject(obj) - if err != nil { - return err - } - - body, err := h.generateEmailBody(obj) - if err != nil { - return err - } - - return h.smtpCredentials.SendMail(recipient, subj, body) -} - -func (h *tokenHandler) generateEmailSubject(obj *emailParams) (string, error) { - var buf bytes.Buffer - err := h.tplSubj.Execute(&buf, obj) - return buf.String(), err -} - -func (h *tokenHandler) generateEmailBody(obj *emailParams) (string, error) { - var buf bytes.Buffer - err := h.tplBody.Execute(&buf, obj) - return buf.String(), err -} - -func (h *tokenHandler) initBodyTemplate(bodyTemplPath string) error { - var body string - if bodyTemplPath == "" { - body = defaultBody - } else { - f, err := os.Open(bodyTemplPath) - if err != nil { - return err - } - defer f.Close() - - data, err := io.ReadAll(f) - if err != nil { - return err - } - body = string(data) - } - - tpl, err := template.New("tpl_body").Parse(body) - if err != nil { - return err - } - - h.tplBody = tpl - return nil -} - -func (h *tokenHandler) initSubjectTemplate(subjTempl string) error { - var subj string - if subjTempl == "" { - subj = defaultSubject - } else { - subj = subjTempl - } - - tpl, err := template.New("tpl_subj").Parse(subj) - if err != nil { - return err - } - h.tplSubj = tpl - return nil -} diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/sciencemesh.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/sciencemesh.go index 49dbde86b..6eb572d64 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/sciencemesh.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/sciencemesh.go @@ -21,13 +21,13 @@ package sciencemesh import ( "net/http" + "github.com/go-chi/chi/v5" + "github.com/rs/zerolog" + "github.com/cs3org/reva/v2/pkg/appctx" "github.com/cs3org/reva/v2/pkg/rhttp/global" "github.com/cs3org/reva/v2/pkg/sharedconf" - "github.com/cs3org/reva/v2/pkg/smtpclient" "github.com/cs3org/reva/v2/pkg/utils/cfg" - "github.com/go-chi/chi/v5" - "github.com/rs/zerolog" ) func init() { @@ -60,14 +60,23 @@ func (s *svc) Close() error { } type config struct { - Prefix string `mapstructure:"prefix"` - SMTPCredentials *smtpclient.SMTPCredentials `mapstructure:"smtp_credentials" validate:"required"` - GatewaySvc string `mapstructure:"gatewaysvc" validate:"required"` - ProviderDomain string `mapstructure:"provider_domain" validate:"required"` - MeshDirectoryURL string `mapstructure:"mesh_directory_url"` - SubjectTemplate string `mapstructure:"subject_template"` - BodyTemplatePath string `mapstructure:"body_template_path"` - OCMMountPoint string `mapstructure:"ocm_mount_point"` + Prefix string `mapstructure:"prefix"` + GatewaySvc string `mapstructure:"gatewaysvc" validate:"required"` + ProviderDomain string `mapstructure:"provider_domain" validate:"required"` + MeshDirectoryURL string `mapstructure:"mesh_directory_url"` + OCMMountPoint string `mapstructure:"ocm_mount_point"` + Events EventOptions `mapstructure:"events"` +} + +// EventOptions are the configurable options for events +type EventOptions struct { + Endpoint string `mapstructure:"natsaddress"` + Cluster string `mapstructure:"natsclusterid"` + TLSInsecure bool `mapstructure:"tlsinsecure"` + TLSRootCACertificate string `mapstructure:"tlsrootcacertificate"` + EnableTLS bool `mapstructure:"enabletls"` + AuthUsername string `mapstructure:"authusername"` + AuthPassword string `mapstructure:"authpassword"` } func (c *config) ApplyDefaults() { diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/token.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/token.go index 4879a9400..ff8d33002 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/token.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/sciencemesh/token.go @@ -21,7 +21,6 @@ package sciencemesh import ( "encoding/json" "errors" - "html/template" "mime" "net/http" @@ -30,21 +29,21 @@ import ( invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/v2/internal/http/services/reqres" "github.com/cs3org/reva/v2/pkg/appctx" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/events/stream" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/smtpclient" + "github.com/cs3org/reva/v2/pkg/utils" "github.com/cs3org/reva/v2/pkg/utils/list" ) type tokenHandler struct { gatewayClient gateway.GatewayAPIClient - smtpCredentials *smtpclient.SMTPCredentials meshDirectoryURL string providerDomain string - tplSubj *template.Template - tplBody *template.Template + eventStream events.Stream } func (h *tokenHandler) init(c *config) error { @@ -54,19 +53,15 @@ func (h *tokenHandler) init(c *config) error { return err } - if c.SMTPCredentials != nil { - h.smtpCredentials = smtpclient.NewSMTPCredentials(c.SMTPCredentials) - } - h.meshDirectoryURL = c.MeshDirectoryURL h.providerDomain = c.ProviderDomain - if err := h.initSubjectTemplate(c.SubjectTemplate); err != nil { - return err - } - - if err := h.initBodyTemplate(c.BodyTemplatePath); err != nil { - return err + if c.Events.Endpoint != "" { + es, err := stream.NatsFromConfig("sciencemesh-token-handler", false, stream.NatsConfig(c.Events)) + if err != nil { + return err + } + h.eventStream = es } return nil @@ -83,41 +78,77 @@ type token struct { // will send an email containing the link the user will use to accept the // invitation. func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - query := r.URL.Query() - token, err := h.gatewayClient.GenerateInviteToken(ctx, &invitepb.GenerateInviteTokenRequest{ - Description: query.Get("description"), - }) + req, err := getGenerateRequest(r) if err != nil { - reqres.WriteError(w, r, reqres.APIErrorServerError, "error generating token", err) + reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "missing parameters in request", err) return } - user := ctxpkg.ContextMustGetUser(ctx) - recipient := query.Get("recipient") - if recipient != "" && h.smtpCredentials != nil { - templObj := &emailParams{ - User: user, - Token: token.InviteToken.Token, - MeshDirectoryURL: h.meshDirectoryURL, - } - if err := h.sendEmail(recipient, templObj); err != nil { - reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending token by mail", err) - return - } + ctx := r.Context() + genTokenRes, err := h.gatewayClient.GenerateInviteToken(ctx, &invitepb.GenerateInviteTokenRequest{ + Description: req.Description, + }) + switch { + case err != nil: + reqres.WriteError(w, r, reqres.APIErrorServerError, "error generating token", err) + return + case genTokenRes.GetStatus().GetCode() == rpc.Code_CODE_NOT_FOUND: + reqres.WriteError(w, r, reqres.APIErrorNotFound, genTokenRes.GetStatus().GetMessage(), nil) + return + case genTokenRes.GetStatus().GetCode() != rpc.Code_CODE_OK: + reqres.WriteError(w, r, reqres.APIErrorServerError, genTokenRes.GetStatus().GetMessage(), errors.New(genTokenRes.GetStatus().GetMessage())) + return } - tknRes := h.prepareGenerateTokenResponse(token.InviteToken) + tknRes := h.prepareGenerateTokenResponse(genTokenRes.GetInviteToken()) if err := json.NewEncoder(w).Encode(tknRes); err != nil { reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err) return } + if h.eventStream != nil { + if err := events.Publish(ctx, h.eventStream, events.ScienceMeshInviteTokenGenerated{ + Sharer: genTokenRes.GetInviteToken().GetUserId(), + RecipientMail: req.Recipient, + Token: tknRes.Token, + Description: tknRes.Description, + Expiration: tknRes.Expiration, + InviteLink: tknRes.InviteLink, + Timestamp: utils.TSNow(), + }); err != nil { + log := appctx.GetLogger(ctx) + log.Error().Err(err). + Msg("failed to publish the science-mesh invite token generated event") + } + } + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) } +// generateRequest is the request body for the Generate endpoint. +type generateRequest struct { + Description string `json:"description"` + Recipient string `json:"recipient" validate:"omitempty,email"` +} + +func getGenerateRequest(r *http.Request) (*generateRequest, error) { + var req generateRequest + contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err == nil && contentType == "application/json" { + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + } + + // validate the request + if err := validate.Struct(req); err != nil { + return nil, err + } + + return &req, nil +} + func (h *tokenHandler) prepareGenerateTokenResponse(tkn *invitepb.InviteToken) *token { res := &token{ Token: tkn.Token, diff --git a/vendor/github.com/cs3org/reva/v2/pkg/events/sciencemesh.go b/vendor/github.com/cs3org/reva/v2/pkg/events/sciencemesh.go new file mode 100644 index 000000000..a2c75f098 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/events/sciencemesh.go @@ -0,0 +1,26 @@ +package events + +import ( + "encoding/json" + + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" +) + +// ScienceMeshInviteTokenGenerated is emitted when a sciencemesh token is generated +type ScienceMeshInviteTokenGenerated struct { + Sharer *user.UserId + RecipientMail string + Token string + Description string + Expiration uint64 + InviteLink string + Timestamp *types.Timestamp +} + +// Unmarshal to fulfill unmarshaller interface +func (ScienceMeshInviteTokenGenerated) Unmarshal(v []byte) (interface{}, error) { + e := ScienceMeshInviteTokenGenerated{} + err := json.Unmarshal(v, &e) + return e, err +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/ocm/provider/authorizer/json/json.go b/vendor/github.com/cs3org/reva/v2/pkg/ocm/provider/authorizer/json/json.go index b2173c6b7..a47ef6b77 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/ocm/provider/authorizer/json/json.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/ocm/provider/authorizer/json/json.go @@ -28,18 +28,23 @@ import ( "sync" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" + "github.com/pkg/errors" + "github.com/cs3org/reva/v2/pkg/appctx" "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/ocm/provider" "github.com/cs3org/reva/v2/pkg/ocm/provider/authorizer/registry" "github.com/cs3org/reva/v2/pkg/utils/cfg" - "github.com/pkg/errors" ) func init() { registry.Register("json", New) } +var ( + ErrNoIP = errtypes.NotSupported("No IP provided") +) + // New returns a new authorizer object. func New(m map[string]interface{}) (provider.Authorizer, error) { var c config @@ -102,7 +107,7 @@ func normalizeDomain(d string) (string, error) { return u.Hostname(), nil } -func (a *authorizer) GetInfoByDomain(ctx context.Context, domain string) (*ocmprovider.ProviderInfo, error) { +func (a *authorizer) GetInfoByDomain(_ context.Context, domain string) (*ocmprovider.ProviderInfo, error) { normalizedDomain, err := normalizeDomain(domain) if err != nil { return nil, err @@ -140,7 +145,7 @@ func (a *authorizer) IsProviderAllowed(ctx context.Context, pi *ocmprovider.Prov case !a.conf.VerifyRequestHostname: return nil case len(pi.Services) == 0: - return errtypes.NotSupported("No IP provided") + return ErrNoIP } var ocmHost string diff --git a/vendor/github.com/cs3org/reva/v2/pkg/ocm/user/user.go b/vendor/github.com/cs3org/reva/v2/pkg/ocm/user/user.go new file mode 100644 index 000000000..6a48f4fe7 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/ocm/user/user.go @@ -0,0 +1,55 @@ +package user + +import ( + "encoding/base64" + "fmt" + "net/url" + "strings" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" +) + +// FederatedID creates a federated user id by +// 1. stripping the protocol from the domain and +// 2. base64 encoding the opaque id with the domain to get a unique identifier that cannot collide with other users +func FederatedID(id *userpb.UserId) *userpb.UserId { + // strip protocol from the domain + domain := id.Idp + if u, err := url.Parse(domain); err == nil && u.Host != "" { + domain = u.Host + } + return &userpb.UserId{ + Type: userpb.UserType_USER_TYPE_FEDERATED, + Idp: domain, + OpaqueId: base64.URLEncoding.EncodeToString([]byte(id.OpaqueId + "@" + domain)), + } +} + +// RemoteID creates a remote user id by +// 1. decoding the base64 encoded opaque id +// 2. splitting the opaque id at the last @ to get the opaque id and the domain +func RemoteID(id *userpb.UserId) *userpb.UserId { + remoteId := &userpb.UserId{ + Type: userpb.UserType_USER_TYPE_PRIMARY, + Idp: id.Idp, + OpaqueId: id.OpaqueId, + } + bytes, err := base64.URLEncoding.DecodeString(id.GetOpaqueId()) + if err != nil { + return remoteId + } + remote := string(bytes) + last := strings.LastIndex(remote, "@") + if last == -1 { + return remoteId + } + remoteId.OpaqueId = remote[:last] + remoteId.Idp = remote[last+1:] + + return remoteId +} + +// FormatOCMUser formats a user id in the form of @ used by the OCM API in shareWith, owner and creator fields +func FormatOCMUser(u *userpb.UserId) string { + return fmt.Sprintf("%s@%s", u.OpaqueId, u.Idp) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 185520c52..71630b82d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -367,7 +367,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1 github.com/cs3org/go-cs3apis/cs3/types/v1beta1 -# github.com/cs3org/reva/v2 v2.23.1-0.20240829154445-c991ee0e085f +# github.com/cs3org/reva/v2 v2.23.1-0.20240904150329-9878984ce702 ## explicit; go 1.21 github.com/cs3org/reva/v2/cmd/revad/internal/grace github.com/cs3org/reva/v2/cmd/revad/runtime @@ -570,6 +570,7 @@ github.com/cs3org/reva/v2/pkg/ocm/share/repository/loader github.com/cs3org/reva/v2/pkg/ocm/share/repository/nextcloud github.com/cs3org/reva/v2/pkg/ocm/share/repository/registry github.com/cs3org/reva/v2/pkg/ocm/storage/received +github.com/cs3org/reva/v2/pkg/ocm/user github.com/cs3org/reva/v2/pkg/owncloud/ocs github.com/cs3org/reva/v2/pkg/password github.com/cs3org/reva/v2/pkg/permission