aboutsummaryrefslogtreecommitdiff
path: root/gn3
diff options
context:
space:
mode:
Diffstat (limited to 'gn3')
-rw-r--r--gn3/app.py2
-rw-r--r--gn3/auth/__init__.py1
-rw-r--r--gn3/auth/authentication/__init__.py24
-rw-r--r--gn3/auth/authentication/exceptions.py4
-rw-r--r--gn3/auth/authentication/oauth2/__init__.py0
-rw-r--r--gn3/auth/authentication/oauth2/endpoints/__init__.py0
-rw-r--r--gn3/auth/authentication/oauth2/endpoints/introspection.py48
-rw-r--r--gn3/auth/authentication/oauth2/endpoints/revocation.py22
-rw-r--r--gn3/auth/authentication/oauth2/endpoints/utilities.py31
-rw-r--r--gn3/auth/authentication/oauth2/grants/__init__.py0
-rw-r--r--gn3/auth/authentication/oauth2/grants/authorisation_code_grant.py85
-rw-r--r--gn3/auth/authentication/oauth2/grants/password_grant.py22
-rw-r--r--gn3/auth/authentication/oauth2/models/__init__.py0
-rw-r--r--gn3/auth/authentication/oauth2/models/authorization_code.py93
-rw-r--r--gn3/auth/authentication/oauth2/server.py72
-rw-r--r--gn3/auth/authentication/oauth2/views.py104
-rw-r--r--gn3/auth/authorisation/checks.py3
-rw-r--r--gn3/auth/authorisation/data/views.py4
-rw-r--r--gn3/auth/authorisation/groups/models.py2
-rw-r--r--gn3/auth/authorisation/groups/views.py5
-rw-r--r--gn3/auth/authorisation/oauth2/oauth2client.py (renamed from gn3/auth/authentication/oauth2/models/oauth2client.py)2
-rw-r--r--gn3/auth/authorisation/oauth2/oauth2token.py (renamed from gn3/auth/authentication/oauth2/models/oauth2token.py)2
-rw-r--r--gn3/auth/authorisation/oauth2/resource_server.py (renamed from gn3/auth/authentication/oauth2/resource_server.py)2
-rw-r--r--gn3/auth/authorisation/privileges.py2
-rw-r--r--gn3/auth/authorisation/resources/checks.py2
-rw-r--r--gn3/auth/authorisation/resources/models.py2
-rw-r--r--gn3/auth/authorisation/resources/views.py4
-rw-r--r--gn3/auth/authorisation/roles/models.py2
-rw-r--r--gn3/auth/authorisation/roles/views.py3
-rw-r--r--gn3/auth/authorisation/users/__init__.py12
-rw-r--r--gn3/auth/authorisation/users/admin/__init__.py2
-rw-r--r--gn3/auth/authorisation/users/admin/ui.py27
-rw-r--r--gn3/auth/authorisation/users/admin/views.py230
-rw-r--r--gn3/auth/authorisation/users/base.py (renamed from gn3/auth/authentication/users.py)0
-rw-r--r--gn3/auth/authorisation/users/collections/views.py4
-rw-r--r--gn3/auth/authorisation/users/masquerade/__init__.py1
-rw-r--r--gn3/auth/authorisation/users/masquerade/models.py67
-rw-r--r--gn3/auth/authorisation/users/masquerade/views.py48
-rw-r--r--gn3/auth/authorisation/users/models.py2
-rw-r--r--gn3/auth/authorisation/users/views.py9
-rw-r--r--gn3/auth/views.py5
41 files changed, 34 insertions, 916 deletions
diff --git a/gn3/app.py b/gn3/app.py
index da87f2b..2a11131 100644
--- a/gn3/app.py
+++ b/gn3/app.py
@@ -27,7 +27,6 @@ from gn3.api.search import search
from gn3.api.metadata import metadata
from gn3.api.sampledata import sampledata
from gn3.auth import oauth2
-from gn3.auth.authentication.oauth2.server import setup_oauth2_server
def create_app(config: Union[Dict, str, None] = None) -> Flask:
@@ -79,5 +78,4 @@ def create_app(config: Union[Dict, str, None] = None) -> Flask:
app.register_blueprint(oauth2, url_prefix="/api/oauth2")
register_error_handlers(app)
- setup_oauth2_server(app)
return app
diff --git a/gn3/auth/__init__.py b/gn3/auth/__init__.py
index a28498d..cd65e9b 100644
--- a/gn3/auth/__init__.py
+++ b/gn3/auth/__init__.py
@@ -1,5 +1,4 @@
"""Top-Level `Auth` module"""
from . import authorisation
-from . import authentication
from .views import oauth2
diff --git a/gn3/auth/authentication/__init__.py b/gn3/auth/authentication/__init__.py
deleted file mode 100644
index 42ceacb..0000000
--- a/gn3/auth/authentication/__init__.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""Handle authentication requests"""
-
-import bcrypt
-
-def credentials_in_database(cursor, email: str, password: str) -> bool:
- """Check whether credentials are in the database."""
- if len(email.strip()) == 0 or len(password.strip()) == 0:
- return False
-
- cursor.execute(
- ("SELECT "
- "users.email, user_credentials.password "
- "FROM users LEFT JOIN user_credentials "
- "ON users.user_id = user_credentials.user_id "
- "WHERE users.email = :email"),
- {"email": email})
- results = cursor.fetchall()
- if len(results) == 0:
- return False
-
- assert len(results) == 1, "Expected one row."
- row = results[0]
- return (email == row[0] and
- bcrypt.checkpw(password.encode("utf-8"), row[1]))
diff --git a/gn3/auth/authentication/exceptions.py b/gn3/auth/authentication/exceptions.py
deleted file mode 100644
index c31e691..0000000
--- a/gn3/auth/authentication/exceptions.py
+++ /dev/null
@@ -1,4 +0,0 @@
-"""Exceptions for authentication"""
-
-class AuthenticationError(Exception):
- """Base exception class for `gn3.auth.authentication` package."""
diff --git a/gn3/auth/authentication/oauth2/__init__.py b/gn3/auth/authentication/oauth2/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gn3/auth/authentication/oauth2/__init__.py
+++ /dev/null
diff --git a/gn3/auth/authentication/oauth2/endpoints/__init__.py b/gn3/auth/authentication/oauth2/endpoints/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gn3/auth/authentication/oauth2/endpoints/__init__.py
+++ /dev/null
diff --git a/gn3/auth/authentication/oauth2/endpoints/introspection.py b/gn3/auth/authentication/oauth2/endpoints/introspection.py
deleted file mode 100644
index cfe2998..0000000
--- a/gn3/auth/authentication/oauth2/endpoints/introspection.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""Handle introspection of tokens."""
-import datetime
-from urllib.parse import urlparse
-
-from flask import request as flask_request
-from authlib.oauth2.rfc7662 import (
- IntrospectionEndpoint as _IntrospectionEndpoint)
-
-from gn3.auth.authentication.oauth2.models.oauth2token import OAuth2Token
-
-from .utilities import query_token as _query_token
-
-def get_token_user_sub(token: OAuth2Token) -> str:# pylint: disable=[unused-argument]
- """
- Return the token's subject as defined in
- https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2
- """
- ## For now a dummy return to prevent issues.
- return "sub"
-
-class IntrospectionEndpoint(_IntrospectionEndpoint):
- """Introspect token."""
- def query_token(self, token_string: str, token_type_hint: str):
- """Query the token."""
- return _query_token(self, token_string, token_type_hint)
-
- def introspect_token(self, token: OAuth2Token) -> dict:
- """Return the introspection information."""
- url = urlparse(flask_request.url)
- return {
- "active": True,
- "scope": token.get_scope(),
- "client_id": token.client.client_id,
- "username": token.user.name,
- "token_type": token.token_type,
- "exp": int(token.expires_at.timestamp()),
- "iat": int(token.issued_at.timestamp()),
- "nbf": int(
- (token.issued_at - datetime.timedelta(seconds=120)).timestamp()),
- # "sub": get_token_user_sub(token),
- "aud": token.client.client_id,
- "iss": f"{url.scheme}://{url.netloc}",
- "jti": token.token_id
- }
-
- def check_permission(self, token, client, request):
- """Check that the client has permission to introspect token."""
- return client.client_type == "internal"
diff --git a/gn3/auth/authentication/oauth2/endpoints/revocation.py b/gn3/auth/authentication/oauth2/endpoints/revocation.py
deleted file mode 100644
index b8517b6..0000000
--- a/gn3/auth/authentication/oauth2/endpoints/revocation.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""Handle token revocation."""
-
-from flask import current_app
-from authlib.oauth2.rfc7009 import RevocationEndpoint as _RevocationEndpoint
-
-from gn3.auth import db
-from gn3.auth.authentication.oauth2.models.oauth2token import (
- save_token, OAuth2Token, revoke_token)
-
-from .utilities import query_token as _query_token
-
-class RevocationEndpoint(_RevocationEndpoint):
- """Revoke the tokens"""
- ENDPOINT_NAME = "revoke"
- def query_token(self, token_string: str, token_type_hint: str):
- """Query the token."""
- return _query_token(self, token_string, token_type_hint)
-
- def revoke_token(self, token: OAuth2Token, request):
- """Revoke token `token`."""
- with db.connection(current_app.config["AUTH_DB"]) as conn:
- save_token(conn, revoke_token(token))
diff --git a/gn3/auth/authentication/oauth2/endpoints/utilities.py b/gn3/auth/authentication/oauth2/endpoints/utilities.py
deleted file mode 100644
index e13784e..0000000
--- a/gn3/auth/authentication/oauth2/endpoints/utilities.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""endpoint utilities"""
-from typing import Any, Optional
-
-from flask import current_app
-from pymonad.maybe import Nothing
-
-from gn3.auth import db
-from gn3.auth.authentication.oauth2.models.oauth2token import (
- OAuth2Token, token_by_access_token, token_by_refresh_token)
-
-def query_token(# pylint: disable=[unused-argument]
- endpoint_object: Any, token_str: str, token_type_hint) -> Optional[
- OAuth2Token]:
- """Retrieve the token from the database."""
- def __identity__(val):
- return val
- token = Nothing
- with db.connection(current_app.config["AUTH_DB"]) as conn:
- if token_type_hint == "access_token":
- token = token_by_access_token(conn, token_str)
- if token_type_hint == "access_token":
- token = token_by_refresh_token(conn, token_str)
-
- return token.maybe(
- token_by_access_token(conn, token_str).maybe(
- token_by_refresh_token(conn, token_str).maybe(
- None, __identity__),
- __identity__),
- __identity__)
-
- return None
diff --git a/gn3/auth/authentication/oauth2/grants/__init__.py b/gn3/auth/authentication/oauth2/grants/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gn3/auth/authentication/oauth2/grants/__init__.py
+++ /dev/null
diff --git a/gn3/auth/authentication/oauth2/grants/authorisation_code_grant.py b/gn3/auth/authentication/oauth2/grants/authorisation_code_grant.py
deleted file mode 100644
index fb8d436..0000000
--- a/gn3/auth/authentication/oauth2/grants/authorisation_code_grant.py
+++ /dev/null
@@ -1,85 +0,0 @@
-"""Classes and function for Authorisation Code flow."""
-import uuid
-import string
-import random
-from typing import Optional
-from datetime import datetime
-
-from flask import current_app as app
-from authlib.oauth2.rfc6749 import grants
-from authlib.oauth2.rfc7636 import create_s256_code_challenge
-
-from gn3.auth import db
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authentication.users import User
-
-from ..models.oauth2client import OAuth2Client
-from ..models.authorization_code import (
- AuthorisationCode, authorisation_code, save_authorisation_code)
-
-class AuthorisationCodeGrant(grants.AuthorizationCodeGrant):
- """Implement the 'Authorisation Code' grant."""
- TOKEN_ENDPOINT_AUTH_METHODS: list[str] = [
- "client_secret_basic", "client_secret_post"]
- AUTHORIZATION_CODE_LENGTH: int = 48
- TOKEN_ENDPOINT_HTTP_METHODS = ['POST']
- GRANT_TYPE = "authorization_code"
- RESPONSE_TYPES = {'code'}
-
- def save_authorization_code(self, code, request):
- """Persist the authorisation code to database."""
- client = request.client
- nonce = "".join(random.sample(string.ascii_letters + string.digits,
- k=self.AUTHORIZATION_CODE_LENGTH))
- return __save_authorization_code__(AuthorisationCode(
- uuid.uuid4(), code, client, request.redirect_uri, request.scope,
- nonce, int(datetime.now().timestamp()),
- create_s256_code_challenge(app.config["SECRET_KEY"]),
- "S256", request.user))
-
- def query_authorization_code(self, code, client):
- """Retrieve the code from the database."""
- return __query_authorization_code__(code, client)
-
- def delete_authorization_code(self, authorization_code):
- """Delete the authorisation code."""
- with db.connection(app.config["AUTH_DB"]) as conn:
- with db.cursor(conn) as cursor:
- cursor.execute(
- "DELETE FROM authorisation_code WHERE code_id=?",
- (str(authorization_code.code_id),))
-
- def authenticate_user(self, authorization_code) -> Optional[User]:
- """Authenticate the user who own the authorisation code."""
- query = (
- "SELECT users.* FROM authorisation_code LEFT JOIN users "
- "ON authorisation_code.user_id=users.user_id "
- "WHERE authorisation_code.code=?")
- with db.connection(app.config["AUTH_DB"]) as conn:
- with db.cursor(conn) as cursor:
- cursor.execute(query, (str(authorization_code.code),))
- res = cursor.fetchone()
- if res:
- return User(
- uuid.UUID(res["user_id"]), res["email"], res["name"])
-
- return None
-
-def __query_authorization_code__(
- code: str, client: OAuth2Client) -> AuthorisationCode:
- """A helper function that creates a new database connection.
-
- This is found to be necessary since the `AuthorizationCodeGrant` class(es)
- do not have a way to pass the database connection."""
- def __auth_code__(conn) -> str:
- the_code = authorisation_code(conn, code, client)
- return the_code.maybe(None, lambda cde: cde) # type: ignore[misc, arg-type, return-value]
-
- return with_db_connection(__auth_code__)
-
-def __save_authorization_code__(code: AuthorisationCode) -> AuthorisationCode:
- """A helper function that creates a new database connection.
-
- This is found to be necessary since the `AuthorizationCodeGrant` class(es)
- do not have a way to pass the database connection."""
- return with_db_connection(lambda conn: save_authorisation_code(conn, code))
diff --git a/gn3/auth/authentication/oauth2/grants/password_grant.py b/gn3/auth/authentication/oauth2/grants/password_grant.py
deleted file mode 100644
index 3233877..0000000
--- a/gn3/auth/authentication/oauth2/grants/password_grant.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""Allows users to authenticate directly."""
-
-from flask import current_app as app
-from authlib.oauth2.rfc6749 import grants
-
-from gn3.auth import db
-from gn3.auth.authentication.users import valid_login, user_by_email
-
-from gn3.auth.authorisation.errors import NotFoundError
-
-class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
- """Implement the 'Password' grant."""
- TOKEN_ENDPOINT_AUTH_METHODS = ["client_secret_basic", "client_secret_post"]
-
- def authenticate_user(self, username, password):
- "Authenticate the user with their username and password."
- with db.connection(app.config["AUTH_DB"]) as conn:
- try:
- user = user_by_email(conn, username)
- return user if valid_login(conn, user, password) else None
- except NotFoundError as _nfe:
- return None
diff --git a/gn3/auth/authentication/oauth2/models/__init__.py b/gn3/auth/authentication/oauth2/models/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gn3/auth/authentication/oauth2/models/__init__.py
+++ /dev/null
diff --git a/gn3/auth/authentication/oauth2/models/authorization_code.py b/gn3/auth/authentication/oauth2/models/authorization_code.py
deleted file mode 100644
index f282814..0000000
--- a/gn3/auth/authentication/oauth2/models/authorization_code.py
+++ /dev/null
@@ -1,93 +0,0 @@
-"""Model and functions for handling the Authorisation Code"""
-from uuid import UUID
-from datetime import datetime
-from typing import NamedTuple
-
-from pymonad.maybe import Just, Maybe, Nothing
-
-from gn3.auth import db
-
-from .oauth2client import OAuth2Client
-
-from ...users import User, user_by_id
-
-__5_MINUTES__ = 300 # in seconds
-
-class AuthorisationCode(NamedTuple):
- """
- The AuthorisationCode model for the auth(entic|oris)ation system.
- """
- # Instance variables
- code_id: UUID
- code: str
- client: OAuth2Client
- redirect_uri: str
- scope: str
- nonce: str
- auth_time: int
- code_challenge: str
- code_challenge_method: str
- user: User
-
- @property
- def response_type(self) -> str:
- """
- For authorisation code flow, the response_type type MUST always be
- 'code'.
- """
- return "code"
-
- def is_expired(self):
- """Check whether the code is expired."""
- return self.auth_time + __5_MINUTES__ < datetime.now().timestamp()
-
- def get_redirect_uri(self):
- """Get the redirect URI"""
- return self.redirect_uri
-
- def get_scope(self):
- """Return the assigned scope for this AuthorisationCode."""
- return self.scope
-
- def get_nonce(self):
- """Get the one-time use token."""
- return self.nonce
-
-def authorisation_code(conn: db.DbConnection ,
- code: str,
- client: OAuth2Client) -> Maybe[AuthorisationCode]:
- """
- Retrieve the authorisation code object that corresponds to `code` and the
- given OAuth2 client.
- """
- with db.cursor(conn) as cursor:
- query = ("SELECT * FROM authorisation_code "
- "WHERE code=:code AND client_id=:client_id")
- cursor.execute(
- query, {"code": code, "client_id": str(client.client_id)})
- result = cursor.fetchone()
- if result:
- return Just(AuthorisationCode(
- UUID(result["code_id"]), result["code"], client,
- result["redirect_uri"], result["scope"], result["nonce"],
- int(result["auth_time"]), result["code_challenge"],
- result["code_challenge_method"],
- user_by_id(conn, UUID(result["user_id"]))))
- return Nothing
-
-def save_authorisation_code(conn: db.DbConnection,
- auth_code: AuthorisationCode) -> AuthorisationCode:
- """Persist the `auth_code` into the database."""
- with db.cursor(conn) as cursor:
- cursor.execute(
- "INSERT INTO authorisation_code VALUES("
- ":code_id, :code, :client_id, :redirect_uri, :scope, :nonce, "
- ":auth_time, :code_challenge, :code_challenge_method, :user_id"
- ")",
- {
- **auth_code._asdict(),
- "code_id": str(auth_code.code_id),
- "client_id": str(auth_code.client.client_id),
- "user_id": str(auth_code.user.user_id)
- })
- return auth_code
diff --git a/gn3/auth/authentication/oauth2/server.py b/gn3/auth/authentication/oauth2/server.py
deleted file mode 100644
index 7d7113a..0000000
--- a/gn3/auth/authentication/oauth2/server.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""Initialise the OAuth2 Server"""
-import uuid
-import datetime
-from typing import Callable
-
-from flask import Flask, current_app
-from authlib.oauth2.rfc6749.errors import InvalidClientError
-from authlib.integrations.flask_oauth2 import AuthorizationServer
-# from authlib.oauth2.rfc7636 import CodeChallenge
-
-from gn3.auth import db
-
-from .models.oauth2client import client
-from .models.oauth2token import OAuth2Token, save_token
-
-from .grants.password_grant import PasswordGrant
-from .grants.authorisation_code_grant import AuthorisationCodeGrant
-
-from .endpoints.revocation import RevocationEndpoint
-from .endpoints.introspection import IntrospectionEndpoint
-
-def create_query_client_func() -> Callable:
- """Create the function that loads the client."""
- def __query_client__(client_id: uuid.UUID):
- # use current_app rather than passing the db_uri to avoid issues
- # when config changes, e.g. while testing.
- with db.connection(current_app.config["AUTH_DB"]) as conn:
- the_client = client(conn, client_id).maybe(
- None, lambda clt: clt) # type: ignore[misc]
- if bool(the_client):
- return the_client
- raise InvalidClientError(
- "No client found for the given CLIENT_ID and CLIENT_SECRET.")
-
- return __query_client__
-
-def create_save_token_func(token_model: type) -> Callable:
- """Create the function that saves the token."""
- def __save_token__(token, request):
- with db.connection(current_app.config["AUTH_DB"]) as conn:
- save_token(
- conn, token_model(
- token_id=uuid.uuid4(), client=request.client,
- user=request.user,
- **{
- "refresh_token": None, "revoked": False,
- "issued_at": datetime.datetime.now(),
- **token
- }))
-
- return __save_token__
-
-def setup_oauth2_server(app: Flask) -> None:
- """Set's up the oauth2 server for the flask application."""
- server = AuthorizationServer()
- server.register_grant(PasswordGrant)
-
- # Figure out a common `code_verifier` for GN2 and GN3 and set
- # server.register_grant(AuthorisationCodeGrant, [CodeChallenge(required=False)])
- # below
- server.register_grant(AuthorisationCodeGrant)
-
- # register endpoints
- server.register_endpoint(RevocationEndpoint)
- server.register_endpoint(IntrospectionEndpoint)
-
- # init server
- server.init_app(
- app,
- query_client=create_query_client_func(),
- save_token=create_save_token_func(OAuth2Token))
- app.config["OAUTH2_SERVER"] = server
diff --git a/gn3/auth/authentication/oauth2/views.py b/gn3/auth/authentication/oauth2/views.py
deleted file mode 100644
index 2bd3865..0000000
--- a/gn3/auth/authentication/oauth2/views.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""Endpoints for the oauth2 server"""
-import uuid
-import traceback
-
-from authlib.oauth2.rfc6749.errors import InvalidClientError
-from email_validator import validate_email, EmailNotValidError
-from flask import (
- flash,
- request,
- url_for,
- redirect,
- Response,
- Blueprint,
- render_template,
- current_app as app)
-
-from gn3.auth import db
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.errors import ForbiddenAccess
-
-from .resource_server import require_oauth
-from .endpoints.revocation import RevocationEndpoint
-from .endpoints.introspection import IntrospectionEndpoint
-
-from ..users import valid_login, NotFoundError, user_by_email
-
-auth = Blueprint("auth", __name__)
-
-@auth.route("/delete-client/<uuid:client_id>", methods=["GET", "POST"])
-def delete_client(client_id: uuid.UUID):
- """Delete an OAuth2 client."""
- return f"WOULD DELETE OAUTH2 CLIENT {client_id}."
-
-@auth.route("/authorise", methods=["GET", "POST"])
-def authorise():
- """Authorise a user"""
- try:
- server = app.config["OAUTH2_SERVER"]
- client_id = uuid.UUID(request.args.get(
- "client_id",
- request.form.get("client_id", str(uuid.uuid4()))))
- client = server.query_client(client_id)
- if not bool(client):
- flash("Invalid OAuth2 client.", "alert-error")
- if request.method == "GET":
- client = server.query_client(request.args.get("client_id"))
- return render_template(
- "oauth2/authorise-user.html",
- client=client,
- scope=client.scope,
- response_type="code")
-
- form = request.form
- def __authorise__(conn: db.DbConnection) -> Response:
- email_passwd_msg = "Email or password is invalid!"
- redirect_response = redirect(url_for("oauth2.auth.authorise",
- client_id=client_id))
- try:
- email = validate_email(
- form.get("user:email"), check_deliverability=False)
- user = user_by_email(conn, email["email"])
- if valid_login(conn, user, form.get("user:password", "")):
- return server.create_authorization_response(request=request, grant_user=user)
- flash(email_passwd_msg, "alert-error")
- return redirect_response # type: ignore[return-value]
- except EmailNotValidError as _enve:
- app.logger.debug(traceback.format_exc())
- flash(email_passwd_msg, "alert-error")
- return redirect_response # type: ignore[return-value]
- except NotFoundError as _nfe:
- app.logger.debug(traceback.format_exc())
- flash(email_passwd_msg, "alert-error")
- return redirect_response # type: ignore[return-value]
-
- return with_db_connection(__authorise__)
- except InvalidClientError as ice:
- return render_template(
- "oauth2/oauth2_error.html", error=ice), ice.status_code
-
-@auth.route("/token", methods=["POST"])
-def token():
- """Retrieve the authorisation token."""
- server = app.config["OAUTH2_SERVER"]
- return server.create_token_response()
-
-@auth.route("/revoke", methods=["POST"])
-def revoke_token():
- """Revoke the token."""
- return app.config["OAUTH2_SERVER"].create_endpoint_response(
- RevocationEndpoint.ENDPOINT_NAME)
-
-@auth.route("/introspect", methods=["POST"])
-@require_oauth("introspect")
-def introspect_token() -> Response:
- """Provide introspection information for the token."""
- # This is dangerous to provide publicly
- authorised_clients = app.config.get(
- "OAUTH2_CLIENTS_WITH_INTROSPECTION_PRIVILEGE", [])
- with require_oauth.acquire("introspect") as the_token:
- if the_token.client.client_id in authorised_clients:
- return app.config["OAUTH2_SERVER"].create_endpoint_response(
- IntrospectionEndpoint.ENDPOINT_NAME)
-
- raise ForbiddenAccess("You cannot access this endpoint")
diff --git a/gn3/auth/authorisation/checks.py b/gn3/auth/authorisation/checks.py
index 1c87c02..17daca4 100644
--- a/gn3/auth/authorisation/checks.py
+++ b/gn3/auth/authorisation/checks.py
@@ -5,12 +5,11 @@ from typing import Callable
from flask import request, current_app as app
from gn3.auth import db
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
from . import privileges as auth_privs
from .errors import InvalidData, AuthorisationError
-from ..authentication.oauth2.resource_server import require_oauth
-
def __system_privileges_in_roles__(conn, user):
"""
This really is a hack since groups are not treated as resources at the
diff --git a/gn3/auth/authorisation/data/views.py b/gn3/auth/authorisation/data/views.py
index 8adf862..81811dd 100644
--- a/gn3/auth/authorisation/data/views.py
+++ b/gn3/auth/authorisation/data/views.py
@@ -29,9 +29,9 @@ from gn3.auth.authorisation.resources.checks import authorised_for
from gn3.auth.authorisation.resources.models import (
user_resources, public_resources, attach_resources_data)
-from gn3.auth.authentication.users import User
-from gn3.auth.authentication.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.users import User
from gn3.auth.authorisation.data.phenotypes import link_phenotype_data
from gn3.auth.authorisation.data.mrna import link_mrna_data, ungrouped_mrna_data
from gn3.auth.authorisation.data.genotypes import (
diff --git a/gn3/auth/authorisation/groups/models.py b/gn3/auth/authorisation/groups/models.py
index 5a3ae50..7212a78 100644
--- a/gn3/auth/authorisation/groups/models.py
+++ b/gn3/auth/authorisation/groups/models.py
@@ -9,7 +9,7 @@ from pymonad.maybe import Just, Maybe, Nothing
from gn3.auth import db
from gn3.auth.dictify import dictify
-from gn3.auth.authentication.users import User, user_by_id
+from gn3.auth.authorisation.users import User, user_by_id
from ..checks import authorised_p
from ..privileges import Privilege
diff --git a/gn3/auth/authorisation/groups/views.py b/gn3/auth/authorisation/groups/views.py
index 628df36..a849a73 100644
--- a/gn3/auth/authorisation/groups/views.py
+++ b/gn3/auth/authorisation/groups/views.py
@@ -12,6 +12,8 @@ from gn3 import db_utils as gn3db
from gn3.auth.dictify import dictify
from gn3.auth.db_utils import with_db_connection
+from gn3.auth.authorisation.users import User
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
from .data import link_data_to_group
from .models import (
@@ -28,9 +30,6 @@ from ..checks import authorised_p
from ..privileges import Privilege, privileges_by_ids
from ..errors import InvalidData, NotFoundError, AuthorisationError
-from ...authentication.users import User
-from ...authentication.oauth2.resource_server import require_oauth
-
groups = Blueprint("groups", __name__)
@groups.route("/list", methods=["GET"])
diff --git a/gn3/auth/authentication/oauth2/models/oauth2client.py b/gn3/auth/authorisation/oauth2/oauth2client.py
index 2a307e3..dc54a41 100644
--- a/gn3/auth/authentication/oauth2/models/oauth2client.py
+++ b/gn3/auth/authorisation/oauth2/oauth2client.py
@@ -7,9 +7,9 @@ from typing import Sequence, Optional, NamedTuple
from pymonad.maybe import Just, Maybe, Nothing
from gn3.auth import db
-from gn3.auth.authentication.users import User, users, user_by_id, same_password
from gn3.auth.authorisation.errors import NotFoundError
+from gn3.auth.authorisation.users import User, users, user_by_id, same_password
class OAuth2Client(NamedTuple):
"""
diff --git a/gn3/auth/authentication/oauth2/models/oauth2token.py b/gn3/auth/authorisation/oauth2/oauth2token.py
index bfe4aaf..bb19039 100644
--- a/gn3/auth/authentication/oauth2/models/oauth2token.py
+++ b/gn3/auth/authorisation/oauth2/oauth2token.py
@@ -6,9 +6,9 @@ from typing import NamedTuple, Optional
from pymonad.maybe import Just, Maybe, Nothing
from gn3.auth import db
-from gn3.auth.authentication.users import User, user_by_id
from gn3.auth.authorisation.errors import NotFoundError
+from gn3.auth.authorisation.users import User, user_by_id
from .oauth2client import client, OAuth2Client
diff --git a/gn3/auth/authentication/oauth2/resource_server.py b/gn3/auth/authorisation/oauth2/resource_server.py
index 223e811..e806dc5 100644
--- a/gn3/auth/authentication/oauth2/resource_server.py
+++ b/gn3/auth/authorisation/oauth2/resource_server.py
@@ -5,7 +5,7 @@ from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator
from authlib.integrations.flask_oauth2 import ResourceProtector
from gn3.auth import db
-from gn3.auth.authentication.oauth2.models.oauth2token import token_by_access_token
+from gn3.auth.authorisation.oauth2.oauth2token import token_by_access_token
class BearerTokenValidator(_BearerTokenValidator):
"""Extends `authlib.oauth2.rfc6750.BearerTokenValidator`"""
diff --git a/gn3/auth/authorisation/privileges.py b/gn3/auth/authorisation/privileges.py
index dbb4129..7907d76 100644
--- a/gn3/auth/authorisation/privileges.py
+++ b/gn3/auth/authorisation/privileges.py
@@ -2,7 +2,7 @@
from typing import Any, Iterable, NamedTuple
from gn3.auth import db
-from gn3.auth.authentication.users import User
+from gn3.auth.authorisation.users import User
class Privilege(NamedTuple):
"""Class representing a privilege: creates immutable objects."""
diff --git a/gn3/auth/authorisation/resources/checks.py b/gn3/auth/authorisation/resources/checks.py
index fafde76..1f5a0f9 100644
--- a/gn3/auth/authorisation/resources/checks.py
+++ b/gn3/auth/authorisation/resources/checks.py
@@ -4,7 +4,7 @@ from functools import reduce
from typing import Sequence
from gn3.auth import db
-from gn3.auth.authentication.users import User
+from gn3.auth.authorisation.users import User
def __organise_privileges_by_resource_id__(rows):
def __organise__(privs, row):
diff --git a/gn3/auth/authorisation/resources/models.py b/gn3/auth/authorisation/resources/models.py
index b301a93..cf7769e 100644
--- a/gn3/auth/authorisation/resources/models.py
+++ b/gn3/auth/authorisation/resources/models.py
@@ -7,7 +7,7 @@ from typing import Any, Dict, Sequence, Optional, NamedTuple
from gn3.auth import db
from gn3.auth.dictify import dictify
-from gn3.auth.authentication.users import User
+from gn3.auth.authorisation.users import User
from gn3.auth.db_utils import with_db_connection
from .checks import authorised_for
diff --git a/gn3/auth/authorisation/resources/views.py b/gn3/auth/authorisation/resources/views.py
index 3b2bbeb..bda67cd 100644
--- a/gn3/auth/authorisation/resources/views.py
+++ b/gn3/auth/authorisation/resources/views.py
@@ -7,6 +7,8 @@ from functools import reduce
from flask import request, jsonify, Response, Blueprint, current_app as app
from gn3.auth.db_utils import with_db_connection
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.users import User, user_by_id, user_by_email
from .checks import authorised_for
from .models import (
@@ -21,8 +23,6 @@ from ..groups.models import Group, GroupRole, group_role_by_id
from ... import db
from ...dictify import dictify
-from ...authentication.oauth2.resource_server import require_oauth
-from ...authentication.users import User, user_by_id, user_by_email
resources = Blueprint("resources", __name__)
diff --git a/gn3/auth/authorisation/roles/models.py b/gn3/auth/authorisation/roles/models.py
index 97e11af..890d33b 100644
--- a/gn3/auth/authorisation/roles/models.py
+++ b/gn3/auth/authorisation/roles/models.py
@@ -7,7 +7,7 @@ from pymonad.either import Left, Right, Either
from gn3.auth import db
from gn3.auth.dictify import dictify
-from gn3.auth.authentication.users import User
+from gn3.auth.authorisation.users import User
from gn3.auth.authorisation.errors import AuthorisationError
from ..checks import authorised_p
diff --git a/gn3/auth/authorisation/roles/views.py b/gn3/auth/authorisation/roles/views.py
index 3670aab..d00e596 100644
--- a/gn3/auth/authorisation/roles/views.py
+++ b/gn3/auth/authorisation/roles/views.py
@@ -5,11 +5,10 @@ from flask import jsonify, Response, Blueprint, current_app
from gn3.auth import db
from gn3.auth.dictify import dictify
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
from .models import user_role
-from ...authentication.oauth2.resource_server import require_oauth
-
roles = Blueprint("roles", __name__)
@roles.route("/view/<uuid:role_id>", methods=["GET"])
diff --git a/gn3/auth/authorisation/users/__init__.py b/gn3/auth/authorisation/users/__init__.py
index e69de29..5f0c89c 100644
--- a/gn3/auth/authorisation/users/__init__.py
+++ b/gn3/auth/authorisation/users/__init__.py
@@ -0,0 +1,12 @@
+"""Initialise the users' package."""
+from .base import (
+ User,
+ users,
+ save_user,
+ user_by_id,
+ # valid_login,
+ user_by_email,
+ hash_password, # only used in tests... maybe make gn-auth a GN3 dependency
+ same_password,
+ set_user_password
+)
diff --git a/gn3/auth/authorisation/users/admin/__init__.py b/gn3/auth/authorisation/users/admin/__init__.py
deleted file mode 100644
index 8aa0743..0000000
--- a/gn3/auth/authorisation/users/admin/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-"""The admin module"""
-from .views import admin
diff --git a/gn3/auth/authorisation/users/admin/ui.py b/gn3/auth/authorisation/users/admin/ui.py
deleted file mode 100644
index 242c7a6..0000000
--- a/gn3/auth/authorisation/users/admin/ui.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""UI utilities for the auth system."""
-from functools import wraps
-from flask import flash, url_for, redirect
-
-from gn3.auth.authentication.users import User
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.roles.models import user_roles
-
-from gn3.session import logged_in, session_user, clear_session_info
-
-def is_admin(func):
- """Verify user is a system admin."""
- @wraps(func)
- @logged_in
- def __admin__(*args, **kwargs):
- admin_roles = [
- role for role in with_db_connection(
- lambda conn: user_roles(
- conn, User(**session_user())))
- if role.role_name == "system-administrator"]
- if len(admin_roles) > 0:
- return func(*args, **kwargs)
- flash("Expected a system administrator.", "alert-danger")
- flash("You have been logged out of the system.", "alert-info")
- clear_session_info()
- return redirect(url_for("oauth2.admin.login"))
- return __admin__
diff --git a/gn3/auth/authorisation/users/admin/views.py b/gn3/auth/authorisation/users/admin/views.py
deleted file mode 100644
index c9f1887..0000000
--- a/gn3/auth/authorisation/users/admin/views.py
+++ /dev/null
@@ -1,230 +0,0 @@
-"""UI for admin stuff"""
-import uuid
-import json
-import random
-import string
-from functools import partial
-from datetime import datetime, timezone, timedelta
-
-from email_validator import validate_email, EmailNotValidError
-from flask import (
- flash,
- request,
- url_for,
- redirect,
- Blueprint,
- current_app,
- render_template)
-
-
-from gn3 import session
-from gn3.auth import db
-from gn3.auth.db_utils import with_db_connection
-
-from gn3.auth.authentication.oauth2.models.oauth2client import (
- save_client,
- OAuth2Client,
- oauth2_clients,
- client as oauth2_client,
- delete_client as _delete_client)
-from gn3.auth.authentication.users import (
- User,
- user_by_id,
- valid_login,
- user_by_email,
- hash_password)
-
-from .ui import is_admin
-
-admin = Blueprint("admin", __name__)
-
-@admin.before_request
-def update_expires():
- """Update session expiration."""
- if session.session_info() and not session.update_expiry():
- flash("Session has expired. Logging out...", "alert-warning")
- session.clear_session_info()
- return redirect(url_for("oauth2.admin.login"))
- return None
-
-@admin.route("/dashboard", methods=["GET"])
-@is_admin
-def dashboard():
- """Admin dashboard."""
- return render_template("admin/dashboard.html")
-
-@admin.route("/login", methods=["GET", "POST"])
-def login():
- """Log in to GN3 directly without OAuth2 client."""
- if request.method == "GET":
- return render_template(
- "admin/login.html",
- next_uri=request.args.get("next", "oauth2.admin.dashboard"))
-
- form = request.form
- next_uri = form.get("next_uri", "oauth2.admin.dashboard")
- error_message = "Invalid email or password provided."
- login_page = redirect(url_for("oauth2.admin.login", next=next_uri))
- try:
- email = validate_email(form.get("email", "").strip(),
- check_deliverability=False)
- password = form.get("password")
- with db.connection(current_app.config["AUTH_DB"]) as conn:
- user = user_by_email(conn, email["email"])
- if valid_login(conn, user, password):
- session.update_session_info(
- user=user._asdict(),
- expires=(
- datetime.now(tz=timezone.utc) + timedelta(minutes=10)))
- return redirect(url_for(next_uri))
- flash(error_message, "alert-danger")
- return login_page
- except EmailNotValidError as _enve:
- flash(error_message, "alert-danger")
- return login_page
-
-@admin.route("/logout", methods=["GET"])
-def logout():
- """Log out the admin."""
- if not session.session_info():
- flash("Not logged in.", "alert-info")
- return redirect(url_for("oauth2.admin.login"))
- session.clear_session_info()
- flash("Logged out", "alert-success")
- return redirect(url_for("oauth2.admin.login"))
-
-def random_string(length: int = 64) -> str:
- """Generate a random string."""
- return "".join(
- random.choice(string.ascii_letters + string.digits + string.punctuation)
- for _idx in range(0, length))
-
-def __response_types__(grant_types: tuple[str, ...]) -> tuple[str, ...]:
- """Compute response types from grant types."""
- resps = {
- "password": ("token",),
- "authorization_code": ("token", "code"),
- "refresh_token": ("token",)
- }
- return tuple(set(
- resp_typ for types_list
- in (types for grant, types in resps.items() if grant in grant_types)
- for resp_typ in types_list))
-
-@admin.route("/register-client", methods=["GET", "POST"])
-@is_admin
-def register_client():
- """Register an OAuth2 client."""
- def __list_users__(conn):
- with db.cursor(conn) as cursor:
- cursor.execute("SELECT * FROM users")
- return tuple(
- User(uuid.UUID(row["user_id"]), row["email"], row["name"])
- for row in cursor.fetchall())
- if request.method == "GET":
- return render_template(
- "admin/register-client.html",
- scope=current_app.config["OAUTH2_SCOPE"],
- users=with_db_connection(__list_users__),
- current_user=session.session_user())
-
- form = request.form
- raw_client_secret = random_string()
- default_redirect_uri = form["redirect_uri"]
- grant_types = form.getlist("grants[]")
- client = OAuth2Client(
- client_id = uuid.uuid4(),
- client_secret = hash_password(raw_client_secret),
- client_id_issued_at = datetime.now(tz=timezone.utc),
- client_secret_expires_at = datetime.fromtimestamp(0),
- client_metadata = {
- "client_name": "GN2 Dev Server",
- "token_endpoint_auth_method": [
- "client_secret_post", "client_secret_basic"],
- "client_type": "confidential",
- "grant_types": ["password", "authorization_code", "refresh_token"],
- "default_redirect_uri": default_redirect_uri,
- "redirect_uris": [default_redirect_uri] + form.get("other_redirect_uri", "").split(),
- "response_type": __response_types__(tuple(grant_types)),
- "scope": form.getlist("scope[]")
- },
- user = with_db_connection(partial(
- user_by_id, user_id=uuid.UUID(form["user"])))
- )
- client = with_db_connection(partial(save_client, the_client=client))
- return render_template(
- "admin/registered-client.html",
- client=client,
- client_secret = raw_client_secret)
-
-def __parse_client__(sqlite3_row) -> dict:
- """Parse the client details into python datatypes."""
- return {
- **dict(sqlite3_row),
- "client_metadata": json.loads(sqlite3_row["client_metadata"])
- }
-
-@admin.route("/list-client", methods=["GET"])
-@is_admin
-def list_clients():
- """List all registered OAuth2 clients."""
- return render_template(
- "admin/list-oauth2-clients.html",
- clients=with_db_connection(oauth2_clients))
-
-@admin.route("/view-client/<uuid:client_id>", methods=["GET"])
-@is_admin
-def view_client(client_id: uuid.UUID):
- """View details of OAuth2 client with given `client_id`."""
- return render_template(
- "admin/view-oauth2-client.html",
- client=with_db_connection(partial(oauth2_client, client_id=client_id)),
- scope=current_app.config["OAUTH2_SCOPE"])
-
-@admin.route("/edit-client", methods=["POST"])
-@is_admin
-def edit_client():
- """Edit the details of the given client."""
- form = request.form
- the_client = with_db_connection(partial(
- oauth2_client, client_id=uuid.UUID(form["client_id"])))
- if the_client.is_nothing():
- flash("No such client.", "alert-danger")
- return redirect(url_for("oauth2.admin.list_clients"))
- the_client = the_client.value
- client_metadata = {
- **the_client.client_metadata,
- "default_redirect_uri": form["default_redirect_uri"],
- "redirect_uris": list(set(
- [form["default_redirect_uri"]] +
- form["other_redirect_uris"].split("\r\n"))),
- "grants": form.getlist("grants[]"),
- "scope": form.getlist("scope[]")
- }
- with_db_connection(partial(save_client, the_client=OAuth2Client(
- the_client.client_id,
- the_client.client_secret,
- the_client.client_id_issued_at,
- the_client.client_secret_expires_at,
- client_metadata,
- the_client.user)))
- flash("Client updated.", "alert-success")
- return redirect(url_for("oauth2.admin.view_client",
- client_id=the_client.client_id))
-
-@admin.route("/delete-client", methods=["POST"])
-@is_admin
-def delete_client():
- """Delete the details of the client."""
- form = request.form
- the_client = with_db_connection(partial(
- oauth2_client, client_id=uuid.UUID(form["client_id"])))
- if the_client.is_nothing():
- flash("No such client.", "alert-danger")
- return redirect(url_for("oauth2.admin.list_clients"))
- the_client = the_client.value
- with_db_connection(partial(_delete_client, client=the_client))
- flash((f"Client '{the_client.client_metadata.client_name}' was deleted "
- "successfully."),
- "alert-success")
- return redirect(url_for("oauth2.admin.list_clients"))
diff --git a/gn3/auth/authentication/users.py b/gn3/auth/authorisation/users/base.py
index 0e72ed2..0e72ed2 100644
--- a/gn3/auth/authentication/users.py
+++ b/gn3/auth/authorisation/users/base.py
diff --git a/gn3/auth/authorisation/users/collections/views.py b/gn3/auth/authorisation/users/collections/views.py
index 1fa25a3..775e8bc 100644
--- a/gn3/auth/authorisation/users/collections/views.py
+++ b/gn3/auth/authorisation/users/collections/views.py
@@ -9,8 +9,8 @@ from gn3.auth.db_utils import with_db_connection
from gn3.auth.authorisation.checks import require_json
from gn3.auth.authorisation.errors import NotFoundError
-from gn3.auth.authentication.users import User, user_by_id
-from gn3.auth.authentication.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.users import User, user_by_id
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
from .models import (
add_traits,
diff --git a/gn3/auth/authorisation/users/masquerade/__init__.py b/gn3/auth/authorisation/users/masquerade/__init__.py
deleted file mode 100644
index 69d64f0..0000000
--- a/gn3/auth/authorisation/users/masquerade/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Package to deal with masquerading."""
diff --git a/gn3/auth/authorisation/users/masquerade/models.py b/gn3/auth/authorisation/users/masquerade/models.py
deleted file mode 100644
index 9f24b6b..0000000
--- a/gn3/auth/authorisation/users/masquerade/models.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""Functions for handling masquerade."""
-from uuid import uuid4
-from functools import wraps
-from datetime import datetime
-
-from flask import current_app as app
-
-from gn3.auth import db
-
-from gn3.auth.authorisation.errors import ForbiddenAccess
-from gn3.auth.authorisation.roles.models import user_roles
-
-from gn3.auth.authentication.users import User
-from gn3.auth.authentication.oauth2.models.oauth2token import (
- OAuth2Token, save_token)
-
-__FIVE_HOURS__ = (60 * 60 * 5)
-
-def can_masquerade(func):
- """Security decorator."""
- @wraps(func)
- def __checker__(*args, **kwargs):
- if len(args) == 3:
- conn, token, _masq_user = args
- elif len(args) == 2:
- conn, token = args
- elif len(args) == 1:
- conn = args[0]
- token = kwargs["original_token"]
- else:
- conn = kwargs["conn"]
- token = kwargs["original_token"]
-
- masq_privs = [priv for role in user_roles(conn, token.user)
- for priv in role.privileges
- if priv.privilege_id == "system:user:masquerade"]
- if len(masq_privs) == 0:
- raise ForbiddenAccess(
- "You do not have the ability to masquerade as another user.")
- return func(*args, **kwargs)
- return __checker__
-
-@can_masquerade
-def masquerade_as(
- conn: db.DbConnection,
- original_token: OAuth2Token,
- masqueradee: User) -> OAuth2Token:
- """Get a token that enables `masquerader` to act as `masqueradee`."""
- token_details = app.config["OAUTH2_SERVER"].generate_token(
- client=original_token.client,
- grant_type="authorization_code",
- user=masqueradee,
- expires_in=__FIVE_HOURS__,
- include_refresh_token=True)
- new_token = OAuth2Token(
- token_id=uuid4(),
- client=original_token.client,
- token_type=token_details["token_type"],
- access_token=token_details["access_token"],
- refresh_token=token_details.get("refresh_token"),
- scope=original_token.scope,
- revoked=False,
- issued_at=datetime.now(),
- expires_in=token_details["expires_in"],
- user=masqueradee)
- save_token(conn, new_token)
- return new_token
diff --git a/gn3/auth/authorisation/users/masquerade/views.py b/gn3/auth/authorisation/users/masquerade/views.py
deleted file mode 100644
index 43286a1..0000000
--- a/gn3/auth/authorisation/users/masquerade/views.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""Endpoints for user masquerade"""
-from uuid import UUID
-from functools import partial
-
-from flask import request, jsonify, Response, Blueprint
-
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.errors import InvalidData
-from gn3.auth.authorisation.checks import require_json
-
-from gn3.auth.authentication.users import user_by_id
-from gn3.auth.authentication.oauth2.resource_server import require_oauth
-
-from .models import masquerade_as
-
-masq = Blueprint("masquerade", __name__)
-
-@masq.route("/", methods=["POST"])
-@require_oauth("profile user masquerade")
-@require_json
-def masquerade() -> Response:
- """Masquerade as a particular user."""
- with require_oauth.acquire("profile user masquerade") as token:
- masqueradee_id = UUID(request.json["masquerade_as"])#type: ignore[index]
- if masqueradee_id == token.user.user_id:
- raise InvalidData("You are not allowed to masquerade as yourself.")
-
- masq_user = with_db_connection(partial(
- user_by_id, user_id=masqueradee_id))
- def __masq__(conn):
- new_token = masquerade_as(conn, original_token=token, masqueradee=masq_user)
- return new_token
- def __dump_token__(tok):
- return {
- key: value for key, value in (tok._asdict().items())
- if key in ("access_token", "refresh_token", "expires_in",
- "token_type")
- }
- return jsonify({
- "original": {
- "user": token.user._asdict(),
- "token": __dump_token__(token)
- },
- "masquerade_as": {
- "user": masq_user._asdict(),
- "token": __dump_token__(with_db_connection(__masq__))
- }
- })
diff --git a/gn3/auth/authorisation/users/models.py b/gn3/auth/authorisation/users/models.py
index 89c1d22..0157154 100644
--- a/gn3/auth/authorisation/users/models.py
+++ b/gn3/auth/authorisation/users/models.py
@@ -7,7 +7,7 @@ from gn3.auth.authorisation.roles.models import Role
from gn3.auth.authorisation.checks import authorised_p
from gn3.auth.authorisation.privileges import Privilege
-from gn3.auth.authentication.users import User
+from .base import User
@authorised_p(
("system:user:list",),
diff --git a/gn3/auth/authorisation/users/views.py b/gn3/auth/authorisation/users/views.py
index 826e222..f75b51e 100644
--- a/gn3/auth/authorisation/users/views.py
+++ b/gn3/auth/authorisation/users/views.py
@@ -10,9 +10,11 @@ from flask import request, jsonify, Response, Blueprint, current_app
from gn3.auth import db
from gn3.auth.dictify import dictify
from gn3.auth.db_utils import with_db_connection
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.users import User, save_user, set_user_password
+from gn3.auth.authorisation.oauth2.oauth2token import token_by_access_token
from .models import list_users
-from .masquerade.views import masq
from .collections.views import collections
from ..groups.models import user_group as _user_group
@@ -21,12 +23,7 @@ from ..roles.models import assign_default_roles, user_roles as _user_roles
from ..errors import (
NotFoundError, UsernameError, PasswordError, UserRegistrationError)
-from ...authentication.oauth2.resource_server import require_oauth
-from ...authentication.users import User, save_user, set_user_password
-from ...authentication.oauth2.models.oauth2token import token_by_access_token
-
users = Blueprint("users", __name__)
-users.register_blueprint(masq, url_prefix="/masquerade")
users.register_blueprint(collections, url_prefix="/collections")
@users.route("/", methods=["GET"])
diff --git a/gn3/auth/views.py b/gn3/auth/views.py
index 56eace7..da64049 100644
--- a/gn3/auth/views.py
+++ b/gn3/auth/views.py
@@ -1,21 +1,16 @@
"""The Auth(oris|entic)ation routes/views"""
from flask import Blueprint
-from .authentication.oauth2.views import auth
-
from .authorisation.data.views import data
from .authorisation.users.views import users
-from .authorisation.users.admin import admin
from .authorisation.roles.views import roles
from .authorisation.groups.views import groups
from .authorisation.resources.views import resources
oauth2 = Blueprint("oauth2", __name__)
-oauth2.register_blueprint(auth, url_prefix="/")
oauth2.register_blueprint(data, url_prefix="/data")
oauth2.register_blueprint(users, url_prefix="/user")
oauth2.register_blueprint(roles, url_prefix="/role")
-oauth2.register_blueprint(admin, url_prefix="/admin")
oauth2.register_blueprint(groups, url_prefix="/group")
oauth2.register_blueprint(resources, url_prefix="/resource")