1use std::{any::Any, collections::HashMap, str, sync::Arc};
22
23use axum::{
24 async_trait,
25 extract::FromRequestParts,
26 http::{
27 header::{self},
28 request::Parts,
29 StatusCode,
30 },
31};
32use sec::Secret;
33use thiserror::Error;
34
35use crate::{storage::ImageLocation, ImageDigest};
36
37use super::{
38 www_authenticate::{self},
39 ContainerRegistry,
40};
41
42#[derive(Debug)]
44pub enum Unverified {
45 UsernameAndPassword {
47 username: String,
49 password: Secret<String>,
51 },
52 NoCredentials,
54}
55
56impl Unverified {
57 #[inline(always)]
59 pub fn is_no_credentials(&self) -> bool {
60 matches!(self, Unverified::NoCredentials)
61 }
62}
63
64#[async_trait]
65impl<S> FromRequestParts<S> for Unverified {
66 type Rejection = StatusCode;
67
68 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
69 if let Some(auth_header) = parts.headers.get(header::AUTHORIZATION) {
70 let (_unparsed, basic) = www_authenticate::basic_auth_response(auth_header.as_bytes())
71 .map_err(|_| StatusCode::BAD_REQUEST)?;
72
73 Ok(Unverified::UsernameAndPassword {
74 username: str::from_utf8(&basic.username)
75 .map_err(|_| StatusCode::BAD_REQUEST)?
76 .to_owned(),
77 password: Secret::new(
78 str::from_utf8(&basic.password)
79 .map_err(|_| StatusCode::BAD_REQUEST)?
80 .to_owned(),
81 ),
82 })
83 } else {
84 Ok(Unverified::NoCredentials)
85 }
86 }
87}
88
89#[derive(Debug)]
95pub struct ValidCredentials(pub Box<dyn Any + Send + Sync>);
96
97impl ValidCredentials {
98 #[inline(always)]
100 pub fn new<T: Send + Sync + 'static>(inner: T) -> Self {
101 ValidCredentials(Box::new(inner))
102 }
103
104 pub fn extract_ref<T: 'static>(&self) -> &T {
106 self.0.downcast_ref::<T>().expect("could not downcast `ValidCredentials` into expected type - was auth provider called with the wrong set of credentials?")
107 }
108}
109
110#[async_trait]
111impl FromRequestParts<Arc<ContainerRegistry>> for ValidCredentials {
112 type Rejection = StatusCode;
113
114 #[inline(always)]
115 async fn from_request_parts(
116 parts: &mut Parts,
117 state: &Arc<ContainerRegistry>,
118 ) -> Result<Self, Self::Rejection> {
119 let unverified = Unverified::from_request_parts(parts, state).await?;
120
121 match state.auth_provider.check_credentials(&unverified).await {
123 Some(creds) => Ok(creds),
124 None => Err(StatusCode::UNAUTHORIZED),
125 }
126 }
127}
128
129#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131#[repr(u8)]
132pub enum Permissions {
133 NoAccess = 0,
135 WriteOnly = 2,
137 ReadOnly = 4,
139 ReadWrite = 6,
141}
142
143impl Permissions {
144 #[inline(always)]
146 #[must_use = "should not check read permissions and discard the result"]
147 pub fn has_read_permission(self) -> bool {
148 match self {
149 Permissions::NoAccess | Permissions::WriteOnly => false,
150 Permissions::ReadOnly | Permissions::ReadWrite => true,
151 }
152 }
153
154 #[inline(always)]
156 #[must_use = "should not check write permissions and discard the result"]
157 pub fn has_write_permission(self) -> bool {
158 match self {
159 Permissions::NoAccess | Permissions::ReadOnly => false,
160 Permissions::WriteOnly | Permissions::ReadWrite => true,
161 }
162 }
163
164 #[inline(always)]
166 pub fn require_read(self) -> Result<(), MissingPermission> {
167 if !self.has_read_permission() {
168 Err(MissingPermission)
169 } else {
170 Ok(())
171 }
172 }
173
174 #[inline(always)]
176 pub fn require_write(self) -> Result<(), MissingPermission> {
177 if !self.has_write_permission() {
178 Err(MissingPermission)
179 } else {
180 Ok(())
181 }
182 }
183}
184
185#[derive(Debug, Error)]
187#[error("not permitted")]
188pub struct MissingPermission;
189
190#[async_trait]
194pub trait AuthProvider: Send + Sync {
195 async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials>;
203
204 async fn image_permissions(
209 &self,
210 creds: &ValidCredentials,
211 image: &ImageLocation,
212 ) -> Permissions;
213
214 async fn blob_permissions(&self, creds: &ValidCredentials, blob: &ImageDigest) -> Permissions;
223}
224
225#[derive(Debug)]
230pub struct Anonymous<A> {
231 anon_permissions: Permissions,
232 inner: A,
233}
234
235impl<A> Anonymous<A> {
236 pub fn new(anon_permissions: Permissions, inner: A) -> Self {
238 Self {
239 anon_permissions,
240 inner,
241 }
242 }
243}
244
245#[derive(Debug)]
247enum AnonCreds {
248 Anonymous,
250 Valid(ValidCredentials),
252}
253
254#[async_trait]
255impl<A> AuthProvider for Anonymous<A>
256where
257 A: AuthProvider,
258{
259 async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
260 match unverified {
261 Unverified::NoCredentials => Some(ValidCredentials::new(AnonCreds::Anonymous)),
262 _other => self.inner.check_credentials(unverified).await,
263 }
264 }
265
266 async fn image_permissions(
267 &self,
268 creds: &ValidCredentials,
269 image: &ImageLocation,
270 ) -> Permissions {
271 match creds.extract_ref::<AnonCreds>() {
272 AnonCreds::Anonymous => self.anon_permissions,
273 _other => self.inner.image_permissions(creds, image).await,
274 }
275 }
276
277 async fn blob_permissions(&self, creds: &ValidCredentials, blob: &ImageDigest) -> Permissions {
278 match creds.extract_ref::<AnonCreds>() {
279 AnonCreds::Anonymous => self.anon_permissions,
280 _other => self.inner.blob_permissions(creds, blob).await,
281 }
282 }
283}
284
285#[async_trait]
286impl AuthProvider for Permissions {
287 #[inline(always)]
288 async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
289 match unverified {
290 Unverified::NoCredentials => None,
291 _other => Some(ValidCredentials::new(())),
292 }
293 }
294
295 #[inline(always)]
296 async fn image_permissions(
297 &self,
298 _creds: &ValidCredentials,
299 _image: &ImageLocation,
300 ) -> Permissions {
301 *self
302 }
303
304 #[inline(always)]
305 async fn blob_permissions(
306 &self,
307 _creds: &ValidCredentials,
308 _blob: &ImageDigest,
309 ) -> Permissions {
310 *self
311 }
312}
313
314#[async_trait]
315impl AuthProvider for HashMap<String, Secret<String>> {
316 async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
317 match unverified {
318 Unverified::UsernameAndPassword {
319 username: unverified_username,
320 password: unverified_password,
321 } => {
322 if let Some(correct_password) = self.get(unverified_username) {
323 if constant_time_eq::constant_time_eq(
324 correct_password.reveal().as_bytes(),
325 unverified_password.reveal().as_bytes(),
326 ) {
327 return Some(ValidCredentials::new(unverified_username.clone()));
328 }
329 }
330
331 None
332 }
333 Unverified::NoCredentials => None,
334 }
335 }
336
337 #[inline(always)]
338 async fn image_permissions(
339 &self,
340 _creds: &ValidCredentials,
341 _image: &ImageLocation,
342 ) -> Permissions {
343 Permissions::ReadWrite
344 }
345
346 #[inline(always)]
347 async fn blob_permissions(
348 &self,
349 _creds: &ValidCredentials,
350 _blob: &ImageDigest,
351 ) -> Permissions {
352 Permissions::ReadWrite
353 }
354}
355
356#[async_trait]
357impl<T> AuthProvider for Box<T>
358where
359 T: AuthProvider,
360{
361 #[inline(always)]
362 async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
363 <T as AuthProvider>::check_credentials(self, unverified).await
364 }
365
366 #[inline(always)]
367 async fn image_permissions(
368 &self,
369 _creds: &ValidCredentials,
370 _image: &ImageLocation,
371 ) -> Permissions {
372 Permissions::ReadWrite
373 }
374
375 #[inline(always)]
376 async fn blob_permissions(
377 &self,
378 _creds: &ValidCredentials,
379 _blob: &ImageDigest,
380 ) -> Permissions {
381 Permissions::ReadWrite
382 }
383}
384
385#[async_trait]
386impl<T> AuthProvider for Arc<T>
387where
388 T: AuthProvider,
389{
390 #[inline(always)]
391 async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
392 <T as AuthProvider>::check_credentials(self, unverified).await
393 }
394
395 #[inline(always)]
396 async fn image_permissions(
397 &self,
398 _creds: &ValidCredentials,
399 _image: &ImageLocation,
400 ) -> Permissions {
401 Permissions::ReadWrite
402 }
403
404 #[inline(always)]
405 async fn blob_permissions(
406 &self,
407 _creds: &ValidCredentials,
408 _blob: &ImageDigest,
409 ) -> Permissions {
410 Permissions::ReadWrite
411 }
412}
413
414#[async_trait]
415impl AuthProvider for Secret<String> {
416 #[inline(always)]
417 async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
418 match unverified {
419 Unverified::UsernameAndPassword {
420 username: _,
421 password,
422 } => {
423 if constant_time_eq::constant_time_eq(
424 password.reveal().as_bytes(),
425 self.reveal().as_bytes(),
426 ) {
427 Some(ValidCredentials::new(()))
428 } else {
429 None
430 }
431 }
432 Unverified::NoCredentials => None,
433 }
434 }
435
436 #[inline(always)]
437 async fn image_permissions(
438 &self,
439 _creds: &ValidCredentials,
440 _image: &ImageLocation,
441 ) -> Permissions {
442 Permissions::ReadWrite
443 }
444
445 #[inline(always)]
446 async fn blob_permissions(
447 &self,
448 _creds: &ValidCredentials,
449 _blob: &ImageDigest,
450 ) -> Permissions {
451 Permissions::ReadWrite
452 }
453}