github.com/cs3org/reva/v2@v2.27.7/pkg/utils/utils.go (about) 1 // Copyright 2018-2021 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 utils 20 21 import ( 22 "encoding/json" 23 "errors" 24 "math/rand" 25 "net" 26 "net/http" 27 "net/url" 28 "os" 29 "os/user" 30 "path" 31 "path/filepath" 32 "regexp" 33 "strconv" 34 "strings" 35 "time" 36 37 appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" 38 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 39 grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" 40 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 41 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 42 types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 43 "github.com/golang/protobuf/proto" 44 "google.golang.org/protobuf/encoding/protojson" 45 ) 46 47 var ( 48 matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") 49 matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") 50 matchEmail = regexp.MustCompile(`^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$`) 51 52 // ShareStorageProviderID is the provider id used by the sharestorageprovider 53 ShareStorageProviderID = "a0ca6a90-a365-4782-871e-d44447bbc668" 54 // ShareStorageSpaceID is the space id used by the sharestorageprovider share jail space 55 ShareStorageSpaceID = "a0ca6a90-a365-4782-871e-d44447bbc668" 56 57 // PublicStorageProviderID is the storage id used by the sharestorageprovider 58 PublicStorageProviderID = "7993447f-687f-490d-875c-ac95e89a62a4" 59 // PublicStorageSpaceID is the space id used by the sharestorageprovider 60 PublicStorageSpaceID = "7993447f-687f-490d-875c-ac95e89a62a4" 61 62 // OCMStorageProviderID is the storage id used by the ocmreceived storageprovider 63 OCMStorageProviderID = "89f37a33-858b-45fa-8890-a1f2b27d90e1" 64 // OCMStorageSpaceID is the space id used by the ocmreceived storageprovider 65 OCMStorageSpaceID = "89f37a33-858b-45fa-8890-a1f2b27d90e1" 66 67 // SpaceGrant is used to signal the storageprovider that the grant is on a space 68 SpaceGrant struct{} 69 ) 70 71 // Skip evaluates whether a source endpoint contains any of the prefixes. 72 // i.e: /a/b/c/d/e contains prefix /a/b/c 73 func Skip(source string, prefixes []string) bool { 74 for i := range prefixes { 75 if strings.HasPrefix(source, prefixes[i]) { 76 return true 77 } 78 } 79 return false 80 } 81 82 // GetClientIP retrieves the client IP from incoming requests 83 func GetClientIP(r *http.Request) (string, error) { 84 var clientIP string 85 forwarded := r.Header.Get("X-FORWARDED-FOR") 86 87 if forwarded != "" { 88 clientIP = forwarded 89 } else { 90 if ip, _, err := net.SplitHostPort(r.RemoteAddr); err != nil { 91 ipObj := net.ParseIP(r.RemoteAddr) 92 if ipObj == nil { 93 return "", err 94 } 95 clientIP = ipObj.String() 96 } else { 97 clientIP = ip 98 } 99 } 100 return clientIP, nil 101 } 102 103 // ToSnakeCase converts a CamelCase string to a snake_case string. 104 func ToSnakeCase(str string) string { 105 snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}") 106 snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") 107 return strings.ToLower(snake) 108 } 109 110 // ResolvePath converts relative local paths to absolute paths 111 func ResolvePath(path string) (string, error) { 112 usr, err := user.Current() 113 if err != nil { 114 return "", err 115 } 116 homeDir := usr.HomeDir 117 118 if path == "~" { 119 path = homeDir 120 } else if strings.HasPrefix(path, "~/") { 121 path = filepath.Join(homeDir, path[2:]) 122 } 123 124 return filepath.Abs(path) 125 } 126 127 // RandString is a helper to create tokens. 128 func RandString(n int) string { 129 rand.Seed(time.Now().UTC().UnixNano()) 130 var l = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 131 b := make([]rune, n) 132 for i := range b { 133 b[i] = l[rand.Intn(len(l))] 134 } 135 return string(b) 136 } 137 138 // TSToUnixNano converts a protobuf Timestamp to uint64 139 // with nanoseconds resolution. 140 func TSToUnixNano(ts *types.Timestamp) uint64 { 141 if ts == nil { 142 return 0 143 } 144 return uint64(time.Unix(int64(ts.Seconds), int64(ts.Nanos)).UnixNano()) 145 } 146 147 // TSToTime converts a protobuf Timestamp to Go's time.Time. 148 func TSToTime(ts *types.Timestamp) time.Time { 149 if ts == nil { 150 return time.Time{} 151 } 152 return time.Unix(int64(ts.Seconds), int64(ts.Nanos)) 153 } 154 155 // TimeToTS converts Go's time.Time to a protobuf Timestamp. 156 func TimeToTS(t time.Time) *types.Timestamp { 157 return &types.Timestamp{ 158 Seconds: uint64(t.Unix()), // implicitly returns UTC 159 Nanos: uint32(t.Nanosecond()), 160 } 161 } 162 163 // LaterTS returns the timestamp which occurs later. 164 func LaterTS(t1 *types.Timestamp, t2 *types.Timestamp) *types.Timestamp { 165 if TSToUnixNano(t1) > TSToUnixNano(t2) { 166 return t1 167 } 168 return t2 169 } 170 171 // TSNow returns the current UTC timestamp 172 func TSNow() *types.Timestamp { 173 t := time.Now().UTC() 174 return &types.Timestamp{ 175 Seconds: uint64(t.Unix()), 176 Nanos: uint32(t.Nanosecond()), 177 } 178 } 179 180 // MTimeToTS converts a string in the form "<unix>.<nanoseconds>" into a CS3 Timestamp 181 func MTimeToTS(v string) (ts types.Timestamp, err error) { 182 p := strings.SplitN(v, ".", 2) 183 var sec, nsec uint64 184 if sec, err = strconv.ParseUint(p[0], 10, 64); err == nil { 185 if len(p) > 1 { 186 nsec, err = strconv.ParseUint(p[1], 10, 32) 187 } 188 } 189 return types.Timestamp{Seconds: sec, Nanos: uint32(nsec)}, err 190 } 191 192 // MTimeToTime converts a string in the form "<unix>.<nanoseconds>" into a go time.Time 193 func MTimeToTime(v string) (t time.Time, err error) { 194 p := strings.SplitN(v, ".", 2) 195 var sec, nsec int64 196 if sec, err = strconv.ParseInt(p[0], 10, 64); err == nil { 197 if len(p) > 1 { 198 nsec, err = strconv.ParseInt(p[1], 10, 64) 199 } 200 } 201 return time.Unix(sec, nsec), err 202 } 203 204 // TimeToOCMtime converts a Go time.Time to a string in the form "<unix>.<nanoseconds>" 205 func TimeToOCMtime(t time.Time) string { 206 return strconv.FormatInt(t.Unix(), 10) + "." + strconv.FormatInt(int64(t.Nanosecond()), 10) 207 } 208 209 // ExtractGranteeID returns the ID, user or group, set in the GranteeId object 210 func ExtractGranteeID(grantee *provider.Grantee) (*userpb.UserId, *grouppb.GroupId) { 211 switch t := grantee.Id.(type) { 212 case *provider.Grantee_UserId: 213 return t.UserId, nil 214 case *provider.Grantee_GroupId: 215 return nil, t.GroupId 216 default: 217 return nil, nil 218 } 219 } 220 221 // UserEqual returns whether two users have the same field values. 222 func UserEqual(u, v *userpb.UserId) bool { 223 return u != nil && v != nil && u.Idp == v.Idp && u.OpaqueId == v.OpaqueId 224 } 225 226 // UserIDEqual returns whether two users have the same opaqueid values. The idp is ignored 227 func UserIDEqual(u, v *userpb.UserId) bool { 228 return u != nil && v != nil && u.OpaqueId == v.OpaqueId 229 } 230 231 // GroupEqual returns whether two groups have the same field values. 232 func GroupEqual(u, v *grouppb.GroupId) bool { 233 return u != nil && v != nil && u.Idp == v.Idp && u.OpaqueId == v.OpaqueId 234 } 235 236 // ResourceIDEqual returns whether two resources have the same field values. 237 func ResourceIDEqual(u, v *provider.ResourceId) bool { 238 return u != nil && v != nil && u.StorageId == v.StorageId && u.OpaqueId == v.OpaqueId && u.SpaceId == v.SpaceId 239 } 240 241 // ResourceEqual returns whether two resources have the same field values. 242 func ResourceEqual(u, v *provider.Reference) bool { 243 return u != nil && v != nil && u.Path == v.Path && ((u.ResourceId == nil && v.ResourceId == nil) || (ResourceIDEqual(u.ResourceId, v.ResourceId))) 244 } 245 246 // GranteeEqual returns whether two grantees have the same field values. 247 func GranteeEqual(u, v *provider.Grantee) bool { 248 if u == nil || v == nil { 249 return false 250 } 251 uu, ug := ExtractGranteeID(u) 252 vu, vg := ExtractGranteeID(v) 253 return u.Type == v.Type && (UserEqual(uu, vu) || GroupEqual(ug, vg)) 254 } 255 256 // IsEmailValid checks whether the provided email has a valid format. 257 func IsEmailValid(e string) bool { 258 if len(e) < 3 || len(e) > 254 { 259 return false 260 } 261 return matchEmail.MatchString(e) 262 } 263 264 // IsValidWebAddress checks whether the provided address is a valid URL. 265 func IsValidWebAddress(address string) bool { 266 _, err := url.ParseRequestURI(address) 267 return err == nil 268 } 269 270 // IsValidPhoneNumber checks whether the provided phone number has a valid format. 271 func IsValidPhoneNumber(number string) bool { 272 re := regexp.MustCompile(`^(?:(?:\(?(?:00|\+)([1-4]\d\d|[1-9]\d?)\)?)?[\-\.\ \\\/]?)?((?:\(?\d{1,}\)?[\-\.\ \\\/]?){0,})(?:[\-\.\ \\\/]?(?:#|ext\.?|extension|x)[\-\.\ \\\/]?(\d+))?$`) 273 return re.MatchString(number) 274 } 275 276 // IsValidName cheks if the given name doesn't contain any non-alpha, space or dash characters. 277 func IsValidName(name string) bool { 278 re := regexp.MustCompile(`^[A-Za-z\s\-]*$`) 279 return re.MatchString(name) 280 } 281 282 // MarshalProtoV1ToJSON marshals a proto V1 message to a JSON byte array 283 // TODO: update this once we start using V2 in CS3APIs 284 func MarshalProtoV1ToJSON(m proto.Message) ([]byte, error) { 285 mV2 := proto.MessageV2(m) 286 return protojson.Marshal(mV2) 287 } 288 289 // UnmarshalJSONToProtoV1 decodes a JSON byte array to a specified proto message type 290 // TODO: update this once we start using V2 in CS3APIs 291 func UnmarshalJSONToProtoV1(b []byte, m proto.Message) error { 292 mV2 := proto.MessageV2(m) 293 if err := protojson.Unmarshal(b, mV2); err != nil { 294 return err 295 } 296 return nil 297 } 298 299 // IsRelativeReference returns true if the given reference qualifies as relative 300 // when the resource id is set and the path starts with a . 301 // 302 // TODO(corby): Currently if the path begins with a dot, the ResourceId is set but has empty storageId and OpaqueId 303 // then the reference is still being viewed as relative. We need to check if we want that because in some 304 // places we might not want to set both StorageId and OpaqueId so we can't do a hard check if they are set. 305 func IsRelativeReference(ref *provider.Reference) bool { 306 return ref.ResourceId != nil && strings.HasPrefix(ref.Path, ".") 307 } 308 309 // IsAbsoluteReference returns true if the given reference qualifies as absolute 310 // when either only the resource id is set or only the path is set and starts with / 311 // 312 // TODO(corby): Currently if the path is empty, the ResourceId is set but has empty storageId and OpaqueId 313 // then the reference is still being viewed as absolute. We need to check if we want that because in some 314 // places we might not want to set both StorageId and OpaqueId so we can't do a hard check if they are set. 315 func IsAbsoluteReference(ref *provider.Reference) bool { 316 return (ref.ResourceId != nil && ref.Path == "") || (ref.ResourceId == nil) && strings.HasPrefix(ref.Path, "/") 317 } 318 319 // IsAbsolutePathReference returns true if the given reference qualifies as a global path 320 // when only the path is set and starts with / 321 func IsAbsolutePathReference(ref *provider.Reference) bool { 322 return ref.ResourceId == nil && strings.HasPrefix(ref.Path, "/") 323 } 324 325 // MakeRelativePath prefixes the path with a . to use it in a relative reference 326 func MakeRelativePath(p string) string { 327 p = path.Join("/", p) 328 329 if p == "/" { 330 return "." 331 } 332 return "." + p 333 } 334 335 // UserTypeMap translates account type string to CS3 UserType 336 func UserTypeMap(accountType string) userpb.UserType { 337 var t userpb.UserType 338 switch accountType { 339 case "primary": 340 t = userpb.UserType_USER_TYPE_PRIMARY 341 case "secondary": 342 t = userpb.UserType_USER_TYPE_SECONDARY 343 case "service": 344 t = userpb.UserType_USER_TYPE_SERVICE 345 case "application": 346 t = userpb.UserType_USER_TYPE_APPLICATION 347 case "guest": 348 t = userpb.UserType_USER_TYPE_GUEST 349 case "federated": 350 t = userpb.UserType_USER_TYPE_FEDERATED 351 case "lightweight": 352 t = userpb.UserType_USER_TYPE_LIGHTWEIGHT 353 // FIXME new user type 354 case "spaceowner": 355 t = 8 356 } 357 return t 358 } 359 360 // UserTypeToString translates CS3 UserType to user-readable string 361 func UserTypeToString(accountType userpb.UserType) string { 362 var t string 363 switch accountType { 364 case userpb.UserType_USER_TYPE_PRIMARY: 365 t = "primary" 366 case userpb.UserType_USER_TYPE_SECONDARY: 367 t = "secondary" 368 case userpb.UserType_USER_TYPE_SERVICE: 369 t = "service" 370 case userpb.UserType_USER_TYPE_APPLICATION: 371 t = "application" 372 case userpb.UserType_USER_TYPE_GUEST: 373 t = "guest" 374 case userpb.UserType_USER_TYPE_FEDERATED: 375 t = "federated" 376 case userpb.UserType_USER_TYPE_LIGHTWEIGHT: 377 t = "lightweight" 378 // FIXME new user type 379 case 8: 380 t = "spaceowner" 381 } 382 return t 383 } 384 385 // GetViewMode converts a human-readable string to a view mode for opening a resource in an app. 386 func GetViewMode(viewMode string) gateway.OpenInAppRequest_ViewMode { 387 switch viewMode { 388 case "view": 389 return gateway.OpenInAppRequest_VIEW_MODE_VIEW_ONLY 390 case "read": 391 return gateway.OpenInAppRequest_VIEW_MODE_READ_ONLY 392 case "write": 393 return gateway.OpenInAppRequest_VIEW_MODE_READ_WRITE 394 default: 395 return gateway.OpenInAppRequest_VIEW_MODE_INVALID 396 } 397 } 398 399 // GetAppViewMode converts a human-readable string to an appprovider view mode for opening a resource in an app. 400 func GetAppViewMode(viewMode string) appprovider.ViewMode { 401 switch viewMode { 402 case "view": 403 return appprovider.ViewMode_VIEW_MODE_VIEW_ONLY 404 case "read": 405 return appprovider.ViewMode_VIEW_MODE_READ_ONLY 406 case "write": 407 return appprovider.ViewMode_VIEW_MODE_READ_WRITE 408 case "preview": 409 return appprovider.ViewMode_VIEW_MODE_PREVIEW 410 default: 411 return appprovider.ViewMode_VIEW_MODE_INVALID 412 } 413 } 414 415 // AppendPlainToOpaque adds a new key value pair as a plain string on the given opaque and returns it 416 func AppendPlainToOpaque(o *types.Opaque, key, value string) *types.Opaque { 417 o = ensureOpaque(o) 418 419 o.Map[key] = &types.OpaqueEntry{ 420 Decoder: "plain", 421 Value: []byte(value), 422 } 423 return o 424 } 425 426 // AppendJSONToOpaque adds a new key value pair as a json on the given opaque and returns it. Ignores errors 427 func AppendJSONToOpaque(o *types.Opaque, key string, value interface{}) *types.Opaque { 428 o = ensureOpaque(o) 429 430 b, _ := json.Marshal(value) 431 o.Map[key] = &types.OpaqueEntry{ 432 Decoder: "json", 433 Value: b, 434 } 435 return o 436 } 437 438 // ReadPlainFromOpaque reads a plain string from the given opaque map 439 func ReadPlainFromOpaque(o *types.Opaque, key string) string { 440 if o.GetMap() == nil { 441 return "" 442 } 443 if e, ok := o.Map[key]; ok && e.Decoder == "plain" { 444 return string(e.Value) 445 } 446 return "" 447 } 448 449 // ReadJSONFromOpaque reads and unmarshals a value from the opaque in the given interface{} (Make sure it's a pointer!) 450 func ReadJSONFromOpaque(o *types.Opaque, key string, valptr interface{}) error { 451 if o.GetMap() == nil { 452 return errors.New("not found") 453 } 454 455 e, ok := o.Map[key] 456 if !ok || e.Decoder != "json" { 457 return errors.New("not found") 458 } 459 460 return json.Unmarshal(e.Value, valptr) 461 } 462 463 // ExistsInOpaque returns true if the key exists in the opaque (ignoring the value) 464 func ExistsInOpaque(o *types.Opaque, key string) bool { 465 if o.GetMap() == nil { 466 return false 467 } 468 469 _, ok := o.Map[key] 470 return ok 471 } 472 473 // MergeOpaques will merge the opaques. If a key exists in both opaques 474 // the values from the first opaque will be taken 475 func MergeOpaques(o *types.Opaque, p *types.Opaque) *types.Opaque { 476 p = ensureOpaque(p) 477 for k, v := range o.GetMap() { 478 p.Map[k] = v 479 } 480 return p 481 } 482 483 // ensures the opaque is initialized 484 func ensureOpaque(o *types.Opaque) *types.Opaque { 485 if o == nil { 486 o = &types.Opaque{} 487 } 488 if o.Map == nil { 489 o.Map = map[string]*types.OpaqueEntry{} 490 } 491 return o 492 } 493 494 // RemoveItem removes the given item, its children and all empty parent folders 495 func RemoveItem(path string) error { 496 if err := os.RemoveAll(path); err != nil { 497 return err 498 } 499 500 for { 501 path = filepath.Dir(path) 502 if err := os.Remove(path); err != nil { 503 // remove will fail when the dir is not empty. 504 // We can exit in that case 505 return nil 506 } 507 508 } 509 510 }