aauth 0.0.2

Rust implementation of the AAuth authorization protocol
Documentation
use crate::interaction_code::generate_code;
use crate::server::deferred::{DeferRequirement, PendingInput};
use crate::types::AAuthProtocolError;

use super::access::{AccessTokenContext, AccessTokenPolicy};
use super::decision::{AuthGrant, PersonTokenDecision, ResourceConsentDecision, TokenPolicyDecision};
use super::error::PolicyError;
use super::person::{PersonTokenContext, PersonTokenPolicy};
use super::resource::{ResourceAccessContext, ResourceConsentPolicy};

#[derive(Debug, Clone, Default)]
pub struct AlwaysGrantPersonPolicy {
    pub sub: String,
}

impl AlwaysGrantPersonPolicy {
    pub fn new(sub: impl Into<String>) -> Self {
        Self { sub: sub.into() }
    }
}

#[async_trait::async_trait]
impl PersonTokenPolicy for AlwaysGrantPersonPolicy {
    async fn evaluate(&self, ctx: &PersonTokenContext) -> Result<PersonTokenDecision, PolicyError> {
        if ctx.audience_is_person_server() {
            Ok(PersonTokenDecision::Grant(AuthGrant {
                sub: self.sub.clone(),
                scope: ctx.resource_claims.scope.clone(),
            }))
        } else {
            Ok(PersonTokenDecision::Federate)
        }
    }

    async fn resume(
        &self,
        ctx: &PersonTokenContext,
        input: PendingInput,
    ) -> Result<PersonTokenDecision, PolicyError> {
        match input {
            PendingInput::InteractionCompleted | PendingInput::ClarificationResponse(_) => {
                self.evaluate(ctx).await
            }
            PendingInput::Cancelled => Ok(PersonTokenDecision::Deny(AAuthProtocolError {
                error: "access_denied".into(),
                error_description: Some("Request cancelled".into()),
                error_uri: None,
            })),
            PendingInput::ClaimsSubmission(_) => Err(PolicyError::Message(
                "claims submission not expected".into(),
            )),
        }
    }
}

#[derive(Debug, Clone)]
pub struct FixedSubPersonPolicy {
    pub sub: String,
}

#[async_trait::async_trait]
impl PersonTokenPolicy for FixedSubPersonPolicy {
    async fn evaluate(&self, ctx: &PersonTokenContext) -> Result<PersonTokenDecision, PolicyError> {
        AlwaysGrantPersonPolicy::new(&self.sub)
            .evaluate(ctx)
            .await
    }

    async fn resume(
        &self,
        ctx: &PersonTokenContext,
        input: PendingInput,
    ) -> Result<PersonTokenDecision, PolicyError> {
        AlwaysGrantPersonPolicy::new(&self.sub)
            .resume(ctx, input)
            .await
    }
}

#[derive(Debug, Clone, Default)]
pub struct AlwaysGrantAccessPolicy {
    pub sub: String,
}

impl AlwaysGrantAccessPolicy {
    pub fn new(sub: impl Into<String>) -> Self {
        Self { sub: sub.into() }
    }
}

#[async_trait::async_trait]
impl AccessTokenPolicy for AlwaysGrantAccessPolicy {
    async fn evaluate(&self, ctx: &AccessTokenContext) -> Result<TokenPolicyDecision, PolicyError> {
        Ok(TokenPolicyDecision::Grant(AuthGrant {
            sub: self.sub.clone(),
            scope: ctx.resource_claims.scope.clone(),
        }))
    }

    async fn resume(
        &self,
        ctx: &AccessTokenContext,
        input: PendingInput,
    ) -> Result<TokenPolicyDecision, PolicyError> {
        match input {
            PendingInput::InteractionCompleted | PendingInput::ClarificationResponse(_) => {
                self.evaluate(ctx).await
            }
            PendingInput::Cancelled => Ok(TokenPolicyDecision::Deny(AAuthProtocolError {
                error: "access_denied".into(),
                error_description: Some("Request cancelled".into()),
                error_uri: None,
            })),
            PendingInput::ClaimsSubmission(_) => self.evaluate(ctx).await,
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct AlwaysGrantResourcePolicy;

#[async_trait::async_trait]
impl ResourceConsentPolicy for AlwaysGrantResourcePolicy {
    async fn evaluate(
        &self,
        _ctx: &ResourceAccessContext,
    ) -> Result<ResourceConsentDecision, PolicyError> {
        Ok(ResourceConsentDecision::GrantOpaque)
    }

    async fn resume(
        &self,
        _ctx: &ResourceAccessContext,
        input: PendingInput,
    ) -> Result<ResourceConsentDecision, PolicyError> {
        match input {
            PendingInput::InteractionCompleted => Ok(ResourceConsentDecision::GrantOpaque),
            PendingInput::Cancelled => Ok(ResourceConsentDecision::Deny(AAuthProtocolError {
                error: "access_denied".into(),
                error_description: Some("Request cancelled".into()),
                error_uri: None,
            })),
            _ => Ok(ResourceConsentDecision::GrantOpaque),
        }
    }
}

#[derive(Debug, Clone)]
pub struct DeferInteractionResourcePolicy {
    pub interaction_url: String,
}

#[async_trait::async_trait]
impl ResourceConsentPolicy for DeferInteractionResourcePolicy {
    async fn evaluate(
        &self,
        _ctx: &ResourceAccessContext,
    ) -> Result<ResourceConsentDecision, PolicyError> {
        Ok(ResourceConsentDecision::Defer(DeferRequirement::Interaction {
            url: self.interaction_url.clone(),
            code: generate_code(),
        }))
    }

    async fn resume(
        &self,
        _ctx: &ResourceAccessContext,
        input: PendingInput,
    ) -> Result<ResourceConsentDecision, PolicyError> {
        match input {
            PendingInput::InteractionCompleted => Ok(ResourceConsentDecision::GrantOpaque),
            PendingInput::Cancelled => Ok(ResourceConsentDecision::Deny(AAuthProtocolError {
                error: "access_denied".into(),
                error_description: Some("Request cancelled".into()),
                error_uri: None,
            })),
            _ => Ok(ResourceConsentDecision::GrantOpaque),
        }
    }
}

#[derive(Debug, Clone)]
pub struct ClarificationThenGrantPersonPolicy {
    pub sub: String,
    pub question: String,
}

#[async_trait::async_trait]
impl PersonTokenPolicy for ClarificationThenGrantPersonPolicy {
    async fn evaluate(&self, _ctx: &PersonTokenContext) -> Result<PersonTokenDecision, PolicyError> {
        Ok(PersonTokenDecision::Defer(DeferRequirement::Clarification {
            question: self.question.clone(),
            timeout: None,
        }))
    }

    async fn resume(
        &self,
        ctx: &PersonTokenContext,
        input: PendingInput,
    ) -> Result<PersonTokenDecision, PolicyError> {
        match input {
            PendingInput::ClarificationResponse(_) | PendingInput::InteractionCompleted => {
                if ctx.audience_is_person_server() {
                    Ok(PersonTokenDecision::Grant(AuthGrant {
                        sub: self.sub.clone(),
                        scope: ctx.resource_claims.scope.clone(),
                    }))
                } else {
                    Ok(PersonTokenDecision::Federate)
                }
            }
            PendingInput::Cancelled => Ok(PersonTokenDecision::Deny(AAuthProtocolError {
                error: "access_denied".into(),
                error_description: Some("Request cancelled".into()),
                error_uri: None,
            })),
            PendingInput::ClaimsSubmission(_) => Err(PolicyError::Message(
                "claims submission not expected".into(),
            )),
        }
    }
}

#[derive(Debug, Clone)]
pub struct DeferInteractionPersonPolicy<P> {
    pub inner: P,
    pub interaction_url: String,
}

#[async_trait::async_trait]
impl<P> PersonTokenPolicy for DeferInteractionPersonPolicy<P>
where
    P: PersonTokenPolicy + Send + Sync + Clone,
{
    async fn evaluate(&self, _ctx: &PersonTokenContext) -> Result<PersonTokenDecision, PolicyError> {
        Ok(PersonTokenDecision::Defer(DeferRequirement::Interaction {
            url: self.interaction_url.clone(),
            code: generate_code(),
        }))
    }

    async fn resume(
        &self,
        ctx: &PersonTokenContext,
        input: PendingInput,
    ) -> Result<PersonTokenDecision, PolicyError> {
        self.inner.resume(ctx, input).await
    }
}

#[derive(Debug, Clone)]
pub struct ClarificationThenGrantAccessPolicy {
    pub sub: String,
    pub question: String,
}

#[async_trait::async_trait]
impl AccessTokenPolicy for ClarificationThenGrantAccessPolicy {
    async fn evaluate(&self, _ctx: &AccessTokenContext) -> Result<TokenPolicyDecision, PolicyError> {
        Ok(TokenPolicyDecision::Defer(DeferRequirement::Clarification {
            question: self.question.clone(),
            timeout: None,
        }))
    }

    async fn resume(
        &self,
        ctx: &AccessTokenContext,
        input: PendingInput,
    ) -> Result<TokenPolicyDecision, PolicyError> {
        match input {
            PendingInput::ClarificationResponse(_) | PendingInput::InteractionCompleted => {
                Ok(TokenPolicyDecision::Grant(AuthGrant {
                    sub: self.sub.clone(),
                    scope: ctx.resource_claims.scope.clone(),
                }))
            }
            PendingInput::Cancelled => Ok(TokenPolicyDecision::Deny(AAuthProtocolError {
                error: "access_denied".into(),
                error_description: Some("Request cancelled".into()),
                error_uri: None,
            })),
            PendingInput::ClaimsSubmission(_) => Err(PolicyError::Message(
                "claims submission not expected".into(),
            )),
        }
    }
}

#[derive(Debug, Clone)]
pub struct DeferInteractionAccessPolicy<P> {
    pub inner: P,
    pub interaction_url: String,
}

#[async_trait::async_trait]
impl<P> AccessTokenPolicy for DeferInteractionAccessPolicy<P>
where
    P: AccessTokenPolicy + Send + Sync + Clone,
{
    async fn evaluate(&self, _ctx: &AccessTokenContext) -> Result<TokenPolicyDecision, PolicyError> {
        Ok(TokenPolicyDecision::Defer(DeferRequirement::Interaction {
            url: self.interaction_url.clone(),
            code: generate_code(),
        }))
    }

    async fn resume(
        &self,
        ctx: &AccessTokenContext,
        input: PendingInput,
    ) -> Result<TokenPolicyDecision, PolicyError> {
        self.inner.resume(ctx, input).await
    }
}

#[derive(Debug, Clone)]
pub struct DeferClaimsAccessPolicy {
    pub sub: String,
    pub required_claims: Vec<String>,
}

#[async_trait::async_trait]
impl AccessTokenPolicy for DeferClaimsAccessPolicy {
    async fn evaluate(&self, _ctx: &AccessTokenContext) -> Result<TokenPolicyDecision, PolicyError> {
        Ok(TokenPolicyDecision::Defer(DeferRequirement::Claims {
            required_claims: self.required_claims.clone(),
        }))
    }

    async fn resume(
        &self,
        ctx: &AccessTokenContext,
        input: PendingInput,
    ) -> Result<TokenPolicyDecision, PolicyError> {
        match input {
            PendingInput::ClaimsSubmission(_) | PendingInput::InteractionCompleted => {
                Ok(TokenPolicyDecision::Grant(AuthGrant {
                    sub: self.sub.clone(),
                    scope: ctx.resource_claims.scope.clone(),
                }))
            }
            PendingInput::Cancelled => Ok(TokenPolicyDecision::Deny(AAuthProtocolError {
                error: "access_denied".into(),
                error_description: Some("Request cancelled".into()),
                error_uri: None,
            })),
            PendingInput::ClarificationResponse(_) => Err(PolicyError::Message(
                "clarification not expected".into(),
            )),
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct DeferApprovalAccessPolicy {
    pub sub: String,
}

#[async_trait::async_trait]
impl AccessTokenPolicy for DeferApprovalAccessPolicy {
    async fn evaluate(&self, _ctx: &AccessTokenContext) -> Result<TokenPolicyDecision, PolicyError> {
        Ok(TokenPolicyDecision::Defer(DeferRequirement::Approval))
    }

    async fn resume(
        &self,
        ctx: &AccessTokenContext,
        input: PendingInput,
    ) -> Result<TokenPolicyDecision, PolicyError> {
        match input {
            PendingInput::InteractionCompleted => Ok(TokenPolicyDecision::Grant(AuthGrant {
                sub: self.sub.clone(),
                scope: ctx.resource_claims.scope.clone(),
            })),
            PendingInput::Cancelled => Ok(TokenPolicyDecision::Deny(AAuthProtocolError {
                error: "access_denied".into(),
                error_description: Some("Request cancelled".into()),
                error_uri: None,
            })),
            _ => Ok(TokenPolicyDecision::Grant(AuthGrant {
                sub: self.sub.clone(),
                scope: ctx.resource_claims.scope.clone(),
            })),
        }
    }
}