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  }