基於位運算的許可權設計

伍陸柒發表於2021-10-27

基於位運算的許可權設計

由於這裡許可權是基於 Bit 的所以需要大家對以及位操作符需要有一定的認識

[TOC]

前置知識

  • MDN 位運算子
  • 單一許可權有且只有一位為 1
  • 從右向左,由低到高

操作符速記:

  • &按位與:對應位都是 1 則為 1
  • |按位或:對應位都是 0 則為 0
  • ^按位異或:對應位都相同則是 0,不同則為 1

實際案例

我們以四種許可權的 CRUD 來舉例,使用 4 位的 bit 來進行。這裡有一點需要注意,單一許可權有且只有一位為 1

變數二進位制描述
C0b0001
D0b0010
U0b0100
R0b1000

校驗某些許可權

const curPermission = 0b1001; // 當前使用者的許可權,「增」「查」

const allowCreate = (curPermission & C) === C; // => true
const allowUpdate = (curPermission & U) === U; // => false

從上面程式碼可知,當前使用者的許可權為Ob1001第一位與第四位是1,則說明擁有的許可權

當使用者的許可權使用按位與只有相同位都為 1 時才可以得到 1
對比增

由圖與程式碼可以看出,我們可以使用&得到的值對比定義好的變數是否相等可知,當前是否有某個許可權

新增某個許可權

let curPermission = 0b0100; // 當前使用者只有「改」許可權

// C = 0b0001  D = 0b0010
// 新增「增」「刪」的許可權
curPermission = curPermission | C | U; // => 0b0111

按位或「增」「刪」

最終我們得到的許可權包含原有的「改」以及新加入的「增」「刪」

刪除某個許可權

刪除時我們使用先按位取反再按位與&(~P)的操作

let curPermission = 0b1110; // 當前使用者許可權,「刪」「改」「查」

// R = 0b1000
curPermission = curPermission & ~C; // => Ob0110 刪除了「查」的許可權

刪除許可權

最終我們得到的許可權只有「刪」「改」,已經將「查」許可權刪除

Toggle 操作

使用按位異或無則增有則減(對應位不同為 1,相同為 0),從結果來看實際上是一個 Toggle 操作

let curPermission = 0b1000; // 當前使用者許可權,「查」

// 無則增;C = 0b0001
curPermission ^ C; // => Ob1001 得到的為「增」「查」
// 有則減
curPermission ^ C; // => Ob1001 得到的為「查」,又將「增」許可權刪除了

Toggle操作

複合型別

我們可以使用複合操作來進行更方便快捷的操作,可用於上面任意操作,這裡只用校驗來舉例

當我們的頁面中有下圖中的操作列,既有刪除按鈕也就修改按鈕,就出現下面幾種情況:

  1. 當有「刪」許可權時顯示Delete按鈕
  2. 當有「改」許可權時顯示Edit按鈕
  3. 當「刪」「改」都沒有時將operation列隱藏

const curPermission = 0b1000; // 當前使用者許可權
const D = 0b0010; // 刪
const U = 0b0100; // 改
const DandU = 0b0110; // 刪、改 都有
const allowDelete = (p: number) => (p & D) === D;
const allowUpdate = (p: number) => (p & U) === U;
const allowDeleteAndUpdate = (p: number) => (p & DandU) === DandU;

const COLUMNS = [
  {
    title: 'operation',
    dataIndex: 'operation',
    render: () => (
      <>
        {allowUpdate(curPermission) && <button>Edit</button>}
        {allowDelete(curPermission) && <button>Delete</button>}
      </>
    ),
  },
];
const retColumns = COLUMNS.filter((x) => {
  if (x.dataIndex === 'operation') {
    return allowDeleteAndUpdate(curPermission);
  }
  return true;
});
// retColumns 是我們最終使用的 Table columns 資料

同理,程式碼中的複合型別可以應用至其它的操作,作為作業有心的同學可以 coding

優點

  • 一個引數可以代表多種型別,不需要多個許可權編碼
  • 可以使用複合型別,比如既有新增又有修改則可以定義cosnt allowCreateAndUpdate = 0b1010,使用時(curAccess & allowCreateAndUpdate) === allowCreateAndUpdate
  • 可擴充性高,比如再新增一個是否可執行許可權,則可以使用 5 位 bit 0b10000

缺點

位運算子將它的運算元視為32位元的二進位制串 -- 來自 MDN

這樣的話可用的許可權數有限,可以使用結構體名稱空間來進行管控

結構體也就是物件,我們將具體的許可權放在定好的結構體中

const permissionList = [
  {
    pid: 1, // position id 也就是 位置ID
    code: 0b0001, // 對應的編碼
  }
];

名稱空間,其實也可以使用上面結構體描述,這裡我們使用字串來進行描述,有一套預設規則:pos,code

const permissionList = ['pos1,0b0001', 'pos2,0b0011'];

在具體使用時根據自己的不同規則編寫好對應許可權的操作方法,提供給具體的業務同學使用

TypeScript 加成

使用 Enum 與位賦值操作符以及 namespace 的靜態方法

/** 定義 */
enum AuthCode {
  Read = 0b001, // 也可以寫成 1
  Write = 0b010, // 也可以寫成 r << 1 或 2
  /** 執行 execute */
  Exec = 0b100, // 也可以寫成 r << 2 或 4

  // 以下為複合型別
  /** 0b011 */
  ReadAndWrite = 0b011,
  /** Union of all host auth */
  HostAuthMask = 0b111,
}
namespace Auth {
  /**
   * 驗證當前許可權是否存在
   * @param validCode - 要驗證的許可權編碼(即使用者返回的編碼)
   * @param code - 定義好的許可權編碼
   */
  export const validator = (validCode: AuthCode, code: AuthCode): boolean => {
    return (validCode & code) === code;
  };
  // curry 處理 validator()
  /**
   * 給使用者加入許可權
   * @param userCode - 當前使用者擁有的許可權
   * @param waitingCode - 待加入給使用者的許可權
   * @returns 返回加入許可權後的所有許可權
   */
  export const add = (
    userCode: AuthCode,
    waitingCode: AuthCode | AuthCode[]
  ): AuthCode => {
    let code: number;
    if (Array.isArray(waitingCode)) {
      code = waitingCode.reduce((acc, cur) => {
        return acc | cur;
      }, 0);
    } else {
      code = waitingCode;
    }
    return userCode | code;
  };

  /**
   * 刪除使用者的許可權
   * @param userCode - 當前使用者擁有的許可權
   * @param rmCode - 要刪除的許可權
   */
  export const remove = (
    userCode: AuthCode,
    rmCode: AuthCode | AuthCode[]
  ): AuthCode => {
    let code: number;
    if (Array.isArray(rmCode)) {
      code = rmCode.reduce((acc, cur) => {
        return acc | cur;
      }, 0);
    } else {
      code = rmCode;
    }
    return userCode & ~code;
  };

  /**
   * 使用者許可權 Toggle
   * @description 無則增,有則減
   * @param userCode - 當前使用者擁有的許可權
   * @param tglCode - 要 toggle 的許可權
   */
  export const toggle = (userCode: AuthCode, tglCode: AuthCode) => {
    return userCode ^ tglCode;
  };
}

// test validator
// const userCode = 0b011; // 獲取到使用者在當前頁的許可權碼(讀、寫)
// console.log(Auth.validator(userCode, AuthCode.Read)); // => true; 當前使用者擁有 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.ReadAndWrite)); // => true; 當前使用者擁有 讀寫 許可權
// console.log(Auth.validator(userCode, AuthCode.Exec)); // => false; 當前使用者沒有 執行 許可權
// console.log(Auth.validator(userCode, AuthCode.Read | AuthCode.Exec)); // => false; 當前使用者沒有 讀,執行 許可權;兩個許可權都有才為真

// test add
// let userCode = 0b000; // 獲取到使用者在當前頁的許可權碼(無許可權)
// console.log((userCode = Auth.add(userCode, AuthCode.Read))); // => 1 === 0b001; 給當前使用者加入 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.Read)); // => true; 驗證當前使用者已經擁有 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.Write)); // => false; 驗證當前使用者沒有 寫 許可權
// console.log((userCode = Auth.add(userCode, [AuthCode.Write, AuthCode.Exec]))); // => 7 === 0b111; 給當前使用者加入 寫、執行 許可權
// console.log(Auth.validator(userCode, AuthCode.HostAuthMask)); // => true; 驗證當前使用者擁有所有許可權

// test remove
// let userCode = 0b111; // 獲取到使用者在當前頁的許可權碼(所有許可權)
// console.log(Auth.validator(userCode, AuthCode.HostAuthMask)); // => true; 驗證當前使用者有所用許可權
// console.log((userCode = Auth.remove(userCode, AuthCode.Read))); // => 6 === 0b110; 移除使用者 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.Read)); // => false; 驗證當前使用者已經刪除 讀 許可權
// console.log(
//   (userCode = Auth.remove(userCode, [AuthCode.Write, AuthCode.Exec]))
// ); // => 0 === 0b000; 移除使用者 讀、執行 許可權
// console.log(Auth.validator(userCode, AuthCode.Write)); // => false; 驗證當前使用者已經刪除 寫 許可權
// console.log(Auth.validator(userCode, AuthCode.Exec)); // => false; 驗證當前使用者已經刪除 執行 許可權

// test toggle
// let userCode = 0b101; // 獲取到使用者在當前頁的許可權碼(執行、讀)
// console.log((userCode = Auth.toggle(userCode, AuthCode.ReadAndWrite))); // => 6 === 0b110; 當前使用者刪除了 讀,新增了 寫(寫 無則增,讀 有則減)
// console.log(Auth.validator(userCode, AuthCode.Read)); // => false; 驗證當前使用者無 讀 許可權
// console.log(Auth.validator(userCode, AuthCode.Write)); // => true; 驗證當前使用者有 寫 許可權

See Also

相關文章