use super::Invocation;
use crate::clients::{RedTeamClients, TOKEN_BUDGET};
use crate::error::{CliError, InterruptContext};
use crate::json::OValue;
use crate::output::terminal_safe;
use crate::writes::{self, Change};
use scm::airs::red_team::RedTeamClient;
use std::ffi::OsStr;
#[derive(Clone, Debug)]
pub struct Contexts {
pub not_sent: InterruptContext,
pub sent: InterruptContext,
}
impl Contexts {
pub fn plain() -> Self {
Self {
not_sent: InterruptContext::NothingChanged,
sent: InterruptContext::ChangeMayBeApplied,
}
}
pub fn sent(text: impl Into<String>) -> Self {
Self {
not_sent: InterruptContext::NothingChanged,
sent: InterruptContext::Context(text.into()),
}
}
pub fn before(&self, error: CliError) -> CliError {
match error {
CliError::Interrupted(
InterruptContext::NoDataWritten | InterruptContext::NothingChanged,
) => CliError::Interrupted(self.not_sent.clone()),
other => other,
}
}
}
pub struct Unauthenticated {
pub clients: RedTeamClients,
pub default_output: Option<String>,
pub tenant: String,
pub tsg: String,
}
impl Unauthenticated {
pub fn client(&self) -> &RedTeamClient {
&self.clients.red_team
}
}
impl Invocation {
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(),
})
}
}
pub async fn authenticate(prepared: &Unauthenticated, contexts: &Contexts) -> Result<(), CliError> {
prepared
.clients
.authenticate(None, TOKEN_BUDGET)
.await
.map_err(|error| contexts.before(error))
}
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)
}
pub fn gate(force: bool, interactive: bool, action: &str) -> Result<(), CliError> {
if force || interactive {
Ok(())
} else {
Err(CliError::usage(format!(
"refusing to {} without --force in non-interactive mode",
terminal_safe(action)
)))
}
}
pub fn confirm(invocation: &mut Invocation, message: &str) -> Result<bool, CliError> {
let message = terminal_safe(message).into_owned();
if super::confirm(invocation, &message)? {
Ok(true)
} else {
invocation.output.info("Aborted");
Ok(false)
}
}
pub async fn send<T>(
invocation: &Invocation,
change: Change,
hint: Option<&str>,
contexts: &Contexts,
call: impl AsyncFnMut() -> scm::Result<T>,
) -> Result<T, CliError> {
writes::send_hinted(&invocation.secrets, change, hint, call)
.await
.map_err(|error| match error {
CliError::Interrupted(InterruptContext::NothingChanged) => {
CliError::Interrupted(contexts.not_sent.clone())
}
CliError::Interrupted(InterruptContext::ChangeMayBeApplied) => {
CliError::Interrupted(contexts.sent.clone())
}
other => other,
})
}
pub fn config_object(path: &OsStr) -> Result<serde_json::Value, CliError> {
crate::files::read_config(path).map(serde_json::Value::Object)
}
pub fn body<M>(
value: serde_json::Value,
method: &str,
path: &str,
parse: impl FnOnce(serde_json::Value, scm::validation::Mode) -> scm::Result<M>,
) -> Result<M, CliError> {
parse(value, scm::validation::Mode::Request).map_err(|_| writes::invalid_body(method, path))
}
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);
}
}
pub fn field(doc: &OValue, key: &str) -> String {
doc.get(key)
.and_then(OValue::as_str)
.unwrap_or_default()
.to_owned()
}