github.com/cs3org/reva/v2@v2.27.7/internal/http/services/sciencemesh/token.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 sciencemesh
    20  
    21  import (
    22  	"encoding/json"
    23  	"errors"
    24  	"mime"
    25  	"net/http"
    26  
    27  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    28  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    29  	invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
    30  	ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
    31  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    32  
    33  	"github.com/cs3org/reva/v2/internal/http/services/reqres"
    34  	"github.com/cs3org/reva/v2/pkg/appctx"
    35  	"github.com/cs3org/reva/v2/pkg/events"
    36  	"github.com/cs3org/reva/v2/pkg/events/stream"
    37  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    38  	"github.com/cs3org/reva/v2/pkg/utils"
    39  	"github.com/cs3org/reva/v2/pkg/utils/list"
    40  )
    41  
    42  type tokenHandler struct {
    43  	gatewaySelector  *pool.Selector[gateway.GatewayAPIClient]
    44  	meshDirectoryURL string
    45  	providerDomain   string
    46  	eventStream      events.Stream
    47  }
    48  
    49  func (h *tokenHandler) init(c *config) error {
    50  	var err error
    51  	h.gatewaySelector, err = pool.GatewaySelector(c.GatewaySvc)
    52  	if err != nil {
    53  		return err
    54  	}
    55  
    56  	h.meshDirectoryURL = c.MeshDirectoryURL
    57  	h.providerDomain = c.ProviderDomain
    58  
    59  	if c.Events.Endpoint != "" {
    60  		es, err := stream.NatsFromConfig("sciencemesh-token-handler", false, stream.NatsConfig(c.Events))
    61  		if err != nil {
    62  			return err
    63  		}
    64  		h.eventStream = es
    65  	}
    66  
    67  	return nil
    68  }
    69  
    70  type token struct {
    71  	Token       string `json:"token"`
    72  	Description string `json:"description,omitempty"`
    73  	Expiration  uint64 `json:"expiration,omitempty"`
    74  	InviteLink  string `json:"invite_link,omitempty"`
    75  }
    76  
    77  // Generate generates an invitation token and if a recipient is specified,
    78  // will send an email containing the link the user will use to accept the
    79  // invitation.
    80  func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) {
    81  	req, err := getGenerateRequest(r)
    82  	if err != nil {
    83  		reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "missing parameters in request", err)
    84  		return
    85  	}
    86  
    87  	ctx := r.Context()
    88  	gc, err := h.gatewaySelector.Next()
    89  	if err != nil {
    90  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err)
    91  		return
    92  	}
    93  	genTokenRes, err := gc.GenerateInviteToken(ctx, &invitepb.GenerateInviteTokenRequest{
    94  		Description: req.Description,
    95  	})
    96  	switch {
    97  	case err != nil:
    98  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error generating token", err)
    99  		return
   100  	case genTokenRes.GetStatus().GetCode() == rpc.Code_CODE_NOT_FOUND:
   101  		reqres.WriteError(w, r, reqres.APIErrorNotFound, genTokenRes.GetStatus().GetMessage(), nil)
   102  		return
   103  	case genTokenRes.GetStatus().GetCode() != rpc.Code_CODE_OK:
   104  		reqres.WriteError(w, r, reqres.APIErrorServerError, genTokenRes.GetStatus().GetMessage(), errors.New(genTokenRes.GetStatus().GetMessage()))
   105  		return
   106  	}
   107  
   108  	w.Header().Set("Content-Type", "application/json")
   109  	w.WriteHeader(http.StatusOK) // FIXME this should be a 201 created status. Tracked in https://github.com/cs3org/reva/issues/4838
   110  
   111  	tknRes := h.prepareGenerateTokenResponse(genTokenRes.GetInviteToken())
   112  	if err := json.NewEncoder(w).Encode(tknRes); err != nil {
   113  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err)
   114  	}
   115  
   116  	if h.eventStream != nil {
   117  		if err := events.Publish(ctx, h.eventStream, events.ScienceMeshInviteTokenGenerated{
   118  			Sharer:        genTokenRes.GetInviteToken().GetUserId(),
   119  			RecipientMail: req.Recipient,
   120  			Token:         tknRes.Token,
   121  			Description:   tknRes.Description,
   122  			Expiration:    tknRes.Expiration,
   123  			InviteLink:    tknRes.InviteLink,
   124  			Timestamp:     utils.TSNow(),
   125  		}); err != nil {
   126  			log := appctx.GetLogger(ctx)
   127  			log.Error().Err(err).
   128  				Msg("failed to publish the science-mesh invite token generated event")
   129  		}
   130  	}
   131  
   132  }
   133  
   134  // generateRequest is the request body for the Generate endpoint.
   135  type generateRequest struct {
   136  	Description string `json:"description"`
   137  	Recipient   string `json:"recipient" validate:"omitempty,email"`
   138  }
   139  
   140  func getGenerateRequest(r *http.Request) (*generateRequest, error) {
   141  	var req generateRequest
   142  	contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
   143  	if err == nil && contentType == "application/json" {
   144  		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
   145  			return nil, err
   146  		}
   147  	}
   148  
   149  	// validate the request
   150  	if err := validate.Struct(req); err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	return &req, nil
   155  }
   156  
   157  func (h *tokenHandler) prepareGenerateTokenResponse(tkn *invitepb.InviteToken) *token {
   158  	res := &token{
   159  		Token:       tkn.Token,
   160  		Description: tkn.Description,
   161  	}
   162  	if h.meshDirectoryURL != "" {
   163  		res.InviteLink = h.meshDirectoryURL + "?token=" + tkn.Token + "&providerDomain=" + h.providerDomain
   164  	}
   165  	if tkn.Expiration != nil {
   166  		res.Expiration = tkn.Expiration.Seconds
   167  	}
   168  
   169  	return res
   170  }
   171  
   172  type acceptInviteRequest struct {
   173  	Token          string `json:"token"`
   174  	ProviderDomain string `json:"providerDomain"`
   175  }
   176  
   177  // AcceptInvite accepts an invitation from the user in the remote provider.
   178  func (h *tokenHandler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
   179  	ctx := r.Context()
   180  	log := appctx.GetLogger(ctx)
   181  
   182  	req, err := getAcceptInviteRequest(r)
   183  	if err != nil {
   184  		reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "missing parameters in request", err)
   185  		return
   186  	}
   187  
   188  	if req.Token == "" || req.ProviderDomain == "" {
   189  		reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "token and providerDomain must not be null", nil)
   190  		return
   191  	}
   192  
   193  	gc, err := h.gatewaySelector.Next()
   194  	if err != nil {
   195  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err)
   196  		return
   197  	}
   198  	providerInfo, err := gc.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{
   199  		Domain: req.ProviderDomain,
   200  	})
   201  	if err != nil {
   202  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc get invite by domain info request", err)
   203  		return
   204  	}
   205  	if providerInfo.Status.Code != rpc.Code_CODE_OK {
   206  		reqres.WriteError(w, r, reqres.APIErrorServerError, "grpc forward invite request failed", errors.New(providerInfo.Status.Message))
   207  		return
   208  	}
   209  
   210  	gc, err = h.gatewaySelector.Next()
   211  	if err != nil {
   212  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err)
   213  		return
   214  	}
   215  	forwardInviteReq := &invitepb.ForwardInviteRequest{
   216  		InviteToken: &invitepb.InviteToken{
   217  			Token: req.Token,
   218  		},
   219  		OriginSystemProvider: providerInfo.ProviderInfo,
   220  	}
   221  	forwardInviteResponse, err := gc.ForwardInvite(ctx, forwardInviteReq)
   222  	if err != nil {
   223  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc forward invite request", err)
   224  		return
   225  	}
   226  	if forwardInviteResponse.Status.Code != rpc.Code_CODE_OK {
   227  		switch forwardInviteResponse.Status.Code {
   228  		case rpc.Code_CODE_NOT_FOUND:
   229  			reqres.WriteError(w, r, reqres.APIErrorNotFound, "token not found", nil)
   230  			return
   231  		case rpc.Code_CODE_INVALID_ARGUMENT:
   232  			reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "token has expired", nil)
   233  			return
   234  		case rpc.Code_CODE_ALREADY_EXISTS:
   235  			reqres.WriteError(w, r, reqres.APIErrorAlreadyExist, "user already known", nil)
   236  			return
   237  		case rpc.Code_CODE_PERMISSION_DENIED:
   238  			reqres.WriteError(w, r, reqres.APIErrorUnauthenticated, "remote service not trusted", nil)
   239  			return
   240  		default:
   241  			reqres.WriteError(w, r, reqres.APIErrorServerError, "unexpected error: "+forwardInviteResponse.Status.Message, errors.New(forwardInviteResponse.Status.Message))
   242  			return
   243  		}
   244  	}
   245  
   246  	w.WriteHeader(http.StatusOK)
   247  
   248  	log.Info().Str("token", req.Token).Str("provider", req.ProviderDomain).Msgf("invite forwarded")
   249  }
   250  
   251  func getAcceptInviteRequest(r *http.Request) (*acceptInviteRequest, error) {
   252  	var req acceptInviteRequest
   253  	contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
   254  	if err == nil && contentType == "application/json" {
   255  		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
   256  			return nil, err
   257  		}
   258  	} else {
   259  		req.Token, req.ProviderDomain = r.FormValue("token"), r.FormValue("providerDomain")
   260  	}
   261  	return &req, nil
   262  }
   263  
   264  type remoteUser struct {
   265  	DisplayName string `json:"display_name"`
   266  	Idp         string `json:"idp"`
   267  	UserID      string `json:"user_id"`
   268  	Mail        string `json:"mail"`
   269  }
   270  
   271  // FindAccepted returns the list of all the users that accepted the invitation
   272  // to the authenticated user.
   273  func (h *tokenHandler) FindAccepted(w http.ResponseWriter, r *http.Request) {
   274  	ctx := r.Context()
   275  
   276  	gc, err := h.gatewaySelector.Next()
   277  	if err != nil {
   278  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err)
   279  		return
   280  	}
   281  	res, err := gc.FindAcceptedUsers(ctx, &invitepb.FindAcceptedUsersRequest{})
   282  	if err != nil {
   283  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc find accepted users request", err)
   284  		return
   285  	}
   286  
   287  	users := list.Map(res.AcceptedUsers, func(u *userpb.User) *remoteUser {
   288  		return &remoteUser{
   289  			DisplayName: u.DisplayName,
   290  			Idp:         u.Id.Idp,
   291  			UserID:      u.Id.OpaqueId,
   292  			Mail:        u.Mail,
   293  		}
   294  	})
   295  
   296  	if err := json.NewEncoder(w).Encode(users); err != nil {
   297  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err)
   298  		return
   299  	}
   300  
   301  	w.Header().Set("Content-Type", "application/json")
   302  	w.WriteHeader(http.StatusOK)
   303  }
   304  
   305  // DeleteAccepted deletes the given user from the list of the accepted users.
   306  func (h *tokenHandler) DeleteAccepted(w http.ResponseWriter, r *http.Request) {
   307  	ctx := r.Context()
   308  
   309  	req, err := getDeleteAcceptedRequest(r)
   310  	if err != nil {
   311  		reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "missing parameters in request", err)
   312  		return
   313  	}
   314  
   315  	gc, err := h.gatewaySelector.Next()
   316  	if err != nil {
   317  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err)
   318  		return
   319  	}
   320  	res, err := gc.DeleteAcceptedUser(ctx, &invitepb.DeleteAcceptedUserRequest{
   321  		RemoteUserId: &userpb.UserId{
   322  			Idp:      req.Idp,
   323  			OpaqueId: req.UserID,
   324  			Type:     userpb.UserType_USER_TYPE_FEDERATED,
   325  		},
   326  	})
   327  	if err != nil {
   328  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc get invite by domain info request", err)
   329  		return
   330  	}
   331  	if res.Status.Code != rpc.Code_CODE_OK {
   332  		reqres.WriteError(w, r, reqres.APIErrorServerError, "grpc forward invite request failed", errors.New(res.Status.Message))
   333  		return
   334  	}
   335  	w.WriteHeader(http.StatusOK)
   336  }
   337  
   338  type deleteAcceptedRequest struct {
   339  	Idp    string `json:"idp"`
   340  	UserID string `json:"user_id"`
   341  }
   342  
   343  func getDeleteAcceptedRequest(r *http.Request) (*deleteAcceptedRequest, error) {
   344  	var req deleteAcceptedRequest
   345  	contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
   346  	if err == nil && contentType == "application/json" {
   347  		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
   348  			return nil, err
   349  		}
   350  	} else {
   351  		req.Idp, req.UserID = r.FormValue("idp"), r.FormValue("user_id")
   352  	}
   353  	return &req, nil
   354  }
   355  
   356  func (h *tokenHandler) ListInvite(w http.ResponseWriter, r *http.Request) {
   357  	ctx := r.Context()
   358  
   359  	gc, err := h.gatewaySelector.Next()
   360  	if err != nil {
   361  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err)
   362  		return
   363  	}
   364  	res, err := gc.ListInviteTokens(ctx, &invitepb.ListInviteTokensRequest{})
   365  	if err != nil {
   366  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error listing tokens", err)
   367  		return
   368  	}
   369  
   370  	if res.Status.Code != rpc.Code_CODE_OK {
   371  		reqres.WriteError(w, r, reqres.APIErrorServerError, res.Status.Message, errors.New(res.Status.Message))
   372  		return
   373  	}
   374  
   375  	tokens := make([]*token, 0, len(res.InviteTokens))
   376  	for _, tkn := range res.InviteTokens {
   377  		tokens = append(tokens, h.prepareGenerateTokenResponse(tkn))
   378  	}
   379  
   380  	if err := json.NewEncoder(w).Encode(tokens); err != nil {
   381  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err)
   382  		return
   383  	}
   384  
   385  	w.Header().Set("Content-Type", "application/json")
   386  	w.WriteHeader(http.StatusOK)
   387  }