Class: Clickwrap::Policy

Inherits:
Object
  • Object
show all
Defined in:
lib/clickwrap/policy.rb

Overview

A compiled, frozen policy: what the server will present and what it will accept back.

The browser may answer a policy. It may never choose one. Policy key, revision, document versions, validity, subject binding, retention, and request-evidence fields are all resolved server-side and rechecked at submit, because every one of them is a security decision and a form field is not a safe place to keep one.

A policy's revision is the digest of its compiled snapshot. Publishing freezes that snapshot in the database the first time the policy is presented or captured, so a receipt written today still explains itself after the Ruby source has moved on. The digest covers declared structure and copy; host lambdas (subject fingerprints, protected-outcome recorders) are recorded as present rather than serialized, since their bodies cannot be canonicalized. That boundary is stated in the receipt rather than papered over.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key:, statements:, retention_class_key: nil, request_evidence: nil, persist_presentations_for: nil, persist_presentations_because: nil, capture_channels: nil, locales: nil, tenant_scope: "optional", authority_rule: nil, options: {}) ⇒ Policy

Returns a new instance of Policy.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/clickwrap/policy.rb', line 26

def initialize(key:, statements:, retention_class_key: nil, request_evidence: nil,
               persist_presentations_for: nil, persist_presentations_because: nil,
               capture_channels: nil, locales: nil, tenant_scope: "optional",
               authority_rule: nil, options: {})
  @key = key.to_s
  @statements = statements.freeze
  @retention_class_key = retention_class_key&.to_s
  @request_evidence = request_evidence || RequestEvidencePolicy.new(policy_key: @key)
  @persist_presentations_for = persist_presentations_for
  @persist_presentations_because = persist_presentations_because
  @capture_channels = (capture_channels || Vocabulary::CAPTURE_CHANNELS).map(&:to_s).freeze
  @locales = locales&.map(&:to_s)&.freeze
  @tenant_scope = tenant_scope.to_s
  @authority_rule = authority_rule
  @options = options.freeze

  validate!
  @snapshot = build_snapshot.freeze
  @revision = Digest.digest_canonical(@snapshot).freeze
  freeze
end

Instance Attribute Details

#authority_ruleObject (readonly)

Returns the value of attribute authority_rule.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def authority_rule
  @authority_rule
end

#capture_channelsObject (readonly)

Returns the value of attribute capture_channels.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def capture_channels
  @capture_channels
end

#keyObject (readonly)

Returns the value of attribute key.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def key
  @key
end

#localesObject (readonly)

Returns the value of attribute locales.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def locales
  @locales
end

#optionsObject (readonly)

Returns the value of attribute options.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def options
  @options
end

#persist_presentations_becauseObject (readonly)

Returns the value of attribute persist_presentations_because.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def persist_presentations_because
  @persist_presentations_because
end

#persist_presentations_forObject (readonly)

Returns the value of attribute persist_presentations_for.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def persist_presentations_for
  @persist_presentations_for
end

#request_evidenceObject (readonly)

Returns the value of attribute request_evidence.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def request_evidence
  @request_evidence
end

#retention_class_keyObject (readonly)

Returns the value of attribute retention_class_key.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def retention_class_key
  @retention_class_key
end

#revisionObject (readonly)

Returns the value of attribute revision.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def revision
  @revision
end

#snapshotObject (readonly)

Returns the value of attribute snapshot.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def snapshot
  @snapshot
end

#statementsObject (readonly)

Returns the value of attribute statements.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def statements
  @statements
end

#tenant_scopeObject (readonly)

Returns the value of attribute tenant_scope.



22
23
24
# File 'lib/clickwrap/policy.rb', line 22

def tenant_scope
  @tenant_scope
end

Instance Method Details

#authorization_statementsObject



68
# File 'lib/clickwrap/policy.rb', line 68

def authorization_statements = statements.select { |statement| statement.kind == "authorization" }


67
# File 'lib/clickwrap/policy.rb', line 67

def consent_statements = statements.select { |statement| statement.kind == "consent" }

#document_keysObject



62
# File 'lib/clickwrap/policy.rb', line 62

def document_keys = statements.flat_map(&:document_keys).uniq

#kindsObject



63
# File 'lib/clickwrap/policy.rb', line 63

def kinds = statements.map(&:kind).uniq

#one_time_statementsObject



65
# File 'lib/clickwrap/policy.rb', line 65

def one_time_statements = statements.select(&:one_time?)

#optional_statementsObject



61
# File 'lib/clickwrap/policy.rb', line 61

def optional_statements = statements.select(&:optional?)

#permits_acting_for?Boolean

Delegation, guardianship, service-account action, and impersonation are rejected unless the policy opts in and the host authority adapter agrees. When permitted, the receipt keeps the authenticated principal, asserted actor, and represented party as separate facts; Clickwrap does not decide whether that authority is sufficient.

Returns:

  • (Boolean)


125
# File 'lib/clickwrap/policy.rb', line 125

def permits_acting_for? = options.fetch(:permit_acting_for, false) == true

#permits_acting_for_party?(represented_party) ⇒ Boolean

Returns:

  • (Boolean)


127
128
129
# File 'lib/clickwrap/policy.rb', line 127

def permits_acting_for_party?(represented_party)
  permits_acting_for? && authority_rule&.permits?(represented_party)
end

#permits_capture_channel?(channel) ⇒ Boolean

Returns:

  • (Boolean)


81
# File 'lib/clickwrap/policy.rb', line 81

def permits_capture_channel?(channel) = capture_channels.include?(channel.to_s)

#permits_exemptions?Boolean

Whether a system exemption may stand in for a human action under this policy. It is off unless the policy says otherwise, and an exemption never satisfies agreed_to? or any other human-action predicate even when it is permitted — it answers exempted_from? instead.

Returns:

  • (Boolean)


79
# File 'lib/clickwrap/policy.rb', line 79

def permits_exemptions? = options.fetch(:permit_exemptions, false) == true

#permits_locale?(locale) ⇒ Boolean

Returns:

  • (Boolean)


83
84
85
# File 'lib/clickwrap/policy.rb', line 83

def permits_locale?(locale)
  locales.nil? || locales.include?(locale.to_s)
end

#persist_presentations?Boolean

Returns:

  • (Boolean)


73
# File 'lib/clickwrap/policy.rb', line 73

def persist_presentations? = !persist_presentations_for.nil?

#protected_outcome_statementObject



70
# File 'lib/clickwrap/policy.rb', line 70

def protected_outcome_statement = protected_outcome_statements.first

#protected_outcome_statementsObject



69
# File 'lib/clickwrap/policy.rb', line 69

def protected_outcome_statements = statements.select(&:record_protected_outcome_with)

#records_protected_outcome?Boolean

Returns:

  • (Boolean)


71
# File 'lib/clickwrap/policy.rb', line 71

def records_protected_outcome? = protected_outcome_statement.present?

#required_statementsObject



60
# File 'lib/clickwrap/policy.rb', line 60

def required_statements = statements.select(&:required?)

#statement(statement_key) ⇒ Object



48
49
50
# File 'lib/clickwrap/policy.rb', line 48

def statement(statement_key)
  statements.find { |statement| statement.key == statement_key.to_s }
end

#statement!(statement_key) ⇒ Object



52
53
54
55
56
57
58
# File 'lib/clickwrap/policy.rb', line 52

def statement!(statement_key)
  statement(statement_key) || raise(
    UnknownStatementError,
    "Policy #{key} has no statement #{statement_key.inspect}. It declares: " \
    "#{statements.map(&:key).join(", ")}."
  )
end

#subject_bound?Boolean

Returns:

  • (Boolean)


66
# File 'lib/clickwrap/policy.rb', line 66

def subject_bound? = statements.any?(&:subject_bound?)

#tenant_from_controller(candidate) ⇒ Object

Resolves ambient controller context according to this policy. Personal evidence deliberately discards a current organization; tenant-required evidence fails before rendering if the host cannot supply one.



94
95
96
97
98
99
# File 'lib/clickwrap/policy.rb', line 94

def tenant_from_controller(candidate)
  return nil if tenant_not_applicable?

  validate_tenant!(candidate)
  candidate
end

#tenant_not_applicable?Boolean

Returns:

  • (Boolean)


87
# File 'lib/clickwrap/policy.rb', line 87

def tenant_not_applicable? = tenant_scope == "not_applicable"

#tenant_optional?Boolean

Returns:

  • (Boolean)


89
# File 'lib/clickwrap/policy.rb', line 89

def tenant_optional? = tenant_scope == "optional"

#tenant_required?Boolean

Returns:

  • (Boolean)


88
# File 'lib/clickwrap/policy.rb', line 88

def tenant_required? = tenant_scope == "required"

#to_sObject



131
# File 'lib/clickwrap/policy.rb', line 131

def to_s = "Clickwrap policy #{key} (#{revision})"

#validate_tenant!(tenant) ⇒ Object

Direct service callers own their arguments, so an incompatible explicit value is rejected instead of silently rewritten.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/clickwrap/policy.rb', line 103

def validate_tenant!(tenant)
  if tenant_not_applicable? && tenant.present?
    raise DefinitionError,
          "Policy #{key} says `tenant_is :not_applicable`, but this call supplied a tenant. " \
          "Remove `tenant:`; personal evidence must not change identity when the actor joins " \
          "or switches organizations."
  end

  if tenant_required? && tenant.nil?
    raise DefinitionError,
          "Policy #{key} says `tenant_is :required`, but this call supplied no tenant. Pass " \
          "the server-resolved tenant to presentation, capture, and verification."
  end

  true
end