src.offers.offer_service

Offer service for creating and retrieving credential offers.

 1"""Offer service for creating and retrieving credential offers."""
 2
 3import uuid
 4
 5from src.access_control.access_control_port import AccessControlPort
 6from src.offers.models import Offer
 7from src.offers.offers_client_port import OffersClientPort
 8from src.offers.offers_repository_port import OffersRepositoryPort
 9
10
11class PermissionDeniedError(Exception):
12    """Raised when access control denies the requested action."""
13
14class DoesNotExistInRepositoryError(Exception):
15    """Raised when offer cannot be found in Repository"""
16
17class DoesNotExistInClientError(Exception):
18    """Raised when offer cannot be found in API"""
19
20class OfferService:
21    """Service that orchestrates offer creation."""
22
23    _access_control: AccessControlPort
24    _offers_client: OffersClientPort
25    _offers_repository: OffersRepositoryPort
26
27    def __init__(
28        self,
29        access_control: AccessControlPort,
30        offers_client: OffersClientPort,
31        offers_repository: OffersRepositoryPort,
32    ) -> None:
33        """Initialise the service with its dependencies.
34
35        Args:
36            access_control: Adapter for checking resource permissions.
37            offers_repository: Adapter for persisting offers.
38            offers_client: Adapter for interacting with oid4vci agent.
39            public_url: Publicly accessible base URL of this issuer service,
40                used to build the offer URI.
41        """
42        self._access_control = access_control
43        self._offers_client = offers_client
44        self._offers_repository = offers_repository
45
46    def create_offer(self, award_id: str, bearer_token: str) -> Offer:
47        """Create, persist, and return a new credential offer.
48
49        Args:
50            award_id: The award/achievement to issue.
51            bearer_token: The caller's bearer token used for permission checking.
52
53        Returns:
54            The newly created Offer.
55
56        Raises:
57            PermissionDeniedError: When the caller is not permitted to import the award.
58        """
59        if not self._access_control.may_import(
60            bearer_token, award_id, "Award", "import"
61        ):
62            raise PermissionDeniedError(award_id)
63
64        offer_id = str(uuid.uuid4())
65        # award: Award = Award()  # TODO: Get the actual award from where?
66
67        # TODO: wrap in transaction
68        uri = self._offers_client.create(offer_id)
69        offer = Offer(
70            offer_id = offer_id,
71            award_id = award_id,
72            uri = uri
73        )
74        self._offers_repository.store(offer)
75
76        return offer
77
78
79    def get_offer(self, offer_id: str) -> Offer:
80        """Retrieve an offer by its identifier.
81
82        Args:
83            offer_id: The unique offer identifier.
84
85        Returns:
86            The matching Offer.
87
88        Raises:
89            KeyError: When no offer with the given id exists.
90        """
91
92        upstream_offer = self._offers_client.get(offer_id)
93        stored_offer = self._offers_repository.get(offer_id)
94        return Offer(
95            offer_id = offer_id,
96            award_id = stored_offer.award_id,
97            uri = upstream_offer.uri,
98        )
class PermissionDeniedError(builtins.Exception):
12class PermissionDeniedError(Exception):
13    """Raised when access control denies the requested action."""

Raised when access control denies the requested action.

class DoesNotExistInRepositoryError(builtins.Exception):
15class DoesNotExistInRepositoryError(Exception):
16    """Raised when offer cannot be found in Repository"""

Raised when offer cannot be found in Repository

class DoesNotExistInClientError(builtins.Exception):
18class DoesNotExistInClientError(Exception):
19    """Raised when offer cannot be found in API"""

Raised when offer cannot be found in API

class OfferService:
21class OfferService:
22    """Service that orchestrates offer creation."""
23
24    _access_control: AccessControlPort
25    _offers_client: OffersClientPort
26    _offers_repository: OffersRepositoryPort
27
28    def __init__(
29        self,
30        access_control: AccessControlPort,
31        offers_client: OffersClientPort,
32        offers_repository: OffersRepositoryPort,
33    ) -> None:
34        """Initialise the service with its dependencies.
35
36        Args:
37            access_control: Adapter for checking resource permissions.
38            offers_repository: Adapter for persisting offers.
39            offers_client: Adapter for interacting with oid4vci agent.
40            public_url: Publicly accessible base URL of this issuer service,
41                used to build the offer URI.
42        """
43        self._access_control = access_control
44        self._offers_client = offers_client
45        self._offers_repository = offers_repository
46
47    def create_offer(self, award_id: str, bearer_token: str) -> Offer:
48        """Create, persist, and return a new credential offer.
49
50        Args:
51            award_id: The award/achievement to issue.
52            bearer_token: The caller's bearer token used for permission checking.
53
54        Returns:
55            The newly created Offer.
56
57        Raises:
58            PermissionDeniedError: When the caller is not permitted to import the award.
59        """
60        if not self._access_control.may_import(
61            bearer_token, award_id, "Award", "import"
62        ):
63            raise PermissionDeniedError(award_id)
64
65        offer_id = str(uuid.uuid4())
66        # award: Award = Award()  # TODO: Get the actual award from where?
67
68        # TODO: wrap in transaction
69        uri = self._offers_client.create(offer_id)
70        offer = Offer(
71            offer_id = offer_id,
72            award_id = award_id,
73            uri = uri
74        )
75        self._offers_repository.store(offer)
76
77        return offer
78
79
80    def get_offer(self, offer_id: str) -> Offer:
81        """Retrieve an offer by its identifier.
82
83        Args:
84            offer_id: The unique offer identifier.
85
86        Returns:
87            The matching Offer.
88
89        Raises:
90            KeyError: When no offer with the given id exists.
91        """
92
93        upstream_offer = self._offers_client.get(offer_id)
94        stored_offer = self._offers_repository.get(offer_id)
95        return Offer(
96            offer_id = offer_id,
97            award_id = stored_offer.award_id,
98            uri = upstream_offer.uri,
99        )

Service that orchestrates offer creation.

28    def __init__(
29        self,
30        access_control: AccessControlPort,
31        offers_client: OffersClientPort,
32        offers_repository: OffersRepositoryPort,
33    ) -> None:
34        """Initialise the service with its dependencies.
35
36        Args:
37            access_control: Adapter for checking resource permissions.
38            offers_repository: Adapter for persisting offers.
39            offers_client: Adapter for interacting with oid4vci agent.
40            public_url: Publicly accessible base URL of this issuer service,
41                used to build the offer URI.
42        """
43        self._access_control = access_control
44        self._offers_client = offers_client
45        self._offers_repository = offers_repository

Initialise the service with its dependencies.

Args: access_control: Adapter for checking resource permissions. offers_repository: Adapter for persisting offers. offers_client: Adapter for interacting with oid4vci agent. public_url: Publicly accessible base URL of this issuer service, used to build the offer URI.

def create_offer(self, award_id: str, bearer_token: str) -> src.offers.models.Offer:
47    def create_offer(self, award_id: str, bearer_token: str) -> Offer:
48        """Create, persist, and return a new credential offer.
49
50        Args:
51            award_id: The award/achievement to issue.
52            bearer_token: The caller's bearer token used for permission checking.
53
54        Returns:
55            The newly created Offer.
56
57        Raises:
58            PermissionDeniedError: When the caller is not permitted to import the award.
59        """
60        if not self._access_control.may_import(
61            bearer_token, award_id, "Award", "import"
62        ):
63            raise PermissionDeniedError(award_id)
64
65        offer_id = str(uuid.uuid4())
66        # award: Award = Award()  # TODO: Get the actual award from where?
67
68        # TODO: wrap in transaction
69        uri = self._offers_client.create(offer_id)
70        offer = Offer(
71            offer_id = offer_id,
72            award_id = award_id,
73            uri = uri
74        )
75        self._offers_repository.store(offer)
76
77        return offer

Create, persist, and return a new credential offer.

Args: award_id: The award/achievement to issue. bearer_token: The caller's bearer token used for permission checking.

Returns: The newly created Offer.

Raises: PermissionDeniedError: When the caller is not permitted to import the award.

def get_offer(self, offer_id: str) -> src.offers.models.Offer:
80    def get_offer(self, offer_id: str) -> Offer:
81        """Retrieve an offer by its identifier.
82
83        Args:
84            offer_id: The unique offer identifier.
85
86        Returns:
87            The matching Offer.
88
89        Raises:
90            KeyError: When no offer with the given id exists.
91        """
92
93        upstream_offer = self._offers_client.get(offer_id)
94        stored_offer = self._offers_repository.get(offer_id)
95        return Offer(
96            offer_id = offer_id,
97            award_id = stored_offer.award_id,
98            uri = upstream_offer.uri,
99        )

Retrieve an offer by its identifier.

Args: offer_id: The unique offer identifier.

Returns: The matching Offer.

Raises: KeyError: When no offer with the given id exists.