use std::path::PathBuf;
use clap::Args;
use miette::miette;
use minicbor::Decoder;
use regex::Regex;
use ockam::identity::Identifier;
use ockam_api::cli_state::CliState;
use ockam_api::nodes::models::flow_controls::AddConsumer;
use ockam_api::nodes::models::services::{
StartAuthenticatedServiceRequest, StartAuthenticatorRequest, StartCredentialsService,
StartHopServiceRequest, StartOktaIdentityProviderRequest,
};
use ockam_api::nodes::*;
use ockam_api::trust_context::TrustContextConfigBuilder;
use ockam_api::DefaultAddress;
use ockam_core::api::Request;
use ockam_core::api::ResponseHeader;
use ockam_core::flow_control::FlowControlId;
use ockam_core::Address;
use ockam_multiaddr::MultiAddr;
use crate::service::config::OktaIdentityProviderConfig;
use crate::Result;
pub(crate) fn query_status() -> Request<()> {
Request::get("/node")
}
pub(crate) fn list_tcp_listeners() -> Request<()> {
Request::get("/node/tcp/listener")
}
pub(crate) fn create_tcp_connection(
cmd: &crate::tcp::connection::CreateCommand,
) -> Request<models::transport::CreateTcpConnection> {
let payload = models::transport::CreateTcpConnection::new(cmd.address.clone());
Request::post("/node/tcp/connection").body(payload)
}
pub(crate) fn list_services() -> Request<()> {
Request::get("/node/services")
}
pub(crate) fn list_inlets() -> Request<()> {
Request::get("/node/inlet")
}
pub(crate) fn list_outlets() -> Request<()> {
Request::get("/node/outlet")
}
pub(crate) fn list_secure_channels() -> Request<()> {
Request::get("/node/secure_channel")
}
pub(crate) fn list_workers() -> Request<()> {
Request::get("/node/workers")
}
pub(crate) fn delete_secure_channel(
addr: &Address,
) -> Request<models::secure_channel::DeleteSecureChannelRequest> {
let payload = models::secure_channel::DeleteSecureChannelRequest::new(addr);
Request::delete("/node/secure_channel").body(payload)
}
pub(crate) fn show_secure_channel(
addr: &Address,
) -> Request<models::secure_channel::ShowSecureChannelRequest> {
let payload = models::secure_channel::ShowSecureChannelRequest::new(addr);
Request::get("/node/show_secure_channel").body(payload)
}
pub(crate) fn create_secure_channel_listener(
addr: &Address,
authorized_identifiers: Option<Vec<Identifier>>,
identity_name: Option<String>,
) -> Result<Vec<u8>> {
let payload = models::secure_channel::CreateSecureChannelListenerRequest::new(
addr,
authorized_identifiers,
None,
identity_name,
);
let mut buf = vec![];
Request::post("/node/secure_channel_listener")
.body(payload)
.encode(&mut buf)?;
Ok(buf)
}
pub(crate) fn list_secure_channel_listener() -> Request<()> {
Request::get("/node/secure_channel_listener")
}
pub(crate) fn delete_secure_channel_listener(
addr: &Address,
) -> Request<models::secure_channel::DeleteSecureChannelListenerRequest> {
let payload = models::secure_channel::DeleteSecureChannelListenerRequest::new(addr);
Request::delete("/node/secure_channel_listener").body(payload)
}
pub(crate) fn show_secure_channel_listener(
addr: &Address,
) -> Request<models::secure_channel::ShowSecureChannelListenerRequest> {
let payload = models::secure_channel::ShowSecureChannelListenerRequest::new(addr);
Request::get("/node/show_secure_channel_listener").body(payload)
}
pub(crate) fn start_hop_service(addr: &str) -> Request<StartHopServiceRequest> {
let payload = StartHopServiceRequest::new(addr);
Request::post(node_service(DefaultAddress::HOP_SERVICE)).body(payload)
}
pub(crate) fn start_authenticated_service(addr: &str) -> Request<StartAuthenticatedServiceRequest> {
let payload = StartAuthenticatedServiceRequest::new(addr);
Request::post(node_service(DefaultAddress::AUTHENTICATED_SERVICE)).body(payload)
}
pub(crate) fn start_credentials_service(
public_identity: &str,
addr: &str,
oneway: bool,
) -> Request<StartCredentialsService> {
let payload = StartCredentialsService::new(public_identity, addr, oneway);
Request::post(node_service(DefaultAddress::CREDENTIALS_SERVICE)).body(payload)
}
pub(crate) fn start_authenticator_service(
addr: &str,
project: &str,
) -> Request<StartAuthenticatorRequest> {
let payload = StartAuthenticatorRequest::new(addr, project.as_bytes());
Request::post(node_service(DefaultAddress::DIRECT_AUTHENTICATOR)).body(payload)
}
pub(crate) fn add_consumer(id: FlowControlId, address: MultiAddr) -> Request<AddConsumer> {
let payload = AddConsumer::new(id, address);
Request::post("/node/flow_controls/add_consumer").body(payload)
}
pub(crate) fn start_okta_service(
cfg: &OktaIdentityProviderConfig,
) -> Request<StartOktaIdentityProviderRequest> {
let payload = StartOktaIdentityProviderRequest::new(
&cfg.address,
&cfg.tenant_base_url,
&cfg.certificate,
cfg.attributes.clone(),
cfg.project.as_bytes(),
);
Request::post(format!(
"/node/services/{}",
DefaultAddress::OKTA_IDENTITY_PROVIDER
))
.body(payload)
}
fn node_service(service_name: &str) -> String {
format!("/node/services/{service_name}")
}
pub(crate) fn parse_create_secure_channel_listener_response(resp: &[u8]) -> Result<ResponseHeader> {
let mut dec = Decoder::new(resp);
let response = dec.decode::<ResponseHeader>()?;
Ok(response)
}
#[derive(Clone, Debug, Args)]
pub struct CloudOpts {
#[arg(global = true, value_name = "IDENTITY_NAME", long)]
pub identity: Option<String>,
}
#[derive(Clone, Debug, Args, Default)]
pub struct TrustContextOpts {
#[arg(global = true, long = "project-path", value_name = "PROJECT_JSON_PATH")]
pub project_path: Option<PathBuf>,
#[arg(
global = true,
long,
value_name = "TRUST_CONTEXT_NAME | TRUST_CONTEXT_JSON_PATH"
)]
pub trust_context: Option<String>,
#[arg(global = true, long = "project", value_name = "PROJECT_NAME")]
pub project: Option<String>,
}
impl TrustContextOpts {
pub fn to_config(&self, cli_state: &CliState) -> Result<TrustContextConfigBuilder> {
let trust_context = match &self.trust_context {
Some(tc) => Some(cli_state.trust_contexts.read_config_from_path(tc)?),
None => None,
};
Ok(TrustContextConfigBuilder {
cli_state: cli_state.clone(),
project_path: self.project_path.clone(),
trust_context,
project: self.project.clone(),
authority_identity: None,
credential_name: None,
use_default_trust_context: true,
})
}
}
pub(crate) fn validate_cloud_resource_name(s: &str) -> miette::Result<()> {
let project_name_regex = Regex::new(r"^[a-zA-Z0-9]+([a-zA-Z0-9-_\.]?[a-zA-Z0-9])*$").unwrap();
let is_project_name_valid = project_name_regex.is_match(s);
if !is_project_name_valid {
Err(miette!("Invalid name"))
} else {
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::util::api::validate_cloud_resource_name;
#[test]
fn test_validate_cloud_resource_name() {
let valid_names: Vec<&str> = vec![
"name",
"0001",
"321_11-11-22",
"0_0",
"6.9",
"0-9",
"name_with_underscores",
"name-with-dashes",
"name.with.dots",
"name1with2numbers3",
"11name22with33numbers00",
"76123long.name_with-underscores.and-dashes_and3dots00and.numbers",
];
for name in valid_names {
assert!(validate_cloud_resource_name(name).is_ok());
}
let invalid_names: Vec<&str> = vec![
"name with spaces in between",
" name-with-leading-space",
"name.with.trailing.space ",
" name-with-leading-and-trailing-space ",
" name_with_multiple_leading_space",
"name__with_consecutive_underscore",
"_name_with_leading_underscore",
"name-with-trailing-underscore_",
"name_with_consecutive---dashes",
"name_with_trailing_dashes--",
"---name_with_leading_dashes",
"name-with-consecutive...dots",
"name.with.trailing.dots....",
".name_with-leading.dot",
"name_.with._consecutive-_-dots.-.dashes-._underscores",
"1 2 3 4",
" 1234",
"_",
"__",
". _ .",
];
for name in invalid_names {
assert!(validate_cloud_resource_name(name).is_err());
}
}
}