Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send invitation link from mesh directory when generating and listing OCM tokens #3724

Merged
merged 7 commits into from
Mar 12, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog/unreleased/ocm-shares.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
88 changes: 83 additions & 5 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,9 +70,26 @@ func (h *tokenHandler) init(c *config) error {
if err := h.initBodyTemplate(c.BodyTemplatePath); err != nil {
return err
}

if err := h.initInviteLinkTemplate(c.InviteLinkTemplate); err != nil {
return err
}
return nil
}

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,
// will send an email containing the link the user will use to accept the
// invitation.
Expand All @@ -83,10 +105,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 +119,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 +134,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 +280,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(&res)).To(Succeed())
return &inviteRes, res.StatusCode
}

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