Source code for govbr_auth.core.authorization

"""Construct Gov.br OAuth authorization requests with PKCE binding."""

import base64
import hashlib
from dataclasses import dataclass
from datetime import datetime
from urllib.parse import urlencode

from govbr_auth.core.settings import GovBrSettings
from govbr_auth.core.transactions import TransactionCodec


@dataclass(frozen=True, slots=True)
class AuthorizationRequest:
    """Represent the redirect URL and opaque state of an OAuth authorization request."""

    url: str
    state: str


[docs] class AuthorizationBuilder: """Build Gov.br OAuth authorization requests bound to protected transactions."""
[docs] def __init__(self, settings: GovBrSettings, transactions: TransactionCodec) -> None: """Store the validated provider configuration and transaction collaborator.""" self._settings = settings self._transactions = transactions
[docs] def build(self, *, now: datetime) -> AuthorizationRequest: """Create a transaction and encode its nonce and PKCE challenge in the redirect URL.""" state, transaction = self._transactions.issue(now=now) verifier = transaction.code_verifier.get_secret_value().encode("ascii") challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier).digest()) code_challenge = challenge.rstrip(b"=").decode("ascii") query = urlencode( { "response_type": "code", "client_id": self._settings.client_id, "scope": self._settings.scope, "redirect_uri": str(self._settings.redirect_uri), "nonce": transaction.nonce.get_secret_value(), "state": state, "code_challenge": code_challenge, "code_challenge_method": "S256", } ) return AuthorizationRequest( url=f"{self._settings.authorization_url}?{query}", state=state, )
[docs] def build_logout(self) -> str: """Build the provider logout URL from the configured fixed return URI.""" logout_url = self._settings.logout_url post_logout_redirect_uri = self._settings.post_logout_redirect_uri if logout_url is None or post_logout_redirect_uri is None: raise ValueError( "logout_url and post_logout_redirect_uri must be configured together" ) query = urlencode({"post_logout_redirect_uri": str(post_logout_redirect_uri)}) separator = "&" if "?" in str(logout_url) else "?" return f"{logout_url}{separator}{query}"