Module: Placet
- Defined in:
- lib/placet/dsl.rb,
lib/placet/engine.rb,
lib/placet/errors.rb,
lib/placet/explain.rb,
lib/placet/runtime.rb,
lib/placet/version.rb,
lib/placet/decision.rb,
lib/placet/document.rb,
lib/placet/test_helpers.rb,
lib/placet/action_registry.rb
Defined Under Namespace
Modules: ActionMatch, Explain, TestHelpers Classes: ActionRegistry, Decision, Definition, DefinitionBuilder, DefinitionError, Denied, Determinant, Engine, Error, PolicyBuilder, Relation, RelationBuilder, ScopePlan, Statement
Constant Summary collapse
- VERSION =
"0.1.0"- ACTION_PATTERN_RE =
/\A(\*|(\*|[a-z][a-z0-9_]*):(\*|[a-z][a-z0-9_]*))\z/- CONCRETE_ACTION_RE =
/\A[a-z][a-z0-9_]*:[a-z][a-z0-9_]*\z/- PRINCIPAL_RE =
/\A[a-z][a-z0-9_-]*:[^\s*]+\z/- POLICY_NAME_RE =
/\A[a-z][a-z0-9_-]*\z/
Class Attribute Summary collapse
-
.on_recheck_divergence ⇒ Object
乖離検出時のハンドラ(user, action, record を受け取る)。既定は警告出力.
Class Method Summary collapse
- .authorize!(user, actions, resource = nil) ⇒ Object
-
.decide(user, actions, resource = nil) ⇒ Object
複数 action は AND(fail-fast)。docs/rails-usage.md 4.2.
-
.decide_with_provenance(principals, action) ⇒ Object
由来つきの principal 集合(Hash)で判定し、determinants に由来(via)を付与する。 Engine は仕様どおり principal の配列のみを受けるため、via の装飾はこの層で行う.
- .define(&block) ⇒ Object
- .definition ⇒ Object
- .derive(type_pattern, &block) ⇒ Object
-
.enforcements ⇒ Object
このスレッドで enforcement(authorize! / scoped)が行われた回数。 PEP ヘルパーの呼び忘れ検知(docs/rails-usage.md 4.4)が参照する.
- .engine ⇒ Object
-
.expand_principals(base) ⇒ Object
与えられた principal 集合に derive の 1 段展開を適用する。 戻り値は { principal => 由来の連鎖 (Array) }.
- .export ⇒ Object
- .permit?(user, actions, resource = nil) ⇒ Boolean
-
.principals_for(user, resource = nil) ⇒ Object
principal 集合の導出。戻り値は { principal => 由来の連鎖 (Array) }.
-
.recheck(user, action, records) ⇒ Object
scope 合成の結果(通常は 1 ページ分)へ個体判定を再適用する二段構え。 check / scope に乖離バグがあっても「見えてはいけないものが見える」方向には 倒れない(concept.md 11.5)。乖離レコードは on_recheck_divergence に通知して除外する。 静的 principal の導出(resolver + derive)はレコードに依存しないため 1 回だけ行う.
-
.register_scope_materializer(matcher, materializer) ⇒ Object
ORM アダプタが ScopePlan の実体化を登録する。matcher が true を返した model に materializer が適用される(後着優先。どれにもマッチしなければ インメモリの既定実装)。定義状態とは独立した登録なので reset! では消えない.
- .registry ⇒ Object
- .relation(name, resource:, &block) ⇒ Object
-
.relation_scopes(relations, names, user) ⇒ Object
relation 名の集合を scope 呼び出し結果へ写像する共通ヘルパー (インメモリ実装と ORM アダプタで共有).
-
.relations_for(klass) ⇒ Object
モデルに登録された relation の一覧(テストヘルパー・アダプタが参照する).
-
.reset! ⇒ Object
テスト・再読み込み用: 定義と登録を破棄する(materializer は定義状態ではないため対象外).
- .resolver(&block) ⇒ Object
-
.scoped(user, action, model:) ⇒ Object
一覧の scope 合成。Engine の plan を、登録された materializer(ORM アダプタ) またはインメモリの既定実装でコレクションへ写像する.
-
.validate_action!(action, error_class: Error) ⇒ Object
action 検証の公開入口(Engine に委譲)。アダプタの宣言時 fail-fast にも使う.
Class Attribute Details
.on_recheck_divergence ⇒ Object
乖離検出時のハンドラ(user, action, record を受け取る)。既定は警告出力
136 137 138 |
# File 'lib/placet/runtime.rb', line 136 def on_recheck_divergence @on_recheck_divergence end |
Class Method Details
.authorize!(user, actions, resource = nil) ⇒ Object
64 65 66 67 68 69 70 |
# File 'lib/placet/runtime.rb', line 64 def (user, actions, resource = nil) count_enforcement! decide_each(user, actions, resource) do |action, decision| raise Denied.new(action, decision) unless decision.permit? end true end |
.decide(user, actions, resource = nil) ⇒ Object
複数 action は AND(fail-fast)。docs/rails-usage.md 4.2
55 56 57 58 59 60 61 62 |
# File 'lib/placet/runtime.rb', line 55 def decide(user, actions, resource = nil) last = nil decide_each(user, actions, resource) do |_action, decision| last = decision return decision unless decision.permit? end last end |
.decide_with_provenance(principals, action) ⇒ Object
由来つきの principal 集合(Hash)で判定し、determinants に由来(via)を付与する。 Engine は仕様どおり principal の配列のみを受けるため、via の装飾はこの層で行う
76 77 78 79 80 81 82 83 |
# File 'lib/placet/runtime.rb', line 76 def decide_with_provenance(principals, action) decision = engine.decide(principals.keys, action) decision.determinants.each do |determinant| via = principals[determinant.principal] determinant.via = via unless via.nil? || via.empty? end decision end |
.define(&block) ⇒ Object
11 12 13 14 15 |
# File 'lib/placet/runtime.rb', line 11 def define(&block) DefinitionBuilder.new(definition, registry).instance_eval(&block) definition.validate!(registry) @engine = nil end |
.definition ⇒ Object
7 |
# File 'lib/placet/runtime.rb', line 7 def definition = (@definition ||= Definition.new) |
.derive(type_pattern, &block) ⇒ Object
19 20 21 22 |
# File 'lib/placet/runtime.rb', line 19 def derive(type_pattern, &block) type = type_pattern.delete_suffix(":*").delete_suffix(":") ((@derives ||= {})[type] ||= []) << block end |
.enforcements ⇒ Object
このスレッドで enforcement(authorize! / scoped)が行われた回数。 PEP ヘルパーの呼び忘れ検知(docs/rails-usage.md 4.4)が参照する
114 |
# File 'lib/placet/runtime.rb', line 114 def enforcements = Thread.current[:placet_enforcements] || 0 |
.engine ⇒ Object
9 |
# File 'lib/placet/runtime.rb', line 9 def engine = (@engine ||= Engine.new(definition, registry: registry)) |
.expand_principals(base) ⇒ Object
与えられた principal 集合に derive の 1 段展開を適用する。 戻り値は { principal => 由来の連鎖 (Array) }
37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/placet/runtime.rb', line 37 def (base) out = {} base.each { |p| out[p] = [] } base.each do |p| type, id = p.split(":", 2) derive_hooks(type).each do |hook| Array(hook.call(id)).each { |derived| out[derived] ||= [p] } end end out end |
.export ⇒ Object
138 |
# File 'lib/placet/runtime.rb', line 138 def export = definition.to_canonical |
.permit?(user, actions, resource = nil) ⇒ Boolean
72 |
# File 'lib/placet/runtime.rb', line 72 def permit?(user, actions, resource = nil) = decide(user, actions, resource).permit? |
.principals_for(user, resource = nil) ⇒ Object
principal 集合の導出。戻り値は { principal => 由来の連鎖 (Array) }
50 51 52 |
# File 'lib/placet/runtime.rb', line 50 def principals_for(user, resource = nil) with_relation_principals((Array(@resolver&.call(user))), user, resource) end |
.recheck(user, action, records) ⇒ Object
scope 合成の結果(通常は 1 ページ分)へ個体判定を再適用する二段構え。 check / scope に乖離バグがあっても「見えてはいけないものが見える」方向には 倒れない(concept.md 11.5)。乖離レコードは on_recheck_divergence に通知して除外する。 静的 principal の導出(resolver + derive)はレコードに依存しないため 1 回だけ行う
120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/placet/runtime.rb', line 120 def recheck(user, action, records) base = (Array(@resolver&.call(user))) records.select do |record| principals = with_relation_principals(base, user, record) next true if engine.decide(principals.keys, action).permit? if (handler = @on_recheck_divergence) handler.call(user, action, record) else warn "placet: scope と check の乖離を検出: action=#{action} record=#{record.inspect}" end false end end |
.register_scope_materializer(matcher, materializer) ⇒ Object
ORM アダプタが ScopePlan の実体化を登録する。matcher が true を返した model に materializer が適用される(後着優先。どれにもマッチしなければ インメモリの既定実装)。定義状態とは独立した登録なので reset! では消えない
102 103 104 |
# File 'lib/placet/runtime.rb', line 102 def register_scope_materializer(matcher, materializer) (@scope_materializers ||= []).unshift([matcher, materializer]) end |
.registry ⇒ Object
8 |
# File 'lib/placet/runtime.rb', line 8 def registry = (@registry ||= ActionRegistry.new) |
.relation(name, resource:, &block) ⇒ Object
24 25 26 27 28 29 30 31 32 33 |
# File 'lib/placet/runtime.rb', line 24 def relation(name, resource:, &block) builder = RelationBuilder.new builder.instance_eval(&block) unless builder.check_block && builder.scope_block raise DefinitionError, "relation は check と scope をペアで宣言する: #{name}" end ((@relations ||= {})[resource] ||= []) << Relation.new(name: name.to_s, check: builder.check_block, scope: builder.scope_block) end |
.relation_scopes(relations, names, user) ⇒ Object
relation 名の集合を scope 呼び出し結果へ写像する共通ヘルパー (インメモリ実装と ORM アダプタで共有)
108 109 110 |
# File 'lib/placet/runtime.rb', line 108 def relation_scopes(relations, names, user) relations.select { |r| names.include?(r.name) }.map { |r| r.scope.call(user) } end |
.relations_for(klass) ⇒ Object
モデルに登録された relation の一覧(テストヘルパー・アダプタが参照する)
152 |
# File 'lib/placet/runtime.rb', line 152 def relations_for(klass) = @relations ? @relations.fetch(klass, []) : [] |
.reset! ⇒ Object
テスト・再読み込み用: 定義と登録を破棄する(materializer は定義状態ではないため対象外)
141 142 143 144 145 146 147 148 149 |
# File 'lib/placet/runtime.rb', line 141 def reset! @definition = nil @registry = nil @engine = nil @resolver = nil @derives = nil @relations = nil @on_recheck_divergence = nil end |
.resolver(&block) ⇒ Object
17 |
# File 'lib/placet/runtime.rb', line 17 def resolver(&block) = @resolver = block |
.scoped(user, action, model:) ⇒ Object
一覧の scope 合成。Engine の plan を、登録された materializer(ORM アダプタ) またはインメモリの既定実装でコレクションへ写像する
92 93 94 95 96 97 |
# File 'lib/placet/runtime.rb', line 92 def scoped(user, action, model:) count_enforcement! rels = relations_for(model) plan = engine.scope_plan(principals_for(user).keys, action, relations: rels.map(&:name)) materializer_for(model).call(plan, rels, user, model) end |
.validate_action!(action, error_class: Error) ⇒ Object
action 検証の公開入口(Engine に委譲)。アダプタの宣言時 fail-fast にも使う
86 87 88 |
# File 'lib/placet/runtime.rb', line 86 def validate_action!(action, error_class: Error) engine.validate_action!(action, error_class: error_class) end |