github.com/minio/console@v1.4.1/web-app/src/common/SecureComponent/accessControl.ts (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2022 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 import { store } from "../../store"; 18 import get from "lodash/get"; 19 import { IAM_SCOPES } from "./permissions"; 20 21 const hasPermission = ( 22 resource: string | string[] | undefined, 23 scopes: string[], 24 matchAll?: boolean, 25 containsResource?: boolean, 26 ) => { 27 if (!resource) { 28 return false; 29 } 30 const state = store.getState(); 31 const sessionGrants = state.console.session 32 ? state.console.session.permissions || {} 33 : {}; 34 35 const globalGrants = sessionGrants["arn:aws:s3:::*"] || []; 36 let resources: string[] = []; 37 let resourceGrants: string[] = []; 38 let containsResourceGrants: string[] = []; 39 40 if (resource) { 41 if (Array.isArray(resource)) { 42 resources = [...resources, ...resource]; 43 } else { 44 resources.push(resource); 45 } 46 47 // Filter wildcard items 48 const wildcards = Object.keys(sessionGrants).filter( 49 (item) => item.includes("*") && item !== "arn:aws:s3:::*", 50 ); 51 52 const getMatchingWildcards = (path: string) => { 53 const items = wildcards.map((element) => { 54 const wildcardItemSection = element.split(":").slice(-1)[0]; 55 56 const replaceWildcard = wildcardItemSection 57 .replace("/", "\\/") 58 .replace("*", "($|\\/?(.*?))"); 59 const inRegExp = new RegExp(`${replaceWildcard}`, "gm"); 60 // Avoid calling inRegExp multiple times and instead use the stored value if need it: 61 // https://stackoverflow.com/questions/59694142/regex-testvalue-returns-true-when-logged-but-false-within-an-if-statement 62 const matches = inRegExp.test(path); 63 if (matches) { 64 return element; 65 } 66 return null; 67 }); 68 return items.filter((itm) => itm !== null); 69 }; 70 71 resources.forEach((rsItem) => { 72 // Validation against inner paths & wildcards 73 let wildcardRules = getMatchingWildcards(rsItem); 74 75 let wildcardGrants: string[] = []; 76 77 wildcardRules.forEach((rule) => { 78 if (rule) { 79 const wcResources = get(sessionGrants, rule, []); 80 wildcardGrants = [...wildcardGrants, ...wcResources]; 81 } 82 }); 83 84 let simpleResources = get(sessionGrants, rsItem, []); 85 simpleResources = simpleResources || []; 86 const s3Resources = get(sessionGrants, `arn:aws:s3:::${rsItem}/*`, []); 87 const bucketOnly = get(sessionGrants, `arn:aws:s3:::${rsItem}/`, []); 88 const bckOnlyNoSlash = get(sessionGrants, `arn:aws:s3:::${rsItem}`, []); 89 90 resourceGrants = [ 91 ...simpleResources, 92 ...s3Resources, 93 ...wildcardGrants, 94 ...bucketOnly, 95 ...bckOnlyNoSlash, 96 ]; 97 98 if (containsResource) { 99 const matchResource = `arn:aws:s3:::${rsItem}`; 100 101 Object.entries(sessionGrants).forEach(([key, value]) => { 102 if (key.includes(matchResource)) { 103 containsResourceGrants = [...containsResourceGrants, ...value]; 104 } 105 }); 106 } 107 }); 108 } 109 110 let anyResourceGrant: string[] = []; 111 let validScopes = scopes || []; 112 if (resource === "*") { 113 Object.entries(sessionGrants).forEach(([key, values = []]) => { 114 let validValues = values || []; 115 validScopes.forEach((scope) => { 116 validValues.forEach((val) => { 117 if (val === scope || val === "s3:*") { 118 anyResourceGrant = [...anyResourceGrant, scope]; 119 } 120 }); 121 }); 122 }); 123 } 124 125 return hasAccessToResource( 126 [ 127 ...resourceGrants, 128 ...globalGrants, 129 ...containsResourceGrants, 130 ...anyResourceGrant, 131 ], 132 scopes, 133 matchAll, 134 ); 135 }; 136 137 // hasAccessToResource receives a list of user permissions to perform on a specific resource, then compares those permissions against 138 // a list of required permissions and return true or false depending of the level of required access (match all permissions, 139 // match some of the permissions) 140 const hasAccessToResource = ( 141 userPermissionsOnBucket: string[] | null | undefined, 142 requiredPermissions: string[] = [], 143 matchAll?: boolean, 144 ) => { 145 if (!userPermissionsOnBucket) { 146 return false; 147 } 148 149 const s3All = userPermissionsOnBucket.includes(IAM_SCOPES.S3_ALL_ACTIONS); 150 const AdminAll = userPermissionsOnBucket.includes( 151 IAM_SCOPES.ADMIN_ALL_ACTIONS, 152 ); 153 154 const permissions = requiredPermissions.filter(function (n) { 155 return ( 156 userPermissionsOnBucket.indexOf(n) !== -1 || 157 (n.indexOf("s3:") !== -1 && s3All) || 158 (n.indexOf("admin:") !== -1 && AdminAll) 159 ); 160 }); 161 return matchAll 162 ? permissions.length === requiredPermissions.length 163 : permissions.length > 0; 164 }; 165 166 export default hasPermission;