Просмотр исходного кода

feat: implement scope utility functions for validation and extraction

reiji-h 1 год назад
Родитель
Сommit
0e698a8acb
2 измененных файлов с 88 добавлено и 60 удалено
  1. 1 60
      apps/app/src/interfaces/scope.ts
  2. 87 0
      apps/app/src/server/util/scope-utils.ts

+ 1 - 60
apps/app/src/interfaces/scope.ts

@@ -64,7 +64,7 @@ type AddAllToScope<S extends string> =
     : S;
 
 type ScopeOnly = FlattenObject<typeof ORIGINAL_SCOPE_WITH_ACTION>;
-type ScopeWithAll = AddAllToScope<ScopeOnly> ;
+type ScopeWithAll = AddAllToScope<ScopeOnly>;
 export type Scope = ScopeOnly | ScopeWithAll;
 
 // ScopeConstantsの型定義
@@ -114,62 +114,3 @@ const buildScopeConstants = (): ScopeConstantType => {
 };
 
 export const SCOPE = buildScopeConstants();
-
-
-export const isValidScope = (scope: string): boolean => {
-  const scopeParts = scope.split(':').map(x => (x === '*' ? 'ALL' : x.toUpperCase()));
-  let obj: any = SCOPE;
-  scopeParts.forEach((part) => {
-    if (obj[part] == null) {
-      return false;
-    }
-    obj = obj[part];
-  });
-  return obj === scope;
-};
-
-export const isAllScope = (scope: string): scope is Scope => {
-  return scope.endsWith(`:${ALL_SIGN}`);
-};
-
-const getAllScopeValues = (scopeObj: any): Scope[] => {
-  const result: Scope[] = [];
-
-  const traverse = (current: any): void => {
-    if (typeof current !== 'object' || current === null) {
-      if (typeof current === 'string') {
-        result.push(current as Scope);
-      }
-      return;
-    }
-    Object.values(current).forEach((value) => {
-      traverse(value);
-    });
-  };
-  traverse(scopeObj);
-  return result;
-};
-
-export const extractScopes = (scopes?: Scope[]): Scope[] => {
-  if (scopes == null) {
-    return [];
-  }
-  const result = new Set<Scope>(scopes);
-  scopes.forEach((scope) => {
-    if (!isAllScope(scope)) {
-      return;
-    }
-    const scopeParts = scope.split(':').map(x => (x.toUpperCase()));
-    let obj: any = SCOPE;
-    scopeParts.forEach((part) => {
-      if (part === ALL_SIGN) {
-        return;
-      }
-      obj = obj[part];
-    });
-    getAllScopeValues(obj).forEach((value) => {
-      result.add(value);
-    });
-  });
-  return Array.from(result.values());
-};

+ 87 - 0
apps/app/src/server/util/scope-utils.ts

@@ -0,0 +1,87 @@
+import {
+  ACTION, ALL_SIGN, SCOPE, type Scope,
+} from '../../interfaces/scope';
+
+export const isValidScope = (scope: Scope): boolean => {
+  const scopeParts = scope.split(':').map(x => (x === '*' ? 'ALL' : x.toUpperCase()));
+  let obj: any = SCOPE;
+  scopeParts.forEach((part) => {
+    if (obj[part] == null) {
+      return false;
+    }
+    obj = obj[part];
+  });
+  return obj === scope;
+};
+
+export const hasAllScope = (scope: Scope): scope is Scope => {
+  return scope.endsWith(`:${ALL_SIGN}`);
+};
+
+/**
+ * Returns all values of the scope object
+ * For example, SCOPE.READ.USER.API.ALL returns ['read:user:api:access_token', 'read:user:api:api_token']
+ */
+const getAllScopeValues = (scopeObj: any): Scope[] => {
+  const result: Scope[] = [];
+
+  const traverse = (current: any): void => {
+    if (typeof current !== 'object' || current === null) {
+      if (typeof current === 'string') {
+        result.push(current as Scope);
+      }
+      return;
+    }
+    Object.values(current).forEach((value) => {
+      traverse(value);
+    });
+  };
+  traverse(scopeObj);
+  return result;
+};
+
+/**
+ * Returns all implied scopes for a given scope
+ * For example, WRITE permission implies READ permission
+ */
+const getImpliedScopes = (scope: Scope): Scope[] => {
+  const scopeParts = scope.split(':').map(x => (x.toUpperCase()));
+  if (scopeParts[0] === ACTION.READ) {
+    return [scope];
+  }
+  if (scopeParts[0] === ACTION.WRITE) {
+    return [scope, `${ACTION.READ}:${scopeParts.slice(1).join(':')}` as Scope];
+  }
+  return [];
+};
+
+export const extractScopes = (scopes?: Scope[]): Scope[] => {
+  if (scopes == null) {
+    return [];
+  }
+  const result = new Set<Scope>(); // remove duplicates
+  const impliedScopes = new Set<Scope>();
+  scopes.forEach((scope) => {
+    getImpliedScopes(scope).forEach((impliedScope) => {
+      impliedScopes.add(impliedScope);
+    });
+  });
+  impliedScopes.forEach((scope) => {
+    if (!hasAllScope(scope)) {
+      result.add(scope);
+      return;
+    }
+    const scopeParts = scope.split(':').map(x => (x.toUpperCase()));
+    let obj: any = SCOPE;
+    scopeParts.forEach((part) => {
+      if (part === ALL_SIGN) {
+        return;
+      }
+      obj = obj[part];
+    });
+    getAllScopeValues(obj).forEach((value) => {
+      result.add(value);
+    });
+  });
+  return Array.from(result.values());
+};