github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/ListBuckets/UploadPermissionUtils.ts (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2023 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 export const extractFileExtn = (resourceStr: string) => { 18 //file extensions may contain query string. so exclude query strings ! 19 return (resourceStr.match(/\.([^.]*?)(?=\?|#|$)/) || [])[1]; 20 }; 21 export const getPolicyAllowedFileExtensions = ( 22 sessionGrants: Record<string, string[]>, 23 uploadPath: string, 24 scopes: string[] = [], 25 ) => { 26 const sessionGrantWildCards = getSessionGrantsWildCard( 27 sessionGrants, 28 uploadPath, 29 scopes, 30 ); 31 32 //get acceptable files if any in the policy. 33 const allowedFileExtensions = sessionGrantWildCards.reduce( 34 (acc: string[], cv: string) => { 35 const extension: string = extractFileExtn(cv); 36 if (extension) { 37 acc.push(`.${extension}`); //strict extension matching. 38 } 39 return acc; 40 }, 41 [], 42 ); 43 44 const uniqueExtensions = [...new Set(allowedFileExtensions)]; 45 return uniqueExtensions.join(","); 46 }; 47 48 // The resource should not have the extensions (*.ext) for the hasPermission to work. 49 // so sanitize this and also use to extract the allowed extensions outside of permission check. 50 export const getSessionGrantsWildCard = ( 51 sessionGrants: Record<string, string[]>, 52 uploadPath: string, 53 scopes: string[] = [], 54 ) => { 55 //get only the path matching grants to reduce processing. 56 const grantsWithExtension = Object.keys(sessionGrants).reduce( 57 (acc: Record<string, string[]>, grantKey: string) => { 58 if (extractFileExtn(grantKey) && grantKey.includes(uploadPath)) { 59 acc[grantKey] = sessionGrants[grantKey]; 60 } 61 return acc; 62 }, 63 {}, 64 ); 65 66 const checkPathsForPermission = (sessionGrantKey: string) => { 67 const grantActions = grantsWithExtension[sessionGrantKey]; 68 const hasScope = grantActions.some((actionKey) => 69 scopes.find((scopeKey) => { 70 let wildCardMatch = false; 71 const hasWildCard = scopeKey.indexOf("*") !== -1; 72 if (hasWildCard) { 73 const scopeActionKey = scopeKey.substring(0, scopeKey.length - 1); 74 75 wildCardMatch = actionKey.includes(scopeActionKey); 76 } 77 78 return wildCardMatch || actionKey === scopeKey; 79 }), 80 ); 81 82 const sessionGrantKeyPath = sessionGrantKey.substring( 83 0, 84 sessionGrantKey.indexOf("/*."), //start of extension part. 85 ); 86 const isUploadPathMatching = 87 sessionGrantKeyPath === `arn:aws:s3:::${uploadPath}`; 88 89 const hasGrant = 90 isUploadPathMatching && sessionGrantKey !== "arn:aws:s3:::*"; 91 92 return hasScope && hasGrant; 93 }; 94 95 return Object.keys(grantsWithExtension).filter(checkPathsForPermission); 96 };