github.com/cs3org/reva/v2@v2.27.7/internal/http/services/ocmd/shares.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 ocmd
    20  
    21  import (
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"mime"
    26  	"net/http"
    27  	"strings"
    28  
    29  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    30  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    31  	ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
    32  	ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
    33  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    34  	ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
    35  	providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    36  	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    37  	"github.com/cs3org/reva/v2/internal/http/services/reqres"
    38  	"github.com/cs3org/reva/v2/pkg/appctx"
    39  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    40  	"github.com/cs3org/reva/v2/pkg/utils"
    41  	"github.com/go-playground/validator/v10"
    42  )
    43  
    44  var validate = validator.New()
    45  
    46  type sharesHandler struct {
    47  	gatewaySelector            *pool.Selector[gateway.GatewayAPIClient]
    48  	exposeRecipientDisplayName bool
    49  }
    50  
    51  func (h *sharesHandler) init(c *config) error {
    52  	var err error
    53  
    54  	gatewaySelector, err := pool.GatewaySelector(c.GatewaySvc)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	h.gatewaySelector = gatewaySelector
    59  
    60  	h.exposeRecipientDisplayName = c.ExposeRecipientDisplayName
    61  	return nil
    62  }
    63  
    64  type createShareRequest struct {
    65  	ShareWith         string    `json:"shareWith" validate:"required"`                  // identifier of the recipient of the share
    66  	Name              string    `json:"name" validate:"required"`                       // name of the resource
    67  	Description       string    `json:"description"`                                    // (optional) description of the resource
    68  	ProviderID        string    `json:"providerId" validate:"required"`                 // unique identifier of the resource at provider side
    69  	Owner             string    `json:"owner" validate:"required"`                      // unique identifier of the owner at provider side
    70  	Sender            string    `json:"sender" validate:"required"`                     // unique indentifier of the user who wants to share the resource at provider side
    71  	OwnerDisplayName  string    `json:"ownerDisplayName"`                               // display name of the owner of the resource
    72  	SenderDisplayName string    `json:"senderDisplayName"`                              // dispay name of the user who wants to share the resource
    73  	ShareType         string    `json:"shareType" validate:"required,oneof=user group"` // recipient share type (user or group)
    74  	ResourceType      string    `json:"resourceType" validate:"required,oneof=file folder"`
    75  	Expiration        uint64    `json:"expiration"`
    76  	Protocols         Protocols `json:"protocol" validate:"required"`
    77  }
    78  
    79  // CreateShare sends all the information to the consumer needed to start
    80  // synchronization between the two services.
    81  func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) {
    82  	ctx := r.Context()
    83  	log := appctx.GetLogger(ctx)
    84  	req, err := getCreateShareRequest(r)
    85  	if err != nil {
    86  		reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil)
    87  		return
    88  	}
    89  
    90  	_, meshProvider, err := getIDAndMeshProvider(req.Sender)
    91  	log.Debug().Msgf("Determined Mesh Provider '%s' from req.Sender '%s'", meshProvider, req.Sender)
    92  	if err != nil {
    93  		reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil)
    94  		return
    95  	}
    96  
    97  	clientIP, err := utils.GetClientIP(r)
    98  	if err != nil {
    99  		reqres.WriteError(w, r, reqres.APIErrorServerError, fmt.Sprintf("error retrieving client IP from request: %s", r.RemoteAddr), err)
   100  		return
   101  	}
   102  	providerInfo := ocmprovider.ProviderInfo{
   103  		Domain: meshProvider,
   104  		Services: []*ocmprovider.Service{
   105  			{
   106  				Host: clientIP,
   107  			},
   108  		},
   109  	}
   110  	gatewayClient, err := h.gatewaySelector.Next()
   111  	if err != nil {
   112  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error getting gateway client", err)
   113  		return
   114  	}
   115  	providerAllowedResp, err := gatewayClient.IsProviderAllowed(ctx, &ocmprovider.IsProviderAllowedRequest{
   116  		Provider: &providerInfo,
   117  	})
   118  	if err != nil {
   119  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc is provider allowed request", err)
   120  		return
   121  	}
   122  	if providerAllowedResp.Status.Code != rpc.Code_CODE_OK {
   123  		reqres.WriteError(w, r, reqres.APIErrorUnauthenticated, "provider not authorized", errors.New(providerAllowedResp.Status.Message))
   124  		return
   125  	}
   126  
   127  	shareWith, _, err := getIDAndMeshProvider(req.ShareWith)
   128  	if err != nil {
   129  		reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil)
   130  		return
   131  	}
   132  
   133  	userRes, err := gatewayClient.GetUser(ctx, &userpb.GetUserRequest{
   134  		UserId: &userpb.UserId{OpaqueId: shareWith}, SkipFetchingUserGroups: true,
   135  	})
   136  	if err != nil {
   137  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error searching recipient", err)
   138  		return
   139  	}
   140  	if userRes.Status.Code != rpc.Code_CODE_OK {
   141  		reqres.WriteError(w, r, reqres.APIErrorNotFound, "user not found", errors.New(userRes.Status.Message))
   142  		return
   143  	}
   144  
   145  	owner, err := getUserIDFromOCMUser(req.Owner)
   146  	if err != nil {
   147  		reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil)
   148  		return
   149  	}
   150  
   151  	sender, err := getUserIDFromOCMUser(req.Sender)
   152  	if err != nil {
   153  		reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil)
   154  		return
   155  	}
   156  
   157  	createShareReq := &ocmcore.CreateOCMCoreShareRequest{
   158  		Description:  req.Description,
   159  		Name:         req.Name,
   160  		ResourceId:   req.ProviderID,
   161  		Owner:        owner,
   162  		Sender:       sender,
   163  		ShareWith:    userRes.User.Id,
   164  		ResourceType: getResourceTypeFromOCMRequest(req.ResourceType),
   165  		ShareType:    getOCMShareType(req.ShareType),
   166  		Protocols:    getProtocols(req.Protocols),
   167  	}
   168  
   169  	if req.Expiration != 0 {
   170  		createShareReq.Expiration = &types.Timestamp{
   171  			Seconds: req.Expiration,
   172  		}
   173  	}
   174  
   175  	createShareResp, err := gatewayClient.CreateOCMCoreShare(ctx, createShareReq)
   176  	if err != nil {
   177  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error creating ocm share", err)
   178  		return
   179  	}
   180  
   181  	if createShareResp.Status.Code != rpc.Code_CODE_OK {
   182  		// TODO: define errors in the cs3apis
   183  		reqres.WriteError(w, r, reqres.APIErrorServerError, "error creating ocm share", errors.New(createShareResp.Status.Message))
   184  		return
   185  	}
   186  
   187  	response := map[string]any{}
   188  
   189  	if h.exposeRecipientDisplayName {
   190  		response["recipientDisplayName"] = userRes.User.DisplayName
   191  	}
   192  
   193  	_ = json.NewEncoder(w).Encode(response)
   194  	w.WriteHeader(http.StatusCreated)
   195  }
   196  
   197  func getUserIDFromOCMUser(user string) (*userpb.UserId, error) {
   198  	id, idp, err := getIDAndMeshProvider(user)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	return &userpb.UserId{
   203  		OpaqueId: id,
   204  		Idp:      idp,
   205  		// the remote user is a federated account for the local reva
   206  		Type: userpb.UserType_USER_TYPE_FEDERATED,
   207  	}, nil
   208  }
   209  
   210  func getIDAndMeshProvider(user string) (string, string, error) {
   211  	// the user is in the form of dimitri@apiwise.nl
   212  	split := strings.Split(user, "@")
   213  	if len(split) < 2 {
   214  		return "", "", errors.New("not in the form <id>@<provider>")
   215  	}
   216  	return strings.Join(split[:len(split)-1], "@"), split[len(split)-1], nil
   217  }
   218  
   219  func getCreateShareRequest(r *http.Request) (*createShareRequest, error) {
   220  	var req createShareRequest
   221  	contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
   222  	if err == nil && contentType == "application/json" {
   223  		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
   224  			return nil, err
   225  		}
   226  	} else {
   227  		return nil, errors.New("body request not recognised")
   228  	}
   229  	// validate the request
   230  	if err := validate.Struct(req); err != nil {
   231  		return nil, err
   232  	}
   233  	return &req, nil
   234  }
   235  
   236  func getResourceTypeFromOCMRequest(t string) providerpb.ResourceType {
   237  	switch t {
   238  	case "file":
   239  		return providerpb.ResourceType_RESOURCE_TYPE_FILE
   240  	case "folder":
   241  		return providerpb.ResourceType_RESOURCE_TYPE_CONTAINER
   242  	default:
   243  		return providerpb.ResourceType_RESOURCE_TYPE_INVALID
   244  	}
   245  }
   246  
   247  func getOCMShareType(t string) ocm.ShareType {
   248  	if t == "user" {
   249  		return ocm.ShareType_SHARE_TYPE_USER
   250  	}
   251  	return ocm.ShareType_SHARE_TYPE_GROUP
   252  }
   253  
   254  func getProtocols(p Protocols) []*ocm.Protocol {
   255  	prot := make([]*ocm.Protocol, 0, len(p))
   256  	for _, data := range p {
   257  		prot = append(prot, data.ToOCMProtocol())
   258  	}
   259  	return prot
   260  }