github.com/cs3org/reva/v2@v2.27.7/pkg/utils/grpc.go (about)

     1  package utils
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  
     9  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    10  	group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
    11  	user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    12  	invitev1beta1 "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
    13  	permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
    14  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    15  	storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    16  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    17  	"google.golang.org/grpc/metadata"
    18  )
    19  
    20  // SpaceRole defines the user role on space
    21  type SpaceRole func(*storageprovider.ResourcePermissions) bool
    22  
    23  // Possible roles in spaces
    24  var (
    25  	AllRole     SpaceRole = func(perms *storageprovider.ResourcePermissions) bool { return true }
    26  	ViewerRole  SpaceRole = func(perms *storageprovider.ResourcePermissions) bool { return perms.Stat }
    27  	EditorRole  SpaceRole = func(perms *storageprovider.ResourcePermissions) bool { return perms.InitiateFileUpload }
    28  	ManagerRole SpaceRole = func(perms *storageprovider.ResourcePermissions) bool { return perms.DenyGrant }
    29  )
    30  
    31  var _errStatusCodeTmpl = "unexpected status code while %s: %v"
    32  
    33  // Package error checkers
    34  var (
    35  	IsErrNotFound         = func(err error) bool { return IsStatusCodeError(err, rpc.Code_CODE_NOT_FOUND) }
    36  	IsErrPermissionDenied = func(err error) bool { return IsStatusCodeError(err, rpc.Code_CODE_PERMISSION_DENIED) }
    37  )
    38  
    39  // GetServiceUserContext returns an authenticated context of the given service user
    40  // Deprecated: Use GetServiceUserContextWithContext()
    41  func GetServiceUserContext(serviceUserID string, gwc gateway.GatewayAPIClient, serviceUserSecret string) (context.Context, error) {
    42  	return GetServiceUserContextWithContext(context.Background(), gwc, serviceUserID, serviceUserSecret)
    43  }
    44  
    45  // GetServiceUserContextWithContext returns an authenticated context of the given service user
    46  func GetServiceUserContextWithContext(ctx context.Context, gwc gateway.GatewayAPIClient, serviceUserID string, serviceUserSecret string) (context.Context, error) {
    47  	token, err := GetServiceUserToken(ctx, gwc, serviceUserID, serviceUserSecret)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	return metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, token), nil
    53  }
    54  
    55  // GetServiceUserToken returns a reva authentication token for the given service user
    56  func GetServiceUserToken(ctx context.Context, gwc gateway.GatewayAPIClient, serviceUserID string, serviceUserSecret string) (string, error) {
    57  	authRes, err := gwc.Authenticate(ctx, &gateway.AuthenticateRequest{
    58  		Type:         "serviceaccounts",
    59  		ClientId:     serviceUserID,
    60  		ClientSecret: serviceUserSecret,
    61  	})
    62  	if err != nil {
    63  		return "", err
    64  	}
    65  
    66  	if err := checkStatusCode("authenticating service user", authRes.GetStatus().GetMessage(), authRes.GetStatus().GetCode()); err != nil {
    67  		return "", err
    68  	}
    69  
    70  	return authRes.Token, nil
    71  }
    72  
    73  // GetUser gets the specified user
    74  // Deprecated: Use GetUserWithContext()
    75  func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) {
    76  	return GetUserWithContext(context.Background(), userID, gwc)
    77  }
    78  
    79  // GetUserWithContext gets the specified user
    80  func GetUserWithContext(ctx context.Context, userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) {
    81  	getUserResponse, err := gwc.GetUser(ctx, &user.GetUserRequest{UserId: userID})
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	if err := checkStatusCode("getting user", getUserResponse.GetStatus().GetMessage(), getUserResponse.GetStatus().GetCode()); err != nil {
    87  		return nil, err
    88  
    89  	}
    90  	return getUserResponse.GetUser(), nil
    91  }
    92  
    93  // GetUserWithContext gets the specified accepted user
    94  func GetAcceptedUserWithContext(ctx context.Context, userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) {
    95  	getAcceptedUserResponse, err := gwc.GetAcceptedUser(ctx, &invitev1beta1.GetAcceptedUserRequest{RemoteUserId: userID})
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	if err := checkStatusCode("getting accepted user", getAcceptedUserResponse.GetStatus().GetMessage(), getAcceptedUserResponse.GetStatus().GetCode()); err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	return getAcceptedUserResponse.GetRemoteUser(), nil
   105  }
   106  
   107  // GetSpace returns the given space
   108  func GetSpace(ctx context.Context, spaceID string, gwc gateway.GatewayAPIClient) (*storageprovider.StorageSpace, error) {
   109  	res, err := gwc.ListStorageSpaces(ctx, listStorageSpaceRequest(spaceID))
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	if err := checkStatusCode("getting space", res.GetStatus().GetMessage(), res.GetStatus().GetCode()); err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	if len(res.StorageSpaces) == 0 {
   119  		return nil, statusCodeError{"getting space", "", rpc.Code_CODE_NOT_FOUND}
   120  	}
   121  
   122  	return res.StorageSpaces[0], nil
   123  }
   124  
   125  // GetGroupMembers returns all members of the given group
   126  func GetGroupMembers(ctx context.Context, groupID string, gwc gateway.GatewayAPIClient) ([]string, error) {
   127  	r, err := gwc.GetGroup(ctx, &group.GetGroupRequest{GroupId: &group.GroupId{OpaqueId: groupID}})
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	if err := checkStatusCode("getting group", r.GetStatus().GetMessage(), r.GetStatus().GetCode()); err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	users := make([]string, 0, len(r.GetGroup().GetMembers()))
   137  	for _, u := range r.GetGroup().GetMembers() {
   138  		users = append(users, u.GetOpaqueId())
   139  	}
   140  
   141  	return users, nil
   142  }
   143  
   144  // ResolveID returns either the given userID or all members of the given groupID (if userID is nil)
   145  func ResolveID(ctx context.Context, userid *user.UserId, groupid *group.GroupId, gwc gateway.GatewayAPIClient) ([]string, error) {
   146  	if userid != nil {
   147  		return []string{userid.GetOpaqueId()}, nil
   148  	}
   149  
   150  	if ctx == nil {
   151  		return nil, errors.New("need ctx to resolve group id")
   152  	}
   153  
   154  	return GetGroupMembers(ctx, groupid.GetOpaqueId(), gwc)
   155  }
   156  
   157  // GetSpaceMembers returns all members of the given space that have at least the given role. `nil` role will be interpreted as all
   158  func GetSpaceMembers(ctx context.Context, spaceID string, gwc gateway.GatewayAPIClient, role SpaceRole) ([]string, error) {
   159  	if ctx == nil {
   160  		return nil, errors.New("need authenticated context to find space members")
   161  	}
   162  
   163  	space, err := GetSpace(ctx, spaceID, gwc)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	var users []string
   169  	switch space.SpaceType {
   170  	case "personal":
   171  		users = append(users, space.GetOwner().GetId().GetOpaqueId())
   172  	case "project":
   173  		if users, err = gatherProjectSpaceMembers(ctx, space, gwc, role); err != nil {
   174  			return nil, err
   175  		}
   176  	default:
   177  		// TODO: shares? other space types?
   178  		return nil, fmt.Errorf("unsupported space type: %s", space.SpaceType)
   179  	}
   180  
   181  	return users, nil
   182  }
   183  
   184  // GetResourceByID is a convenience method to get a resource by its resourceID
   185  func GetResourceByID(ctx context.Context, resourceid *storageprovider.ResourceId, gwc gateway.GatewayAPIClient) (*storageprovider.ResourceInfo, error) {
   186  	return GetResource(ctx, &storageprovider.Reference{ResourceId: resourceid}, gwc)
   187  }
   188  
   189  // GetResource returns a resource by reference
   190  func GetResource(ctx context.Context, ref *storageprovider.Reference, gwc gateway.GatewayAPIClient) (*storageprovider.ResourceInfo, error) {
   191  	res, err := gwc.Stat(ctx, &storageprovider.StatRequest{Ref: ref})
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	if err := checkStatusCode("getting resource", res.GetStatus().GetMessage(), res.GetStatus().GetCode()); err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	return res.GetInfo(), nil
   201  }
   202  
   203  // CheckPermission checks if the user role contains the given permission
   204  func CheckPermission(ctx context.Context, perm string, gwc gateway.GatewayAPIClient) (bool, error) {
   205  	user := ctxpkg.ContextMustGetUser(ctx)
   206  	resp, err := gwc.CheckPermission(ctx, &permissions.CheckPermissionRequest{
   207  		SubjectRef: &permissions.SubjectReference{
   208  			Spec: &permissions.SubjectReference_UserId{
   209  				UserId: user.Id,
   210  			},
   211  		},
   212  		Permission: perm,
   213  	})
   214  	return resp.GetStatus().GetCode() == rpc.Code_CODE_OK, err
   215  }
   216  
   217  // IsStatusCodeError returns true if `err` was caused because of status code `code`
   218  func IsStatusCodeError(err error, code rpc.Code) bool {
   219  	sce, ok := err.(statusCodeError)
   220  	if !ok {
   221  		return false
   222  	}
   223  	return sce.code == code
   224  }
   225  
   226  // StatusCodeErrorToCS3Status translate the `statusCodeError` type to CS3 Status
   227  // returns nil if `err` does not match to the `statusCodeError` type
   228  func StatusCodeErrorToCS3Status(err error) *rpc.Status {
   229  	var sce statusCodeError
   230  	ok := errors.As(err, &sce)
   231  	if !ok {
   232  		return nil
   233  	}
   234  	if sce.message == "" {
   235  		sce.message = sce.reason
   236  	}
   237  	return &rpc.Status{Message: sce.message, Code: sce.code}
   238  }
   239  
   240  // IsSpaceRoot checks if the given resource info is referring to a space root
   241  func IsSpaceRoot(ri *storageprovider.ResourceInfo) bool {
   242  	f := ri.GetId()
   243  	s := ri.GetSpace().GetRoot()
   244  	return f.GetOpaqueId() == s.GetOpaqueId() && f.GetSpaceId() == s.GetSpaceId()
   245  }
   246  
   247  func checkStatusCode(reason, message string, code rpc.Code) error {
   248  	if code == rpc.Code_CODE_OK {
   249  		return nil
   250  	}
   251  	return statusCodeError{reason, message, code}
   252  }
   253  
   254  func gatherProjectSpaceMembers(ctx context.Context, space *storageprovider.StorageSpace, gwc gateway.GatewayAPIClient, role SpaceRole) ([]string, error) {
   255  	var permissionsMap map[string]*storageprovider.ResourcePermissions
   256  	if err := ReadJSONFromOpaque(space.GetOpaque(), "grants", &permissionsMap); err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	groupsMap := make(map[string]struct{})
   261  	if opaqueGroups, ok := space.Opaque.Map["groups"]; ok {
   262  		_ = json.Unmarshal(opaqueGroups.GetValue(), &groupsMap)
   263  	}
   264  
   265  	if role == nil {
   266  		role = AllRole
   267  	}
   268  
   269  	// we use a map to avoid duplicates
   270  	usermap := make(map[string]struct{})
   271  	for id, perm := range permissionsMap {
   272  		if !role(perm) {
   273  			continue
   274  		}
   275  
   276  		if _, isGroup := groupsMap[id]; !isGroup {
   277  			usermap[id] = struct{}{}
   278  			continue
   279  		}
   280  
   281  		usrs, err := GetGroupMembers(ctx, id, gwc)
   282  		if err != nil {
   283  			// TODO: continue?
   284  			return nil, err
   285  		}
   286  
   287  		for _, u := range usrs {
   288  			usermap[u] = struct{}{}
   289  		}
   290  	}
   291  
   292  	users := make([]string, 0, len(usermap))
   293  	for id := range usermap {
   294  		users = append(users, id)
   295  	}
   296  
   297  	return users, nil
   298  }
   299  
   300  func listStorageSpaceRequest(spaceID string) *storageprovider.ListStorageSpacesRequest {
   301  	return &storageprovider.ListStorageSpacesRequest{
   302  		Opaque: AppendPlainToOpaque(nil, "unrestricted", "true"),
   303  		Filters: []*storageprovider.ListStorageSpacesRequest_Filter{
   304  			{
   305  				Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_ID,
   306  				Term: &storageprovider.ListStorageSpacesRequest_Filter_Id{
   307  					Id: &storageprovider.StorageSpaceId{
   308  						OpaqueId: spaceID,
   309  					},
   310  				},
   311  			},
   312  		},
   313  	}
   314  }
   315  
   316  // statusCodeError is a helper struct to return errors
   317  type statusCodeError struct {
   318  	reason  string
   319  	message string // represents the v1beta11.Status.Message
   320  	code    rpc.Code
   321  }
   322  
   323  // Error implements error interface
   324  func (sce statusCodeError) Error() string {
   325  	if sce.reason != "" {
   326  		return fmt.Sprintf(_errStatusCodeTmpl, sce.reason, sce.code)
   327  	}
   328  	return fmt.Sprintf(_errStatusCodeTmpl, sce.message, sce.code)
   329  }