Skip to content

Commit

Permalink
fix(resource-recorder): check project permission by project id (#1839)
Browse files Browse the repository at this point in the history
* nit

* feat: allow ulid in project endpoint paths

* refa

* feat: check by proj id

* fix: test
  • Loading branch information
jonaro00 authored Jul 25, 2024
1 parent 80892d3 commit 4b7f3fc
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 39 deletions.
8 changes: 6 additions & 2 deletions backends/src/client/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,14 @@ impl ProjectsDal for ServicesApiClient {
}

#[instrument(skip_all)]
async fn head_user_project(&self, user_token: &str, project_name: &str) -> Result<bool, Error> {
async fn head_user_project(
&self,
user_token: &str,
project_ident: &str,
) -> Result<bool, Error> {
self.request_raw(
Method::HEAD,
format!("projects/{}", project_name).as_str(),
format!("projects/{}", project_ident).as_str(),
None::<()>,
Some(header_map_with_bearer(user_token)),
)
Expand Down
23 changes: 3 additions & 20 deletions backends/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,7 @@ pub trait ClaimExt {
async fn owns_project<G: ProjectsDal>(
&self,
projects_dal: &G,
project_name: &str,
) -> Result<bool, client::Error>;
/// Verify if the claim subject has ownership of a project.
async fn owns_project_id<G: ProjectsDal>(
&self,
projects_dal: &G,
project_id: &str,
project_ident: &str,
) -> Result<bool, client::Error>;
}

Expand Down Expand Up @@ -84,20 +78,9 @@ impl ClaimExt for Claim {
async fn owns_project<G: ProjectsDal>(
&self,
projects_dal: &G,
project_name: &str,
) -> Result<bool, client::Error> {
let token = self.token.as_ref().expect("token to be set");
projects_dal.head_user_project(token, project_name).await
}

#[instrument(skip_all)]
async fn owns_project_id<G: ProjectsDal>(
&self,
projects_dal: &G,
project_id: &str,
project_ident: &str,
) -> Result<bool, client::Error> {
let token = self.token.as_ref().expect("token to be set");
let projects = projects_dal.get_user_project_ids(token).await?;
Ok(projects.iter().any(|id| id == project_id))
projects_dal.head_user_project(token, project_ident).await
}
}
6 changes: 4 additions & 2 deletions backends/src/test_utils/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub async fn get_mocked_gateway_server() -> MockServer {

let p = projects.clone();
Mock::given(method(http::Method::HEAD))
.and(path_regex("/projects/[a-z0-9-]+"))
.and(path_regex("/projects/[a-zA-Z0-9-]+"))
.respond_with(move |req: &Request| {
let Some(bearer) = req.headers.get("AUTHORIZATION") else {
return ResponseTemplate::new(401);
Expand All @@ -77,7 +77,9 @@ pub async fn get_mocked_gateway_server() -> MockServer {

let user = bearer.to_str().unwrap().split_whitespace().nth(1).unwrap();

if p.iter().any(|p| p.owner_id == user && p.name == project) {
if p.iter()
.any(|p| p.owner_id == user && (p.name == project || p.id == project))
{
ResponseTemplate::new(200)
} else {
ResponseTemplate::new(401)
Expand Down
4 changes: 2 additions & 2 deletions cargo-shuttle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2275,8 +2275,8 @@ impl Shuttle {
state => {
debug!("deployment logs stream received state: {state} when it expected to receive running state");
println!(
"Deployment entered an unexpected state - Please create a ticket to report this."
);
"Deployment entered an unexpected state - Please create a ticket to report this."
);
}
}

Expand Down
34 changes: 22 additions & 12 deletions gateway/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use shuttle_common::claims::Claim;
use shuttle_common::models::error::{ApiError, InvalidProjectName, ProjectNotFound};
use thiserror::Error;
use tracing::error;
use ulid::Ulid;

use crate::api::latest::RouterState;
use crate::service;
Expand Down Expand Up @@ -78,17 +79,27 @@ where
.await
.map_err(Error::StatusCode)?;

let scope = match Path::<ProjectName>::from_request_parts(parts, state).await {
Ok(Path(p)) => p,
Err(_) => Path::<(ProjectName, String)>::from_request_parts(parts, state)
.await
.map(|Path((p, _))| p)
.map_err(|_| InvalidProjectName)?,
};

let RouterState { service, .. } = RouterState::from_ref(state);

let allowed = claim.is_admin()
// Enables checking HEAD at /projects/{ulid}, used by res-rec to check permission for a proj id
let scope = match Path::<Ulid>::from_request_parts(parts, state).await {
Ok(Path(ulid)) => {
let p = service.find_project_by_id(&ulid.to_string()).await?;
ProjectName::new(&p.name).expect("valid project name")
}
Err(_) => {
// Normal check for project name in path
match Path::<ProjectName>::from_request_parts(parts, state).await {
Ok(Path(p)) => p,
Err(_) => Path::<(ProjectName, String)>::from_request_parts(parts, state)
.await
.map(|Path((p, _))| p)
.map_err(|_| InvalidProjectName)?,
}
}
};

if claim.is_admin()
|| claim.is_deployer()
|| service
.permit_client
Expand All @@ -97,9 +108,8 @@ where
&service.find_project_by_name(&scope).await?.id,
"develop", // TODO: make this configurable per endpoint?
)
.await?;

if allowed {
.await?
{
Ok(Self { claim, scope })
} else {
Err(ProjectNotFound(scope.to_string()).into())
Expand Down
2 changes: 1 addition & 1 deletion resource-recorder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ where
if !claim.is_admin()
&& !claim.is_deployer()
&& !claim
.owns_project_id(&self.gateway_client, project_id)
.owns_project(&self.gateway_client, project_id)
.await
.map_err(|_| Status::internal("could not verify project ownership"))?
{
Expand Down

0 comments on commit 4b7f3fc

Please sign in to comment.