# A web application that stores samples from a collection of NFC sensors.
#
# https://github.com/cuplsensor/cuplbackend
#
# Original Author: Malcolm Mackay
# Email: malcolm@plotsensor.com
# Website: https://cupl.co.uk
#
# Copyright (c) 2021. Plotsensor Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation, either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the
# GNU Affero General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>.
"""
backendapp.api.tokenauth
~~~~~~~~~~~~~~~~~~~~~~~~~
Access to some API resources is controlled via token-based authentication.
https://www.okta.com/uk/identity-101/what-is-token-based-authentication/
For the Admin API, nearly all resources require an admintoken. This can only be obtained with knowledge of a secret key.
It is read from an environment variable known only to a system administrator.
For the Consumer API, some resources require a tagtoken. These prove that an end-user has recently
captured a tag and therefore has physical access to a device.
"""
from flask import request
from jose import jwt
from flask_restful import abort
from typing import Union
import traceback
[docs]class TokenAuth:
"""
Token authenticator base class.
"""
[docs] def __init__(self, issuer: str, audience: str):
"""
Instantiate a token authenticator. A JSON Web Token includes a header, a payload and a signature. For more
information see https://jwt.io/.
A token is rejected if the payload does not include a set of claims. Each is a key-value pair.
Specify the expected values of two standard claims: issuer and audience.
https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields
:param issuer: Identifies the principal that issued the token (this application).
:param audience: Identifies the audience that the token is intended for.
"""
self.issuer = issuer
self.audience = audience
[docs] def verify_token(self, token: str, key: Union[str, dict]) -> dict:
"""
Decode a JSON Web Token and verify its signature with a key. Confirm that the received token
has expected values for the audience and issuer claims in its payload.
If verification fails, :py:func:`abort` is called. This raises an HTTP Exception with
the 401 unauthorized status.
:param token: The JSON Web Token.
:param key: Either an individual JSON Web Key or a JWK set.
:return: The decoded JSON Web Token.
"""
try:
decoded = jwt.decode(
token,
key,
algorithms=self.algorithms,
audience=self.audience,
issuer=self.issuer
)
except jwt.ExpiredSignatureError:
abort(401, description="token is expired")
except jwt.JWTClaimsError:
abort(401, description="incorrect claims please check the audience and issuer")
except jwt.JWTError:
abort(401, description="bad token")
except Exception:
abort(400, description=traceback.format_exc())
return {'decoded': decoded, 'token': token}
[docs]class TokenAuthSymmetric(TokenAuth):
"""
An authenticator for tokens with signatures that are encrypted and decrypted with the same key.
"""
[docs] def __init__(self, issuer: str, audience: str, secret: str):
"""
Instantiate an authenticator for tokens with symmetrically encrypted signatures. Verification fails
when the signature is not decrypted with the same secret key used for encryption.
:param issuer: The principal that issued the token (this application).
:param audience: The audience that the token is intended for.
:param secret: Used to generate and verify the token signature. Must not be shared outside this application.
"""
super().__init__(issuer, audience)
self.secret = secret
self.algorithms = ["HS256"]
[docs] def get_decoded_token(self) -> dict:
"""
Obtain a JSON Web Token from the authorization HTTP header and verify that it was issued by this
application, using a secret known only to this application.
:return: The decoded token.
"""
unverified_token = get_token_auth_header()
return self.verify_token(token=unverified_token, key=self.secret)