"""
API operations on defining cloud authorizations.
Through means of cloud authorization a user is able to grant a Galaxy server a secure access to his/her
cloud-based resources without sharing his/her long-lasting credentials.
User provides a provider-specific configuration, which Galaxy users to request temporary credentials
from the provider to access the user's resources.
"""
import logging
from galaxy.exceptions import (
ActionInputError,
InternalServerError,
MalformedId,
RequestParameterInvalidException,
RequestParameterMissingException,
)
from galaxy.managers import cloudauthzs
from galaxy.model.base import transaction
from galaxy.structured_app import StructuredApp
from galaxy.util import unicodify
from galaxy.web import expose_api
from . import BaseGalaxyAPIController
log = logging.getLogger(__name__)
[docs]class CloudAuthzController(BaseGalaxyAPIController):
"""
RESTfull controller for defining cloud authorizations.
"""
[docs] def __init__(self, app: StructuredApp):
super().__init__(app)
self.cloudauthz_manager = cloudauthzs.CloudAuthzManager(app)
self.cloudauthz_serializer = cloudauthzs.CloudAuthzsSerializer(app)
self.cloudauthz_deserializer = cloudauthzs.CloudAuthzsDeserializer(app)
[docs] @expose_api
def index(self, trans, **kwargs):
"""
GET /api/cloud/authz
Lists all the cloud authorizations user has defined.
:type trans: galaxy.webapps.base.webapp.GalaxyWebTransaction
:param trans: Galaxy web transaction
:param kwargs: empty dict
:rtype: list of dict
:return: a list of cloud authorizations (each represented in key-value pair format) defined for the user.
"""
rtv = []
for cloudauthz in trans.user.cloudauthz:
rtv.append(
self.cloudauthz_serializer.serialize_to_view(
cloudauthz, user=trans.user, trans=trans, **self._parse_serialization_params(kwargs, "summary")
)
)
return rtv
[docs] @expose_api
def create(self, trans, payload, **kwargs):
"""
* POST /api/cloud/authz
Request to store the payload as a cloudauthz (cloud authorization) configuration for a user.
:type trans: galaxy.webapps.base.webapp.GalaxyWebTransaction
:param trans: Galaxy web transaction
:type payload: dict
:param payload: A dictionary structure containing the following keys:
* provider: the cloud-based resource provider to which this configuration belongs to.
* config: a dictionary containing all the configuration required to request temporary credentials
from the provider. See the following page for details:
https://galaxyproject.org/authnz/
* authn_id: the (encoded) ID of a third-party authentication of a user. To have this ID, user must
have logged-in to this Galaxy server using third-party identity (e.g., Google), or has
associated his/her Galaxy account with a third-party OIDC-based identity. See this page:
https://galaxyproject.org/authnz/config/
* description: [Optional] a brief description for this configuration.
:param kwargs: empty dict
:rtype: dict
:return: a dictionary with the following kvp:
* status: HTTP response code
* message: A message complementary to the response code.
"""
msg_template = f"Rejected user `{trans.user.id}`'s request to create cloudauthz config because of {{}}."
if not isinstance(payload, dict):
raise ActionInputError(
"Invalid payload data type. The payload is expected to be a dictionary, but "
f"received data of type `{type(payload)}`."
)
missing_arguments = []
provider = payload.get("provider", None)
if provider is None:
missing_arguments.append("provider")
config = payload.get("config", None)
if config is None:
missing_arguments.append("config")
authn_id = payload.get("authn_id", None)
if authn_id is None and provider.lower() not in ["azure", "gcp"]:
missing_arguments.append("authn_id")
if len(missing_arguments) > 0:
log.debug(msg_template.format(f"missing required config {missing_arguments}"))
raise RequestParameterMissingException(
f"The following required arguments are missing in the payload: {missing_arguments}"
)
description = payload.get("description", "")
if not isinstance(config, dict):
log.debug(msg_template.format(f"invalid config type `{type(config)}`, expected `dict`"))
raise RequestParameterInvalidException(
f"Invalid type for the required `config` variable; expected `dict` but received `{type(config)}`."
)
if authn_id:
try:
decoded_authn_id = self.decode_id(authn_id)
except MalformedId as e:
log.debug(msg_template.format(f"cannot decode authz_id `{authn_id}`"))
raise e
try:
trans.app.authnz_manager.can_user_assume_authn(trans, decoded_authn_id)
except Exception as e:
raise e
# No two authorization configuration with
# exact same key/value should exist.
for ca in trans.user.cloudauthz:
if ca.equals(trans.user.id, provider, authn_id, config):
log.debug(
"Rejected user `%s`'s request to create cloud authorization because a similar config "
"already exists.",
trans.user.id,
)
raise ActionInputError("A similar cloud authorization configuration is already defined.")
try:
new_cloudauthz = self.cloudauthz_manager.create(
user_id=trans.user.id, provider=provider, config=config, authn_id=authn_id, description=description
)
view = self.cloudauthz_serializer.serialize_to_view(
new_cloudauthz, trans=trans, **self._parse_serialization_params(kwargs, "summary")
)
log.debug(f"Created a new cloudauthz record for the user id `{str(trans.user.id)}` ")
return view
except Exception as e:
log.exception(msg_template.format("exception while creating the new cloudauthz record"))
raise InternalServerError(
"An unexpected error has occurred while responding to the create request of the "
"cloudauthz API." + unicodify(e)
)
[docs] @expose_api
def delete(self, trans, encoded_authz_id, **kwargs):
"""
* DELETE /api/cloud/authz/{encoded_authz_id}
Deletes the CloudAuthz record with the given ``encoded_authz_id`` from database.
:type trans: galaxy.webapps.base.webapp.GalaxyWebTransaction
:param trans: Galaxy web transaction
:type encoded_authz_id: string
:param encoded_authz_id: The encoded ID of the CloudAuthz record to be marked deleted.
:rtype JSON
:return The cloudauthz record marked as deleted, serialized as a JSON object.
"""
msg_template = f"Rejected user `{str(trans.user.id)}`'s request to delete cloudauthz config because of {{}}."
try:
authz_id = self.decode_id(encoded_authz_id)
except MalformedId as e:
log.debug(msg_template.format(f"cannot decode authz_id `{encoded_authz_id}`"))
raise e
try:
cloudauthz = trans.app.authnz_manager.try_get_authz_config(trans.sa_session, trans.user.id, authz_id)
trans.sa_session.delete(cloudauthz)
with transaction(trans.sa_session):
trans.sa_session.commit()
log.debug(f"Deleted a cloudauthz record with id `{authz_id}` for the user id `{str(trans.user.id)}` ")
view = self.cloudauthz_serializer.serialize_to_view(
cloudauthz, trans=trans, **self._parse_serialization_params(kwargs, "summary")
)
trans.response.status = "200"
return view
except Exception as e:
log.exception(
msg_template.format(f"exception while deleting the cloudauthz record with ID: `{encoded_authz_id}`.")
)
raise InternalServerError(
"An unexpected error has occurred while responding to the DELETE request of the "
"cloudauthz API." + unicodify(e)
)
[docs] @expose_api
def update(self, trans, encoded_authz_id, payload, **kwargs):
"""
PUT /api/cloud/authz/{encoded_authz_id}
Updates the values for the cloudauthz configuration with the given ``encoded_authz_id``.
With this API only the following attributes of a cloudauthz configuration
can be updated: `authn_id`, `provider`, `config`, `deleted`.
:type trans: galaxy.webapps.base.webapp.GalaxyWebTransaction
:param trans: Galaxy web transaction
:type encoded_authz_id: string
:param encoded_authz_id: The encoded ID of the CloudAuthz record to be updated.
:type payload: dict
:param payload: A dictionary structure containing the attributes to modified with their new values.
It can contain any number of the following attributes:
* provider: the cloud-based resource provider
to which this configuration belongs to.
* authn_id: the (encoded) ID of a third-party authentication of a user.
To have this ID, user must have logged-in to this Galaxy server
using third-party identity (e.g., Google), or has associated
their Galaxy account with a third-party OIDC-based identity.
See this page: https://galaxyproject.org/authnz/config/
Note: A user can associate a cloudauthz record with their own
authentications only. If the given authentication with authn_id
belongs to a different user, Galaxy will throw the
ItemAccessibilityException exception.
* config: a dictionary containing all the configuration required to
request temporary credentials from the provider.
See the following page for details:
https://galaxyproject.org/authnz/
* deleted: a boolean type marking the specified cloudauthz as (un)deleted.
"""
msg_template = f"Rejected user `{str(trans.user.id)}`'s request to delete cloudauthz config because of {{}}."
try:
authz_id = self.decode_id(encoded_authz_id)
except MalformedId as e:
log.debug(msg_template.format(f"cannot decode authz_id `{encoded_authz_id}`"))
raise e
try:
cloudauthz_to_update = trans.app.authnz_manager.try_get_authz_config(
trans.sa_session, trans.user.id, authz_id
)
self.cloudauthz_deserializer.deserialize(cloudauthz_to_update, payload, trans=trans)
self.cloudauthz_serializer.serialize_to_view(cloudauthz_to_update, view="summary")
return self.cloudauthz_serializer.serialize_to_view(cloudauthz_to_update, view="summary")
except MalformedId as e:
raise e
except Exception as e:
log.exception(
msg_template.format(f"exception while updating the cloudauthz record with ID: `{encoded_authz_id}`.")
)
raise InternalServerError(
"An unexpected error has occurred while responding to the PUT request of the "
"cloudauthz API." + unicodify(e)
)