1use std::fmt;
9
10use thiserror::Error;
11
12use crate::{
13 payload_strategies::ApiAuthorizationContextPairStrategy,
14 payload_strategy::{PayloadSeed, PayloadStrategy, PayloadStrategyLimits, PayloadVariantRole},
15};
16
17mod comparison;
18mod policy;
19
20pub use comparison::{
21 AuthorizationDifferentialError, AuthorizationDifferentialReceiptId,
22 AuthorizationDifferentialResult, AuthorizationReviewBodyState, AuthorizationReviewMediaClass,
23 AuthorizationReviewOutcome, AuthorizationReviewView, AuthorizationReviewViewError,
24 AuthorizationViewReceiptId, AuthorizationViewRole, DimensionEquivalence,
25};
26pub use policy::{
27 AuthorizationResourceScopeId, AuthorizationReviewExpectation, AuthorizationReviewMethod,
28 AuthorizationReviewPolicy, AuthorizationReviewPolicyError, AuthorizationReviewPolicyId,
29 AUTHORIZATION_REVIEW_ALGORITHM_VERSION, AUTHORIZATION_REVIEW_POLICY_SCHEMA,
30 HARD_MAX_AUTHORIZATION_REVIEW_DIFF_PATHS, HARD_MAX_AUTHORIZATION_REVIEW_IGNORED_PATHS,
31 HARD_MAX_AUTHORIZATION_REVIEW_PATH_BYTES, HARD_MAX_AUTHORIZATION_REVIEW_POLICY_BYTES,
32 HARD_MAX_AUTHORIZATION_REVIEW_SELECTED_PATHS,
33 HARD_MAX_AUTHORIZATION_REVIEW_UNORDERED_ARRAY_PATHS,
34};
35
36pub struct PrimaryAuthorizationPrincipal {
41 value: String,
42}
43
44impl PrimaryAuthorizationPrincipal {
45 pub fn new(value: impl Into<Vec<u8>>) -> Result<Self, AuthorizationPrincipalError> {
47 Ok(Self {
48 value: validate_authorization_header(value.into())?,
49 })
50 }
51}
52
53impl fmt::Debug for PrimaryAuthorizationPrincipal {
54 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
55 formatter.write_str("PrimaryAuthorizationPrincipal(<redacted>)")
56 }
57}
58
59pub struct PeerAuthorizationPrincipal {
64 value: String,
65}
66
67impl PeerAuthorizationPrincipal {
68 pub fn new(value: impl Into<Vec<u8>>) -> Result<Self, AuthorizationPrincipalError> {
70 Ok(Self {
71 value: validate_authorization_header(value.into())?,
72 })
73 }
74}
75
76impl fmt::Debug for PeerAuthorizationPrincipal {
77 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78 formatter.write_str("PeerAuthorizationPrincipal(<redacted>)")
79 }
80}
81
82pub struct AuthorizationPrincipalPair {
84 primary: PrimaryAuthorizationPrincipal,
85 peer: PeerAuthorizationPrincipal,
86}
87
88pub(crate) struct AuthorizationPrincipalExecutionHandoff {
94 pub(crate) primary_authorization: String,
95 pub(crate) peer_authorization: String,
96 pub(crate) proof: AuthorizationPrincipalPairProof,
97}
98
99impl AuthorizationPrincipalPair {
100 pub fn new(
102 primary: PrimaryAuthorizationPrincipal,
103 peer: PeerAuthorizationPrincipal,
104 ) -> Result<Self, AuthorizationPrincipalError> {
105 if primary.value.as_bytes() == peer.value.as_bytes() {
106 return Err(AuthorizationPrincipalError::IdenticalCredentials);
107 }
108 Ok(Self { primary, peer })
109 }
110
111 pub fn into_proof(self) -> AuthorizationPrincipalPairProof {
113 let AuthorizationPrincipalExecutionHandoff {
114 primary_authorization,
115 peer_authorization,
116 proof,
117 } = self.into_execution_handoff();
118 drop((primary_authorization, peer_authorization));
119 proof
120 }
121
122 pub(crate) fn into_execution_handoff(self) -> AuthorizationPrincipalExecutionHandoff {
125 AuthorizationPrincipalExecutionHandoff {
126 primary_authorization: self.primary.value,
127 peer_authorization: self.peer.value,
128 proof: AuthorizationPrincipalPairProof { _private: () },
129 }
130 }
131}
132
133impl fmt::Debug for AuthorizationPrincipalPair {
134 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
135 formatter.write_str("AuthorizationPrincipalPair(<redacted>)")
136 }
137}
138
139#[derive(PartialEq, Eq)]
144pub struct AuthorizationPrincipalPairProof {
145 _private: (),
146}
147
148impl fmt::Debug for AuthorizationPrincipalPairProof {
149 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
150 formatter.write_str("AuthorizationPrincipalPairProof(<redacted>)")
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
156#[non_exhaustive]
157pub enum AuthorizationPrincipalError {
158 #[error("authorization principal is not a bounded safe HTTP header value")]
160 InvalidValue,
161 #[error("authorization review requires distinct principal credentials")]
163 IdenticalCredentials,
164}
165
166fn validate_authorization_header(value: Vec<u8>) -> Result<String, AuthorizationPrincipalError> {
167 let limits = PayloadStrategyLimits::default();
168 let seed =
169 PayloadSeed::new(value, limits).map_err(|_| AuthorizationPrincipalError::InvalidValue)?;
170 let artifact = ApiAuthorizationContextPairStrategy::new()
171 .derive_one(PayloadVariantRole::Candidate, &seed, limits)
172 .map_err(|_| AuthorizationPrincipalError::InvalidValue)?;
173 String::from_utf8(artifact.as_bytes().to_vec())
174 .map_err(|_| AuthorizationPrincipalError::InvalidValue)
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 const PRIMARY_SECRET: &str = "PRIMARY-AUTHORIZATION-MUST-NOT-LEAK-7C3A19";
182 const PEER_SECRET: &str = "PEER-AUTHORIZATION-MUST-NOT-LEAK-82FD44";
183
184 fn principals() -> AuthorizationPrincipalPair {
185 AuthorizationPrincipalPair::new(
186 PrimaryAuthorizationPrincipal::new(format!("Bearer {PRIMARY_SECRET}")).unwrap(),
187 PeerAuthorizationPrincipal::new(format!("Bearer {PEER_SECRET}")).unwrap(),
188 )
189 .unwrap()
190 }
191
192 #[test]
193 fn principal_pair_is_distinct_move_only_and_value_free() {
194 let pair = principals();
195 let rendered = format!("{pair:?}");
196 assert!(!rendered.contains(PRIMARY_SECRET));
197 assert!(!rendered.contains(PEER_SECRET));
198 assert_eq!(
199 format!("{:?}", pair.into_proof()),
200 "AuthorizationPrincipalPairProof(<redacted>)"
201 );
202 }
203
204 #[test]
205 fn execution_handoff_keeps_roles_and_proof_atomic() {
206 let handoff = principals().into_execution_handoff();
207 assert_eq!(
208 handoff.primary_authorization,
209 format!("Bearer {PRIMARY_SECRET}")
210 );
211 assert_eq!(handoff.peer_authorization, format!("Bearer {PEER_SECRET}"));
212 assert_eq!(
213 format!("{:?}", handoff.proof),
214 "AuthorizationPrincipalPairProof(<redacted>)"
215 );
216 }
217
218 #[test]
219 fn identical_credentials_are_rejected_without_echoing_values() {
220 let error = AuthorizationPrincipalPair::new(
221 PrimaryAuthorizationPrincipal::new("Bearer same").unwrap(),
222 PeerAuthorizationPrincipal::new("Bearer same").unwrap(),
223 )
224 .unwrap_err();
225 assert_eq!(error, AuthorizationPrincipalError::IdenticalCredentials);
226 assert!(!error.to_string().contains("Bearer"));
227 assert!(!format!("{error:?}").contains("same"));
228 }
229
230 #[test]
231 fn unsafe_and_oversized_credentials_fail_closed() {
232 for invalid in [
233 b"".as_slice(),
234 b" leading",
235 b"trailing ",
236 b"line\nbreak",
237 b"carriage\rreturn",
238 b"tab\tvalue",
239 b"null\0value",
240 &[0xff],
241 ] {
242 assert_eq!(
243 PrimaryAuthorizationPrincipal::new(invalid.to_vec()).unwrap_err(),
244 AuthorizationPrincipalError::InvalidValue
245 );
246 assert_eq!(
247 PeerAuthorizationPrincipal::new(invalid.to_vec()).unwrap_err(),
248 AuthorizationPrincipalError::InvalidValue
249 );
250 }
251
252 let oversized = vec![b'x'; crate::DEFAULT_MAX_PAYLOAD_ARTIFACT_BYTES as usize + 1];
253 assert_eq!(
254 PrimaryAuthorizationPrincipal::new(oversized.clone()).unwrap_err(),
255 AuthorizationPrincipalError::InvalidValue
256 );
257 assert_eq!(
258 PeerAuthorizationPrincipal::new(oversized).unwrap_err(),
259 AuthorizationPrincipalError::InvalidValue
260 );
261 }
262
263 #[test]
264 fn role_debug_is_exactly_redacted() {
265 let primary =
266 PrimaryAuthorizationPrincipal::new(format!("Bearer {PRIMARY_SECRET}")).unwrap();
267 let peer = PeerAuthorizationPrincipal::new(format!("Bearer {PEER_SECRET}")).unwrap();
268 assert_eq!(
269 format!("{primary:?}"),
270 "PrimaryAuthorizationPrincipal(<redacted>)"
271 );
272 assert_eq!(
273 format!("{peer:?}"),
274 "PeerAuthorizationPrincipal(<redacted>)"
275 );
276 }
277}