use super::*;
pub trait PeerRpcTokenIssuer: Send + Sync {
fn issue_peer_token(
&self,
request_id: &str,
request_hash: Option<&str>,
now_ms: u64,
ttl_ms: u64,
) -> Result<String, PeerRpcError>;
}
#[derive(Debug, Clone)]
pub struct HashTokenPeerTokenIssuer<P = HashTokenProvider> {
provider: P,
claims: TokenClaims,
}
#[cfg(any(test, feature = "insecure-testing"))]
#[derive(Clone)]
pub struct StaticPeerRpcTokenIssuer {
token: Vec<u8>,
}
#[cfg(any(test, feature = "insecure-testing"))]
impl std::fmt::Debug for StaticPeerRpcTokenIssuer {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("StaticPeerRpcTokenIssuer")
.field("token", &"REDACTED")
.finish()
}
}
#[cfg(any(test, feature = "insecure-testing"))]
impl Drop for StaticPeerRpcTokenIssuer {
fn drop(&mut self) {
use zeroize::Zeroize;
self.token.zeroize();
}
}
pub trait PeerRpcDispatcher: Send + Sync {
fn dispatch_peer_query(
&self,
envelope: PeerRpcEnvelope,
) -> Result<PeerRpcResponse, PeerRpcError>;
fn dispatch_peer_command(
&self,
envelope: PeerRpcEnvelope,
) -> Result<PeerRpcResponse, PeerRpcError>;
}
pub trait PeerRpcAuthenticator: Send + Sync {
fn authenticate(
&self,
token: Option<&str>,
expected_request_hash: Option<&str>,
now_ms: u64,
) -> Result<(), PeerRpcError>;
}
#[cfg(any(test, feature = "insecure-testing"))]
#[derive(Debug, Clone, Copy, Default)]
pub struct AllowPeerAuthenticator;
#[cfg(any(test, feature = "insecure-testing"))]
impl PeerRpcAuthenticator for AllowPeerAuthenticator {
fn authenticate(
&self,
_token: Option<&str>,
_expected_request_hash: Option<&str>,
_now_ms: u64,
) -> Result<(), PeerRpcError> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct HashTokenPeerAuthenticator<P = HashTokenProvider> {
provider: P,
claims: TokenClaims,
}
impl<P> HashTokenPeerAuthenticator<P>
where
P: TokenProvider,
{
pub fn new(provider: P, mut claims: TokenClaims) -> Self {
claims.salt = "peer".to_string();
Self { provider, claims }
}
}
impl<P> PeerRpcAuthenticator for HashTokenPeerAuthenticator<P>
where
P: TokenProvider + Send + Sync,
{
fn authenticate(
&self,
token: Option<&str>,
expected_request_hash: Option<&str>,
now_ms: u64,
) -> Result<(), PeerRpcError> {
let token = token.ok_or(PeerRpcError::Unauthorized)?;
let token = token
.strip_prefix("Bearer ")
.or_else(|| token.strip_prefix("bearer "))
.unwrap_or(token);
let claims = CommandTokenValidator::new(&self.provider, self.claims.clone())
.validate_and_get_claims(token, "peer", None, now_ms, expected_request_hash)
.map_err(peer_auth_error)?;
if expected_request_hash.is_some() && claims.request_hash.is_none() {
return Err(PeerRpcError::Forbidden);
}
Ok(())
}
}
impl<P> HashTokenPeerTokenIssuer<P>
where
P: TokenProvider,
{
pub fn new(provider: P, mut claims: TokenClaims) -> Self {
claims.salt = "peer".to_string();
Self { provider, claims }
}
}
impl<P> PeerRpcTokenIssuer for HashTokenPeerTokenIssuer<P>
where
P: TokenProvider + Send + Sync,
{
fn issue_peer_token(
&self,
request_id: &str,
request_hash: Option<&str>,
now_ms: u64,
ttl_ms: u64,
) -> Result<String, PeerRpcError> {
CommandTokenFactory::new(&self.provider, self.claims.clone())
.create_v1_with_jti_and_hash(
"peer",
None,
Some("*"),
Some(LOCAL_ADMIN_SUBJECT),
now_ms,
ttl_ms,
Some(request_id.to_string()),
request_hash.map(ToOwned::to_owned),
)
.map_err(peer_auth_error)
}
}
#[cfg(any(test, feature = "insecure-testing"))]
impl StaticPeerRpcTokenIssuer {
pub fn new(token: impl Into<String>) -> Self {
Self {
token: token.into().into_bytes(),
}
}
}
#[cfg(any(test, feature = "insecure-testing"))]
impl PeerRpcTokenIssuer for StaticPeerRpcTokenIssuer {
fn issue_peer_token(
&self,
_request_id: &str,
_request_hash: Option<&str>,
_now_ms: u64,
_ttl_ms: u64,
) -> Result<String, PeerRpcError> {
String::from_utf8(self.token.clone()).map_err(|_| PeerRpcError::Unauthorized)
}
}
fn peer_auth_error(error: CommandTokenError) -> PeerRpcError {
match error {
CommandTokenError::Forbidden => PeerRpcError::Forbidden,
CommandTokenError::InvalidFormat | CommandTokenError::Unauthorized => {
PeerRpcError::Unauthorized
}
}
}