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;