Skip to content

Commit

Permalink
Track last access of API client (#906)
Browse files Browse the repository at this point in the history
  • Loading branch information
leplatrem authored Jun 4, 2024
1 parent d8caa4a commit 04303db
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 3 deletions.
4 changes: 2 additions & 2 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@
"filename": "tests/unit/test_auth.py",
"hashed_secret": "b32224ba01e706962030343e7d3d964b9db7034f",
"is_verified": false,
"line_number": 198
"line_number": 218
}
]
},
"generated_at": "2024-04-05T18:55:16Z"
"generated_at": "2024-05-29T11:38:33Z"
}
5 changes: 5 additions & 0 deletions ctms/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,11 @@ def get_active_api_client_ids(db: Session) -> List[str]:
return [row.client_id for row in rows]


def update_api_client_last_access(db: Session, api_client: ApiClient):
api_client.last_access = func.now()
db.add(api_client)


def get_contacts_from_newsletter(dbsession, newsletter_name):
entries = (
dbsession.query(Newsletter)
Expand Down
5 changes: 4 additions & 1 deletion ctms/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ctms.auth import get_subject_from_token
from ctms.config import Settings
from ctms.crud import get_api_client_by_id
from ctms.crud import get_api_client_by_id, update_api_client_last_access
from ctms.database import SessionLocal
from ctms.metrics import oauth2_scheme
from ctms.schemas import ApiClientSchema
Expand Down Expand Up @@ -67,6 +67,9 @@ def get_api_client(
log_context["auth_fail"] = "No client record"
raise credentials_exception

# Track last usage of API client.
update_api_client_last_access(db, api_client)

return api_client


Expand Down
2 changes: 2 additions & 0 deletions ctms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Boolean,
Column,
Date,
DateTime,
ForeignKey,
Index,
Integer,
Expand Down Expand Up @@ -183,6 +184,7 @@ class ApiClient(Base, TimestampMixin):
email = Column(String(255), nullable=False)
enabled = Column(Boolean, default=True)
hashed_secret = Column(String, nullable=False)
last_access = Column(DateTime(timezone=True))


class MozillaFoundationContact(Base, TimestampMixin):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Add column to track API use
Revision ID: f2041a868dca
Revises: cea70cf9d626
Create Date: 2024-05-27 18:11:06.463499
"""
# pylint: disable=no-member invalid-name
# no-member is triggered by alembic.op, which has dynamically added functions
# invalid-name is triggered by migration file names with a date prefix
# invalid-name is triggered by top-level alembic constants like revision instead of REVISION

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "f2041a868dca" # pragma: allowlist secret
down_revision = "cea70cf9d626" # pragma: allowlist secret
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"api_client",
sa.Column("last_access", sa.DateTime(timezone=True), nullable=True),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("api_client", "last_access")
# ### end Alembic commands ###
20 changes: 20 additions & 0 deletions tests/unit/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,26 @@ def test_get_ctms_with_token(
assert resp.status_code == 200


def test_successful_login_tracks_last_access(
dbsession, example_contact, anon_client, test_token_settings, client_id_and_secret
):
client_id = client_id_and_secret[0]
api_client = get_api_client_by_id(dbsession, client_id)
before = api_client.last_access

token = create_access_token(
{"sub": f"api_client:{client_id}"}, **test_token_settings
)
anon_client.get(
f"/ctms/{example_contact.email.email_id}",
headers={"Authorization": f"Bearer {token}"},
)

dbsession.flush()
after = get_api_client_by_id(dbsession, client_id).last_access
assert before != after


def test_get_ctms_with_invalid_token_fails(
example_contact, anon_client, test_token_settings, client_id_and_secret
):
Expand Down

0 comments on commit 04303db

Please sign in to comment.