-
Notifications
You must be signed in to change notification settings - Fork 279
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'release/4.1.0' into release/4.0.3
- Loading branch information
Showing
110 changed files
with
8,743 additions
and
1,729 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import datetime | ||
|
||
import structlog | ||
from flask import render_template | ||
from sqlalchemy import and_, or_, func | ||
|
||
from aleph.core import db | ||
from aleph.model import Role | ||
from aleph.model.common import make_token | ||
from aleph.logic.mail import email_role | ||
from aleph.logic.roles import update_role | ||
from aleph.logic.util import ui_url, hash_api_key | ||
|
||
# Number of days after which API keys expire | ||
API_KEY_EXPIRATION_DAYS = 90 | ||
|
||
# Number of days before an API key expires | ||
API_KEY_EXPIRES_SOON_DAYS = 7 | ||
|
||
log = structlog.get_logger(__name__) | ||
|
||
|
||
def generate_user_api_key(role): | ||
event = "regenerated" if role.has_api_key else "generated" | ||
params = {"role": role, "event": event} | ||
plain = render_template("email/api_key_generated.txt", **params) | ||
html = render_template("email/api_key_generated.html", **params) | ||
subject = f"API key {event}" | ||
email_role(role, subject, html=html, plain=plain) | ||
|
||
now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0) | ||
api_key = make_token() | ||
role.api_key_digest = hash_api_key(api_key) | ||
role.api_key_expires_at = now + datetime.timedelta(days=API_KEY_EXPIRATION_DAYS) | ||
role.api_key_expiration_notification_sent = None | ||
|
||
db.session.add(role) | ||
db.session.commit() | ||
update_role(role) | ||
|
||
return api_key | ||
|
||
|
||
def send_api_key_expiration_notifications(): | ||
_send_api_key_expiration_notification( | ||
days=7, | ||
subject="Your API key will expire in 7 days", | ||
plain_template="email/api_key_expires_soon.txt", | ||
html_template="email/api_key_expires_soon.html", | ||
) | ||
|
||
_send_api_key_expiration_notification( | ||
days=0, | ||
subject="Your API key has expired", | ||
plain_template="email/api_key_expired.txt", | ||
html_template="email/api_key_expired.html", | ||
) | ||
|
||
|
||
def _send_api_key_expiration_notification( | ||
days, | ||
subject, | ||
plain_template, | ||
html_template, | ||
): | ||
now = datetime.date.today() | ||
threshold = now + datetime.timedelta(days=days) | ||
|
||
query = Role.all_users() | ||
query = query.yield_per(1000) | ||
query = query.where( | ||
and_( | ||
and_( | ||
Role.api_key_digest != None, # noqa: E711 | ||
func.date(Role.api_key_expires_at) <= threshold, | ||
), | ||
or_( | ||
Role.api_key_expiration_notification_sent == None, # noqa: E711 | ||
Role.api_key_expiration_notification_sent > days, | ||
), | ||
) | ||
) | ||
|
||
for role in query: | ||
expires_at = role.api_key_expires_at | ||
params = { | ||
"role": role, | ||
"expires_at": expires_at, | ||
"settings_url": ui_url("settings"), | ||
} | ||
plain = render_template(plain_template, **params) | ||
html = render_template(html_template, **params) | ||
log.info(f"Sending API key expiration notification: {role} at {expires_at}") | ||
email_role(role, subject, html=html, plain=plain) | ||
|
||
query.update({Role.api_key_expiration_notification_sent: days}) | ||
db.session.commit() | ||
|
||
|
||
def reset_api_key_expiration(): | ||
now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0) | ||
expires_at = now + datetime.timedelta(days=API_KEY_EXPIRATION_DAYS) | ||
|
||
query = Role.all_users() | ||
query = query.yield_per(500) | ||
query = query.where( | ||
and_( | ||
Role.api_key_digest != None, # noqa: E711 | ||
Role.api_key_expires_at == None, # noqa: E711 | ||
) | ||
) | ||
|
||
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) | ||
role.api_key = None | ||
db.session.add(role) | ||
log.info(f"Hashing API key: {role}") | ||
log.info(f"Comitting partition {index}") | ||
db.session.commit() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
aleph/migrate/versions/131674bde902_add_primary_key_constraint_to_role_membership.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"""add primary key constraint to role_membership table | ||
Revision ID: 131674bde902 | ||
Revises: 8adf50aadcb0 | ||
Create Date: 2024-07-17 14:37:25.269913 | ||
""" | ||
|
||
# revision identifiers, used by Alembic. | ||
revision = "131674bde902" | ||
down_revision = "8adf50aadcb0" | ||
|
||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
|
||
def upgrade(): | ||
op.create_primary_key("role_membership_pkey", "role_membership", ["member_id", "group_id"]) | ||
|
||
|
||
def downgrade(): | ||
pass |
28 changes: 28 additions & 0 deletions
28
aleph/migrate/versions/31e24765dee3_add_api_key_digest_column.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
"""Add api_key_digest column | ||
Revision ID: 31e24765dee3 | ||
Revises: d46fc882ec6b | ||
Create Date: 2024-07-04 11:07:19.915782 | ||
""" | ||
|
||
# revision identifiers, used by Alembic. | ||
revision = "31e24765dee3" | ||
down_revision = "d46fc882ec6b" | ||
|
||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
|
||
def upgrade(): | ||
op.add_column("role", sa.Column("api_key_digest", sa.Unicode())) | ||
op.create_index( | ||
index_name="ix_role_api_key_digest", | ||
table_name="role", | ||
columns=["api_key_digest"], | ||
unique=True, | ||
) | ||
|
||
|
||
def downgrade(): | ||
op.drop_column("role", "api_key_digest") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
"""API key expiration | ||
Revision ID: d46fc882ec6b | ||
Revises: 131674bde902 | ||
Create Date: 2024-05-02 11:43:50.993948 | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
# revision identifiers, used by Alembic. | ||
revision = "d46fc882ec6b" | ||
down_revision = "131674bde902" | ||
|
||
|
||
def upgrade(): | ||
op.add_column("role", sa.Column("api_key_expires_at", sa.DateTime())) | ||
op.add_column( | ||
"role", sa.Column("api_key_expiration_notification_sent", sa.Integer()) | ||
) | ||
op.create_index( | ||
index_name="ix_role_api_key_expires_at", | ||
table_name="role", | ||
columns=["api_key_expires_at"], | ||
) | ||
|
||
|
||
def downgrade(): | ||
op.drop_column("role", "api_key_expires_at") | ||
op.drop_column("role", "api_key_expiration_notification_sent") |
Oops, something went wrong.