1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//! The red team wrapper of the shared write machinery (DESIGN §15.3): the
//! unauthenticated client (a confirmation is asked before the token is fetched), the
//! token, the credential registration of user-supplied documents (§15.4) and the answer
//! fields. The D81 gate and confirmation, the one-attempt write with its outcome and hint
//! lines (D133), the interruption contexts, the bounded input files (D143) and the
//! request-body validation (D116) are `write_common`'s, re-exported here for the red team
//! command modules.
use super::Invocation;
pub use super::write_common::{Contexts, body, config_object, confirm, gate, send};
use crate::clients::{RedTeamClients, TOKEN_BUDGET};
use crate::error::CliError;
use crate::json::OValue;
use scm::airs::red_team::RedTeamClient;
/// The red team client of the selected tenant, not yet authenticated, and the tenant's
/// `defaultOutput`.
pub struct Unauthenticated {
pub clients: RedTeamClients,
pub default_output: Option<String>,
/// The selected tenant's store name and `mgmtTsgId` (backups, §15.18).
pub tenant: String,
pub tsg: String,
}
impl Unauthenticated {
pub fn client(&self) -> &RedTeamClient {
&self.clients.red_team
}
}
impl Invocation {
/// Load the tenant, check the management credentials (§3.5) and build the red team
/// client, without any request (a confirmation may follow, §15.3).
pub fn red_team_unauthenticated(&mut self) -> Result<Unauthenticated, CliError> {
let context = self.load_tenant()?;
let default_output = context.config.default_output().map(str::to_owned);
crate::tenant::config::require_management(&context.config, Some(context.name()))?;
let clients = RedTeamClients::build(
&context.config,
&self.session,
&self.secrets,
self.debug.as_ref(),
)?;
Ok(Unauthenticated {
clients,
default_output,
tenant: context.name().to_owned(),
tsg: crate::tenant::config::js_trim(
context
.config
.effective_str("mgmtTsgId")
.unwrap_or_default(),
)
.to_owned(),
})
}
}
/// Fetch the OAuth token of a write (the SDK's token budget); an interruption reports
/// `contexts.not_sent`.
pub async fn authenticate(prepared: &Unauthenticated, contexts: &Contexts) -> Result<(), CliError> {
prepared
.clients
.authenticate(None, TOKEN_BUDGET)
.await
.map_err(|error| contexts.before(error))
}
/// The tenant, the client and the token of a write that asks nothing first.
pub async fn connect(
invocation: &mut Invocation,
contexts: &Contexts,
) -> Result<Unauthenticated, CliError> {
let prepared = invocation
.red_team_unauthenticated()
.map_err(|error| contexts.before(error))?;
authenticate(&prepared, contexts).await?;
Ok(prepared)
}
/// Register the credential values of a document the user supplied (§15.4: the same
/// rules as for printed documents), without changing it.
pub fn register_secrets(
invocation: &Invocation,
value: &serde_json::Value,
shape: crate::redteam::secrets::Shape,
) {
if let Ok(mut copy) = crate::json::to_ordered(value) {
crate::redteam::secrets::redact(&mut copy, shape, &invocation.secrets);
}
}
/// A string field of an answer (`""` when absent).
pub fn field(doc: &OValue, key: &str) -> String {
doc.get(key)
.and_then(OValue::as_str)
.unwrap_or_default()
.to_owned()
}