ace-lab 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,7 @@ module Ace
15
15
  ID = /\A[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}\z/
16
16
 
17
17
  def initialize(document, proposal_resolver = nil)
18
+ @document = document
18
19
  @operations = document.fetch("operations", {})
19
20
  @authorizations = document.fetch("authorizations", {})
20
21
  @proposal_resolver = proposal_resolver
@@ -23,6 +24,10 @@ module Ace
23
24
  end
24
25
  end
25
26
 
27
+ # The trusted installation document as loaded; the ordinary producer
28
+ # binds its configuration digest to these exact bytes.
29
+ attr_reader :document
30
+
26
31
  def operation!(name, project:, service_id:)
27
32
  raise ArgumentError, "invalid operation name" unless name.to_s.match?(NAME)
28
33
  operation = @operations[name]
@@ -101,6 +106,72 @@ module Ace
101
106
  decision
102
107
  end
103
108
 
109
+ # ---- ordinary fixed merge admission policy (spec 8x3.t.xz9.4) ------
110
+ #
111
+ # The installed ordinary section is the separately configured
112
+ # ordinary receiver mapping and admission policy. It supplies
113
+ # identity and placement only: the caller selects no endpoint,
114
+ # credential or receiver. An absent project section or a disabled
115
+ # admission flag is rollback: new admission is unavailable while
116
+ # canonical records and read-only status stay preserved.
117
+ ORDINARY_SECTION_KEYS = %w[projects].freeze
118
+ ORDINARY_PROJECT_KEYS = %w[receiver_id socket_path receiver_uid principal admission].freeze
119
+ ORDINARY_AUTHORIZATION_KEYS = %w[operation project_id assignment_id attempt_id queue_entry
120
+ input_digest forge pr_url head_repository head_ref candidate_head base_repository
121
+ base_ref expected_base_head expires_at].freeze
122
+ # expires_at bounds the decision itself; it is never exact-matched
123
+ # against the request binding, which carries no expiry.
124
+ ORDINARY_BINDING_KEYS = ORDINARY_AUTHORIZATION_KEYS - %w[expires_at].freeze
125
+
126
+ def ordinary_receiver!(project)
127
+ section = @document["ordinary"]
128
+ return nil unless section.is_a?(Hash)
129
+ validate_ordinary_section!(section)
130
+ entry = section.fetch("projects")[project.to_s]
131
+ return nil unless entry.is_a?(Hash)
132
+ entry
133
+ end
134
+
135
+ # The ordinary admission decision: exact-match on every closed
136
+ # binding key, like authorize!, but over the ordinary request
137
+ # identity (forge/PR/repository/ref) instead of execution selectors.
138
+ def authorize_ordinary!(reference, binding)
139
+ raise ArgumentError, "invalid authorization reference" unless reference.to_s.match?(ID)
140
+ decision = @authorizations[reference]
141
+ raise SecurityError, "authorization reference is unresolved" unless decision.is_a?(Hash)
142
+ unless ORDINARY_AUTHORIZATION_KEYS.all? { |key| decision.key?(key) }
143
+ raise SecurityError, "authorization reference is not an ordinary merge decision"
144
+ end
145
+ unless ORDINARY_BINDING_KEYS.all? { |key| decision[key] == binding[key] }
146
+ raise SecurityError, "authorization does not match the exact ordinary merge request"
147
+ end
148
+ raise SecurityError, "authorization has expired" if parse_time(decision["expires_at"]) <= Time.now.utc
149
+ decision
150
+ end
151
+
152
+ def validate_ordinary_section!(section)
153
+ unless (section.keys - ORDINARY_SECTION_KEYS).empty? && section["projects"].is_a?(Hash)
154
+ raise Ace::Lab::InvalidConfigurationError, "ordinary section must map projects"
155
+ end
156
+ section.fetch("projects").each do |project, entry|
157
+ unless project.is_a?(String) && project.match?(ID) && entry.is_a?(Hash) &&
158
+ (entry.keys - ORDINARY_PROJECT_KEYS).empty?
159
+ raise Ace::Lab::InvalidConfigurationError, "ordinary project section is invalid"
160
+ end
161
+ unless entry["receiver_id"].is_a?(String) && entry["receiver_id"].match?(ID) &&
162
+ entry["socket_path"].is_a?(String) && entry["socket_path"].start_with?("/") &&
163
+ entry["receiver_uid"].is_a?(Integer) && entry["receiver_uid"] >= 0 &&
164
+ entry["principal"].is_a?(String) && !entry["principal"].empty?
165
+ raise Ace::Lab::InvalidConfigurationError, "ordinary receiver mapping is incomplete"
166
+ end
167
+ admission = entry.fetch("admission", "enabled")
168
+ unless %w[enabled disabled].include?(admission)
169
+ raise Ace::Lab::InvalidConfigurationError, "ordinary admission flag is invalid"
170
+ end
171
+ end
172
+ true
173
+ end
174
+
104
175
  # Inspection never renews effect permission or selects effect argv.
105
176
  def inspection!(name, project:, service_id:, executor_uid:)
106
177
  raise ArgumentError, "invalid inspection operation" unless name.is_a?(String) && name.match?(NAME)