From c153b04c620b8b93a09f186db99ebfa67ee4f8a4 Mon Sep 17 00:00:00 2001 From: Davor Date: Fri, 27 May 2022 21:26:32 +0200 Subject: [PATCH 1/8] Added User and No access roles in DB - TODO: add update db script to add missing roles --- web/login/login.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/web/login/login.py b/web/login/login.py index e33b974..14341f6 100644 --- a/web/login/login.py +++ b/web/login/login.py @@ -256,16 +256,19 @@ def consent(): # Default access level roles = [] if app_obj: - role_objects = ( + role_object = ( db.session.query(AppRole) .filter(AppRole.app_id == app_obj.id) .filter(AppRole.user_id == user.uuid) + .first() ) - for role_obj in role_objects: - app_role = RoleService.get_role_by_id(role_obj.role_id) - if (app_role is None): - roles.append('user') - continue + print(role_object) + if role_object is None or role_object.role_id is None: + # If there is no role in app_roles or the role_id for an app is null user has no permissions + # TODO: how to handle if the user has no access for an app? + current_app.logger.error(f"User has no access for: {app_obj.name}") + app_role = RoleService.get_role_by_id(role_object.role_id) + if (app_role is not None): roles.append(app_role.name) current_app.logger.info(f"Using '{roles}' when applying consent for {kratos_id}") From 2a28c4d55b896337f742f3644268f218110dde8d Mon Sep 17 00:00:00 2001 From: Davor Date: Mon, 30 May 2022 12:25:42 +0200 Subject: [PATCH 2/8] reject consent request when the user doesn't have permissions for app reject --- web/login/login.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/web/login/login.py b/web/login/login.py index 14341f6..34a9bfc 100644 --- a/web/login/login.py +++ b/web/login/login.py @@ -262,14 +262,19 @@ def consent(): .filter(AppRole.user_id == user.uuid) .first() ) - print(role_object) if role_object is None or role_object.role_id is None: # If there is no role in app_roles or the role_id for an app is null user has no permissions - # TODO: how to handle if the user has no access for an app? current_app.logger.error(f"User has no access for: {app_obj.name}") - app_role = RoleService.get_role_by_id(role_object.role_id) - if (app_role is not None): - roles.append(app_role.name) + return redirect( + consent_request.reject( + error="No access", + error_description="The user has no access for app", + error_hint="Contact your administrator", + status_code=401, + ) + ) + else: + roles.append(role_object.role.name) current_app.logger.info(f"Using '{roles}' when applying consent for {kratos_id}") From 7b5a3f9eb9c0b4c6221eecfb29d0ab76dcf7bb82 Mon Sep 17 00:00:00 2001 From: Davor Date: Mon, 6 Jun 2022 11:10:44 +0200 Subject: [PATCH 3/8] adde user role to DB --- .../versions/5f462d2d9d25_convert_role_column_to_table.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/migrations/versions/5f462d2d9d25_convert_role_column_to_table.py b/migrations/versions/5f462d2d9d25_convert_role_column_to_table.py index 53a8a1d..bfc7843 100644 --- a/migrations/versions/5f462d2d9d25_convert_role_column_to_table.py +++ b/migrations/versions/5f462d2d9d25_convert_role_column_to_table.py @@ -30,8 +30,10 @@ def upgrade(): # Insert default role "admin" as ID 1 op.execute(sa.insert(role_table).values(id=1,name="admin")) + op.execute(sa.insert(role_table).values(id=2,name="user")) # Set role_id 1 to all current "admin" users op.execute("UPDATE app_role SET role_id = 1 WHERE role = 'admin'") + op.execute("UPDATE app_role SET role_id = 2 WHERE role = 'user'") # Drop old column op.drop_column("app_role", "role") From 603c1aa71e92cc8aa871562089b6ce2e65209d3b Mon Sep 17 00:00:00 2001 From: Davor Date: Wed, 8 Jun 2022 17:59:36 +0200 Subject: [PATCH 4/8] Added migration script --- ...462d2d9d25_convert_role_column_to_table.py | 2 -- .../versions/b514cca2d47b_add_user_role.py | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 migrations/versions/b514cca2d47b_add_user_role.py diff --git a/migrations/versions/5f462d2d9d25_convert_role_column_to_table.py b/migrations/versions/5f462d2d9d25_convert_role_column_to_table.py index bfc7843..53a8a1d 100644 --- a/migrations/versions/5f462d2d9d25_convert_role_column_to_table.py +++ b/migrations/versions/5f462d2d9d25_convert_role_column_to_table.py @@ -30,10 +30,8 @@ def upgrade(): # Insert default role "admin" as ID 1 op.execute(sa.insert(role_table).values(id=1,name="admin")) - op.execute(sa.insert(role_table).values(id=2,name="user")) # Set role_id 1 to all current "admin" users op.execute("UPDATE app_role SET role_id = 1 WHERE role = 'admin'") - op.execute("UPDATE app_role SET role_id = 2 WHERE role = 'user'") # Drop old column op.drop_column("app_role", "role") diff --git a/migrations/versions/b514cca2d47b_add_user_role.py b/migrations/versions/b514cca2d47b_add_user_role.py new file mode 100644 index 0000000..9209492 --- /dev/null +++ b/migrations/versions/b514cca2d47b_add_user_role.py @@ -0,0 +1,31 @@ +"""add user role + +Revision ID: b514cca2d47b +Revises: 5f462d2d9d25 +Create Date: 2022-06-08 17:24:51.305129 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b514cca2d47b' +down_revision = '5f462d2d9d25' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### end Alembic commands ### + + # Insert role "user" as ID 2 + op.execute("INSERT INTO `role` (id, `name`) VALUES (2, 'user')") + # Set role_id 2 to all current "user" users which by have NULL role ID + op.execute("UPDATE app_role SET role_id = 2 WHERE role_id IS NULL") + + +def downgrade(): + op.execute("UPDATE app_role SET role_id = NULL WHERE role_id = 2") + op.execute("DELETE FROM `role` WHERE id = 2") + pass From b6f3765a3a091010f20a996fa1b83e5aaa7134cc Mon Sep 17 00:00:00 2001 From: Davor Date: Wed, 8 Jun 2022 18:02:51 +0200 Subject: [PATCH 5/8] remove 'pass' from the end of downgrade --- migrations/versions/b514cca2d47b_add_user_role.py | 1 - 1 file changed, 1 deletion(-) diff --git a/migrations/versions/b514cca2d47b_add_user_role.py b/migrations/versions/b514cca2d47b_add_user_role.py index 9209492..fc18087 100644 --- a/migrations/versions/b514cca2d47b_add_user_role.py +++ b/migrations/versions/b514cca2d47b_add_user_role.py @@ -28,4 +28,3 @@ def upgrade(): def downgrade(): op.execute("UPDATE app_role SET role_id = NULL WHERE role_id = 2") op.execute("DELETE FROM `role` WHERE id = 2") - pass From c1e62089b69dbfd23d300a022847ab8b3d49a094 Mon Sep 17 00:00:00 2001 From: Davor Date: Fri, 10 Jun 2022 16:43:10 +0200 Subject: [PATCH 6/8] added migration script for users to add 'No access' roles in app_roles --- areas/apps/apps_service.py | 12 +++++++++ .../versions/b514cca2d47b_add_user_role.py | 25 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 areas/apps/apps_service.py diff --git a/areas/apps/apps_service.py b/areas/apps/apps_service.py new file mode 100644 index 0000000..e48d588 --- /dev/null +++ b/areas/apps/apps_service.py @@ -0,0 +1,12 @@ +from .models import App, AppRole + +class AppsService: + @staticmethod + def get_apps(): + apps = App.query.all() + return [{"id": app.id, "name": app.name, "slug": app.slug} for app in apps] + + @staticmethod + def get_app_roles(): + app_roles = AppRole.query.all() + return [{"user_id": app_role.user_id, "app_id": app_role.app_id, "role_id": app_role.role_id} for app_role in app_roles] \ No newline at end of file diff --git a/migrations/versions/b514cca2d47b_add_user_role.py b/migrations/versions/b514cca2d47b_add_user_role.py index fc18087..69caedb 100644 --- a/migrations/versions/b514cca2d47b_add_user_role.py +++ b/migrations/versions/b514cca2d47b_add_user_role.py @@ -8,6 +8,7 @@ Create Date: 2022-06-08 17:24:51.305129 from alembic import op import sqlalchemy as sa +from areas.apps.apps_service import AppsService # revision identifiers, used by Alembic. revision = 'b514cca2d47b' @@ -21,10 +22,34 @@ def upgrade(): # Insert role "user" as ID 2 op.execute("INSERT INTO `role` (id, `name`) VALUES (2, 'user')") + # Insert role "no access" as ID 3 + op.execute("INSERT INTO `role` (id, `name`) VALUES (3, 'no access')") # Set role_id 2 to all current "user" users which by have NULL role ID op.execute("UPDATE app_role SET role_id = 2 WHERE role_id IS NULL") + # Add 'no access' role for all users that don't have any roles for specific apps + app_ids = [app['id'] for app in AppsService.get_apps()] + app_roles = AppsService.get_app_roles() + user_ids = [app_role['user_id'] for app_role in app_roles] + + for user_id in user_ids: + existing_app_ids = [x['app_id'] for x in list(filter(lambda role: role['user_id'] == user_id, app_roles))] + missing_app_ids = [x for x in app_ids if x not in existing_app_ids] + + if len(missing_app_ids) > 0: + insert_statement = "INSERT INTO app_role (user_id, app_id, role_id) VALUES" + for app_id in missing_app_ids: + insert_statement += " ('"+ user_id +"'," + str(app_id) +",3)," + op.execute(insert_statement[:-1]) + def downgrade(): + # Revert all users role_id to NULL where role is 'user' op.execute("UPDATE app_role SET role_id = NULL WHERE role_id = 2") + # Delete role 'user' from roles op.execute("DELETE FROM `role` WHERE id = 2") + + # Delete all user app roles where role is 'no access' with role_id 3 + op.execute("DELETE FROM app_role WHERE role_id = 3") + # Delete role 'no access' from roles + op.execute("DELETE FROM `role` WHERE id = 3") From a7c0b0a626a8c01e9aa88e4394b086f1db55c0a3 Mon Sep 17 00:00:00 2001 From: Davor Date: Tue, 14 Jun 2022 17:37:24 +0200 Subject: [PATCH 7/8] add apps migration --- .../versions/b514cca2d47b_add_user_role.py | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/migrations/versions/b514cca2d47b_add_user_role.py b/migrations/versions/b514cca2d47b_add_user_role.py index 69caedb..0586942 100644 --- a/migrations/versions/b514cca2d47b_add_user_role.py +++ b/migrations/versions/b514cca2d47b_add_user_role.py @@ -1,4 +1,4 @@ -"""add user role +"""update apps and add 'user' and 'no access' role Revision ID: b514cca2d47b Revises: 5f462d2d9d25 @@ -8,8 +8,6 @@ Create Date: 2022-06-08 17:24:51.305129 from alembic import op import sqlalchemy as sa -from areas.apps.apps_service import AppsService - # revision identifiers, used by Alembic. revision = 'b514cca2d47b' down_revision = '5f462d2d9d25' @@ -20,6 +18,28 @@ depends_on = None def upgrade(): # ### end Alembic commands ### + # Check and update app table in DB + apps = { + "dashboard": "Dashboard", + "wekan": "Wekan", + "wordpress": "WordPress", + "nextcloud": "Nextcloud", + "zulip": "Zulip" + } + # app table + app_table = sa.table('app', sa.column('id', sa.Integer), sa.column( + 'name', sa.String), sa.column('slug', sa.String)) + + existing_apps = op.get_bind().execute(app_table.select()).fetchall() + existing_app_slugs = [app['slug'] for app in existing_apps] + for app_slug in apps.keys(): + if app_slug in existing_app_slugs: + op.execute(f'UPDATE app SET `name` = "{apps.get(app_slug)}" WHERE slug = "{app_slug}"') + else: + op.execute(f'INSERT INTO app (`name`, slug) VALUES ("{apps.get(app_slug)}","{app_slug}")') + + # Fetch all apps including newly created + existing_apps = op.get_bind().execute(app_table.select()).fetchall() # Insert role "user" as ID 2 op.execute("INSERT INTO `role` (id, `name`) VALUES (2, 'user')") # Insert role "no access" as ID 3 @@ -27,20 +47,21 @@ def upgrade(): # Set role_id 2 to all current "user" users which by have NULL role ID op.execute("UPDATE app_role SET role_id = 2 WHERE role_id IS NULL") - # Add 'no access' role for all users that don't have any roles for specific apps - app_ids = [app['id'] for app in AppsService.get_apps()] - app_roles = AppsService.get_app_roles() - user_ids = [app_role['user_id'] for app_role in app_roles] + # Add 'no access' role for all users that don't have any roles for specific apps + app_roles_table = sa.table('app_role', sa.column('user_id', sa.String), sa.column( + 'app_id', sa.Integer), sa.column('role_id', sa.Integer)) + + app_ids = [app['id'] for app in existing_apps] + app_roles = op.get_bind().execute(app_roles_table.select()).fetchall() + user_ids = set([app_role['user_id'] for app_role in app_roles]) for user_id in user_ids: - existing_app_ids = [x['app_id'] for x in list(filter(lambda role: role['user_id'] == user_id, app_roles))] - missing_app_ids = [x for x in app_ids if x not in existing_app_ids] - - if len(missing_app_ids) > 0: - insert_statement = "INSERT INTO app_role (user_id, app_id, role_id) VALUES" - for app_id in missing_app_ids: - insert_statement += " ('"+ user_id +"'," + str(app_id) +",3)," - op.execute(insert_statement[:-1]) + existing_user_app_ids = [x['app_id'] for x in list(filter(lambda role: role['user_id'] == user_id, app_roles))] + missing_user_app_ids = [x for x in app_ids if x not in existing_user_app_ids] + + if len(missing_user_app_ids) > 0: + values = [{'user_id': user_id, 'app_id': app_id, 'role_id': 3} for app_id in missing_user_app_ids] + op.bulk_insert(app_roles_table, values) def downgrade(): From f6480d805b57d79a581f5c555e95d1a665084894 Mon Sep 17 00:00:00 2001 From: Maarten de Waard Date: Wed, 15 Jun 2022 14:18:09 +0200 Subject: [PATCH 8/8] deny app access if role_id is 3 (no access) --- web/login/login.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/login/login.py b/web/login/login.py index 34a9bfc..ef54a18 100644 --- a/web/login/login.py +++ b/web/login/login.py @@ -262,7 +262,8 @@ def consent(): .filter(AppRole.user_id == user.uuid) .first() ) - if role_object is None or role_object.role_id is None: + # Role ID 3 is always "No access" due to migration b514cca2d47b + if role_object is None or role_object.role_id is None or role_object.role_id == 3: # If there is no role in app_roles or the role_id for an app is null user has no permissions current_app.logger.error(f"User has no access for: {app_obj.name}") return redirect(