use miette::IntoDiagnostic;
use minicbor::Decoder;
use ockam::identity::OneTimeCode;
use ockam::identity::{secure_channel_required, AttributesEntry};
use ockam::identity::{Identifier, IdentitySecureChannelLocalInfo};
use ockam_core::api::{Method, Request, RequestHeader, Response};
use ockam_core::errcode::{Kind, Origin};
use ockam_core::{async_trait, Result, Routed, Worker};
use ockam_node::Context;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tracing::trace;
use crate::authenticator::direct::types::{AddMember, CreateToken};
use crate::authenticator::enrollment_tokens::authenticator::MAX_TOKEN_DURATION;
use crate::authenticator::enrollment_tokens::types::Token;
use crate::authenticator::enrollment_tokens::EnrollmentTokenAuthenticator;
use crate::cloud::AuthorityNode;
use crate::DefaultAddress;
pub struct EnrollmentTokenIssuer(pub(super) EnrollmentTokenAuthenticator);
impl EnrollmentTokenIssuer {
async fn issue_token(
&self,
enroller: &Identifier,
attrs: HashMap<String, String>,
token_duration: Option<Duration>,
ttl_count: Option<u64>,
) -> Result<OneTimeCode> {
let otc = OneTimeCode::new();
let max_token_duration = token_duration.unwrap_or(MAX_TOKEN_DURATION);
let ttl_count = ttl_count.unwrap_or(1);
let tkn = Token {
attrs,
issued_by: enroller.clone(),
created_at: Instant::now(),
ttl: max_token_duration,
ttl_count,
};
self.0
.tokens
.write()
.map(|mut r| {
r.insert(*otc.code(), tkn);
otc
})
.map_err(|_| {
ockam_core::Error::new(
Origin::Other,
Kind::Internal,
"failed to get read lock on tokens table",
)
})
}
}
#[ockam_core::worker]
impl Worker for EnrollmentTokenIssuer {
type Context = Context;
type Message = Vec<u8>;
async fn handle_message(&mut self, c: &mut Context, m: Routed<Self::Message>) -> Result<()> {
if let Ok(i) = IdentitySecureChannelLocalInfo::find_info(m.local_message()) {
let from = i.their_identity_id();
let mut dec = Decoder::new(m.as_body());
let req: RequestHeader = dec.decode()?;
trace! {
target: "ockam_api::authenticator::direct::enrollment_token_issuer",
from = %from,
id = %req.id(),
method = ?req.method(),
path = %req.path(),
body = %req.has_body(),
"request"
}
let res = match (req.method(), req.path()) {
(Some(Method::Post), "/") | (Some(Method::Post), "/tokens") => {
let att: CreateToken = dec.decode()?;
let duration = att.ttl_secs().map(Duration::from_secs);
let ttl_count = att.ttl_count();
match self
.issue_token(&from, att.into_owned_attributes(), duration, ttl_count)
.await
{
Ok(otc) => Response::ok(&req).body(&otc).to_vec()?,
Err(error) => {
Response::internal_error(&req, &error.to_string()).to_vec()?
}
}
}
_ => Response::unknown_path(&req).to_vec()?,
};
c.send(m.return_route(), res).await
} else {
secure_channel_required(c, m).await
}
}
}
#[async_trait]
pub trait Members {
async fn add_member(
&self,
ctx: &Context,
identifier: Identifier,
attributes: HashMap<&str, &str>,
) -> miette::Result<()>;
async fn delete_member(&self, ctx: &Context, identifier: Identifier) -> miette::Result<()>;
async fn list_member_ids(&self, ctx: &Context) -> miette::Result<Vec<Identifier>>;
async fn list_members(
&self,
ctx: &Context,
) -> miette::Result<HashMap<Identifier, AttributesEntry>>;
}
#[async_trait]
impl Members for AuthorityNode {
async fn add_member(
&self,
ctx: &Context,
identifier: Identifier,
attributes: HashMap<&str, &str>,
) -> miette::Result<()> {
let req = Request::post("/").body(AddMember::new(identifier).with_attributes(attributes));
self.secure_client
.tell(ctx, DefaultAddress::DIRECT_AUTHENTICATOR, req)
.await
.into_diagnostic()?
.success()
.into_diagnostic()
}
async fn delete_member(&self, ctx: &Context, identifier: Identifier) -> miette::Result<()> {
let req = Request::delete(format!("/{identifier}"));
self.secure_client
.tell(ctx, DefaultAddress::DIRECT_AUTHENTICATOR, req)
.await
.into_diagnostic()?
.success()
.into_diagnostic()
}
async fn list_member_ids(&self, ctx: &Context) -> miette::Result<Vec<Identifier>> {
let req = Request::get("/member_ids");
self.secure_client
.ask(ctx, DefaultAddress::DIRECT_AUTHENTICATOR, req)
.await
.into_diagnostic()?
.success()
.into_diagnostic()
}
async fn list_members(
&self,
ctx: &Context,
) -> miette::Result<HashMap<Identifier, AttributesEntry>> {
let req = Request::get("/");
self.secure_client
.ask(ctx, DefaultAddress::DIRECT_AUTHENTICATOR, req)
.await
.into_diagnostic()?
.success()
.into_diagnostic()
}
}
#[async_trait]
pub trait TokenIssuer {
async fn create_token(
&self,
ctx: &Context,
attributes: HashMap<&str, &str>,
duration: Option<Duration>,
ttl_count: Option<u64>,
) -> miette::Result<OneTimeCode>;
}
#[async_trait]
impl TokenIssuer for AuthorityNode {
async fn create_token(
&self,
ctx: &Context,
attributes: HashMap<&str, &str>,
duration: Option<Duration>,
ttl_count: Option<u64>,
) -> miette::Result<OneTimeCode> {
let body = CreateToken::new()
.with_attributes(attributes)
.with_ttl(duration)
.with_ttl_count(ttl_count);
let req = Request::post("/").body(body);
self.secure_client
.ask(ctx, DefaultAddress::ENROLLMENT_TOKEN_ISSUER, req)
.await
.into_diagnostic()?
.success()
.into_diagnostic()
}
}
#[async_trait]
pub trait TokenAcceptor {
async fn present_token(&self, ctx: &Context, token: OneTimeCode) -> miette::Result<()>;
}
#[async_trait]
impl TokenAcceptor for AuthorityNode {
async fn present_token(&self, ctx: &Context, token: OneTimeCode) -> miette::Result<()> {
let req = Request::post("/").body(token);
self.secure_client
.ask(ctx, DefaultAddress::DIRECT_AUTHENTICATOR, req)
.await
.into_diagnostic()?
.success()
.into_diagnostic()
}
}