Module dcso.portal.auth.auth
Expand source code
# Copyright (c) 2020, 2021, DCSO GmbH
from datetime import datetime
from typing import Optional
from .rbac import RBACMixin
from .token import Token
from ..abstracts import APIAbstract
from ..exceptions import PortalAPIResponse, PortalException
from ..util.graphql import GraphQLRequest
_DEFAULT_TOKEN_RESOURCE = "PortalPythonSDK"
class Authentication:
def __init__(self, graphql_response: Optional[dict]):
self.id: str = ''
self.username: str = ''
self.token: Optional[Token] = None
self.token_is_temporary: bool = False
self.groups = [] # legacy: replaced by permissions
self.totp_required: bool = True
self.totp_activated: Optional[datetime] = None
self.totp_barcode: Optional[str] = None
if graphql_response:
self._handle_graphql_response(graphql_response)
def _handle_graphql_response(self, res: dict):
try:
self.token = Token(graphql_response=res)
if res['user']:
self.id = res['user']['id']
self.username = res['user']['username']
self.totp_required = res['otp']['required']
self.totp_activated = res['otp']['activated']
self.token_is_temporary = res['isTemporaryToken']
self.totp_barcode = res['otpSVGQRCode']
except KeyError as exc:
raise PortalAPIResponse(f"failed handling authentication response ({exc})")
class Auth(RBACMixin):
"""Auth provides authentication, authorization, and user management.
Typical use:
api = APIClient('https://api.example.com/api')
auth = Auth(api)
user = auth.authenticate("alice", "alice.password")
"""
@property
def token(self) -> str:
return self._api.token
@token.setter
def token(self, token: str) -> None:
self._api.token = token
def __init__(self, api: APIAbstract):
self._api: APIAbstract = api
def authenticate(self, username: str, password: str, resource: str = _DEFAULT_TOKEN_RESOURCE,
set_api_token: bool = True) -> Authentication:
"""Authenticates with DCSO Portal using credentials or `username` and `password`,
for the given `resource`. Resource is by default `PortalPythonSDK`, but it can
be, for example, the name of the application or script.
When the authenticating user has two-factor authentication (2FA) enforced, a second
pass is needed using the temporary token received with by method and passed
on to the `second_authentication_totp` method.
When `set_api_token` is True, and authentication succeeds, the non-temporary
token will be stored and used for further API queries.
An Authentication instance is returned which contains general user information,
token, and details about this token.
Raises `PortalAPIError` When the GraphQL API endpoint returned an error.
When there was an issue with the request itself, or decoding JSON failed,
the `PortalAPIRequest` exception is raised.
"""
variables = {
"portalauth": {
"username": username,
"password": password,
"resource": resource
}
}
request = GraphQLRequest(api_url=self._api.api_url,
query=_GRAPHQL_MUTATION_AUTHN, variables=variables)
try:
response = request.execute_dict()
except PortalException:
raise
try:
authn = Authentication(graphql_response=response['data']['portalauth'])
if not authn.token_is_temporary and set_api_token:
self._api.token = authn.token.token
return authn
except PortalAPIResponse:
raise
def second_authentication_totp(self, username: str, temp_token: str, totp: str,
set_api_token: bool = True,
resource: str = _DEFAULT_TOKEN_RESOURCE) -> Authentication:
"""Two-factor authentication using Time-based One-Time Password (TOTP).
The `username` must be the same which was used with the `authenticate` method. The
`temp_token` argument is set to the temporary token returned by the method
`authenticate`. Finally, the `totp` argument is the code visible in the
authenticator application which holds the TOTP secret.
The `resource` argument is by default `PortalPythonSDK`, but it can
be, for example, the name of the application or script. This must match the resource
that was used with `authenticate()`.
When `set_api_token` is True, and authentication succeeds, the non-temporary
token will be stored and used for further API queries.
An Authentication instance is returned which contains general user information,
token, and details about this token.
Raises `PortalAPIError` When the GraphQL API endpoint returned an error.
When there was an issue with the request itself, or decoding JSON failed,
the `PortalAPIRequest` exception is raised.
"""
variables = {
"portalauth": {
"username": username,
"temporaryToken": temp_token,
"otpCode": totp,
"resource": resource,
}
}
request = GraphQLRequest(api_url=self._api.api_url,
query=_GRAPHQL_MUTATION_AUTHN, variables=variables)
try:
response = request.execute_dict()
except PortalException:
raise
try:
authn = Authentication(graphql_response=response['data']['portalauth'])
if set_api_token:
self._api.token = authn.token.token
return authn
except PortalAPIResponse:
raise
def refresh_jwt_token(self, username: str, token: str, resource: str = _DEFAULT_TOKEN_RESOURCE,
set_api_token: bool = True) -> Authentication:
"""Refreshes a User Token (JWT) with DCSO Portal
JWTs (JSON Web Tokens) usually have a short lifespan, but they can be refreshed
provided the previous `token` is still valid. The `username` and `resource` must
match those of the original token. A new token will be returned, rendering
previous unusable.
An Authentication instance is returned which contains general user information,
token, and details about this token.
Raises `PortalAPIError` When the GraphQL API endpoint returned an error.
When there was an issue with the request itself, or decoding JSON failed,
the `PortalAPIRequest` exception is raised.
"""
variables = {
"portalauth": {
"username": username,
"refreshToken": token,
"resource": resource,
}
}
request = GraphQLRequest(api_url=self._api.api_url,
query=_GRAPHQL_MUTATION_AUTHN, variables=variables)
try:
response = request.execute_dict()
except PortalException as exc:
raise
try:
authn = Authentication(graphql_response=response['data']['portalauth'])
if set_api_token:
self._api.token = authn.token.token
return authn
except PortalAPIResponse:
raise
_GRAPHQL_MUTATION_AUTHN = """
mutation ($portalauth: auth_AuthorizationInput!) {
portalauth: auth_createAuthorization(input: $portalauth) {
user {
id
username
accessTo {
service { id code }
group { code }
}
}
token
isTemporaryToken
otp {
required
activated
}
otpSVGQRCode
}
}
"""
Classes
class Auth (api: APIAbstract)
-
Auth provides authentication, authorization, and user management.
Typical use:
api = APIClient('https://api.example.com/api') auth = Auth(api) user = auth.authenticate("alice", "alice.password")
Expand source code
class Auth(RBACMixin): """Auth provides authentication, authorization, and user management. Typical use: api = APIClient('https://api.example.com/api') auth = Auth(api) user = auth.authenticate("alice", "alice.password") """ @property def token(self) -> str: return self._api.token @token.setter def token(self, token: str) -> None: self._api.token = token def __init__(self, api: APIAbstract): self._api: APIAbstract = api def authenticate(self, username: str, password: str, resource: str = _DEFAULT_TOKEN_RESOURCE, set_api_token: bool = True) -> Authentication: """Authenticates with DCSO Portal using credentials or `username` and `password`, for the given `resource`. Resource is by default `PortalPythonSDK`, but it can be, for example, the name of the application or script. When the authenticating user has two-factor authentication (2FA) enforced, a second pass is needed using the temporary token received with by method and passed on to the `second_authentication_totp` method. When `set_api_token` is True, and authentication succeeds, the non-temporary token will be stored and used for further API queries. An Authentication instance is returned which contains general user information, token, and details about this token. Raises `PortalAPIError` When the GraphQL API endpoint returned an error. When there was an issue with the request itself, or decoding JSON failed, the `PortalAPIRequest` exception is raised. """ variables = { "portalauth": { "username": username, "password": password, "resource": resource } } request = GraphQLRequest(api_url=self._api.api_url, query=_GRAPHQL_MUTATION_AUTHN, variables=variables) try: response = request.execute_dict() except PortalException: raise try: authn = Authentication(graphql_response=response['data']['portalauth']) if not authn.token_is_temporary and set_api_token: self._api.token = authn.token.token return authn except PortalAPIResponse: raise def second_authentication_totp(self, username: str, temp_token: str, totp: str, set_api_token: bool = True, resource: str = _DEFAULT_TOKEN_RESOURCE) -> Authentication: """Two-factor authentication using Time-based One-Time Password (TOTP). The `username` must be the same which was used with the `authenticate` method. The `temp_token` argument is set to the temporary token returned by the method `authenticate`. Finally, the `totp` argument is the code visible in the authenticator application which holds the TOTP secret. The `resource` argument is by default `PortalPythonSDK`, but it can be, for example, the name of the application or script. This must match the resource that was used with `authenticate()`. When `set_api_token` is True, and authentication succeeds, the non-temporary token will be stored and used for further API queries. An Authentication instance is returned which contains general user information, token, and details about this token. Raises `PortalAPIError` When the GraphQL API endpoint returned an error. When there was an issue with the request itself, or decoding JSON failed, the `PortalAPIRequest` exception is raised. """ variables = { "portalauth": { "username": username, "temporaryToken": temp_token, "otpCode": totp, "resource": resource, } } request = GraphQLRequest(api_url=self._api.api_url, query=_GRAPHQL_MUTATION_AUTHN, variables=variables) try: response = request.execute_dict() except PortalException: raise try: authn = Authentication(graphql_response=response['data']['portalauth']) if set_api_token: self._api.token = authn.token.token return authn except PortalAPIResponse: raise def refresh_jwt_token(self, username: str, token: str, resource: str = _DEFAULT_TOKEN_RESOURCE, set_api_token: bool = True) -> Authentication: """Refreshes a User Token (JWT) with DCSO Portal JWTs (JSON Web Tokens) usually have a short lifespan, but they can be refreshed provided the previous `token` is still valid. The `username` and `resource` must match those of the original token. A new token will be returned, rendering previous unusable. An Authentication instance is returned which contains general user information, token, and details about this token. Raises `PortalAPIError` When the GraphQL API endpoint returned an error. When there was an issue with the request itself, or decoding JSON failed, the `PortalAPIRequest` exception is raised. """ variables = { "portalauth": { "username": username, "refreshToken": token, "resource": resource, } } request = GraphQLRequest(api_url=self._api.api_url, query=_GRAPHQL_MUTATION_AUTHN, variables=variables) try: response = request.execute_dict() except PortalException as exc: raise try: authn = Authentication(graphql_response=response['data']['portalauth']) if set_api_token: self._api.token = authn.token.token return authn except PortalAPIResponse: raise
Ancestors
Instance variables
var token : str
-
Expand source code
@property def token(self) -> str: return self._api.token
Methods
def authenticate(self, username: str, password: str, resource: str = 'PortalPythonSDK', set_api_token: bool = True) ‑> Authentication
-
Authenticates with DCSO Portal using credentials or
username
andpassword
, for the givenresource
. Resource is by defaultPortalPythonSDK
, but it can be, for example, the name of the application or script.When the authenticating user has two-factor authentication (2FA) enforced, a second pass is needed using the temporary token received with by method and passed on to the
second_authentication_totp
method.When
set_api_token
is True, and authentication succeeds, the non-temporary token will be stored and used for further API queries.An Authentication instance is returned which contains general user information, token, and details about this token.
Raises
PortalAPIError
When the GraphQL API endpoint returned an error. When there was an issue with the request itself, or decoding JSON failed, thePortalAPIRequest
exception is raised.Expand source code
def authenticate(self, username: str, password: str, resource: str = _DEFAULT_TOKEN_RESOURCE, set_api_token: bool = True) -> Authentication: """Authenticates with DCSO Portal using credentials or `username` and `password`, for the given `resource`. Resource is by default `PortalPythonSDK`, but it can be, for example, the name of the application or script. When the authenticating user has two-factor authentication (2FA) enforced, a second pass is needed using the temporary token received with by method and passed on to the `second_authentication_totp` method. When `set_api_token` is True, and authentication succeeds, the non-temporary token will be stored and used for further API queries. An Authentication instance is returned which contains general user information, token, and details about this token. Raises `PortalAPIError` When the GraphQL API endpoint returned an error. When there was an issue with the request itself, or decoding JSON failed, the `PortalAPIRequest` exception is raised. """ variables = { "portalauth": { "username": username, "password": password, "resource": resource } } request = GraphQLRequest(api_url=self._api.api_url, query=_GRAPHQL_MUTATION_AUTHN, variables=variables) try: response = request.execute_dict() except PortalException: raise try: authn = Authentication(graphql_response=response['data']['portalauth']) if not authn.token_is_temporary and set_api_token: self._api.token = authn.token.token return authn except PortalAPIResponse: raise
def refresh_jwt_token(self, username: str, token: str, resource: str = 'PortalPythonSDK', set_api_token: bool = True) ‑> Authentication
-
Refreshes a User Token (JWT) with DCSO Portal
JWTs (JSON Web Tokens) usually have a short lifespan, but they can be refreshed provided the previous
token
is still valid. Theusername
andresource
must match those of the original token. A new token will be returned, rendering previous unusable.An Authentication instance is returned which contains general user information, token, and details about this token.
Raises
PortalAPIError
When the GraphQL API endpoint returned an error. When there was an issue with the request itself, or decoding JSON failed, thePortalAPIRequest
exception is raised.Expand source code
def refresh_jwt_token(self, username: str, token: str, resource: str = _DEFAULT_TOKEN_RESOURCE, set_api_token: bool = True) -> Authentication: """Refreshes a User Token (JWT) with DCSO Portal JWTs (JSON Web Tokens) usually have a short lifespan, but they can be refreshed provided the previous `token` is still valid. The `username` and `resource` must match those of the original token. A new token will be returned, rendering previous unusable. An Authentication instance is returned which contains general user information, token, and details about this token. Raises `PortalAPIError` When the GraphQL API endpoint returned an error. When there was an issue with the request itself, or decoding JSON failed, the `PortalAPIRequest` exception is raised. """ variables = { "portalauth": { "username": username, "refreshToken": token, "resource": resource, } } request = GraphQLRequest(api_url=self._api.api_url, query=_GRAPHQL_MUTATION_AUTHN, variables=variables) try: response = request.execute_dict() except PortalException as exc: raise try: authn = Authentication(graphql_response=response['data']['portalauth']) if set_api_token: self._api.token = authn.token.token return authn except PortalAPIResponse: raise
def second_authentication_totp(self, username: str, temp_token: str, totp: str, set_api_token: bool = True, resource: str = 'PortalPythonSDK') ‑> Authentication
-
Two-factor authentication using Time-based One-Time Password (TOTP).
The
username
must be the same which was used with theauthenticate
method. Thetemp_token
argument is set to the temporary token returned by the methodauthenticate
. Finally, thetotp
argument is the code visible in the authenticator application which holds the TOTP secret.The
resource
argument is by defaultPortalPythonSDK
, but it can be, for example, the name of the application or script. This must match the resource that was used withauthenticate()
.When
set_api_token
is True, and authentication succeeds, the non-temporary token will be stored and used for further API queries.An Authentication instance is returned which contains general user information, token, and details about this token.
Raises
PortalAPIError
When the GraphQL API endpoint returned an error. When there was an issue with the request itself, or decoding JSON failed, thePortalAPIRequest
exception is raised.Expand source code
def second_authentication_totp(self, username: str, temp_token: str, totp: str, set_api_token: bool = True, resource: str = _DEFAULT_TOKEN_RESOURCE) -> Authentication: """Two-factor authentication using Time-based One-Time Password (TOTP). The `username` must be the same which was used with the `authenticate` method. The `temp_token` argument is set to the temporary token returned by the method `authenticate`. Finally, the `totp` argument is the code visible in the authenticator application which holds the TOTP secret. The `resource` argument is by default `PortalPythonSDK`, but it can be, for example, the name of the application or script. This must match the resource that was used with `authenticate()`. When `set_api_token` is True, and authentication succeeds, the non-temporary token will be stored and used for further API queries. An Authentication instance is returned which contains general user information, token, and details about this token. Raises `PortalAPIError` When the GraphQL API endpoint returned an error. When there was an issue with the request itself, or decoding JSON failed, the `PortalAPIRequest` exception is raised. """ variables = { "portalauth": { "username": username, "temporaryToken": temp_token, "otpCode": totp, "resource": resource, } } request = GraphQLRequest(api_url=self._api.api_url, query=_GRAPHQL_MUTATION_AUTHN, variables=variables) try: response = request.execute_dict() except PortalException: raise try: authn = Authentication(graphql_response=response['data']['portalauth']) if set_api_token: self._api.token = authn.token.token return authn except PortalAPIResponse: raise
Inherited members
class Authentication (graphql_response: Union[dict, NoneType])
-
Expand source code
class Authentication: def __init__(self, graphql_response: Optional[dict]): self.id: str = '' self.username: str = '' self.token: Optional[Token] = None self.token_is_temporary: bool = False self.groups = [] # legacy: replaced by permissions self.totp_required: bool = True self.totp_activated: Optional[datetime] = None self.totp_barcode: Optional[str] = None if graphql_response: self._handle_graphql_response(graphql_response) def _handle_graphql_response(self, res: dict): try: self.token = Token(graphql_response=res) if res['user']: self.id = res['user']['id'] self.username = res['user']['username'] self.totp_required = res['otp']['required'] self.totp_activated = res['otp']['activated'] self.token_is_temporary = res['isTemporaryToken'] self.totp_barcode = res['otpSVGQRCode'] except KeyError as exc: raise PortalAPIResponse(f"failed handling authentication response ({exc})")