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 }