cloud_sdk/authentication/
policy.rs1use core::fmt;
2
3use crate::transport::{EndpointIdentity, EndpointScheme};
4use crate::{ProviderId, ServiceId};
5
6use super::ScopeValue;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum ScopeRequirement<T> {
11 Required(T),
13 Optional(T),
15 Forbidden,
17}
18
19#[derive(Clone, Copy)]
21pub struct AuthenticationScope<'a> {
22 provider: Option<ProviderId>,
23 service: Option<ServiceId>,
24 endpoint: Option<EndpointIdentity<'a>>,
25 audience: Option<ScopeValue<'a>>,
26 account: Option<ScopeValue<'a>>,
27 tenant: Option<ScopeValue<'a>>,
28}
29
30impl<'a> AuthenticationScope<'a> {
31 #[must_use]
35 pub const fn unscoped() -> Self {
36 Self {
37 provider: None,
38 service: None,
39 endpoint: None,
40 audience: None,
41 account: None,
42 tenant: None,
43 }
44 }
45
46 #[must_use]
48 pub const fn with_provider(mut self, value: ProviderId) -> Self {
49 self.provider = Some(value);
50 self
51 }
52
53 #[must_use]
55 pub const fn with_service(mut self, value: ServiceId) -> Self {
56 self.service = Some(value);
57 self
58 }
59
60 #[must_use]
62 pub const fn with_endpoint(mut self, value: EndpointIdentity<'a>) -> Self {
63 self.endpoint = Some(value);
64 self
65 }
66
67 #[must_use]
69 pub const fn with_audience(mut self, value: ScopeValue<'a>) -> Self {
70 self.audience = Some(value);
71 self
72 }
73
74 #[must_use]
76 pub const fn with_account(mut self, value: ScopeValue<'a>) -> Self {
77 self.account = Some(value);
78 self
79 }
80
81 #[must_use]
83 pub const fn with_tenant(mut self, value: ScopeValue<'a>) -> Self {
84 self.tenant = Some(value);
85 self
86 }
87}
88
89impl fmt::Debug for AuthenticationScope<'_> {
90 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
91 formatter
92 .debug_struct("AuthenticationScope")
93 .field("provider", &self.provider)
94 .field("service", &self.service)
95 .field("endpoint", &self.endpoint)
96 .field("audience", &self.audience.map(|_| "[redacted]"))
97 .field("account", &self.account.map(|_| "[redacted]"))
98 .field("tenant", &self.tenant.map(|_| "[redacted]"))
99 .finish()
100 }
101}
102
103#[derive(Clone, Copy, Debug, Eq, PartialEq)]
105pub struct AuthenticationScopePolicy<'a> {
106 provider: ScopeRequirement<ProviderId>,
107 service: ScopeRequirement<ServiceId>,
108 endpoint: ScopeRequirement<EndpointIdentity<'a>>,
109 audience: ScopeRequirement<ScopeValue<'a>>,
110 account: ScopeRequirement<ScopeValue<'a>>,
111 tenant: ScopeRequirement<ScopeValue<'a>>,
112}
113
114impl<'a> AuthenticationScopePolicy<'a> {
115 #[must_use]
117 pub const fn new(
118 provider: ScopeRequirement<ProviderId>,
119 service: ScopeRequirement<ServiceId>,
120 endpoint: ScopeRequirement<EndpointIdentity<'a>>,
121 audience: ScopeRequirement<ScopeValue<'a>>,
122 account: ScopeRequirement<ScopeValue<'a>>,
123 tenant: ScopeRequirement<ScopeValue<'a>>,
124 ) -> Self {
125 Self {
126 provider,
127 service,
128 endpoint,
129 audience,
130 account,
131 tenant,
132 }
133 }
134
135 #[must_use]
137 pub const fn provider_requirement(self) -> ScopeRequirement<ProviderId> {
138 self.provider
139 }
140
141 #[must_use]
143 pub const fn service_requirement(self) -> ScopeRequirement<ServiceId> {
144 self.service
145 }
146
147 #[must_use]
149 pub const fn endpoint_requirement(self) -> ScopeRequirement<EndpointIdentity<'a>> {
150 self.endpoint
151 }
152
153 #[must_use]
155 pub const fn audience_requirement(self) -> ScopeRequirement<ScopeValue<'a>> {
156 self.audience
157 }
158
159 #[must_use]
161 pub const fn account_requirement(self) -> ScopeRequirement<ScopeValue<'a>> {
162 self.account
163 }
164
165 #[must_use]
167 pub const fn tenant_requirement(self) -> ScopeRequirement<ScopeValue<'a>> {
168 self.tenant
169 }
170
171 pub fn validate(self, scope: AuthenticationScope<'a>) -> Result<(), AuthenticationScopeError> {
173 validate_endpoint_security(self.endpoint)?;
174 validate_field(scope.provider, self.provider, ScopeField::Provider)?;
175 validate_field(scope.service, self.service, ScopeField::Service)?;
176 validate_field(scope.endpoint, self.endpoint, ScopeField::Endpoint)?;
177 validate_field(scope.audience, self.audience, ScopeField::Audience)?;
178 validate_field(scope.account, self.account, ScopeField::Account)?;
179 validate_field(scope.tenant, self.tenant, ScopeField::Tenant)
180 }
181}
182
183#[derive(Clone, Copy, Debug, Eq, PartialEq)]
185pub enum ScopeField {
186 Provider,
188 Service,
190 Endpoint,
192 Audience,
194 Account,
196 Tenant,
198}
199
200#[derive(Clone, Copy, Debug, Eq, PartialEq)]
202pub enum ScopeViolation {
203 MissingRequired,
205 SuppliedForbidden,
207 Mismatch,
209 InsecureEndpoint,
211}
212
213#[derive(Clone, Copy, Debug, Eq, PartialEq)]
215pub struct AuthenticationScopeError {
216 field: ScopeField,
217 violation: ScopeViolation,
218}
219
220impl AuthenticationScopeError {
221 #[must_use]
223 pub const fn field(self) -> ScopeField {
224 self.field
225 }
226
227 #[must_use]
229 pub const fn violation(self) -> ScopeViolation {
230 self.violation
231 }
232}
233
234impl fmt::Display for AuthenticationScopeError {
235 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
236 formatter.write_str(match self.violation {
237 ScopeViolation::MissingRequired => "required authentication scope field is missing",
238 ScopeViolation::SuppliedForbidden => {
239 "forbidden authentication scope field was supplied"
240 }
241 ScopeViolation::Mismatch => "authentication scope field does not match policy",
242 ScopeViolation::InsecureEndpoint => "authentication endpoint policy is not HTTPS",
243 })
244 }
245}
246
247impl core::error::Error for AuthenticationScopeError {}
248
249fn validate_field<T: Copy + Eq>(
250 actual: Option<T>,
251 requirement: ScopeRequirement<T>,
252 field: ScopeField,
253) -> Result<(), AuthenticationScopeError> {
254 let violation = match (actual, requirement) {
255 (None, ScopeRequirement::Required(_)) => Some(ScopeViolation::MissingRequired),
256 (Some(_), ScopeRequirement::Forbidden) => Some(ScopeViolation::SuppliedForbidden),
257 (
258 Some(actual),
259 ScopeRequirement::Required(expected) | ScopeRequirement::Optional(expected),
260 ) if actual != expected => Some(ScopeViolation::Mismatch),
261 _ => None,
262 };
263 violation.map_or(Ok(()), |violation| {
264 Err(AuthenticationScopeError { field, violation })
265 })
266}
267
268fn validate_endpoint_security(
269 requirement: ScopeRequirement<EndpointIdentity<'_>>,
270) -> Result<(), AuthenticationScopeError> {
271 let expected = match requirement {
272 ScopeRequirement::Required(value) | ScopeRequirement::Optional(value) => value,
273 ScopeRequirement::Forbidden => return Ok(()),
274 };
275 if expected.scheme() == EndpointScheme::Https {
276 Ok(())
277 } else {
278 Err(AuthenticationScopeError {
279 field: ScopeField::Endpoint,
280 violation: ScopeViolation::InsecureEndpoint,
281 })
282 }
283}