diff options
author | Frederick Muriuki Muriithi | 2023-02-02 11:35:51 +0300 |
---|---|---|
committer | Frederick Muriuki Muriithi | 2023-02-02 12:03:51 +0300 |
commit | dfe5eb18e3ec8dc570d118bfe95c5d4dcb2c7575 (patch) | |
tree | b45da1e9eba405042ef47174215b827739f5a393 | |
parent | 6fc120aca6062f96725adaece85a7b76000affda (diff) | |
download | genenetwork3-dfe5eb18e3ec8dc570d118bfe95c5d4dcb2c7575.tar.gz |
auth: Reorganise modules/packages for easier dev and maintenance
Split the views/routes into separate modules each dealing with a narrower
scope of the application to aid in maintenance, and help with making the
development easier.
17 files changed, 160 insertions, 121 deletions
diff --git a/gn3/auth/__init__.py b/gn3/auth/__init__.py index ba10305..a28498d 100644 --- a/gn3/auth/__init__.py +++ b/gn3/auth/__init__.py @@ -2,6 +2,4 @@ from . import authorisation from . import authentication -## Setup the endpoints -from .authorisation.views import * -from .authentication.oauth2.views import * +from .views import oauth2 diff --git a/gn3/auth/authentication/oauth2/views.py b/gn3/auth/authentication/oauth2/views.py index 3af83e2..e440c6e 100644 --- a/gn3/auth/authentication/oauth2/views.py +++ b/gn3/auth/authentication/oauth2/views.py @@ -1,41 +1,41 @@ """Endpoints for the oauth2 server""" import uuid -from flask import current_app as app +from flask import Blueprint, current_app as app -from gn3.auth.blueprint import oauth2 from .endpoints.revocation import RevocationEndpoint from .endpoints.introspection import IntrospectionEndpoint +auth = Blueprint("auth", __name__) -@oauth2.route("/register-client", methods=["GET", "POST"]) +@auth.route("/register-client", methods=["GET", "POST"]) def register_client(): """Register an OAuth2 client.""" return "WOULD REGISTER ..." -@oauth2.route("/delete-client/<uuid:client_id>", methods=["GET", "POST"]) +@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}." -@oauth2.route("/authorise", methods=["GET", "POST"]) +@auth.route("/authorise", methods=["GET", "POST"]) def authorise(): """Authorise a user""" return "WOULD AUTHORISE THE USER." -@oauth2.route("/token", methods=["POST"]) +@auth.route("/token", methods=["POST"]) def token(): """Retrieve the authorisation token.""" server = app.config["OAUTH2_SERVER"] return server.create_token_response() -@oauth2.route("/revoke", methods=["POST"]) +@auth.route("/revoke", methods=["POST"]) def revoke_token(): """Revoke the token.""" return app.config["OAUTH2_SERVER"].create_endpoint_response( RevocationEndpoint.ENDPOINT_NAME) -@oauth2.route("/introspect", methods=["POST"]) +@auth.route("/introspect", methods=["POST"]) def introspect_token(): """Provide introspection information for the token.""" return app.config["OAUTH2_SERVER"].create_endpoint_response( diff --git a/gn3/auth/authorisation/groups/__init__.py b/gn3/auth/authorisation/groups/__init__.py new file mode 100644 index 0000000..1cb0bba --- /dev/null +++ b/gn3/auth/authorisation/groups/__init__.py @@ -0,0 +1,3 @@ +"""Initialise the `gn3.auth.authorisation.groups` package""" + +from .models import Group, GroupRole diff --git a/gn3/auth/authorisation/groups.py b/gn3/auth/authorisation/groups/models.py index c691457..0750419 100644 --- a/gn3/auth/authorisation/groups.py +++ b/gn3/auth/authorisation/groups/models.py @@ -10,10 +10,10 @@ from gn3.auth import db from gn3.auth.dictify import dictify from gn3.auth.authentication.users import User -from .checks import authorised_p -from .privileges import Privilege -from .errors import AuthorisationError -from .roles import ( +from ..checks import authorised_p +from ..privileges import Privilege +from ..errors import AuthorisationError +from ..roles.models import ( Role, create_role, revoke_user_role_by_name, assign_user_role_by_name) class Group(NamedTuple): diff --git a/gn3/auth/authorisation/groups/views.py b/gn3/auth/authorisation/groups/views.py new file mode 100644 index 0000000..02b3162 --- /dev/null +++ b/gn3/auth/authorisation/groups/views.py @@ -0,0 +1,54 @@ +"""The views/routes for the `gn3.auth.authorisation.groups` package.""" +import uuid + +from flask import request, jsonify, Response, Blueprint, current_app + +from gn3.auth import db +from gn3.auth.dictify import dictify + +from .models import ( + all_groups, GroupCreationError, group_users as _group_users, + create_group as _create_group) + +from ...authentication.oauth2.resource_server import require_oauth + +groups = Blueprint("groups", __name__) + +@groups.route("/list", methods=["GET"]) +@require_oauth("profile group") +def list_groups(): + """Return the list of groups that exist.""" + with db.connection(current_app.config["AUTH_DB"]) as conn: + the_groups = all_groups(conn) + + return jsonify(the_groups.maybe( + [], lambda grps: [dictify(grp) for grp in grps])) + +@groups.route("/create", methods=["POST"]) +@require_oauth("profile group") +def create_group(): + """Create a new group.""" + with require_oauth.acquire("profile group") as the_token: + group_name=request.form.get("group_name", "").strip() + if not bool(group_name): + raise GroupCreationError("Could not create the group.") + + db_uri = current_app.config["AUTH_DB"] + with db.connection(db_uri) as conn: + user = the_token.user + new_group = _create_group( + conn, group_name, user, request.form.get("group_description")) + return jsonify({ + **dictify(new_group), "group_leader": dictify(user) + }) + +@groups.route("/members/<uuid:group_id>", methods=["GET"]) +@require_oauth("profile group") +def group_members(group_id: uuid.UUID) -> Response: + """Retrieve all the members of a group.""" + with require_oauth.acquire("profile group") as the_token:# pylint: disable=[unused-variable] + db_uri = current_app.config["AUTH_DB"] + ## Check that user has appropriate privileges and remove the pylint disable above + with db.connection(db_uri) as conn: + return jsonify(tuple( + dictify(user) for user in _group_users(conn, group_id))) diff --git a/gn3/auth/authorisation/resources/__init__.py b/gn3/auth/authorisation/resources/__init__.py new file mode 100644 index 0000000..869ab60 --- /dev/null +++ b/gn3/auth/authorisation/resources/__init__.py @@ -0,0 +1,2 @@ +"""Initialise the `gn3.auth.authorisation.resources` package.""" +from .models import Resource, ResourceCategory diff --git a/gn3/auth/authorisation/resources.py b/gn3/auth/authorisation/resources/models.py index fe096e8..1959362 100644 --- a/gn3/auth/authorisation/resources.py +++ b/gn3/auth/authorisation/resources/models.py @@ -7,9 +7,10 @@ from gn3.auth import db from gn3.auth.dictify import dictify from gn3.auth.authentication.users import User -from .checks import authorised_p -from .errors import AuthorisationError -from .groups import Group, user_group, is_group_leader, authenticated_user_group +from ..checks import authorised_p +from ..errors import AuthorisationError +from ..groups.models import ( + Group, user_group, is_group_leader, authenticated_user_group) class MissingGroupError(AuthorisationError): """Raised for any resource operation without a group.""" diff --git a/gn3/auth/authorisation/resources/views.py b/gn3/auth/authorisation/resources/views.py new file mode 100644 index 0000000..009cae6 --- /dev/null +++ b/gn3/auth/authorisation/resources/views.py @@ -0,0 +1,4 @@ +"""The views/routes for the resources package""" +from flask import Blueprint + +resources = Blueprint("resources", __name__) diff --git a/gn3/auth/authorisation/roles/__init__.py b/gn3/auth/authorisation/roles/__init__.py new file mode 100644 index 0000000..293a12f --- /dev/null +++ b/gn3/auth/authorisation/roles/__init__.py @@ -0,0 +1,3 @@ +"""Initialise the `gn3.auth.authorisation.roles` package""" + +from .models import Role diff --git a/gn3/auth/authorisation/roles.py b/gn3/auth/authorisation/roles/models.py index e9f3fb0..b1aac75 100644 --- a/gn3/auth/authorisation/roles.py +++ b/gn3/auth/authorisation/roles/models.py @@ -10,9 +10,9 @@ from gn3.auth import db from gn3.auth.dictify import dictify from gn3.auth.authentication.users import User -from .checks import authorised_p -from .privileges import Privilege -from .errors import NotFoundError +from ..checks import authorised_p +from ..privileges import Privilege +from ..errors import NotFoundError class Role(NamedTuple): """Class representing a role: creates immutable objects.""" diff --git a/gn3/auth/authorisation/roles/views.py b/gn3/auth/authorisation/roles/views.py new file mode 100644 index 0000000..975fb19 --- /dev/null +++ b/gn3/auth/authorisation/roles/views.py @@ -0,0 +1,26 @@ +"""The views/routes for the `gn3.auth.authorisation.roles` package.""" +import uuid + +from flask import jsonify, Response, Blueprint, current_app + +from gn3.auth import db +from gn3.auth.dictify import dictify + +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"]) +@require_oauth("role") +def view_role(role_id: uuid.UUID) -> Response: + """Retrieve a user role with id `role_id`""" + def __error__(exc: Exception): + raise exc + with require_oauth.acquire("profile role") as the_token: + db_uri = current_app.config["AUTH_DB"] + with db.connection(db_uri) as conn: + the_role = user_role(conn, the_token.user, role_id) + return the_role.either( + __error__, lambda a_role: jsonify(dictify(a_role))) diff --git a/gn3/auth/authorisation/users/__init__.py b/gn3/auth/authorisation/users/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/gn3/auth/authorisation/users/__init__.py diff --git a/gn3/auth/authorisation/views.py b/gn3/auth/authorisation/users/views.py index 03c4b03..460f81c 100644 --- a/gn3/auth/authorisation/views.py +++ b/gn3/auth/authorisation/users/views.py @@ -1,29 +1,27 @@ -"""Endpoints for the authorisation stuff.""" -import uuid +"""User authorisation endpoints.""" import traceback from typing import Tuple, Optional import sqlite3 -from flask import request, jsonify, Response, current_app +from flask import request, jsonify, Response, Blueprint, current_app from gn3.auth import db from gn3.auth.dictify import dictify -from gn3.auth.blueprint import oauth2 -from .errors import NotFoundError, UserRegistrationError -from .resources import user_resources as _user_resources -from .roles import user_role, assign_default_roles, user_roles as _user_roles -from .groups import ( - all_groups, GroupCreationError, user_group as _user_group, - group_users as _group_users, create_group as _create_group) +from ..groups.models import user_group as _user_group +from ..errors import NotFoundError, UserRegistrationError +from ..resources.models import user_resources as _user_resources +from ..roles.models import assign_default_roles, user_roles as _user_roles -from ..authentication.oauth2.resource_server import require_oauth -from ..authentication.users import save_user, set_user_password -from ..authentication.oauth2.models.oauth2token import token_by_access_token +from ...authentication.oauth2.resource_server import require_oauth +from ...authentication.users import save_user, set_user_password +from ...authentication.oauth2.models.oauth2token import token_by_access_token -@oauth2.route("/user", methods=["GET"]) +users = Blueprint("users", __name__) + +@users.route("/", methods=["GET"]) @require_oauth("profile") -def user_details(): +def user_details() -> Response: """Return user's details.""" with require_oauth.acquire("profile") as the_token: user = the_token.user @@ -32,18 +30,23 @@ def user_details(): "group": False } with db.connection(current_app.config["AUTH_DB"]) as conn, db.cursor(conn) as cursor: - return jsonify(_user_group(cursor, user).maybe( - user_dets, - lambda group: {**user_dets, "group": dictify(group)})) + the_group = _user_group(cursor, user).maybe(# type: ignore[misc] + False, lambda grp: grp)# type: ignore[arg-type] + return jsonify({ + **user_dets, + "group": dictify(the_group) if the_group else False + }) -@oauth2.route("/user-roles", methods=["GET"]) +@users.route("/roles", methods=["GET"]) @require_oauth("role") -def user_roles(): +def user_roles() -> Response: """Return the non-resource roles assigned to the user.""" with require_oauth.acquire("role") as token: with db.connection(current_app.config["AUTH_DB"]) as conn: - return jsonify(_user_roles(conn, token.user).maybe( - tuple(), lambda roles: tuple(dictify(role) for role in roles))) + return jsonify(tuple( + dictify(role) for role in + _user_roles(conn, token.user).maybe(# type: ignore[misc] + tuple(), lambda roles: roles))) def __email_valid__(email: str) -> Tuple[bool, Optional[str]]: """Validate the email address.""" @@ -81,8 +84,8 @@ def __assert_not_logged_in__(conn: db.DbConnection): raise UserRegistrationError( "Cannot register user while authenticated") -@oauth2.route("/register-user", methods=["POST"]) -def register_user(): +@users.route("/register", methods=["POST"]) +def register_user() -> Response: """Register a user.""" with db.connection(current_app.config["AUTH_DB"]) as conn: __assert_not_logged_in__(conn) @@ -111,7 +114,7 @@ def register_user(): "user_id": user.user_id, "email": user.email, "name": user.name - }), 200 + }) except sqlite3.IntegrityError as sq3ie: current_app.logger.debug(traceback.format_exc()) raise UserRegistrationError( @@ -120,64 +123,23 @@ def register_user(): raise Exception( "unknown_error", "The system experienced an unexpected error.") -@oauth2.route("/groups", methods=["GET"]) -@require_oauth("profile group") -def groups(): - """Return the list of groups that exist.""" - with db.connection(current_app.config["AUTH_DB"]) as conn: - the_groups = all_groups(conn) - - return jsonify(the_groups.maybe( - [], lambda grps: [dictify(grp) for grp in grps])) - -@oauth2.route("/create-group", methods=["POST"]) -@require_oauth("profile group") -def create_group(): - """Create a new group.""" - with require_oauth.acquire("profile group") as the_token: - group_name=request.form.get("group_name", "").strip() - if not bool(group_name): - raise GroupCreationError("Could not create the group.") - - db_uri = current_app.config["AUTH_DB"] - with db.connection(db_uri) as conn: - user = the_token.user - new_group = _create_group( - conn, group_name, user, request.form.get("group_description")) - return jsonify({ - **dictify(new_group), "group_leader": dictify(user) - }) - -@oauth2.route("/role/<uuid:role_id>", methods=["GET"]) -@require_oauth("role") -def role(role_id: uuid.UUID) -> Response: - """Retrieve a user role with id `role_id`""" - def __error__(exc: Exception): - raise exc - with require_oauth.acquire("profile role") as the_token: - db_uri = current_app.config["AUTH_DB"] - with db.connection(db_uri) as conn: - the_role = user_role(conn, the_token.user, role_id) - return the_role.either( - __error__, lambda a_role: jsonify(dictify(a_role))) - -@oauth2.route("/user-group", methods=["GET"]) +@users.route("/group", methods=["GET"]) @require_oauth("profile group") -def user_group(): +def user_group() -> Response: """Retrieve the group in which the user is a member.""" with require_oauth.acquire("profile group") as the_token: db_uri = current_app.config["AUTH_DB"] with db.connection(db_uri) as conn, db.cursor(conn) as cursor: - group = _user_group(cursor, the_token.user).maybe( - False, lambda grp: grp) + group = _user_group(cursor, the_token.user).maybe(# type: ignore[misc] + False, lambda grp: grp)# type: ignore[arg-type] if group: return jsonify(dictify(group)) raise NotFoundError("User is not a member of any group.") -@oauth2.route("/user-resources") +@users.route("/resources") @require_oauth("profile resource") -def user_resources(): +def user_resources() -> Response: """Retrieve the resources a user has access to.""" with require_oauth.acquire("profile resource") as the_token: db_uri = current_app.config["AUTH_DB"] @@ -185,13 +147,3 @@ def user_resources(): return jsonify([ dictify(resource) for resource in _user_resources(conn, the_token.user)]) - -@oauth2.route("/group-users/<uuid:group_id>", methods=["GET"]) -@require_oauth("profile group") -def group_users(group_id: uuid.UUID) -> Response: - """Retrieve all the members of a group.""" - with require_oauth.acquire("profile group") as the_token: - db_uri = current_app.config["AUTH_DB"] - with db.connection(db_uri) as conn: - return jsonify(tuple( - dictify(user) for user in _group_users(conn, group_id))) diff --git a/gn3/auth/blueprint.py b/gn3/auth/blueprint.py deleted file mode 100644 index 39a031b..0000000 --- a/gn3/auth/blueprint.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Setup blueprint for the auth system""" -from flask import Blueprint - -oauth2 = Blueprint("oauth2", __name__) diff --git a/tests/unit/auth/test_groups.py b/tests/unit/auth/test_groups.py index 219b82a..18f9b23 100644 --- a/tests/unit/auth/test_groups.py +++ b/tests/unit/auth/test_groups.py @@ -9,7 +9,7 @@ from gn3.auth.authentication.users import User from gn3.auth.authorisation.roles import Role from gn3.auth.authorisation.privileges import Privilege from gn3.auth.authorisation.errors import AuthorisationError -from gn3.auth.authorisation.groups import ( +from gn3.auth.authorisation.groups.models import ( Group, GroupRole, user_group, create_group, MembershipError, create_group_role) @@ -46,7 +46,7 @@ def test_create_group(# pylint: disable=[too-many-arguments] THEN: verify they are only able to create the group if they have the appropriate privileges """ - mocker.patch("gn3.auth.authorisation.groups.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn) with fxtr_app.app_context() as flask_context: flask_context.g.user = user with db.connection(auth_testdb_path) as conn: @@ -62,7 +62,7 @@ def test_create_group_raises_exception_with_non_privileged_user(# pylint: disabl WHEN: the user attempts to create a group THEN: verify the system raises an exception """ - mocker.patch("gn3.auth.authorisation.groups.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn) with fxtr_app.app_context() as flask_context: flask_context.g.user = user with db.connection(auth_testdb_path) as conn: @@ -89,8 +89,8 @@ def test_create_group_role(mocker, fxtr_users_in_group, fxtr_app, user, expected THEN: verify they are only able to create the role if they have the appropriate privileges and that the role is attached to the given group """ - mocker.patch("gn3.auth.authorisation.groups.uuid4", uuid_fn) - mocker.patch("gn3.auth.authorisation.roles.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.roles.models.uuid4", uuid_fn) conn, _group, _users = fxtr_users_in_group with fxtr_app.app_context() as flask_context, db.cursor(conn) as cursor: flask_context.g.user = user @@ -114,8 +114,8 @@ def test_create_group_role_raises_exception_with_unauthorised_users( THEN: verify they are only able to create the role if they have the appropriate privileges and that the role is attached to the given group """ - mocker.patch("gn3.auth.authorisation.groups.uuid4", uuid_fn) - mocker.patch("gn3.auth.authorisation.roles.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.roles.models.uuid4", uuid_fn) conn, _group, _users = fxtr_users_in_group with fxtr_app.app_context() as flask_context: flask_context.g.user = user @@ -132,7 +132,7 @@ def test_create_multiple_groups(mocker, fxtr_app, fxtr_users): THEN: The system should prevent that, and respond with an appropriate error message """ - mocker.patch("gn3.auth.authorisation.groups.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn) user = User( UUID("ecb52977-3004-469e-9428-2a1856725c7f"), "group@lead.er", "Group Leader") diff --git a/tests/unit/auth/test_resources.py b/tests/unit/auth/test_resources.py index a0236c4..a360442 100644 --- a/tests/unit/auth/test_resources.py +++ b/tests/unit/auth/test_resources.py @@ -6,7 +6,7 @@ import pytest from gn3.auth import db from gn3.auth.authorisation.groups import Group from gn3.auth.authorisation.errors import AuthorisationError -from gn3.auth.authorisation.resources import ( +from gn3.auth.authorisation.resources.models import ( Resource, user_resources, create_resource, ResourceCategory, public_resources) @@ -32,7 +32,7 @@ uuid_fn = lambda : uuid.UUID("d32611e3-07fc-4564-b56c-786c6db6de2b") "test_resource", resource_category, False),)))) def test_create_resource(mocker, fxtr_app, fxtr_users_in_group, user, expected): """Test that resource creation works as expected.""" - mocker.patch("gn3.auth.authorisation.resources.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.resources.models.uuid4", uuid_fn) conn, _group, _users = fxtr_users_in_group with fxtr_app.app_context() as flask_context, db.cursor(conn) as cursor: flask_context.g.user = user @@ -52,7 +52,7 @@ def test_create_resource(mocker, fxtr_app, fxtr_users_in_group, user, expected): def test_create_resource_raises_for_unauthorised_users( mocker, fxtr_app, fxtr_users_in_group, user, expected): """Test that resource creation works as expected.""" - mocker.patch("gn3.auth.authorisation.resources.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.resources.models.uuid4", uuid_fn) conn, _group, _users = fxtr_users_in_group with fxtr_app.app_context() as flask_context: flask_context.g.user = user diff --git a/tests/unit/auth/test_roles.py b/tests/unit/auth/test_roles.py index 78ff8a6..9152042 100644 --- a/tests/unit/auth/test_roles.py +++ b/tests/unit/auth/test_roles.py @@ -6,7 +6,7 @@ import pytest from gn3.auth import db from gn3.auth.authorisation.privileges import Privilege from gn3.auth.authorisation.errors import AuthorisationError -from gn3.auth.authorisation.roles import Role, user_roles, create_role +from gn3.auth.authorisation.roles.models import Role, user_roles, create_role from tests.unit.auth import conftest from tests.unit.auth.fixtures import TEST_USERS @@ -36,7 +36,7 @@ def test_create_role(# pylint: disable=[too-many-arguments] THEN: verify they are only able to create the role if they have the appropriate privileges """ - mocker.patch("gn3.auth.authorisation.roles.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.roles.models.uuid4", uuid_fn) with fxtr_app.app_context() as flask_context: flask_context.g.user = user with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor: @@ -55,7 +55,7 @@ def test_create_role_raises_exception_for_unauthorised_users(# pylint: disable=[ THEN: verify they are only able to create the role if they have the appropriate privileges """ - mocker.patch("gn3.auth.authorisation.roles.uuid4", uuid_fn) + mocker.patch("gn3.auth.authorisation.roles.models.uuid4", uuid_fn) with fxtr_app.app_context() as flask_context: flask_context.g.user = user with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor: |