Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions libs/ferriskey-cli-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,53 @@ impl FerriskeyClient {
self.get_list(&self.endpoint(&format!("realms/{realm}/roles")))
}

pub fn delete_role(&self, realm: &str, role_id: &str) -> Result<(), FerriskeyClientError> {
let response = self
.http
.delete(self.endpoint(&format!("realms/{realm}/roles/{role_id}")))
.bearer_auth(&self.token)
.send()?;

if !response.status().is_success() {
let status = response.status();
let body = response.text().unwrap_or_default();
return Err(FerriskeyClientError::Api { status, body });
}

Ok(())
}

pub fn list_client_roles(
&self,
realm: &str,
client_uuid: &str,
) -> Result<Vec<CreatedRole>, FerriskeyClientError> {
self.get_list(&self.endpoint(&format!("realms/{realm}/clients/{client_uuid}/roles")))
}

pub fn delete_client_role(
&self,
realm: &str,
client_uuid: &str,
role_id: &str,
) -> Result<(), FerriskeyClientError> {
let response = self
.http
.delete(self.endpoint(&format!(
"realms/{realm}/clients/{client_uuid}/roles/{role_id}"
)))
.bearer_auth(&self.token)
.send()?;

if !response.status().is_success() {
let status = response.status();
let body = response.text().unwrap_or_default();
return Err(FerriskeyClientError::Api { status, body });
}

Ok(())
}

pub fn create_client_role(
&self,
realm: &str,
Expand Down
3 changes: 2 additions & 1 deletion libs/ferriskey-cli-commands/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ pub use self::context::{
pub use self::login::LoginCommand;
pub use self::realm::{
ImportSource, RealmCommand, RealmDeleteArgs, RealmImportArgs, RealmNameArgs, RealmRoleCommand,
RealmRoleCreateArgs, RealmRoleListArgs, RealmRoleSubcommand, RealmSubcommand,
RealmRoleCreateArgs, RealmRoleDeleteArgs, RealmRoleGetArgs, RealmRoleListArgs,
RealmRoleSubcommand, RealmSubcommand,
};
pub use self::source::{
SourceAddArgs, SourceCommand, SourceKind, SourceRemoveArgs, SourceSubcommand,
Expand Down
50 changes: 48 additions & 2 deletions libs/ferriskey-cli-commands/src/realm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,14 @@ pub struct RealmRoleCommand {
/// Available realm role commands.
#[derive(Debug, Subcommand)]
pub enum RealmRoleSubcommand {
/// Create a realm role.
/// Create a realm or client role.
Create(RealmRoleCreateArgs),
/// List realm roles.
/// List realm or client roles.
List(RealmRoleListArgs),
/// Show a realm or client role's details.
Get(RealmRoleGetArgs),
/// Delete a realm or client role.
Delete(RealmRoleDeleteArgs),
}

/// Arguments for `realm role create`.
Expand All @@ -61,6 +65,10 @@ pub struct RealmRoleCreateArgs {
/// Realm name. Defaults to the selected context realm.
#[arg(long)]
pub realm: Option<String>,

/// Create a client role instead of a realm role, scoped to this client id.
#[arg(long)]
pub client: Option<String>,
}

/// Arguments for `realm role list`.
Expand All @@ -69,6 +77,44 @@ pub struct RealmRoleListArgs {
/// Realm name. Defaults to the selected context realm.
#[arg(long)]
pub realm: Option<String>,

/// List roles of this client instead of realm roles.
#[arg(long)]
pub client: Option<String>,
}

/// Arguments for `realm role get`.
#[derive(Debug, Args)]
pub struct RealmRoleGetArgs {
/// Role name.
pub name: String,

/// Realm name. Defaults to the selected context realm.
#[arg(long)]
pub realm: Option<String>,

/// Look up a client role instead of a realm role, scoped to this client id.
#[arg(long)]
pub client: Option<String>,
}

/// Arguments for `realm role delete`.
#[derive(Debug, Args)]
pub struct RealmRoleDeleteArgs {
/// Role name.
pub name: String,

/// Realm name. Defaults to the selected context realm.
#[arg(long)]
pub realm: Option<String>,

/// Delete a client role instead of a realm role, scoped to this client id.
#[arg(long)]
pub client: Option<String>,

/// Skip the confirmation prompt (required in non-interactive shells).
#[arg(long, short = 'f', default_value_t = false)]
pub force: bool,
}

/// Arguments using a realm name.
Expand Down
94 changes: 91 additions & 3 deletions libs/ferriskey-cli-core/src/realm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use ferriskey_cli_client::{
};
use ferriskey_cli_commands::{
RealmCommand, RealmDeleteArgs, RealmImportArgs, RealmNameArgs, RealmRoleCommand,
RealmRoleCreateArgs, RealmRoleListArgs, RealmRoleSubcommand, RealmSubcommand,
RealmRoleCreateArgs, RealmRoleDeleteArgs, RealmRoleGetArgs, RealmRoleListArgs,
RealmRoleSubcommand, RealmSubcommand,
};
use serde::Serialize;
use thiserror::Error;
Expand Down Expand Up @@ -59,6 +60,12 @@ fn run_role(
RealmRoleSubcommand::List(args) => {
list_roles(output_format, context_override, inline_context, args)
}
RealmRoleSubcommand::Get(args) => {
get_role(output_format, context_override, inline_context, args)
}
RealmRoleSubcommand::Delete(args) => {
delete_role(output_format, context_override, inline_context, args)
}
}
}

Expand All @@ -82,6 +89,10 @@ pub enum RealmCommandError {
MissingAuthRealm,
#[error("realm '{0}' not found")]
RealmNotFound(String),
#[error("client '{0}' not found in realm")]
ClientNotFound(String),
#[error("role '{0}' not found")]
RoleNotFound(String),
#[error(transparent)]
Import(Box<import::ImportError>),
#[error("unsupported output format: {0}")]
Expand Down Expand Up @@ -223,6 +234,31 @@ fn delete_realm(
render_message(output_format, &format!("realm '{}' deleted", args.name))
}

/// Resolve a client id (e.g. `my-app`) to the client's uuid, as required by
/// the client-role endpoints.
fn resolve_client_uuid(client: &FerriskeyClient, realm: &str, client_id: &str) -> Result<String> {
client
.get_client(realm, client_id)?
.and_then(|found| found.id)
.ok_or_else(|| RealmCommandError::ClientNotFound(client_id.to_owned()))
}

/// List roles in the scope selected by `--client`: a specific client's roles
/// when given, realm roles otherwise.
fn list_roles_in_scope(
client: &FerriskeyClient,
realm: &str,
client_id: Option<&str>,
) -> Result<Vec<CreatedRole>> {
match client_id {
Some(client_id) => {
let uuid = resolve_client_uuid(client, realm, client_id)?;
Ok(client.list_client_roles(realm, &uuid)?)
}
None => Ok(client.list_realm_roles(realm)?),
}
}

fn create_role(
output_format: &str,
context_override: Option<&str>,
Expand All @@ -237,7 +273,13 @@ fn create_role(
description: args.description,
permissions: args.permissions,
};
let role = client.create_role(&realm, &request)?;
let role = match args.client {
Some(client_id) => {
let uuid = resolve_client_uuid(&client, &realm, &client_id)?;
client.create_client_role(&realm, &uuid, &request)?
}
None => client.create_role(&realm, &request)?,
};
render_role(output_format, to_role_view(role))
}

Expand All @@ -250,11 +292,57 @@ fn list_roles(
let context = resolve_context(context_override, inline_context)?;
let realm = resolve_realm(&context, args.realm)?;
let client = auth_client(&context)?;
let roles = client.list_realm_roles(&realm)?;
let roles = list_roles_in_scope(&client, &realm, args.client.as_deref())?;
let views: Vec<RoleView> = roles.into_iter().map(to_role_view).collect();
render_role_list(output_format, &views)
}

fn get_role(
output_format: &str,
context_override: Option<&str>,
inline_context: Option<StoredContext>,
args: RealmRoleGetArgs,
) -> Result<()> {
let context = resolve_context(context_override, inline_context)?;
let realm = resolve_realm(&context, args.realm)?;
let client = auth_client(&context)?;
let role = list_roles_in_scope(&client, &realm, args.client.as_deref())?
.into_iter()
.find(|r| r.name == args.name)
.ok_or_else(|| RealmCommandError::RoleNotFound(args.name.clone()))?;
render_role(output_format, to_role_view(role))
}

fn delete_role(
output_format: &str,
context_override: Option<&str>,
inline_context: Option<StoredContext>,
args: RealmRoleDeleteArgs,
) -> Result<()> {
confirm(&format!("Delete role '{}'?", args.name), args.force)?;
let context = resolve_context(context_override, inline_context)?;
let realm = resolve_realm(&context, args.realm)?;
let client = auth_client(&context)?;
let client_uuid = args
.client
.as_deref()
.map(|client_id| resolve_client_uuid(&client, &realm, client_id))
.transpose()?;
let roles = match &client_uuid {
Some(uuid) => client.list_client_roles(&realm, uuid)?,
None => client.list_realm_roles(&realm)?,
};
let role = roles
.into_iter()
.find(|r| r.name == args.name)
.ok_or_else(|| RealmCommandError::RoleNotFound(args.name.clone()))?;
match &client_uuid {
Some(uuid) => client.delete_client_role(&realm, uuid, &role.id)?,
None => client.delete_role(&realm, &role.id)?,
}
render_message(output_format, &format!("role '{}' deleted", args.name))
}

fn import_realm(
output_format: &str,
context_override: Option<&str>,
Expand Down
Loading