728x90
반응형

Access Control은 Graph Database 방식을 나열합니다.

많은 경우에 특정 종류의 리소스에 접근하거나 생성하거나 변경하려면 사용자 권한을 처리해야 해요. 일반적인 예시는 파일 시스템인데, 이 블로그 포스팅에서 자세히 살펴볼게요. 루비 바인딩을 사용해서 Neo4j Graph Database로 작지만 작동하는 예제 애플리케이션을 만들어볼 거예요.

준비

Ubuntu에서 이 예제의 환경을 설정하기 위해 다음 명령어를 사용했어요.

sudo apt-get install jruby
sudo jruby -S gem install neo4j

라이브러리를 가져오기 위해 다음 코드가 사용되었어요.

require 'rubygems'
require 'neo4j'
require 'neo4j/extensions/find_path'

Node 공간을 향하여

그렇다면 사용자 권한은 무엇에 관한 걸까요? 분명히 사용자에 관한 것이고 일반적으로 사용자 그룹에 관한 것이죠. 우리는 이것을 조금 추상화하고 용어를 사용하겠습니다. Principal, 단일 사용자 또는 그룹일 수 있어요.
사용자 권한의 다른 측면은 보호해야 할 리소스예요. 우리의 경우에는 파일 시스템이 있으므로 폴더와 파일이 있을 거예요. 여기서는 용어를 사용하겠습니다. Content.
지금까지 수집한 내용을 바탕으로 애플리케이션을 지원하는 그래프 작성을 시작해 보죠. 그래프로 작업할 때는 그래픽 방식으로 생각하는 것이 유익하므로 여기서부터 시작할게요. 그래프는 아마도 사물을 연결하는 것에 관한 것이므로 첫 번째 단계는 관계를 만드는 것이에요. Neo4j에는 다음이 내장되어 있어요. 참조 Node, 언제든지 쉽게 접근할 수 있죠. 우리는 이를 사용하여 하나는 주체용이고 다른 하나는 콘텐츠용으로 자체 "하위 참조 Node"를 생성해요. 지금까지의 그래프는 다음과 같아요.

하위 참조 Node를 생성(및 가져오기)하려면 다음 함수를 사용합니다.

def get_or_create_sub_ref( name )
  result = Neo4j.ref_node.rels.outgoing( name ).nodes.first
  if ( result.nil? )
    result = Neo4j::Node.new :name => name.to_s.capitalize.gsub("_", " ")
    Neo4j.ref_node.rels.outgoing( name ) << result
  end
  return result
end

이 함수는 하위 참조 Node를 사용해야 할 때마다 호출돼요. 여기서 중요한 부분은 다음과 같아요.

  • ref_node: 내장 참조 Node
  • rels: Node에 연결된 관계
  • outgoing: 관계의 방향(관계는 항상 방향이 지정되지만 순회 시 방향을 무시하도록 선택할 수 있음)
  • ( name ): 따라갈 관계 유형(순회에서도 유형을 무시할 수 있지만 우리의 경우에는 이를 사용하고 싶어요)
  • nodes: 관계의 반대편에 있는 Node
  • first: 발견된 첫 번째 Node - 각 유형마다 하나의 하위 참조 Node만 있어야 해요.

하위 참조 Node를 찾을 수 없으면 하위 참조 Node가 생성되어 참조 Node에 연결돼요. 보시다시피 키가 있는 속성을 추가하고 있어요. name 시각화 목적으로만 존재하는 Node에도 적용돼요(이 게시물의 이미지는 다음을 사용하여 생성됩니다. 네오클립스).

기본 구조

주체 부분에서는 다음을 사용하여 최상위 수준을 해당 하위 참조 Node에 연결합니다. PRINCIPAL 관계의 유형. 그 외에는 사용자와 그룹만 있으므로 IS_MEMBER_OF_GROUP 이를 인코딩하는 관계 유형이에요. 그래프로 보면 다음과 같아요.

이를 생성하는 코드는 다음과 같아요.

def new_principal( name, member_of_groups = [] )
  principal = Neo4j::Node.new
  principal[ :name ] = name
  if member_of_groups.empty?
    get_or_create_sub_ref( :PRINCIPALS ).rels.outgoing( :PRINCIPAL ) << principal
  else
    for group in member_of_groups
      principal.rels.outgoing( :IS_MEMBER_OF_GROUP ) << group
    end
  end
  return principal
end

새로운 principal이 그룹의 구성원이 아니라면, principal 하위 참조 `Node`에 연결된 최상위 principal로 추가돼요. 그렇지 않은 경우에는 그냥 그룹에 추가되는 거죠. Neo4j에서는 그래프의 모든 작업이 트랜잭션에 캡슐화되어야 하니까, 위 함수를 호출하는 방법은 다음과 같아요.

Neo4j::Transaction.run do
  all_principals = new_principal( "All principals" )
  root = new_principal( "root", [ all_principals ] )
  regular_users = new_principal( "Regular users", [ all_principals ] )
  user1 = new_principal( "user1", [ regular_users ] )
  user2 = new_principal( "user2", [ regular_users ] )
end

콘텐츠 부분은 principal 부분과 매우 유사해요. 주요 차이점은 이 경우 항목에는 상위 항목이 하나만 있을 수 있다는 점이죠. 아래에 그래픽으로 표현해봤어요.

그리고 다음은 구조를 생성하는 코드입니다.

def new_content( name, parent = nil )
  content = Neo4j::Node.new
  content[ :name ] = name
  if ( parent.nil? )
    get_or_create_sub_ref( :CONTENT_ROOTS ).rels.outgoing( :CONTENT_ROOT ) << content
  else
    parent.rels.outgoing( :HAS_CHILD_CONTENT ) << content
  end
  return content
end

Principal이 생성된 방식과 유사하게 콘텐츠 데이터를 생성하는 코드는 다음과 같아요.

Neo4j::Transaction.run do
  root_folder = new_content( "Root folder" )
  temp_folder = new_content( "Temp", root_folder )
  home_folder = new_content( "Home", root_folder )
  user1_home_folder = new_content( "user1 home", home_folder )
  user2_home_folder = new_content( "user2 home", home_folder )
  a_file = new_content( "MyFile.pdf", user1_home_folder )
end

핵심은

이제 기본 구조가 마련되었으니, 데이터와 관련해서 남은 건 작지만 중요한 부분인 권한 정보에요! 우리는 읽기 및 쓰기 권한에 대한 선택적 부울 플래그를 사용하여 보안 `Relationship`을 추가하는 간단한 구성표를 사용하고 있어요. 여기서 더 이야기할 건 많지 않네요. 이게 우리가 원하는 전체 그래프의 모습이에요 (더 큰 버전을 보려면 클릭하세요).

작은 기능을 사용하면 보안 정보를 추가하는 데 도움이 되죠.

def apply_security( content, principal, map_with_flags )
  security_relationship = Neo4j::Relationship.new( :SECURITY, principal, content )
  map_with_flags.each_pair {|key, value| security_relationship[ key ] = value}
end

이제 보안 데이터를 추가할 차례에요.

Neo4j::Transaction.run do
  apply_security( root_folder, root, { "w" => true } )
  apply_security( root_folder, all_principals, { "r" => true } )
  apply_security( temp_folder, all_principals, { "w" => true } )
  apply_security( user1_home_folder, regular_users, { "r" => false, "w" => false } )
  apply_security( user1_home_folder, user1, { "r" => true, "w" => true } )
  apply_security( user2_home_folder, user2, { "r" => true, "w" => true } )
end

일부 콘텐츠에 대해 실제 principal이 일부 작업에 대한 권한을 확인하려면 몇 가지 작업을 수행해야 해요. 다음은 권한 플래그를 검색하는 데 사용하는 알고리즘이에요.

  1. 콘텐츠 `Node`에서 파일 시스템 구조를 통해 위로 이동하고 권한 정보에 대한 각 레벨을 조사해요.
  2. 각 레벨에는 해당 principal과 관련이 있거나 동일한 principal이 있는지 확인해요.
  3. 반드시 해당 principal과 가장 가까운 principal의 허가 정보를 활용해야 해요.
  4. 권한 정보가 발견되면 이를 반환해요. 그렇지 않으면 파일 시스템의 다음 레벨로 계속 이동해요.

이를 코드로 구현할 때, 다음과 같은 함수를 사용해요. depth_of_principal() 우리가 통과한 principal과 해당 principal 사이의 거리를 계산하는 함수죠. 나중에 자세히 설명할게요. 권한을 확인하는 코드는 다음과 같아요.

def has_access( content, principal, flag )
  for current_content in content.incoming( :HAS_CHILD_CONTENT ).depth( :all )
    lowest_score = nil
    lowest_modifier = nil
    for rel in current_content.rels.incoming( :SECURITY )
      rel_principal = rel.start_node
      if !rel[ flag ].nil?
        score = depth_of_principal( rel_principal, principal )
        if !score.nil?
          modifier = rel[ flag ]
          if lowest_score.nil? || score < lowest_score ||
            ( score == lowest_score && modifier )
            lowest_score = score
            lowest_modifier = modifier
          end
        end
      end
    end
    if !lowest_modifier.nil?
      return lowest_modifier
    end
  end
  return false
end

주체 간의 거리를 확인하는 기능은 다음과 같아요 (그리고 이들이 동일한 경로에 있는지 확인하는 거죠).

def depth_of_principal( principal, reference_principal )
  result = reference_principal.outgoing( :IS_MEMBER_OF_GROUP ).depth( :all ).path_to( principal )
  return result.nil? ? nil : result.size
end

마지막으로 모든 것이 작동하는지 확인하고 싶으니까, 권한 정보를 인쇄하는 유틸리티 함수는 다음과 같아요.

def print_has_access( content, principal, flag )
  print principal[ :name ] + " +" + flag.upcase + " access to " + content[ :name ] + "? " +
    has_access( content, principal, flag ).to_s + "n"
end

기능을 사용하는 방법은 다음과 같습니다.

Neo4j::Transaction.run do
  print_has_access( home_folder, root, "w" )
  print_has_access( home_folder, user1, "w" )
  print_has_access( a_file, root, "r" )
  print_has_access( a_file, user2, "r" )
  print_has_access( a_file, user1, "w" )
end

다음 단계

전체 소스 코드는 에서 찾을 수 있어요.
다음은 여러분의 여정에 도움이 될 몇 가지 유용한 리소스입니다.

  • Neo4j 위키
  • Neo4j.rb 깃허브에서
  • 메일링 리스트 – 도움을 받기 좋은 곳이죠
  • Neo4j를 알아가는 10가지 방법

읽어주셔서 감사합니다! 어떤 피드백이라도 환영이에요!

Graph Database에 대해 더 자세히 알고 싶으신가요? 아래를 클릭하여 O'Reilly의 무료 전자책을 다운로드하고 지금 여러분의 애플리케이션에 그래프 기술을 사용하는 방법을 알아보세요.

  • acl
  • 그래프DB
  • ruby

에이치시스템즈의 LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

728x90
반응형

+ Recent posts