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 }