From b66d3843ecc0b0b96881ff9ff4580ad62e1c70f2 Mon Sep 17 00:00:00 2001 From: Edoardo Marangoni Date: Thu, 8 Aug 2024 12:28:50 +0200 Subject: [PATCH 1/4] feat: Check if a user can deploy an app before deploying it --- lib/backend-api/schema.graphql | 12 ++++++++++++ lib/backend-api/src/query.rs | 13 +++++++++++++ lib/backend-api/src/types.rs | 19 +++++++++++++++++++ lib/cli/src/commands/app/deploy.rs | 23 ++++++++++++++++++++++- 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/backend-api/schema.graphql b/lib/backend-api/schema.graphql index 2c58375a4bb..281826450c7 100644 --- a/lib/backend-api/schema.graphql +++ b/lib/backend-api/schema.graphql @@ -43,6 +43,7 @@ type User implements Node & PackageOwner & Owner { id: ID! globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! avatar(size: Int = 80): String! isViewer: Boolean! hasUsablePassword: Boolean @@ -76,12 +77,19 @@ type User implements Node & PackageOwner & Owner { interface PackageOwner { globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! +} + +enum OwnerAction { + DEPLOY_APP + PUBLISH_PACKAGE } """An owner of a package.""" interface Owner { globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! } """ @@ -204,6 +212,7 @@ type Namespace implements Node & PackageOwner & Owner { userSet(offset: Int, before: String, after: String, first: Int, last: Int): UserConnection! globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! avatar: String! packages(offset: Int, before: String, after: String, first: Int, last: Int): PackageConnection! apps(sortBy: DeployAppsSortBy, offset: Int, before: String, after: String, first: Int, last: Int): DeployAppConnection! @@ -372,6 +381,7 @@ type Package implements Likeable & Node & PackageOwner { viewerHasLiked: Boolean! globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! alias: String displayName: String! @@ -926,6 +936,7 @@ type DeployApp implements Node & Owner { activeVersion: DeployAppVersion! globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! url: String! adminUrl: String! permalink: String! @@ -2388,6 +2399,7 @@ type Query { getPackageRelease(hash: String!): PackageWebc getPackageInstanceByVersionOrHash(name: String!, version: String, hash: String): PackageInstance categories(offset: Int, before: String, after: String, first: Int, last: Int): CategoryConnection! + viewerCan(action: OwnerAction!, ownerName: String!): Boolean! blogposts(tags: [String!], before: String, after: String, first: Int, last: Int): BlogPostConnection! getBlogpost(slug: String, featured: Boolean): BlogPost allBlogpostTags(offset: Int, before: String, after: String, first: Int, last: Int): BlogPostTagConnection diff --git a/lib/backend-api/src/query.rs b/lib/backend-api/src/query.rs index 3b5a43174b3..3ee3852852f 100644 --- a/lib/backend-api/src/query.rs +++ b/lib/backend-api/src/query.rs @@ -15,6 +15,19 @@ use crate::{ GraphQLApiFailure, WasmerClient, }; +pub async fn viewer_can_deploy_to_namespace( + client: &WasmerClient, + owner_name: &str, +) -> Result { + client + .run_graphql_strict(types::ViewerCan::build(ViewerCanVariables { + action: OwnerAction::DeployApp, + owner_name, + })) + .await + .map(|v| v.viewer_can) +} + pub async fn redeploy_app_by_id( client: &WasmerClient, app_id: impl Into, diff --git a/lib/backend-api/src/types.rs b/lib/backend-api/src/types.rs index c7ef5b2b36a..dcec89d7fae 100644 --- a/lib/backend-api/src/types.rs +++ b/lib/backend-api/src/types.rs @@ -41,6 +41,25 @@ mod queries { Viewer, } + #[derive(cynic::QueryVariables, Debug)] + pub struct ViewerCanVariables<'a> { + pub action: OwnerAction, + pub owner_name: &'a str, + } + + #[derive(cynic::QueryFragment, Debug)] + #[cynic(graphql_type = "Query", variables = "ViewerCanVariables")] + pub struct ViewerCan { + #[arguments(action: $action, ownerName: $owner_name)] + pub viewer_can: bool, + } + + #[derive(cynic::Enum, Clone, Copy, Debug)] + pub enum OwnerAction { + DeployApp, + PublishPackage, + } + #[derive(cynic::QueryVariables, Debug)] pub struct RevokeTokenVariables { pub token: String, diff --git a/lib/cli/src/commands/app/deploy.rs b/lib/cli/src/commands/app/deploy.rs index c503e74d234..ed9104f6660 100644 --- a/lib/cli/src/commands/app/deploy.rs +++ b/lib/cli/src/commands/app/deploy.rs @@ -279,10 +279,31 @@ impl AsyncCliCommand for CmdAppDeploy { None }; - let owner = self + let mut owner = self .get_owner(&client, &mut app_yaml, maybe_edge_app.as_ref()) .await?; + if !wasmer_api::query::viewer_can_deploy_to_namespace(&client, &owner).await? { + eprintln!( + "Cannot deploy app to namespace {owner}, as the current user is not authorized." + ); + if self.non_interactive { + anyhow::bail!("Please, check the app configuration or the current user with the `whoami` command!"); + } else { + let user = wasmer_api::query::current_user_with_namespaces(&client, None).await?; + owner = crate::utils::prompts::prompt_for_namespace( + "Who should own this app?", + None, + Some(&user), + )?; + + app_yaml + .as_mapping_mut() + .unwrap() + .insert("owner".into(), owner.clone().into()); + } + } + if app_yaml.get("name").is_none() && self.app_name.is_some() { app_yaml.as_mapping_mut().unwrap().insert( "name".into(), From 1823c1c35bad5e57c5569f60b9a425de78c5b9c1 Mon Sep 17 00:00:00 2001 From: Edoardo Marangoni Date: Thu, 8 Aug 2024 12:33:32 +0200 Subject: [PATCH 2/4] fix(cli/deploy): Remove `app_id` and `name` if user can't deploy to namespace --- lib/cli/src/commands/app/deploy.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/cli/src/commands/app/deploy.rs b/lib/cli/src/commands/app/deploy.rs index ed9104f6660..3c47fef4624 100644 --- a/lib/cli/src/commands/app/deploy.rs +++ b/lib/cli/src/commands/app/deploy.rs @@ -301,6 +301,14 @@ impl AsyncCliCommand for CmdAppDeploy { .as_mapping_mut() .unwrap() .insert("owner".into(), owner.clone().into()); + + if app_yaml.get("app_id").is_some() { + app_yaml.as_mapping_mut().unwrap().remove("app_id"); + } + + if app_yaml.get("name").is_some() { + app_yaml.as_mapping_mut().unwrap().remove("name"); + } } } From f33b33d029dad78bf8c9c239a79c840f073b18be Mon Sep 17 00:00:00 2001 From: Edoardo Marangoni Date: Thu, 8 Aug 2024 13:22:19 +0200 Subject: [PATCH 3/4] fix(cli/deploy): Show namespace in bold --- lib/cli/src/commands/app/deploy.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cli/src/commands/app/deploy.rs b/lib/cli/src/commands/app/deploy.rs index 3c47fef4624..abd9f5dfe46 100644 --- a/lib/cli/src/commands/app/deploy.rs +++ b/lib/cli/src/commands/app/deploy.rs @@ -285,7 +285,8 @@ impl AsyncCliCommand for CmdAppDeploy { if !wasmer_api::query::viewer_can_deploy_to_namespace(&client, &owner).await? { eprintln!( - "Cannot deploy app to namespace {owner}, as the current user is not authorized." + "Cannot deploy app to namespace {}, as the current user is not authorized.", + owner.bold() ); if self.non_interactive { anyhow::bail!("Please, check the app configuration or the current user with the `whoami` command!"); From cfeb6226746654d413a5c18b3b511d8127f0b64b Mon Sep 17 00:00:00 2001 From: Edoardo Marangoni Date: Thu, 8 Aug 2024 18:23:08 +0200 Subject: [PATCH 4/4] fix(cli/deploy): change messages --- lib/cli/src/commands/app/deploy.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/cli/src/commands/app/deploy.rs b/lib/cli/src/commands/app/deploy.rs index abd9f5dfe46..6f229520247 100644 --- a/lib/cli/src/commands/app/deploy.rs +++ b/lib/cli/src/commands/app/deploy.rs @@ -284,12 +284,9 @@ impl AsyncCliCommand for CmdAppDeploy { .await?; if !wasmer_api::query::viewer_can_deploy_to_namespace(&client, &owner).await? { - eprintln!( - "Cannot deploy app to namespace {}, as the current user is not authorized.", - owner.bold() - ); + eprintln!("It seems you don't have access to {}", owner.bold()); if self.non_interactive { - anyhow::bail!("Please, check the app configuration or the current user with the `whoami` command!"); + anyhow::bail!("Please, change the owner before deploying or check your current user with `{} whoami`.", std::env::args().next().unwrap_or("wasmer".into())); } else { let user = wasmer_api::query::current_user_with_namespaces(&client, None).await?; owner = crate::utils::prompts::prompt_for_namespace(