Skip to content

Commit

Permalink
Implement find project by namespace (#147)
Browse files Browse the repository at this point in the history
* feat: implemented find project by namespace domain

* feat: implemented find by namespace grpc
  • Loading branch information
paulobressan authored Oct 2, 2024
1 parent 96a2586 commit 9937569
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 41 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 16 additions & 3 deletions src/domain/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::sync::Arc;

use super::{error::Error, project::cache::ProjectDrivenCache};
use super::{
error::Error,
project::{cache::ProjectDrivenCache, ProjectUserRole},
};

use crate::domain::Result;

Expand All @@ -20,10 +23,11 @@ pub enum Credential {
ApiKey(SecretId),
}

pub async fn assert_project_permission(
pub async fn assert_permission(
project_cache: Arc<dyn ProjectDrivenCache>,
credential: &Credential,
project_id: &str,
role: Option<ProjectUserRole>,
) -> Result<()> {
match credential {
Credential::Auth0(user_id) => {
Expand All @@ -35,7 +39,16 @@ pub async fn assert_project_permission(
return Err(Error::Unauthorized("user doesnt have permission".into()));
}

Ok(())
match role {
Some(role) => {
let permission = result.unwrap();
if role != permission.role {
return Err(Error::Unauthorized("user doesnt have permission".into()));
}
Ok(())
}
None => Ok(()),
}
}
Credential::ApiKey(secret_project_id) => {
if project_id != secret_project_id {
Expand Down
80 changes: 52 additions & 28 deletions src/domain/project/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use tracing::{error, info};
use uuid::Uuid;

use crate::domain::{
auth::{Auth0Driven, Credential, UserId},
auth::{assert_permission, Auth0Driven, Credential, UserId},
error::Error,
event::{
EventDrivenBridge, ProjectCreated, ProjectDeleted, ProjectSecretCreated,
Expand All @@ -32,6 +32,18 @@ pub async fn fetch(cache: Arc<dyn ProjectDrivenCache>, cmd: FetchCmd) -> Result<
cache.find(&user_id, &cmd.page, &cmd.page_size).await
}

pub async fn fetch_by_namespace(
cache: Arc<dyn ProjectDrivenCache>,
cmd: FetchByNamespaceCmd,
) -> Result<Project> {
let Some(project) = cache.find_by_namespace(&cmd.namespace).await? else {
return Err(Error::CommandMalformed("invalid project namespace".into()));
};
assert_permission(cache.clone(), &cmd.credential, &project.id, None).await?;

Ok(project)
}

pub async fn create(
cache: Arc<dyn ProjectDrivenCache>,
event: Arc<dyn EventDrivenBridge>,
Expand Down Expand Up @@ -461,33 +473,6 @@ fn assert_credential(credential: &Credential) -> Result<UserId> {
)),
}
}
async fn assert_permission(
cache: Arc<dyn ProjectDrivenCache>,
credential: &Credential,
project_id: &str,
role: Option<ProjectUserRole>,
) -> Result<()> {
match credential {
Credential::Auth0(user_id) => {
let result = cache.find_user_permission(user_id, project_id).await?;
if result.is_none() {
return Err(Error::Unauthorized("user doesnt have permission".into()));
}

match role {
Some(role) => {
let permission = result.unwrap();
if role != permission.role {
return Err(Error::Unauthorized("user doesnt have permission".into()));
}
Ok(())
}
None => Ok(()),
}
}
Credential::ApiKey(_) => Err(Error::Unauthorized("rpc doesnt support api-key".into())),
}
}

#[derive(Debug, Clone)]
pub struct FetchCmd {
Expand All @@ -513,6 +498,21 @@ impl FetchCmd {
})
}
}

#[derive(Debug, Clone)]
pub struct FetchByNamespaceCmd {
pub credential: Credential,
pub namespace: String,
}
impl FetchByNamespaceCmd {
pub fn new(credential: Credential, namespace: String) -> Self {
Self {
credential,
namespace,
}
}
}

#[derive(Debug, Clone)]
pub struct CreateCmd {
pub credential: Credential,
Expand Down Expand Up @@ -776,6 +776,14 @@ mod tests {
}
}
}
impl Default for FetchByNamespaceCmd {
fn default() -> Self {
Self {
credential: Credential::Auth0("user id".into()),
namespace: "sonic-vegas".into(),
}
}
}
impl Default for CreateCmd {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -910,6 +918,22 @@ mod tests {
assert!(result.is_ok());
}

#[tokio::test]
async fn it_should_fetch_project_by_name() {
let mut cache = MockProjectDrivenCache::new();
cache
.expect_find_user_permission()
.return_once(|_, _| Ok(Some(ProjectUser::default())));
cache
.expect_find_by_namespace()
.return_once(|_| Ok(Some(Project::default())));

let cmd = FetchByNamespaceCmd::default();

let result = fetch_by_namespace(Arc::new(cache), cmd).await;
assert!(result.is_ok());
}

#[tokio::test]
async fn it_should_create_project() {
let mut cache = MockProjectDrivenCache::new();
Expand Down
42 changes: 36 additions & 6 deletions src/domain/resource/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tracing::{error, info};
use uuid::Uuid;

use crate::domain::{
auth::{assert_project_permission, Credential},
auth::{assert_permission, Credential},
error::Error,
event::{EventDrivenBridge, ResourceCreated, ResourceDeleted},
metadata::{KnownField, MetadataDriven},
Expand All @@ -27,7 +27,13 @@ pub async fn fetch(
metadata: Arc<dyn MetadataDriven>,
cmd: FetchCmd,
) -> Result<Vec<Resource>> {
assert_project_permission(project_cache.clone(), &cmd.credential, &cmd.project_id).await?;
assert_permission(
project_cache.clone(),
&cmd.credential,
&cmd.project_id,
None,
)
.await?;

let resources = resource_cache
.find(&cmd.project_id, &cmd.page, &cmd.page_size)
Expand Down Expand Up @@ -56,7 +62,13 @@ pub async fn fetch_by_id(
return Err(Error::CommandMalformed("invalid resource id".into()));
};

assert_project_permission(project_cache.clone(), &cmd.credential, &resource.project_id).await?;
assert_permission(
project_cache.clone(),
&cmd.credential,
&resource.project_id,
None,
)
.await?;

match metadata.render_hbs(&resource.kind.to_lowercase(), &resource.spec) {
Ok(annotations) => resource.annotations = Some(annotations),
Expand All @@ -73,7 +85,13 @@ pub async fn create(
event: Arc<dyn EventDrivenBridge>,
cmd: CreateCmd,
) -> Result<()> {
assert_project_permission(project_cache.clone(), &cmd.credential, &cmd.project_id).await?;
assert_permission(
project_cache.clone(),
&cmd.credential,
&cmd.project_id,
None,
)
.await?;

if resource_cache
.find_by_name(&cmd.project_id, &cmd.name)
Expand Down Expand Up @@ -143,7 +161,13 @@ pub async fn update(
return Err(Error::CommandMalformed("invalid resource id".into()));
};

assert_project_permission(project_cache.clone(), &cmd.credential, &resource.project_id).await?;
assert_permission(
project_cache.clone(),
&cmd.credential,
&resource.project_id,
None,
)
.await?;

let Some(project) = project_cache.find_by_id(&resource.project_id).await? else {
return Err(Error::CommandMalformed("invalid project id".into()));
Expand Down Expand Up @@ -179,7 +203,13 @@ pub async fn delete(
return Err(Error::CommandMalformed("invalid resource id".into()));
};

assert_project_permission(project_cache.clone(), &cmd.credential, &resource.project_id).await?;
assert_permission(
project_cache.clone(),
&cmd.credential,
&resource.project_id,
None,
)
.await?;

let Some(project) = project_cache.find_by_id(&resource.project_id).await? else {
return Err(Error::CommandMalformed("invalid project id".into()));
Expand Down
12 changes: 9 additions & 3 deletions src/domain/usage/command.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::sync::Arc;

use crate::domain::{
auth::{assert_project_permission, Credential},
auth::{assert_permission, Credential},
error::Error,
project::cache::ProjectDrivenCache,
project::{cache::ProjectDrivenCache, ProjectUserRole},
Result, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX,
};

Expand All @@ -14,7 +14,13 @@ pub async fn fetch_report(
usage_cache: Arc<dyn UsageDrivenCache>,
cmd: FetchCmd,
) -> Result<Vec<UsageReport>> {
assert_project_permission(project_cache.clone(), &cmd.credential, &cmd.project_id).await?;
assert_permission(
project_cache.clone(),
&cmd.credential,
&cmd.project_id,
Some(ProjectUserRole::Owner),
)
.await?;

usage_cache
.find_report(&cmd.project_id, &cmd.page, &cmd.page_size)
Expand Down
21 changes: 21 additions & 0 deletions src/drivers/grpc/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@ impl proto::project_service_server::ProjectService for ProjectServiceImpl {

Ok(tonic::Response::new(message))
}
async fn fetch_project_by_namespace(
&self,
request: tonic::Request<proto::FetchProjectByNamespaceRequest>,
) -> Result<tonic::Response<proto::FetchProjectByNamespaceResponse>, tonic::Status> {
let credential = match request.extensions().get::<Credential>() {
Some(credential) => credential.clone(),
None => return Err(Status::unauthenticated("invalid credential")),
};

let req = request.into_inner();

let cmd = project::command::FetchByNamespaceCmd::new(credential, req.namespace);

let project = project::command::fetch_by_namespace(self.cache.clone(), cmd.clone()).await?;

let message = proto::FetchProjectByNamespaceResponse {
records: vec![project.into()],
};

Ok(tonic::Response::new(message))
}

async fn create_project(
&self,
Expand Down

0 comments on commit 9937569

Please sign in to comment.