Skip to content

Commit 8abae9e

Browse files
refactor(rust): project-member commands, and adds the show command
1 parent e467cb7 commit 8abae9e

File tree

25 files changed

+657
-219
lines changed

25 files changed

+657
-219
lines changed

implementations/rust/ockam/ockam_api/src/authenticator/direct/client.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ pub trait Members {
2020
attributes: BTreeMap<String, String>,
2121
) -> miette::Result<()>;
2222

23-
async fn delete_member(&self, ctx: &Context, identifier: Identifier) -> miette::Result<()>;
23+
async fn show_member(
24+
&self,
25+
ctx: &Context,
26+
identifier: Identifier,
27+
) -> miette::Result<AttributesEntry>;
2428

25-
async fn delete_all_members(&self, ctx: &Context, except: Identifier) -> miette::Result<()>;
29+
async fn delete_member(&self, ctx: &Context, identifier: Identifier) -> miette::Result<()>;
2630

2731
async fn list_member_ids(&self, ctx: &Context) -> miette::Result<Vec<Identifier>>;
2832

@@ -49,6 +53,20 @@ impl Members for AuthorityNodeClient {
4953
.into_diagnostic()
5054
}
5155

56+
async fn show_member(
57+
&self,
58+
ctx: &Context,
59+
identifier: Identifier,
60+
) -> miette::Result<AttributesEntry> {
61+
let req = Request::get(format!("/{identifier}"));
62+
self.get_secure_client()
63+
.ask(ctx, DefaultAddress::DIRECT_AUTHENTICATOR, req)
64+
.await
65+
.into_diagnostic()?
66+
.success()
67+
.into_diagnostic()
68+
}
69+
5270
async fn delete_member(&self, ctx: &Context, identifier: Identifier) -> miette::Result<()> {
5371
let req = Request::delete(format!("/{identifier}"));
5472
self.get_secure_client()
@@ -59,18 +77,6 @@ impl Members for AuthorityNodeClient {
5977
.into_diagnostic()
6078
}
6179

62-
async fn delete_all_members(&self, ctx: &Context, except: Identifier) -> miette::Result<()> {
63-
let member_ids = self.list_member_ids(ctx).await?;
64-
for id in member_ids {
65-
if id != except {
66-
if let Err(e) = self.delete_member(ctx, id.clone()).await {
67-
warn!("Failed to delete member {}: {}", id, e);
68-
}
69-
}
70-
}
71-
Ok(())
72-
}
73-
7480
async fn list_member_ids(&self, ctx: &Context) -> miette::Result<Vec<Identifier>> {
7581
let req = Request::get("/member_ids");
7682
self.get_secure_client()

implementations/rust/ockam/ockam_api/src/authenticator/direct/direct_authenticator.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,47 @@ impl DirectAuthenticator {
133133
Ok(Either::Left(()))
134134
}
135135

136+
#[instrument(skip_all, fields(enroller = %enroller))]
137+
pub async fn show_member(
138+
&self,
139+
enroller: &Identifier,
140+
identifier: &Identifier,
141+
) -> Result<DirectAuthenticatorResult<AttributesEntry>> {
142+
let check = EnrollerAccessControlChecks::check_identifier(
143+
self.members.clone(),
144+
self.identities_attributes.clone(),
145+
enroller,
146+
&self.account_authority,
147+
)
148+
.await?;
149+
150+
if !check.is_enroller {
151+
warn!("Non-enroller {} is trying to retrieve a member", enroller);
152+
return Ok(Either::Right(DirectAuthenticatorError(
153+
"Non-enroller is trying to retrieve a member".to_string(),
154+
)));
155+
}
156+
157+
match self.members.get_member(identifier).await? {
158+
Some(member) => {
159+
let entry = AttributesEntry::new(
160+
member.attributes().clone(),
161+
member.added_at(),
162+
None,
163+
Some(member.added_by().clone()),
164+
);
165+
Ok(Either::Left(entry))
166+
}
167+
None => {
168+
warn!("Member {} not found", identifier);
169+
Ok(Either::Right(DirectAuthenticatorError(format!(
170+
"Member {} not found",
171+
identifier
172+
))))
173+
}
174+
}
175+
}
176+
136177
#[instrument(skip_all, fields(enroller = %enroller))]
137178
pub async fn list_members(
138179
&self,

implementations/rust/ockam/ockam_api/src/authenticator/direct/direct_authenticator_worker.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ impl DirectAuthenticatorWorker {
3636

3737
#[ockam_core::worker]
3838
impl Worker for DirectAuthenticatorWorker {
39-
type Context = Context;
4039
type Message = Vec<u8>;
40+
type Context = Context;
4141

4242
async fn handle_message(&mut self, c: &mut Context, m: Routed<Self::Message>) -> Result<()> {
4343
let secure_channel_info = match IdentitySecureChannelLocalInfo::find_info(m.local_message())
@@ -97,6 +97,15 @@ impl Worker for DirectAuthenticatorWorker {
9797
Either::Right(error) => Response::forbidden(&req, &error.0).to_vec()?,
9898
}
9999
}
100+
(Some(Method::Get), [id]) | (Some(Method::Get), ["members", id]) => {
101+
let identifier = Identifier::try_from(id.to_string())?;
102+
let res = self.authenticator.show_member(&from, &identifier).await?;
103+
104+
match res {
105+
Either::Left(body) => Response::ok().with_headers(&req).body(body).to_vec()?,
106+
Either::Right(error) => Response::forbidden(&req, &error.0).to_vec()?,
107+
}
108+
}
100109
(Some(Method::Delete), [id]) | (Some(Method::Delete), ["members", id]) => {
101110
let identifier = Identifier::try_from(id.to_string())?;
102111
let res = self.authenticator.delete_member(&from, &identifier).await?;

implementations/rust/ockam/ockam_command/src/lease/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ async fn create_project_client(
6767
let node = InMemoryNode::start_with_project_name_and_identity(
6868
ctx,
6969
&opts.state,
70-
identity_opts.identity.clone(),
70+
identity_opts.identity_name.clone(),
7171
trust_opts.project_name.clone(),
7272
)
7373
.await?;
7474

7575
let identity = opts
7676
.state
77-
.get_identity_name_or_default(&identity_opts.identity)
77+
.get_identity_name_or_default(&identity_opts.identity_name)
7878
.await?;
7979
let project = opts
8080
.state

implementations/rust/ockam/ockam_command/src/message/send.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl Command for SendCommand {
9090
} else {
9191
let identity_name = opts
9292
.state
93-
.get_identity_name_or_default(&self.identity_opts.identity)
93+
.get_identity_name_or_default(&self.identity_opts.identity_name)
9494
.await?;
9595

9696
info!("starting an in memory node to send a message");

implementations/rust/ockam/ockam_command/src/project/enroll.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl Command for EnrollCommand {
8484

8585
let identity = opts
8686
.state
87-
.get_named_identity_or_default(&self.identity_opts.identity)
87+
.get_named_identity_or_default(&self.identity_opts.identity_name)
8888
.await?;
8989
let project = self.store_project(&opts).await?;
9090

implementations/rust/ockam/ockam_command/src/project/ticket.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ impl Command for TicketCommand {
100100

101101
let identity = opts
102102
.state
103-
.get_identity_name_or_default(&self.identity_opts.identity)
103+
.get_identity_name_or_default(&self.identity_opts.identity_name)
104104
.await?;
105105

106106
let authority_node_client = node
Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
use async_trait::async_trait;
22
use clap::Args;
33
use colorful::Colorful;
4+
use serde::Serialize;
5+
use std::collections::BTreeMap;
6+
use std::fmt::Display;
47

58
use ockam::identity::Identifier;
69
use ockam::Context;
710
use ockam_api::authenticator::direct::Members;
8-
use ockam_api::fmt_ok;
9-
use ockam_api::nodes::InMemoryNode;
11+
use ockam_api::colors::color_primary;
12+
use ockam_api::{fmt_log, fmt_ok};
1013
use ockam_multiaddr::MultiAddr;
1114

12-
use crate::project_member::{create_authority_client, create_member_attributes, get_project};
15+
use crate::project_member::{authority_client, create_member_attributes};
1316
use crate::shared_args::{IdentityOpts, RetryOpts};
1417
use crate::{docs, Command, CommandGlobalOpts, Error};
1518

1619
const LONG_ABOUT: &str = include_str!("./static/add/long_about.txt");
1720
const AFTER_LONG_HELP: &str = include_str!("./static/add/after_long_help.txt");
1821

19-
/// This attribute in credential allows member to create a relay on the Project node, the name of the relay should be
20-
/// equal to the value of that attribute. If the value is `*` then any name is allowed
22+
/// This credential attribute allows a member to establish a relay on the Project node.
23+
/// The relay's name should match this attribute's value.
24+
/// If the attribute's value is "*", it implies that any name is acceptable for the relay.
2125
pub const OCKAM_RELAY_ATTRIBUTE: &str = "ockam-relay";
2226

23-
/// Add members to a Project, as an authorized enroller directly
27+
/// Add or update members of a Project, directly as an authorized enroller
2428
#[derive(Clone, Debug, Args)]
2529
#[command(
2630
long_about = docs::about(LONG_ABOUT),
@@ -30,22 +34,29 @@ pub struct AddCommand {
3034
#[command(flatten)]
3135
identity_opts: IdentityOpts,
3236

33-
/// Which project add member to
37+
/// The route to the Project to which a member should be added
3438
#[arg(long, short, value_name = "ROUTE_TO_PROJECT")]
3539
to: Option<MultiAddr>,
3640

41+
/// The Identifier of the member to add
3742
#[arg(value_name = "IDENTIFIER")]
3843
member: Identifier,
3944

40-
/// Attributes in `key=value` format to be attached to the member. You can specify this option multiple times for multiple attributes
45+
/// Attributes in `key=value` format to be attached to the member.
46+
/// You can specify this option multiple times for multiple attributes
4147
#[arg(short, long = "attribute", value_name = "ATTRIBUTE")]
4248
attributes: Vec<String>,
4349

44-
/// Name of the relay that the identity will be allowed to create. This name is transformed into attributes to prevent collisions when creating relay names. For example: `--relay foo` is shorthand for `--attribute ockam-relay=foo`
45-
#[arg(long = "relay", value_name = "ENROLLEE_ALLOWED_RELAY_NAME")]
50+
/// Name of the relay that the identity will be allowed to create.
51+
/// This name is transformed into an attribute, and it's used to prevent collisions with other relays names.
52+
/// E.g. `--relay foo` is a shorthand for `--attribute ockam-relay=foo`
53+
#[arg(long = "relay", value_name = "ALLOWED_RELAY_NAME")]
4654
allowed_relay_name: Option<String>,
4755

48-
/// Add the enroller role. If you specify it, this flag is transformed into the attributes `--attribute ockam-role=enroller`. This role allows the Identity using the ticket to enroll other Identities into the Project, typically something that only admins can do
56+
/// Set the enroller role for the member.
57+
/// When this flag is set, it is transformed into the attribute `ockam-role=enroller`.
58+
/// This role grants the Identity holding the ticket the ability to enroll other Identities
59+
/// into the Project, which is a privilege usually reserved for administrators.
4960
#[arg(long = "enroller")]
5061
enroller: bool,
5162

@@ -62,36 +73,69 @@ impl Command for AddCommand {
6273
}
6374

6475
async fn async_run(self, ctx: &Context, opts: CommandGlobalOpts) -> crate::Result<()> {
65-
let project = get_project(&opts.state, &self.to).await?;
76+
let (authority_node_client, project_name) =
77+
authority_client(ctx, &opts, &self.identity_opts, &self.to).await?;
6678

67-
let node = InMemoryNode::start_with_project_name(
68-
ctx,
69-
&opts.state,
70-
Some(project.name().to_string()),
71-
)
72-
.await?;
73-
74-
let authority_node_client =
75-
create_authority_client(&node, &opts.state, &self.identity_opts, &project).await?;
79+
let attributes =
80+
create_member_attributes(&self.attributes, &self.allowed_relay_name, self.enroller)?;
7681

7782
authority_node_client
78-
.add_member(
79-
ctx,
80-
self.member.clone(),
81-
create_member_attributes(
82-
&self.attributes,
83-
&self.allowed_relay_name,
84-
self.enroller,
85-
)?,
86-
)
83+
.add_member(ctx, self.member.clone(), attributes.clone())
8784
.await
8885
.map_err(Error::Retry)?;
8986

90-
opts.terminal.stdout().plain(fmt_ok!(
91-
"Identifier {} is now a Project member. It can get a credential and access Project resources, like portals of other members",
92-
self.member
93-
));
87+
let output = AddMemberOutput {
88+
project: project_name,
89+
identifier: self.member.clone(),
90+
attributes,
91+
};
92+
93+
opts.terminal
94+
.stdout()
95+
.plain(output.to_string())
96+
.json_obj(&output)?
97+
.write_line()?;
98+
99+
Ok(())
100+
}
101+
}
102+
103+
#[derive(Serialize)]
104+
struct AddMemberOutput {
105+
project: String,
106+
identifier: Identifier,
107+
attributes: BTreeMap<String, String>,
108+
}
94109

110+
impl Display for AddMemberOutput {
111+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112+
writeln!(
113+
f,
114+
"{}",
115+
fmt_ok!(
116+
"Identifier {} is now a member of the Project {}",
117+
color_primary(self.identifier.to_string()),
118+
color_primary(&self.project)
119+
)
120+
)?;
121+
if !self.attributes.is_empty() {
122+
let attributes = self
123+
.attributes
124+
.iter()
125+
.map(|(k, v)| format!("{}={}", k, v))
126+
.collect::<Vec<_>>()
127+
.join(", ");
128+
writeln!(
129+
f,
130+
"{}",
131+
fmt_log!("With attributes: {}", color_primary(attributes))
132+
)?;
133+
}
134+
writeln!(
135+
f,
136+
"{}",
137+
fmt_log!("It can get a credential and access Project resources, like portals of other members")
138+
)?;
95139
Ok(())
96140
}
97141
}

0 commit comments

Comments
 (0)