github.com/minio/console@v1.4.1/api/utils.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"crypto/rand"
    21  	"encoding/base64"
    22  	"encoding/json"
    23  	"errors"
    24  	"io"
    25  	"net/http"
    26  	"strings"
    27  	"time"
    28  
    29  	xjwt "github.com/minio/console/pkg/auth/token"
    30  )
    31  
    32  // Do not use:
    33  // https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
    34  // It relies on math/rand and therefore not on a cryptographically secure RNG => It must not be used
    35  // for access/secret keys.
    36  
    37  // The alphabet of random character string. Each character must be unique.
    38  //
    39  // The RandomCharString implementation requires that: 256 / len(letters) is a natural numbers.
    40  // For example: 256 / 64 = 4. However, 5 > 256/62 > 4 and therefore we must not use a alphabet
    41  // of 62 characters.
    42  // The reason is that if 256 / len(letters) is not a natural number then certain characters become
    43  // more likely then others.
    44  const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
    45  
    46  type CustomButtonStyle struct {
    47  	BackgroundColor *string `json:"backgroundColor"`
    48  	TextColor       *string `json:"textColor"`
    49  	HoverColor      *string `json:"hoverColor"`
    50  	HoverText       *string `json:"hoverText"`
    51  	ActiveColor     *string `json:"activeColor"`
    52  	ActiveText      *string `json:"activeText"`
    53  	DisabledColor   *string `json:"disabledColor"`
    54  	DisabledText    *string `json:"disdabledText"`
    55  }
    56  
    57  type CustomTableStyle struct {
    58  	Border          *string `json:"border"`
    59  	DisabledBorder  *string `json:"disabledBorder"`
    60  	DisabledBG      *string `json:"disabledBG"`
    61  	Selected        *string `json:"selected"`
    62  	DeletedDisabled *string `json:"deletedDisabled"`
    63  	HoverColor      *string `json:"hoverColor"`
    64  }
    65  
    66  type CustomInputStyle struct {
    67  	Border          *string `json:"border"`
    68  	HoverBorder     *string `json:"hoverBorder"`
    69  	TextColor       *string `json:"textColor"`
    70  	BackgroundColor *string `json:"backgroundColor"`
    71  }
    72  
    73  type CustomSwitchStyle struct {
    74  	SwitchBackground          *string `json:"switchBackground"`
    75  	BulletBorderColor         *string `json:"bulletBorderColor"`
    76  	BulletBGColor             *string `json:"bulletBGColor"`
    77  	DisabledBackground        *string `json:"disabledBackground"`
    78  	DisabledBulletBorderColor *string `json:"disabledBulletBorderColor"`
    79  	DisabledBulletBGColor     *string `json:"disabledBulletBGColor"`
    80  }
    81  
    82  type CustomStyles struct {
    83  	BackgroundColor       *string            `json:"backgroundColor"`
    84  	FontColor             *string            `json:"fontColor"`
    85  	SecondaryFontColor    *string            `json:"secondaryFontColor"`
    86  	BorderColor           *string            `json:"borderColor"`
    87  	LoaderColor           *string            `json:"loaderColor"`
    88  	BoxBackground         *string            `json:"boxBackground"`
    89  	OkColor               *string            `json:"okColor"`
    90  	ErrorColor            *string            `json:"errorColor"`
    91  	WarnColor             *string            `json:"warnColor"`
    92  	LinkColor             *string            `json:"linkColor"`
    93  	DisabledLinkColor     *string            `json:"disabledLinkColor"`
    94  	HoverLinkColor        *string            `json:"hoverLinkColor"`
    95  	ButtonStyles          *CustomButtonStyle `json:"buttonStyles"`
    96  	SecondaryButtonStyles *CustomButtonStyle `json:"secondaryButtonStyles"`
    97  	RegularButtonStyles   *CustomButtonStyle `json:"regularButtonStyles"`
    98  	TableColors           *CustomTableStyle  `json:"tableColors"`
    99  	InputBox              *CustomInputStyle  `json:"inputBox"`
   100  	Switch                *CustomSwitchStyle `json:"switch"`
   101  }
   102  
   103  func RandomCharStringWithAlphabet(n int, alphabet string) string {
   104  	random := make([]byte, n)
   105  	if _, err := io.ReadFull(rand.Reader, random); err != nil {
   106  		panic(err) // Can only happen if we would run out of entropy.
   107  	}
   108  
   109  	var s strings.Builder
   110  	for _, v := range random {
   111  		j := v % byte(len(alphabet))
   112  		s.WriteByte(alphabet[j])
   113  	}
   114  	return s.String()
   115  }
   116  
   117  func RandomCharString(n int) string {
   118  	return RandomCharStringWithAlphabet(n, letters)
   119  }
   120  
   121  // DifferenceArrays returns the elements in `a` that aren't in `b`.
   122  func DifferenceArrays(a, b []string) []string {
   123  	mb := make(map[string]struct{}, len(b))
   124  	for _, x := range b {
   125  		mb[x] = struct{}{}
   126  	}
   127  	var diff []string
   128  	for _, x := range a {
   129  		if _, found := mb[x]; !found {
   130  			diff = append(diff, x)
   131  		}
   132  	}
   133  	return diff
   134  }
   135  
   136  // IsElementInArray returns true if the string belongs to the slice
   137  func IsElementInArray(a []string, b string) bool {
   138  	for _, e := range a {
   139  		if e == b {
   140  			return true
   141  		}
   142  	}
   143  
   144  	return false
   145  }
   146  
   147  // UniqueKeys returns an array without duplicated keys
   148  func UniqueKeys(a []string) []string {
   149  	keys := make(map[string]bool)
   150  	list := []string{}
   151  	for _, entry := range a {
   152  		if _, value := keys[entry]; !value {
   153  			keys[entry] = true
   154  			list = append(list, entry)
   155  		}
   156  	}
   157  	return list
   158  }
   159  
   160  func NewSessionCookieForConsole(token string) http.Cookie {
   161  	sessionDuration := xjwt.GetConsoleSTSDuration()
   162  	return http.Cookie{
   163  		Path:     "/",
   164  		Name:     "token",
   165  		Value:    token,
   166  		MaxAge:   int(sessionDuration.Seconds()), // default 1 hr
   167  		Expires:  time.Now().Add(sessionDuration),
   168  		HttpOnly: true,
   169  		// if len(GlobalPublicCerts) > 0 is true, that means Console is running with TLS enable and the browser
   170  		// should not leak any cookie if we access the site using HTTP
   171  		Secure: len(GlobalPublicCerts) > 0,
   172  		// read more: https://web.dev/samesite-cookies-explained/
   173  		SameSite: http.SameSiteLaxMode,
   174  	}
   175  }
   176  
   177  func ExpireSessionCookie() http.Cookie {
   178  	return http.Cookie{
   179  		Path:     "/",
   180  		Name:     "token",
   181  		Value:    "",
   182  		MaxAge:   -1,
   183  		Expires:  time.Now().Add(-100 * time.Hour),
   184  		HttpOnly: true,
   185  		// if len(GlobalPublicCerts) > 0 is true, that means Console is running with TLS enable and the browser
   186  		// should not leak any cookie if we access the site using HTTP
   187  		Secure: len(GlobalPublicCerts) > 0,
   188  		// read more: https://web.dev/samesite-cookies-explained/
   189  		SameSite: http.SameSiteLaxMode,
   190  	}
   191  }
   192  
   193  func ValidateEncodedStyles(encodedStyles string) error {
   194  	// encodedStyle JSON validation
   195  	str, err := base64.StdEncoding.DecodeString(encodedStyles)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	var styleElements *CustomStyles
   201  
   202  	err = json.Unmarshal(str, &styleElements)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	if styleElements.BackgroundColor == nil || styleElements.FontColor == nil || styleElements.ButtonStyles == nil || styleElements.BorderColor == nil || styleElements.OkColor == nil {
   208  		return errors.New("specified style is not in the correct format")
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  // SanitizeEncodedPrefix replaces spaces for + since those are lost when you do GET parameters
   215  func SanitizeEncodedPrefix(rawPrefix string) string {
   216  	return strings.ReplaceAll(rawPrefix, " ", "+")
   217  }
   218  
   219  var safeMimeTypes = []string{
   220  	"image/jpeg",
   221  	"image/apng",
   222  	"image/avif",
   223  	"image/webp",
   224  	"image/bmp",
   225  	"image/x-icon",
   226  	"image/gif",
   227  	"image/png",
   228  	"image/heic",
   229  	"image/heif",
   230  	"application/pdf",
   231  	"text/plain",
   232  	"application/json",
   233  	"audio/wav",
   234  	"audio/mpeg",
   235  	"audio/aiff",
   236  	"audio/dsd",
   237  	"video/mp4",
   238  	"video/x-msvideo",
   239  	"video/mpeg",
   240  	"audio/webm",
   241  	"video/webm",
   242  	"video/quicktime",
   243  	"video/x-flv",
   244  	"audio/x-matroska",
   245  	"video/x-matroska",
   246  	"video/x-ms-wmv",
   247  	"application/metastream",
   248  	"video/avchd-stream",
   249  	"audio/mp4",
   250  	"video/mp4",
   251  }
   252  
   253  func isSafeToPreview(str string) bool {
   254  	for _, v := range safeMimeTypes {
   255  		if v == str {
   256  			return true
   257  		}
   258  	}
   259  	return false
   260  }