From 1f458de1ae0b7ccf1dc774e304171074801a2154 Mon Sep 17 00:00:00 2001 From: Christian Stefanescu Date: Fri, 16 May 2025 15:47:39 +0200 Subject: [PATCH 1/2] chore: remove role.api_key column --- .../2c55cafd26ce_remove_role_api_key.py | 22 +++++++++++++++++++ aleph/model/role.py | 1 - 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 aleph/migrate/versions/2c55cafd26ce_remove_role_api_key.py diff --git a/aleph/migrate/versions/2c55cafd26ce_remove_role_api_key.py b/aleph/migrate/versions/2c55cafd26ce_remove_role_api_key.py new file mode 100644 index 000000000..6ae7763a8 --- /dev/null +++ b/aleph/migrate/versions/2c55cafd26ce_remove_role_api_key.py @@ -0,0 +1,22 @@ +"""Remove role.api_key + +Revision ID: 2c55cafd26ce +Revises: 31e24765dee3 +Create Date: 2025-05-16 14:10:34.374856 + +""" + +# revision identifiers, used by Alembic. +revision = "2c55cafd26ce" +down_revision = "31e24765dee3" + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.drop_column("role", "api_key") + + +def downgrade(): + pass diff --git a/aleph/model/role.py b/aleph/model/role.py index fcdaa67a1..8c3de7873 100644 --- a/aleph/model/role.py +++ b/aleph/model/role.py @@ -52,7 +52,6 @@ class Role(db.Model, IdModel, SoftDeleteModel): name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=True) type = db.Column(db.Enum(*TYPES, name="role_type"), nullable=False) - api_key = db.Column(db.Unicode, nullable=True) api_key_digest = db.Column(db.Unicode, nullable=True) api_key_expires_at = db.Column(db.DateTime, nullable=True) api_key_expiration_notification_sent = db.Column(db.Integer, nullable=True) From e0a6a21f115e32386c4a55858569a045e6eea303 Mon Sep 17 00:00:00 2001 From: Christian Stefanescu Date: Fri, 16 May 2025 16:51:27 +0200 Subject: [PATCH 2/2] Update tests after dropping role.api_key --- aleph/logic/api_keys.py | 21 -------------------- aleph/manage.py | 9 +-------- aleph/tests/factories/models/role.py | 2 +- aleph/tests/test_api_keys.py | 29 ---------------------------- aleph/tests/test_manage.py | 2 +- aleph/tests/test_roles_api.py | 4 ++-- aleph/tests/test_sessions_api.py | 4 ++-- aleph/tests/test_view_context.py | 1 - 8 files changed, 7 insertions(+), 65 deletions(-) diff --git a/aleph/logic/api_keys.py b/aleph/logic/api_keys.py index a8b1c0576..18e1be7e5 100644 --- a/aleph/logic/api_keys.py +++ b/aleph/logic/api_keys.py @@ -112,24 +112,3 @@ def reset_api_key_expiration(): query.update({Role.api_key_expires_at: expires_at}) db.session.commit() - - -def hash_plaintext_api_keys(): - query = Role.all_users() - query = query.yield_per(250) - query = query.where( - and_( - Role.api_key != None, # noqa: E711 - Role.api_key_digest == None, # noqa: E711 - ) - ) - - results = db.session.execute(query).scalars() - - for index, partition in enumerate(results.partitions()): - for role in partition: - role.api_key_digest = hash_api_key(role.api_key) - db.session.add(role) - log.info(f"Hashing API key: {role}") - log.info(f"Comitting partition {index}") - db.session.commit() diff --git a/aleph/manage.py b/aleph/manage.py index 549dd33fa..b6b524aab 100644 --- a/aleph/manage.py +++ b/aleph/manage.py @@ -25,7 +25,6 @@ from aleph.index.admin import delete_index from aleph.logic.api_keys import ( reset_api_key_expiration as _reset_api_key_expiration, - hash_plaintext_api_keys as _hash_plaintext_api_keys, ) from aleph.index.entities import iter_proxies from aleph.logic.collections import create_collection, update_collection @@ -416,7 +415,7 @@ def retry_exports_(): def createuser(email, password=None, name=None, admin=False): """Create a user and show their API key.""" role = create_user(email, name, password, is_admin=admin) - print("User created. ID: %s, API Key: %s" % (role.id, role.api_key)) + print(f"User created. ID: {role.id}") @cli.command() @@ -599,9 +598,3 @@ def evilshit(): def reset_api_key_expiration(): """Reset the expiration date of all legacy, non-expiring API keys.""" _reset_api_key_expiration() - - -@cli.command() -def hash_plaintext_api_keys(): - """Hash legacy plaintext API keys.""" - _hash_plaintext_api_keys() diff --git a/aleph/tests/factories/models/role.py b/aleph/tests/factories/models/role.py index 62244abd2..9495bd88c 100644 --- a/aleph/tests/factories/models/role.py +++ b/aleph/tests/factories/models/role.py @@ -12,5 +12,5 @@ class Meta: type = Role.USER name = factory.Faker("name") email = factory.Faker("email") - api_key = factory.Faker("uuid4") + api_key_digest = factory.Faker("uuid4") foreign_id = factory.Faker("uuid4") diff --git a/aleph/tests/test_api_keys.py b/aleph/tests/test_api_keys.py index 1f155c930..d2c1d0f48 100644 --- a/aleph/tests/test_api_keys.py +++ b/aleph/tests/test_api_keys.py @@ -5,9 +5,7 @@ from aleph.logic.api_keys import ( generate_user_api_key, send_api_key_expiration_notifications, - hash_plaintext_api_keys, ) -from aleph.logic.util import hash_api_key from aleph.tests.util import TestCase @@ -195,30 +193,3 @@ def test_send_api_key_expiration_notifications_regenerate(self): assert outbox[4].subject == "[Aleph] Your API key will expire in 7 days" assert outbox[5].subject == "[Aleph] Your API key has expired" - - def test_hash_plaintext_api_keys(self): - user_1 = self.create_user(foreign_id="user_1", email="user1@example.org") - user_1.api_key = "1234567890" - user_1.api_key_digest = None - - user_2 = self.create_user(foreign_id="user_2", email="user2@example.org") - user_2.api_key = None - user_2.api_key_digest = None - - db.session.add_all([user_1, user_2]) - db.session.commit() - - hash_plaintext_api_keys() - - db.session.refresh(user_1) - - # Do not delete the plaintext API key to allow for version rollbacks. - # `api_key` column will be removed in the next version at which point all - # plaintext keys will be deleted. - assert user_1.api_key == "1234567890" - - assert user_1.api_key_digest == hash_api_key("1234567890") - - db.session.refresh(user_2) - assert user_2.api_key is None - assert user_2.api_key_digest is None diff --git a/aleph/tests/test_manage.py b/aleph/tests/test_manage.py index 3636a2163..033c862e1 100644 --- a/aleph/tests/test_manage.py +++ b/aleph/tests/test_manage.py @@ -73,7 +73,7 @@ def test_createuser(self): ) assert result.exit_code == 0 assert result.output.startswith("User created") - assert "API Key:" in result.output + assert "ID:" in result.output roles = Role.query.all() emails = [role.email for role in roles] diff --git a/aleph/tests/test_roles_api.py b/aleph/tests/test_roles_api.py index 8a24d2152..15450418e 100644 --- a/aleph/tests/test_roles_api.py +++ b/aleph/tests/test_roles_api.py @@ -267,7 +267,7 @@ def test_generate_api_key_auth(self): def test_generate_api_key(self): role, headers = self.login() - assert role.api_key is None + assert role.api_key_digest is None # Generate initial API key for new user url = f"/api/2/roles/{role.id}/generate_api_key" @@ -313,4 +313,4 @@ def test_new_roles_no_api_key(self): role = Role.by_email(email) assert role is not None - assert role.api_key is None + assert role.api_key_digest is None diff --git a/aleph/tests/test_sessions_api.py b/aleph/tests/test_sessions_api.py index 7a3632160..dd673f74f 100644 --- a/aleph/tests/test_sessions_api.py +++ b/aleph/tests/test_sessions_api.py @@ -129,13 +129,13 @@ def test_password_login_no_api_key(self): "email": self.role.email, "password": secret, } - assert self.role.api_key is None + assert self.role.api_key_digest is None res = self.client.post("/api/2/sessions/login", data=data) assert res.status_code == 200 db.session.refresh(self.role) - assert self.role.api_key is None + assert self.role.api_key_digest is None class SessionsApiOAuthTestCase(TestCase): diff --git a/aleph/tests/test_view_context.py b/aleph/tests/test_view_context.py index fbdee72b5..337d22438 100644 --- a/aleph/tests/test_view_context.py +++ b/aleph/tests/test_view_context.py @@ -14,7 +14,6 @@ def setUp(self): foreign_id="other", email="jane.doe@example.org", ) - assert self.other_role.api_key is None db.session.add(self.role) db.session.add(self.other_role)