/ backend / src / api / container_spec / mod.rs
mod.rs
  1  use rocket::{
  2      http::Status,
  3      request::{self, FromRequest},
  4      Request, State,
  5  };
  6  
  7  use crate::{
  8      api::container_spec::auth_service::accounts_rs::AccountsRsUserResponse, config::Config,
  9  };
 10  
 11  use self::errors::UnauthorizedResponse;
 12  
 13  pub mod auth_service;
 14  pub mod blobs;
 15  pub mod errors;
 16  pub mod manifests;
 17  pub mod tags;
 18  
 19  const CONTENT_TYPE_HEADER_NAME: &str = "Content-Type";
 20  const CONTENT_RANGE_HEADER_NAME: &str = "Content-Range";
 21  const CONTENT_LENGTH_HEADER_NAME: &str = "Content-Length";
 22  const LOCATION_HEADER_NAME: &str = "Location";
 23  const RANGE_HEADER_NAME: &str = "Range";
 24  const DOCKER_UPLOAD_UUID_HEADER_NAME: &str = "Docker-Upload-UUID";
 25  const DOCKER_CONTENT_DIGEST_HEADER_NAME: &str = "Docker-Content-Digest";
 26  const APPLICATION_TYPE_OCTET_STREAM: &str = "application/octet-stream";
 27  const OCI_SUBJECT_HEADER_NAME: &str = "OCI-Subject";
 28  
 29  pub struct Auth {
 30      username: String,
 31  }
 32  
 33  #[derive(Responder, Debug, Clone)]
 34  pub enum AuthFailure {
 35      Unauthorized(UnauthorizedResponse),
 36      InternalServerError(String),
 37  }
 38  
 39  #[rocket::async_trait]
 40  impl<'r> FromRequest<'r> for Auth {
 41      type Error = AuthFailure;
 42  
 43      async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
 44          let config = match req.guard::<&State<Config>>().await {
 45              rocket::outcome::Outcome::Success(s) => s,
 46              _ => {
 47                  return request::Outcome::Error((
 48                      Status::InternalServerError,
 49                      AuthFailure::InternalServerError("Failed to retrieve config!".to_string()),
 50                  ))
 51              }
 52          };
 53  
 54          let Some(auth_header) = req.headers().get_one("authorization") else {
 55              warn!("Request missing authorization header");
 56              return auth_failure(req, config);
 57          };
 58  
 59          if !auth_header.starts_with("Bearer ") {
 60              error!("Auth header doesn't start with 'Bearer '?");
 61              return auth_failure(req, config);
 62          }
 63  
 64          let client = reqwest::Client::new();
 65          let resp = match client
 66              .get(&config.accounts_rs_me_endpoint)
 67              .header("Authorization", auth_header)
 68              .send()
 69              .await
 70          {
 71              Ok(resp) => resp,
 72              Err(e) => {
 73                  error!("Failed to send user request to accounts service, err: {e:?}");
 74                  return auth_failure(req, config);
 75              }
 76          };
 77  
 78          let resp_status = resp.status();
 79          if !resp_status.is_success() {
 80              error!("Got error response (status {resp_status}) from accounts service");
 81              return auth_failure(req, config);
 82          }
 83  
 84          let user_info: AccountsRsUserResponse = match resp.json().await {
 85              Ok(u) => u,
 86              Err(e) => {
 87                  error!("Failed to deserialize user request to accounts service, err: {e:?}");
 88                  return auth_failure(req, config);
 89              }
 90          };
 91  
 92          request::Outcome::Success(Auth {
 93              username: user_info.success.email,
 94          })
 95      }
 96  }
 97  
 98  fn auth_failure<'r>(request: &'r Request, config: &Config) -> request::Outcome<Auth, AuthFailure> {
 99      let auth_failure = AuthFailure::Unauthorized(UnauthorizedResponse::new(config));
100      request.local_cache(|| auth_failure.clone());
101      return request::Outcome::Error((Status::Unauthorized, auth_failure));
102  }
103  
104  #[derive(Responder)]
105  pub enum SpecComplianceResponse {
106      #[response(status = 200)]
107      Ok(()),
108      #[response(status = 401)]
109      Unauthorized(UnauthorizedResponse),
110      #[response(status = 500)]
111      InternalServerError(()),
112  }
113  
114  #[get("/v2")]
115  pub fn get_spec_compliance(auth: Result<Auth, AuthFailure>) -> SpecComplianceResponse {
116      match auth {
117          Ok(_) => SpecComplianceResponse::Ok(()),
118          Err(AuthFailure::Unauthorized(resp)) => SpecComplianceResponse::Unauthorized(resp),
119          Err(AuthFailure::InternalServerError(err)) => {
120              error!("Internal server error {err:?}");
121              SpecComplianceResponse::InternalServerError(())
122          }
123      }
124  }