gitlab可見性代碼分析

gitlab是一個代碼管理平臺,在公司內部多使用它來進行代碼託管服務。gitlab提供給咱們的一個很是實用的功能就是對項目的訪問須要進行權限審查,每一個人只能看到本身有權限查看的項目。
在gitlab-ce源碼的Gitlab::Access module中定義了項目的全部訪問級別以下:git

NO_ACCESS  = 0
    GUEST      = 10
    REPORTER   = 20
    DEVELOPER  = 30
    MAINTAINER = 40
    # @deprecated
    MASTER     = MAINTAINER
    OWNER      = 50

而數據庫的project_authorizations表中維護了用戶和項目之間的訪問權限關係,它的結構爲:sql

Column    |  Type   | Collation | Nullable | Default 
--------------+---------+-----------+----------+---------
 user_id      | integer |           | not null | 
 project_id   | integer |           | not null | 
 access_level | integer |           | not null |

表結構比較簡單,從表中能夠很直接的看到用戶對項目的訪問權限級別。
gitlab用戶查看項目時,顯示的是該用戶能夠有權限看到項目,該業務邏輯被定義在gitlab/lib/gitlab/project_authorizations.rb類中,幾個類間的部分關鍵關係如圖:
類間關係以下:
類圖.png~~~~數據庫

project_authorizations是調用主體,這個類的初始化方法爲數組

# user - 用於計算受權的用戶對象
    def initialize(user)
      @user = user
    end

這個類的關鍵方法爲calculate,返回的是user能夠訪問的全部項目:gitlab

def calculate
      # postgresql的cte技術能夠理解爲本身建立的一個臨時視圖,
      #你能夠對它繼續進行操做
      # recursive_cte是該類的一個私有方法,下面會進行分析
      cte = recursive_cte
      cte_alias = cte.table.alias(Group.table_name)
      #Arel是一個SQL AST管理器,AST全稱Abstract Syntax Tree(抽象語法樹),使用Arel能夠更方便進行復制查詢
      #構建projects表的Arel
      projects = Project.arel_table
      links = ProjectGroupLink.arel_table

      relations = [
        # 用戶能夠直接訪問的項目
        # 返回project_id和access_level
        user.projects.select_for_project_authorization,

        # 用戶的我的項目
        # 返回project_id和access_level
        user.personal_projects.select_as_maintainer_for_project_authorization,

        # 直接屬於用戶能夠訪問的任何組的項目
        # namespaces存儲用戶及項目組的路徑
        Namespace
          .unscoped  # unscoped方法將會過濾掉調用對象的where語句塊
          .select([alias_as_column(projects[:id], 'project_id'),
                   cte_alias[:access_level]])
          .from(cte_alias)
          .joins(:projects),

        # 與用戶有權訪問的任何名稱空間共享的項目
        Namespace
          .unscoped
          .select([
            links[:project_id],
            least(cte_alias[:access_level], links[:group_access], 'access_level')
          ])
          .from(cte_alias)
          .joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
          .joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
          .joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
          .where('p_ns.share_with_group_lock IS FALSE')
      ]
      #ProjectAuthorization是對應於project_authorization表的model,下面會進行分析
      ProjectAuthorization
        .unscoped
        .with
        .recursive(cte.to_arel)
        .select_from_union(relations)
    end

其中,recursive_cte是該類的一個私有方法,它的目的是構建一個遞歸CTE(Common Table Expressions),獲取當前用戶能夠訪問的全部組,包括任何嵌套組和任何共享組。post

def recursive_cte
      #RecursiveCTE是用於輕鬆構建遞歸CTE語句的類,
      #它的<<方法能夠讓咱們把查詢的添加到裏面的數組上,
      #最後執行to_arel方法合併全部添加的數組中的查詢構造對應的CTE語句
      cte = Gitlab::SQL::RecursiveCTE.new(:namespaces_cte)
      #構建members和namespaces的Arel
      members = Member.arel_table
      namespaces = Namespace.arel_table

      # 用戶所屬的命名空間
      cte << user.groups
        .select([namespaces[:id], members[:access_level]])
        .except(:order)

      if Feature.enabled?(:share_group_with_group)
        # 與任何組共享的命名空間
        cte << Group.select([namespaces[:id], 'group_group_links.group_access AS access_level'])
                    .joins(join_group_group_links)
                    .joins(join_members_on_group_group_links)
      end

      # 用戶所屬的全部組的子組
      cte << Group.select([
          namespaces[:id],
          greatest(members[:access_level], cte.table[:access_level], 'access_level')
        ])
        .joins(join_cte(cte))
        .joins(join_members_on_namespaces)
        .except(:order)
      #返回構造好的CTE語句
      cte
    end

calculate方法中的project_authorization是對應於前面顯示的project_authorization表的model類,裏面定義的類方法select_from_union以下:spa

#前面方法引用該方法時傳入的參數relations
 #是多個對project_id和access_level查詢語句
 #裏面包裝的from_union能夠對relations數組中的多個查詢進行UNION
def self.select_from_union(relations)
    from_union(relations)
      .select(['project_id', 'MAX(access_level) AS access_level'])
      .group(:project_id)
  end
# 生成一個查詢,該查詢使用FROM來使用UNION選擇數據
    #
    # 在UNION中使用FROM在過去能夠產生更好的查詢計劃。所以,咱們一般建議使用這種模式,而不是使用WHERE IN。
    #
    #例如:
    #     users = User.from_union([User.where(id: 1), User.where(id: 2)])
    #
    # 這將產生如下SQL查詢:
    #
    #     SELECT *
    #     FROM (
    #       SELECT *
    #       FROM users
    #       WHERE id = 1
    #
    #       UNION
    #
    #       SELECT *
    #       FROM users
    #       WHERE id = 2
    #     ) users;
    #
    # members -要在聯合中使用的ActiveRecord::Relation對象的數組
    #
    # remove_duplicates - 是否去重,默認爲true
    #
    # alias_as - 別名,默認爲當前表名
    def from_union(members, remove_duplicates: true, alias_as: table_name)
      union = Gitlab::SQL::Union
        .new(members, remove_duplicates: remove_duplicates)
        .to_sql

      from(Arel.sql("(#{union}) #{alias_as}"))
    end
相關文章
相關標籤/搜索