use minicbor::Decoder;
use tracing::trace;
use ockam_core::api::{Method, Request, Response};
use ockam_core::compat::boxed::Box;
use ockam_core::compat::string::String;
use ockam_core::compat::sync::Arc;
use ockam_core::compat::vec::Vec;
use ockam_core::{api, Result, Route, Routed, Worker};
use ockam_node::{Context, RpcClient};
use crate::alloc::string::ToString;
use crate::credential::Credential;
use crate::identity::IdentityIdentifier;
use crate::{CredentialData, Identities, IdentitySecureChannelLocalInfo, PROJECT_MEMBER_SCHEMA};
pub const LEGACY_ID: &str = "project_id";
pub const TRUST_CONTEXT_ID: &str = "trust_context_id";
pub struct CredentialsIssuer {
identities: Arc<Identities>,
issuer: IdentityIdentifier,
trust_context: String,
}
impl CredentialsIssuer {
pub async fn new(
identities: Arc<Identities>,
issuer: IdentityIdentifier,
trust_context: String,
) -> Result<Self> {
Ok(Self {
identities,
issuer,
trust_context,
})
}
async fn issue_credential(&self, from: &IdentityIdentifier) -> Result<Option<Credential>> {
match self
.identities
.repository()
.as_attributes_reader()
.get_attributes(from)
.await?
{
Some(entry) => {
let crd = entry
.attrs()
.iter()
.fold(
CredentialData::builder(from.clone(), self.issuer.clone())
.with_schema(PROJECT_MEMBER_SCHEMA),
|crd, (a, v)| crd.with_attribute(a, v),
)
.with_attribute(LEGACY_ID, self.trust_context.as_bytes()) .with_attribute(TRUST_CONTEXT_ID, self.trust_context.as_bytes());
Ok(Some(
self.identities
.credentials()
.issue_credential(&self.issuer, crd.build()?)
.await?,
))
}
None => Ok(None),
}
}
}
#[ockam_core::worker]
impl Worker for CredentialsIssuer {
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: Request = dec.decode()?;
trace! {
target: "ockam_identity::credentials::credential_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), "/credential") => {
match self.issue_credential(&from).await {
Ok(Some(crd)) => Response::ok(req.id()).body(crd).to_vec()?,
Ok(None) => {
api::forbidden(&req, "unauthorized member").to_vec()?
}
Err(error) => api::internal_error(&req, &error.to_string()).to_vec()?,
}
}
_ => api::unknown_path(&req).to_vec()?,
};
c.send(m.return_route(), res).await
} else {
secure_channel_required(c, m).await
}
}
}
pub async fn secure_channel_required(c: &mut Context, m: Routed<Vec<u8>>) -> Result<()> {
let mut dec = Decoder::new(m.as_body());
let req: Request = dec.decode()?;
let res = api::forbidden(&req, "secure channel required").to_vec()?;
c.send(m.return_route(), res).await
}
pub struct CredentialsIssuerClient {
client: RpcClient,
}
impl CredentialsIssuerClient {
pub async fn new(route: Route, ctx: &Context) -> Result<Self> {
Ok(CredentialsIssuerClient {
client: RpcClient::new(route, ctx).await?,
})
}
pub async fn credential(&self) -> Result<Credential> {
self.client.request(&Request::post("/")).await
}
}