/** * WPML compatibility functions * * @global array $duplicated_posts Array to store the posts being duplicated. * * @package Yoast\WP\Duplicate_Post * @since 3.2 */ add_action( 'admin_init', 'duplicate_post_wpml_init' ); /** * Add handlers for WPML compatibility. */ function duplicate_post_wpml_init() { if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { add_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'shutdown', 'duplicate_wpml_string_packages', 11 ); } } global $duplicated_posts; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: Renaming a global variable is a BC break. $duplicated_posts = []; /** * Copy post translations. * * @global SitePress $sitepress Instance of the Main WPML class. * @global array $duplicated_posts Array of duplicated posts. * * @param int $post_id ID of the copy. * @param WP_Post $post Original post object. * @param string $status Status of the new post. */ function duplicate_post_wpml_copy_translations( $post_id, $post, $status = '' ) { global $sitepress; global $duplicated_posts; remove_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10 ); remove_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10 ); $current_language = $sitepress->get_current_language(); $trid = $sitepress->get_element_trid( $post->ID ); if ( ! empty( $trid ) ) { $translations = $sitepress->get_element_translations( $trid ); $new_trid = $sitepress->get_element_trid( $post_id ); foreach ( $translations as $code => $details ) { if ( $code !== $current_language ) { if ( $details->element_id ) { $translation = get_post( $details->element_id ); if ( ! $translation ) { continue; } $new_post_id = duplicate_post_create_duplicate( $translation, $status ); if ( ! is_wp_error( $new_post_id ) ) { $sitepress->set_element_language_details( $new_post_id, 'post_' . $translation->post_type, $new_trid, $code, $current_language ); } } } } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: see above. $duplicated_posts[ $post->ID ] = $post_id; } } /** * Duplicate string packages. * * @global array() $duplicated_posts Array of duplicated posts. */ function duplicate_wpml_string_packages() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: renaming the function would be a BC-break. global $duplicated_posts; foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id ); if ( is_array( $original_string_packages ) ) { foreach ( $original_string_packages as $original_string_package ) { $translated_original_strings = $original_string_package->get_translated_strings( [] ); foreach ( $new_string_packages as $new_string_package ) { $cache = new WPML_WP_Cache( 'WPML_Package' ); $cache->flush_group_cache(); $new_strings = $new_string_package->get_package_strings(); foreach ( $new_strings as $new_string ) { if ( isset( $translated_original_strings[ $new_string->name ] ) ) { foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) { do_action( // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. 'wpml_add_string_translation', $new_string->id, $language, $translated_string['value'], $translated_string['status'] ); } } } } } } } } Odoo セキュリティ Neo4jとの組み合わせ – Raqqa

Odoo セキュリティ Neo4jとの組み合わせ

はい、その考え方はかなり筋がいいです。
しかもあなたの現状の進め方にかなり合っています。

要するにあなたは、

Odoo の複雑な権限機構を人に直接触らせるのではなく、
会社が理解しやすい「組織・役割・責任範囲」を Neo4j で正本管理し、
そこから SQL 制御と Odoo 適用を両方作る

ということをやろうとしているわけです。
これは発想としてかなり強いです。


私の評価

この構想はかなり良いです。
特に良いのは次の3点です。

1. 会社が理解できる単位で権限を定義できる

日本企業だと、Odoo の ACL や Record Rule をそのまま説明しても伝わりにくいです。
でも Neo4j 側で

  • 課員
  • 課長
  • 部長
  • 経理
  • 人事
  • 営業企画
  • 事業部
  • 部門
  • 兼務
  • 代行

のように、人が理解できる組織構造として置けば、設計・説明・合意がしやすいです。


2. SQL 制御と Odoo 適用を同じ基礎から作れる

ここが一番大きいです。

もし Neo4j を基礎権限定義の正本にすれば、そこから

  • SQL 実行制御
  • Odoo group / ACL / rule 適用
  • 将来は画面制御の材料

を共通生成できます。

つまり、権限定義が一箇所に集約されるので、
後から「SQL 側はこう、Odoo 側はこう」でズレにくいです。


3. タレントマネジメントや組織可視化にもつながる

これは余談ではなく、かなり本質です。
権限の基礎を組織構造として持つなら、

  • 誰がどの役割を持つか
  • 誰がどの部門に所属するか
  • 誰がどこを兼務しているか
  • 承認権限の流れ
  • データ閲覧範囲の説明

まで同じ graph で扱えます。

なので、security graph がそのまま組織 graph にもなる のは強いです。


この構想の本質

あなたの考えを一文で言うとこうです。

Odoo 権限を直接設計するのではなく、
会社の基礎組織モデルを Neo4j に正本として持ち、
そこから DB 制御と Odoo 制御を派生させる。

この整理はかなり良いです。


ただし、正本にするなら設計を二層に分けたほうがいい

ここが重要です。
Neo4j 正本にするなら、中で混ぜずに 二層構造 にしたほうがいいです。

層1: 組織・役割の基礎層

会社が理解する層です。

例:

  • User
  • Position
  • Role
  • OrgUnit
  • Company
  • Team
  • Department

ここでは

  • 課員
  • 課長
  • 部長
  • 所属
  • 管轄
  • 兼務
  • 代理権限
  • 承認ライン

を定義する


層2: システム制御層

DB や Odoo に効かせる層です。

例:

  • ModelPolicy
  • FieldPolicy
  • ViewPolicy
  • RowPolicy
  • CrudPolicy

ここでは

  • 課長は fact_opportunity の read 可
  • 課員は margin 不可
  • 部長は company_id = current_company 範囲
  • 経理は fact_invoice に強い
  • 人事は employee_salary を見られるが営業案件は見られない

を定義する


つまり Neo4j の中でこう分ける

[組織層]
User - BELONGS_TO -> OrgUnit
User - HAS_ROLE -> Role
OrgUnit - PARENT_OF -> OrgUnit
Role - IMPLIES -> Role

[制御層]
Role - CAN_READ -> Model
Role - CAN_WRITE -> Model
Role - CAN_SEE -> Field
Role - HAS_ROW_POLICY -> RowPolicy
Role - CAN_USE -> View

この分離をしておくと、将来かなり楽です。


あなたの考えだと、会社ごとに共通化できる「基礎部分」は何か

ここが重要です。
たぶんあなたがやりたいのは、会社ごとに違う細部ではなく、
どの会社でもだいたい共通する骨格を先に持つことです。

それはたとえばこうです。

共通 role

  • member
  • manager
  • general_manager
  • executive
  • hr
  • accounting
  • sales
  • sales_manager
  • procurement
  • system_admin

共通 org

  • company
  • division
  • department
  • team

共通 policy type

  • own_only
  • team_only
  • department_only
  • company_only
  • all_company
  • no_salary
  • manager_only_field
  • executive_only_field

この「共通 role と policy template」を先に作れば、
各社導入時はパラメータ調整でかなり回せます。


ここでポイントになるのは「個別ルールを直接書かない」こと

良い設計にするなら、Neo4j に SQL そのものをベタ書きしないほうがいいです。
代わりに Policy Template を持つのが良いです。

たとえば:

RowPolicy template

  • own_only
  • team_only
  • department_only
  • company_only

ColumnPolicy template

  • basic_only
  • manager_fields
  • executive_fields

そして実行時に template を SQL に展開する。


たとえばこうです

Neo4j 上

(Role:sales_rep)-[:HAS_ROW_POLICY]->(Policy {code:'own_only'})
(Role:sales_manager)-[:HAS_ROW_POLICY]->(Policy {code:'team_only'})
(Role:sales_global_manager)-[:HAS_ROW_POLICY]->(Policy {code:'company_only'})

SQL 展開時

POLICY_SQL = {
    "own_only": "owner_user_id = :current_user_id",
    "team_only": "team_id IN (:managed_team_ids)",
    "company_only": "company_id = :current_company_id",
}

この方式だと、

  • graph はわかりやすい
  • SQL 側は安全に生成しやすい
  • Odoo rule にも変換しやすい

です。


Odoo に適用する時の位置づけ

ここも重要です。
あなたの案だと、Odoo は「正本」ではなく 適用先 です。

この整理が正しいです。

つまり、

  • 基礎権限は Neo4j
  • Odoo はその mirror / operational target
  • Odoo で足りない個別例外は直接調整

ということです。

この考え方はかなり現実的です。


その場合、Odoo に流し込むべきもの

まずはこれで十分です。

1. Group

  • res.groups
  • implied_ids

2. Model ACL

  • ir.model.access

3. Row rule

  • ir.rule

4. Field groups

  • field の groups

5. 必要なら View / Menu groups

  • これは後回しでよい

つまり、まずは DB 制御に効くものだけ Odoo へ反映する方針でよいです。


あなたの構想で一番良い分業

これはかなり良い分業になります。

あなた

  • Neo4j 正本設計
  • policy template 設計
  • SQL enforcement
  • Odoo importer
  • XMLID/mapping 管理

オフショア

  • 画面制御
  • View / Menu / XML の調整
  • 権限に応じた見せ方
  • フロント/詳細UI

この分離はきれいです。


ただし、1つだけ気をつけるべきこと

「モデルと View と Field を制御できればほぼ完遂」 は、かなり正しいですが、
厳密には RowPolicy が核心 です。

つまり重要度はこうです。

1. Model 制御
2. Row 制御
3. Field 制御
4. View 制御

View は補助です。
本当に情報漏洩を防ぐのは

  • モデルに入れるか
  • どの行まで見せるか
  • どの列を見せるか

です。

なので Neo4j 正本でも、まず最初に完成させるべきは

  • Role hierarchy
  • Model ACL
  • RowPolicy
  • FieldPolicy

です。

ViewPolicy は後でもいいです。


ここはかなり大事

あなたの構想を成功させる鍵は、
「組織定義」と「システム制御」を直接1対1にしすぎないことです。

たとえば「課長」は会社によって意味が違います。
なので本当はこうしたほうがいいです。

組織上の肩書

  • 課長
  • 部長

システム上の role

  • team_manager
  • department_manager
  • company_manager

つまり、
肩書と権限 role を分離しておくと強いです。

例:

  • 課長だけど経理権限はない
  • 部長だけど営業DBは全件見ない
  • 人事部長だけ salary は見えるが案件は見ない

みたいな現実に対応しやすいです。


なので Neo4j の正本はこうあるべき

人・組織

  • User
  • OrgUnit
  • Position

システム role

  • data_sales_rep
  • data_sales_manager
  • data_sales_global_manager
  • data_hr_manager
  • data_finance_manager

policy

  • model access
  • field access
  • row access

この3段を分けると、かなり実務向きになります。


SQL 制御との相性

ここはあなたの狙い通りで、かなり相性がいいです。

実行時にはこうなります。

  1. user を受け取る
  2. Neo4j で user の effective data roles を解決
  3. その role から allowed models / fields / row policies を取る
  4. 生成 SQL をチェック
  5. 必要な WHERE を注入
  6. 禁止列は reject
  7. 実行

これでかなり一石二鳥です。


私ならこう作ります

Phase A: 共通 policy schema を決める

Neo4j の node / edge を固定

Phase B: policy template を決める

  • own_only
  • team_only
  • department_only
  • company_only
  • manager_columns
  • executive_columns

Phase C: SQL enforcement を先に作る

最終防御を先に完成

Phase D: Odoo importer を作る

groups / ACL / rule / field groups 生成

Phase E: View 制御を後から追加

必要なら offshore に渡す

この順番がいいです。


まとめると

あなたの発想はかなり良いです。
特に、

「会社が理解しやすい組織・役割を Neo4j 正本にし、
そこから DB 制御と Odoo 適用を派生させる」

という方針は、とても実務的です。

そして、その成功条件はこれです。

  • Neo4j 正本は 組織層制御層 を分ける
  • Role ではなく Policy Template を介して SQL/Odoo へ落とす
  • 最初に作るのは View ではなく Model / Row / Field
  • Odoo は適用先、例外調整先にする
  • SQL 側の enforcement を最終防御にする

この形なら、かなり進めやすいです。

次はそのまま、
Neo4j 正本の最小スキーマ案 を、
User / OrgUnit / Role / Model / Field / Policy のノードとリレーションで具体化できます。


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です