Expose endpoint to get list of UA activable services

The list of activable UA services was only retrieved from the
client-side of Subiquity (using ubuntu-advantage-tools). Therefore, the
desktop installer would need to reimplement the same logic should they
need access to the list of services ; which is inconvenient.

We now expose a new endpoint in the API that takes the token as
a parameter and returns a status (+ a list of services if the token is
valid and not expired).

  $ curl \
    --unix-socket .subiquity/socket \
    --header 'Content-Type: application/json' \
    http://a/ubuntu_advantage/check_token \
    -d '"C123456"'

The token parameter is expected to be in the body of the request -
rather than in the query string - to avoid ending up in the access logs.

This new endpoint is a read-only GET endpoint. It is not designed as a
replacement for the POST to /a/ubuntu_advantage that the client must
(still) do to pass the token to the model.

We now use this new endpoint internally in Subiquity so that the
retrieval of the activable services is done on the server-side only.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
Olivier Gayot 2022-01-17 18:29:31 +01:00
parent d158d4e701
commit c171aac2c7
4 changed files with 80 additions and 34 deletions

View File

@ -16,21 +16,14 @@
""" """
import logging import logging
import os
from subiquitycore.async_helpers import schedule_task from subiquitycore.async_helpers import schedule_task
from subiquity.client.controller import SubiquityTuiController from subiquity.client.controller import SubiquityTuiController
from subiquity.common.ubuntu_advantage import ( from subiquity.common.types import (
InvalidUATokenError, UbuntuAdvantageInfo,
ExpiredUATokenError, UbuntuAdvantageCheckTokenStatus as TokenStatus,
CheckSubscriptionError,
UAInterface,
UAInterfaceStrategy,
MockedUAInterfaceStrategy,
UAClientUAInterfaceStrategy,
) )
from subiquity.common.types import UbuntuAdvantageInfo
from subiquity.ui.views.ubuntu_advantage import UbuntuAdvantageView from subiquity.ui.views.ubuntu_advantage import UbuntuAdvantageView
from subiquitycore.lsb_release import lsb_release from subiquitycore.lsb_release import lsb_release
@ -44,21 +37,6 @@ class UbuntuAdvantageController(SubiquityTuiController):
endpoint_name = "ubuntu_advantage" endpoint_name = "ubuntu_advantage"
def __init__(self, app):
""" Initializer for client-side UA controller. """
strategy: UAInterfaceStrategy
if app.opts.dry_run:
strategy = MockedUAInterfaceStrategy(scale_factor=app.scale_factor)
else:
# Make sure we execute `$PYTHON "$SNAP/usr/bin/ubuntu-advantage"`.
executable = (
os.environ["PYTHON"],
os.path.join(os.environ["SNAP"], "usr/bin/ubuntu-advantage"),
)
strategy = UAClientUAInterfaceStrategy(executable=executable)
self.ua_interface = UAInterface(strategy)
super().__init__(app)
async def make_ui(self) -> UbuntuAdvantageView: async def make_ui(self) -> UbuntuAdvantageView:
""" Generate the UI, based on the data provided by the model. """ """ Generate the UI, based on the data provided by the model. """
@ -77,21 +55,19 @@ class UbuntuAdvantageController(SubiquityTuiController):
def check_token(self, token: str): def check_token(self, token: str):
""" Asynchronously check the token passed as an argument. """ """ Asynchronously check the token passed as an argument. """
async def inner() -> None: async def inner() -> None:
try: answer = await self.endpoint.check_token.GET(token)
svcs = await \ if answer.status == TokenStatus.INVALID_TOKEN:
self.ua_interface.get_activable_services(token=token)
except InvalidUATokenError:
if isinstance(self.ui.body, UbuntuAdvantageView): if isinstance(self.ui.body, UbuntuAdvantageView):
self.ui.body.show_invalid_token() self.ui.body.show_invalid_token()
except ExpiredUATokenError: elif answer.status == TokenStatus.EXPIRED_TOKEN:
if isinstance(self.ui.body, UbuntuAdvantageView): if isinstance(self.ui.body, UbuntuAdvantageView):
self.ui.body.show_expired_token() self.ui.body.show_expired_token()
except CheckSubscriptionError: elif answer.status == TokenStatus.UNKNOWN_ERROR:
if isinstance(self.ui.body, UbuntuAdvantageView): if isinstance(self.ui.body, UbuntuAdvantageView):
self.ui.body.show_unknown_error() self.ui.body.show_unknown_error()
else: else:
if isinstance(self.ui.body, UbuntuAdvantageView): if isinstance(self.ui.body, UbuntuAdvantageView):
self.ui.body.show_activable_services(svcs) self.ui.body.show_activable_services(answer.services)
self._check_task = schedule_task(inner()) self._check_task = schedule_task(inner())

View File

@ -49,6 +49,7 @@ from subiquity.common.types import (
StorageResponseV2, StorageResponseV2,
TimeZoneInfo, TimeZoneInfo,
UbuntuAdvantageInfo, UbuntuAdvantageInfo,
UbuntuAdvantageCheckTokenAnswer,
WLANSupportInstallState, WLANSupportInstallState,
ZdevInfo, ZdevInfo,
WSLConfigurationBase, WSLConfigurationBase,
@ -320,6 +321,10 @@ class API:
class skip: class skip:
def POST() -> None: ... def POST() -> None: ...
class check_token:
def GET(token: Payload[str]) \
-> UbuntuAdvantageCheckTokenAnswer: ...
class LinkAction(enum.Enum): class LinkAction(enum.Enum):
NEW = enum.auto() NEW = enum.auto()

View File

@ -394,6 +394,20 @@ class UbuntuAdvantageInfo:
token: str token: str
class UbuntuAdvantageCheckTokenStatus(enum.Enum):
VALID_TOKEN = enum.auto()
INVALID_TOKEN = enum.auto()
EXPIRED_TOKEN = enum.auto()
UNKNOWN_ERROR = enum.auto()
@attr.s(auto_attribs=True)
class UbuntuAdvantageCheckTokenAnswer:
status: UbuntuAdvantageCheckTokenStatus
services: Optional[List[dict]]
class ShutdownMode(enum.Enum): class ShutdownMode(enum.Enum):
REBOOT = enum.auto() REBOOT = enum.auto()
POWEROFF = enum.auto() POWEROFF = enum.auto()

View File

@ -15,9 +15,23 @@
""" Module defining the server-side controller class for Ubuntu Advantage. """ """ Module defining the server-side controller class for Ubuntu Advantage. """
import logging import logging
import os
from subiquity.common.apidef import API from subiquity.common.apidef import API
from subiquity.common.types import UbuntuAdvantageInfo from subiquity.common.types import (
UbuntuAdvantageInfo,
UbuntuAdvantageCheckTokenAnswer,
UbuntuAdvantageCheckTokenStatus,
)
from subiquity.common.ubuntu_advantage import (
InvalidUATokenError,
ExpiredUATokenError,
CheckSubscriptionError,
UAInterface,
UAInterfaceStrategy,
MockedUAInterfaceStrategy,
UAClientUAInterfaceStrategy,
)
from subiquity.server.controller import SubiquityController from subiquity.server.controller import SubiquityController
log = logging.getLogger("subiquity.server.controllers.ubuntu_advantage") log = logging.getLogger("subiquity.server.controllers.ubuntu_advantage")
@ -47,6 +61,21 @@ class UbuntuAdvantageController(SubiquityController):
}, },
} }
def __init__(self, app) -> None:
""" Initializer for server-side UA controller. """
strategy: UAInterfaceStrategy
if app.opts.dry_run:
strategy = MockedUAInterfaceStrategy(scale_factor=app.scale_factor)
else:
# Make sure we execute `$PYTHON "$SNAP/usr/bin/ubuntu-advantage"`.
executable = (
os.environ["PYTHON"],
os.path.join(os.environ["SNAP"], "usr/bin/ubuntu-advantage"),
)
strategy = UAClientUAInterfaceStrategy(executable=executable)
self.ua_interface = UAInterface(strategy)
super().__init__(app)
def load_autoinstall_data(self, data: dict) -> None: def load_autoinstall_data(self, data: dict) -> None:
""" Load autoinstall data and update the model. """ """ Load autoinstall data and update the model. """
if data is None: if data is None:
@ -89,3 +118,25 @@ class UbuntuAdvantageController(SubiquityController):
""" When running on a non-LTS release, we want to call this so we can """ When running on a non-LTS release, we want to call this so we can
skip the screen on the client side. """ skip the screen on the client side. """
await self.configured() await self.configured()
async def check_token_GET(self, token: str) \
-> UbuntuAdvantageCheckTokenAnswer:
""" Handle a GET request asking whether the contract token is valid or
not. If it is valid, we provide the list of activable services
associated with the subscription.
"""
services = None
try:
services = await \
self.ua_interface.get_activable_services(token=token)
except InvalidUATokenError:
status = UbuntuAdvantageCheckTokenStatus.INVALID_TOKEN
except ExpiredUATokenError:
status = UbuntuAdvantageCheckTokenStatus.EXPIRED_TOKEN
except CheckSubscriptionError:
status = UbuntuAdvantageCheckTokenStatus.UNKNOWN_ERROR
else:
status = UbuntuAdvantageCheckTokenStatus.VALID_TOKEN
return UbuntuAdvantageCheckTokenAnswer(status=status,
services=services)