Skip to content

Commit

Permalink
Send invitation link from mesh directory when generating and listing …
Browse files Browse the repository at this point in the history
…OCM tokens (#3724)

* send invite link on token generation

* add invite link when getting token list

* add changelog

* fix tests!

* fix changelog name

* fix linter

* fix tests
  • Loading branch information
gmgigi96 authored Mar 12, 2023
1 parent af4c4c8 commit 92d6561
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 19 deletions.
8 changes: 8 additions & 0 deletions changelog/unreleased/send-invite-link-ocm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Enhancement: Send invitation link from mesh directory
when generating and listing OCM tokens

To enhance user expirience, instead of only sending
the token, we send directly the URL for accepting the
invitation workflow.

https://github.com/cs3org/reva/pull/3724
19 changes: 18 additions & 1 deletion internal/http/services/sciencemesh/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type emailParams struct {
User *userpb.User
Token string
MeshDirectoryURL string
InviteLink string
}

const defaultSubject = `ScienceMesh: {{.User.DisplayName}} wants to collaborate with you`
Expand All @@ -39,7 +40,7 @@ const defaultBody = `Hi
{{.User.DisplayName}} ({{.User.Mail}}) wants to start sharing OCM resources with you.
To accept the invite, please visit the following URL:
{{.MeshDirectoryURL}}?token={{.Token}}&providerDomain={{.User.Id.Idp}}
{{.InviteLink}}
Alternatively, you can visit your mesh provider and use the following details:
Token: {{.Token}}
Expand Down Expand Up @@ -116,3 +117,19 @@ func (h *tokenHandler) initSubjectTemplate(subjTempl string) error {
h.tplSubj = tpl
return nil
}

func (h *tokenHandler) initInviteLinkTemplate(inviteTempl string) error {
var t string
if inviteTempl == "" {
t = defaultInviteLink
} else {
t = inviteTempl
}

tpl, err := template.New("tpl_invite").Parse(t)
if err != nil {
return err
}
h.tplInviteLink = tpl
return nil
}
17 changes: 9 additions & 8 deletions internal/http/services/sciencemesh/sciencemesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,15 @@ func (s *svc) Close() error {
}

type config struct {
Prefix string `mapstructure:"prefix"`
SMTPCredentials *smtpclient.SMTPCredentials `mapstructure:"smtp_credentials"`
GatewaySvc string `mapstructure:"gatewaysvc"`
MeshDirectoryURL string `mapstructure:"mesh_directory_url"`
ProviderDomain string `mapstructure:"provider_domain"`
SubjectTemplate string `mapstructure:"subject_template"`
BodyTemplatePath string `mapstructure:"body_template_path"`
OCMMountPoint string `mapstructure:"ocm_mount_point"`
Prefix string `mapstructure:"prefix"`
SMTPCredentials *smtpclient.SMTPCredentials `mapstructure:"smtp_credentials"`
GatewaySvc string `mapstructure:"gatewaysvc"`
MeshDirectoryURL string `mapstructure:"mesh_directory_url"`
ProviderDomain string `mapstructure:"provider_domain"`
SubjectTemplate string `mapstructure:"subject_template"`
BodyTemplatePath string `mapstructure:"body_template_path"`
OCMMountPoint string `mapstructure:"ocm_mount_point"`
InviteLinkTemplate string `mapstructure:"invite_link_template"`
}

func (c *config) init() {
Expand Down
87 changes: 81 additions & 6 deletions internal/http/services/sciencemesh/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import (
"html/template"
"mime"
"net/http"
"strings"

gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
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"
Expand All @@ -36,13 +38,16 @@ import (
"github.com/cs3org/reva/pkg/smtpclient"
)

const defaultInviteLink = "{{.MeshDirectoryURL}}?token={{.Token}}&providerDomain={{.User.Id.Idp}}"

type tokenHandler struct {
gatewayClient gateway.GatewayAPIClient
smtpCredentials *smtpclient.SMTPCredentials
meshDirectoryURL string

tplSubj *template.Template
tplBody *template.Template
tplSubj *template.Template
tplBody *template.Template
tplInviteLink *template.Template
}

func (h *tokenHandler) init(c *config) error {
Expand All @@ -65,7 +70,21 @@ func (h *tokenHandler) init(c *config) error {
if err := h.initBodyTemplate(c.BodyTemplatePath); err != nil {
return err
}
return nil

return h.initInviteLinkTemplate(c.InviteLinkTemplate)
}

type token struct {
Token string `json:"token"`
Description string `json:"description,omitempty"`
Expiration uint64 `json:"expiration,omitempty"`
InviteLink string `json:"invite_link"`
}

type inviteLinkParams struct {
User *userpb.User
Token string
MeshDirectoryURL string
}

// Generate generates an invitation token and if a recipient is specified,
Expand All @@ -83,10 +102,11 @@ func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) {
return
}

user := ctxpkg.ContextMustGetUser(ctx)
recipient := query.Get("recipient")
if recipient != "" && h.smtpCredentials != nil {
templObj := &emailParams{
User: ctxpkg.ContextMustGetUser(ctx),
User: user,
Token: token.InviteToken.Token,
MeshDirectoryURL: h.meshDirectoryURL,
}
Expand All @@ -96,7 +116,13 @@ func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) {
}
}

if err := json.NewEncoder(w).Encode(token.InviteToken); err != nil {
tknRes, err := h.prepareGenerateTokenResponse(user, token.InviteToken)
if err != nil {
reqres.WriteError(w, r, reqres.APIErrorServerError, "error generating response", err)
return
}

if err := json.NewEncoder(w).Encode(tknRes); err != nil {
reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err)
return
}
Expand All @@ -105,6 +131,36 @@ func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}

func (h *tokenHandler) generateInviteLink(user *userpb.User, token *invitepb.InviteToken) (string, error) {
var inviteLink strings.Builder
if err := h.tplInviteLink.Execute(&inviteLink, inviteLinkParams{
User: user,
Token: token.Token,
MeshDirectoryURL: h.meshDirectoryURL,
}); err != nil {
return "", err
}

return inviteLink.String(), nil
}

func (h *tokenHandler) prepareGenerateTokenResponse(user *userpb.User, tkn *invitepb.InviteToken) (*token, error) {
inviteLink, err := h.generateInviteLink(user, tkn)
if err != nil {
return nil, err
}
res := &token{
Token: tkn.Token,
Description: tkn.Description,
InviteLink: inviteLink,
}
if tkn.Expiration != nil {
res.Expiration = tkn.Expiration.Seconds
}

return res, nil
}

type acceptInviteRequest struct {
Token string `json:"token"`
ProviderDomain string `json:"providerDomain"`
Expand Down Expand Up @@ -221,7 +277,26 @@ func (h *tokenHandler) ListInvite(w http.ResponseWriter, r *http.Request) {
return
}

if err := json.NewEncoder(w).Encode(res.InviteTokens); err != nil {
tokens := make([]*token, 0, len(res.InviteTokens))
user := ctxpkg.ContextMustGetUser(ctx)
for _, tkn := range res.InviteTokens {
inviteURL, err := h.generateInviteLink(user, tkn)
if err != nil {
reqres.WriteError(w, r, reqres.APIErrorServerError, "error generating invite URL from OCM token", err)
return
}
t := &token{
Token: tkn.Token,
Description: tkn.Description,
InviteLink: inviteURL,
}
if tkn.Expiration != nil {
t.Expiration = tkn.Expiration.Seconds
}
tokens = append(tokens, t)
}

if err := json.NewEncoder(w).Encode(tokens); err != nil {
reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err)
return
}
Expand Down
15 changes: 11 additions & 4 deletions tests/integration/grpc/ocm_invitation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ import (
"google.golang.org/grpc/metadata"
)

type generateInviteResponse struct {
Token string `json:"token"`
Description string `json:"descriptions"`
Expiration uint64 `json:"expiration"`
InviteLink string `json:"invite_link"`
}

func ctxWithAuthToken(tokenManager token.Manager, user *userpb.User) context.Context {
ctx := context.Background()
scope, err := scope.AddOwnerScope(nil)
Expand Down Expand Up @@ -327,7 +334,7 @@ var _ = Describe("ocm invitation workflow", func() {
return users, res.StatusCode
}

generateToken := func(revaToken, domain string) (*invitepb.InviteToken, int) {
generateToken := func(revaToken, domain string) (*generateInviteResponse, int) {
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, fmt.Sprintf("http://%s/sciencemesh/generate-invite", domain), nil)
Expect(err).ToNot(HaveOccurred())
req.Header.Set("x-access-token", revaToken)
Expand All @@ -336,9 +343,9 @@ var _ = Describe("ocm invitation workflow", func() {
Expect(err).ToNot(HaveOccurred())
defer res.Body.Close()

var token invitepb.InviteToken
Expect(json.NewDecoder(res.Body).Decode(&token)).To(Succeed())
return &token, res.StatusCode
var inviteRes generateInviteResponse
Expect(json.NewDecoder(res.Body).Decode(&inviteRes)).To(Succeed())
return &inviteRes, res.StatusCode
}

Context("einstein and marie do not know each other", func() {
Expand Down

0 comments on commit 92d6561

Please sign in to comment.