diff --git a/migrations/versions/006d4747f858_.py b/migrations/versions/006d4747f858_.py new file mode 100644 index 0000000000000000000000000000000000000000..8a8cf94e9af781d7f8543fb51af1cfed1cb46da2 --- /dev/null +++ b/migrations/versions/006d4747f858_.py @@ -0,0 +1,30 @@ +"""v3.8: Add thread_id to audit_log + +Revision ID: 006d4747f858 +Revises: d3c0f0403a84 +Create Date: 2022-11-30 22:37:42.163199 + +""" + +# revision identifiers, used by Alembic. +revision = '006d4747f858' +down_revision = 'd3c0f0403a84' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.exc import OperationalError, ProgrammingError, InternalError + + +def upgrade(): + try: + op.add_column('pidea_audit', sa.Column('thread_id', sa.Unicode(length=20), nullable=True)) + except (OperationalError, ProgrammingError, InternalError) as exx: + print("Looks like the thread_id already exists in the pidea_audit table.") + print(exx) + except Exception as exx: + print("Could not add thread_id to pidea_audit table.") + print (exx) + + +def downgrade(): + op.drop_column('pidea_audit', 'thread_id') diff --git a/migrations/versions/d3c0f0403a84_.py b/migrations/versions/d3c0f0403a84_.py new file mode 100644 index 0000000000000000000000000000000000000000..5dcd5f565058a1d0ff5ecad3b8f021ca0091ca7e --- /dev/null +++ b/migrations/versions/d3c0f0403a84_.py @@ -0,0 +1,22 @@ +"""v3.8: Merging revisions + +Revision ID: d3c0f0403a84 +Revises: ('fabcf24d9304', 'a28f2733897b') +Create Date: 2022-11-30 22:37:19.986083 + +""" + +# revision identifiers, used by Alembic. +revision = 'd3c0f0403a84' +down_revision = ('fabcf24d9304', 'a28f2733897b') + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/privacyidea/api/auth.py b/privacyidea/api/auth.py index afaae523a1b6e0acdbc0139ff9da815c0fc01c05..1ef10d886174bdab4daa736ad6dbe8b120f62a7e 100644 --- a/privacyidea/api/auth.py +++ b/privacyidea/api/auth.py @@ -75,6 +75,7 @@ from privacyidea.lib.event import event, EventConfiguration from privacyidea.lib import _ import logging import traceback +import threading log = logging.getLogger(__name__) @@ -106,6 +107,7 @@ def before_request(): "privacyidea_server": privacyidea_server, "action": "{0!s} {1!s}".format(request.method, request.url_rule), "action_detail": "", + "thread_id": u"{0!s}".format(threading.current_thread().ident), "info": ""}) username = getParam(request.all_data, "username") diff --git a/privacyidea/api/before_after.py b/privacyidea/api/before_after.py index 742d9352c2cff36eddc92be3d4293eb8c97aaf3a..5b93f8ceec015195deb97949499b7ed0e7038434 100644 --- a/privacyidea/api/before_after.py +++ b/privacyidea/api/before_after.py @@ -74,6 +74,7 @@ from ..lib.error import (privacyIDEAError, from privacyidea.lib.utils import get_client_ip from privacyidea.lib.user import User import datetime +import threading log = logging.getLogger(__name__) @@ -223,6 +224,7 @@ def before_request(): "privacyidea_server": privacyidea_server, "action": "{0!s} {1!s}".format(request.method, request.url_rule), "action_detail": "", + "thread_id": u"{0!s}".format(threading.current_thread().ident), "info": ""}) if g.logged_in_user.get("role") == "admin": diff --git a/privacyidea/lib/auditmodules/base.py b/privacyidea/lib/auditmodules/base.py index 56e9998d335d58ff603b1a8ad08e48dda5255e2b..cb582efecfaa112d8cda15ef72e10f5c5404490b 100644 --- a/privacyidea/lib/auditmodules/base.py +++ b/privacyidea/lib/auditmodules/base.py @@ -143,7 +143,7 @@ class Audit(object): # pragma: no cover 'duration', 'token_type', 'user', 'realm', 'administrator', 'action_detail', 'info', 'privacyidea_server', 'client', 'log_level', 'policies', 'clearance_level', 'sig_check', - 'missing_line', 'resolver'] + 'missing_line', 'resolver', 'thread_id'] def get_total(self, param, AND=True, display_error=True, timelimit=None): """ diff --git a/privacyidea/lib/auditmodules/sqlaudit.py b/privacyidea/lib/auditmodules/sqlaudit.py index a960512375be2db2343d587f32fa85eb4990df43..f181edf8569a589df00938e69e25723dd62afa29 100755 --- a/privacyidea/lib/auditmodules/sqlaudit.py +++ b/privacyidea/lib/auditmodules/sqlaudit.py @@ -305,7 +305,8 @@ class Audit(AuditBase): clearance_level=self.audit_data.get("clearance_level"), policies=self.audit_data.get("policies"), startdate=self.audit_data.get("startdate"), - duration=duration + duration=duration, + thread_id=self.audit_data.get("thread_id") ) self.session.add(le) self.session.commit() @@ -375,6 +376,7 @@ class Audit(AuditBase): :type le: LogEntry :rtype str """ + # TODO: Add thread_id. We really should add a versioning to identify which audit data is signed. s = u"id=%s,date=%s,action=%s,succ=%s,serial=%s,t=%s,u=%s,r=%s,adm=%s," \ u"ad=%s,i=%s,ps=%s,c=%s,l=%s,cl=%s" % (le.id, le.date, @@ -420,7 +422,8 @@ class Audit(AuditBase): 'client': LogEntry.client, 'log_level': LogEntry.loglevel, 'policies': LogEntry.policies, - 'clearance_level': LogEntry.clearance_level} + 'clearance_level': LogEntry.clearance_level, + 'thread_id': LogEntry.thread_id} return sortname.get(key) def csv_generator(self, param=None, user=None, timelimit=None): @@ -587,4 +590,5 @@ class Audit(AuditBase): audit_dict['clearance_level'] = audit_entry.clearance_level audit_dict['startdate'] = audit_entry.startdate.isoformat() if audit_entry.startdate else None audit_dict['duration'] = audit_entry.duration.total_seconds() if audit_entry.duration else None + audit_dict['thread_id'] = audit_entry.thread_id return audit_dict diff --git a/privacyidea/models.py b/privacyidea/models.py index 4101aa3ed4a0d49bbea197cc107baeafd9766f0e..81b1a170d2dc8259a1c445226ef6895d07e6d424 100644 --- a/privacyidea/models.py +++ b/privacyidea/models.py @@ -2747,6 +2747,7 @@ audit_column_length = {"signature": 620, "client": 50, "loglevel": 12, "clearance_level": 12, + "thread_id": 20, "policies": 255} AUDIT_TABLE_NAME = 'pidea_audit' @@ -2780,6 +2781,7 @@ class Audit(MethodsMixin, db.Model): loglevel = db.Column(db.Unicode(audit_column_length.get("loglevel"))) clearance_level = db.Column(db.Unicode(audit_column_length.get( "clearance_level"))) + thread_id = db.Column(db.Unicode(audit_column_length.get("thread_id"))) policies = db.Column(db.Unicode(audit_column_length.get("policies"))) def __init__(self, @@ -2797,6 +2799,7 @@ class Audit(MethodsMixin, db.Model): client="", loglevel="default", clearance_level="default", + thread_id="0", policies="", startdate=None, duration=None @@ -2819,6 +2822,7 @@ class Audit(MethodsMixin, db.Model): self.client = convert_column_to_unicode(client) self.loglevel = convert_column_to_unicode(loglevel) self.clearance_level = convert_column_to_unicode(clearance_level) + self.thread_id = convert_column_to_unicode(thread_id) self.policies = convert_column_to_unicode(policies) diff --git a/privacyidea/static/components/audit/views/audit.log.html b/privacyidea/static/components/audit/views/audit.log.html index 60c8c04d7eb5a7beefae9f9655fe6ac6115772a3..78cd1cda82604a0097f27560227103ac41d2349f 100644 --- a/privacyidea/static/components/audit/views/audit.log.html +++ b/privacyidea/static/components/audit/views/audit.log.html @@ -115,6 +115,7 @@ </th> <th ng-show="audit_columns.includes('sig_check')" translate>sig_check</th> <th ng-show="audit_columns.includes('missing_line')" translate>missing_line</th> + <th ng-show="audit_columns.includes('thread_id')" translate>thread_id</th> <th ng-show="audit_columns.includes('clearance_level')" translate>clearance</th> <th ng-show="audit_columns.includes('log_level')" translate>log level</th> <th ng-show="audit_columns.includes('privacyidea_server')" @@ -170,6 +171,7 @@ </span></td> <td ng-if="audit_columns.includes('missing_line')"><span status-class="{{ audit.missing_line }}"> {{ audit.missing_line }}</span></td> + <td ng-if="audit_columns.includes('thread_id')">{{ audit.thread_id }}</td> <td ng-if="audit_columns.includes('clearance_level')">{{ audit.clearance_level }}</td> <td ng-if="audit_columns.includes('log_level')">{{ audit.log_level }}</td> <td ng-if="audit_columns.includes('privacyidea_server')">{{ audit.privacyidea_server }}</td> diff --git a/tests/test_api_audit.py b/tests/test_api_audit.py index 205abd7c95ef07cf107e009c075e4bcbdfe16cd8..1b6712b3911de64ec623c3fddad760244f5a9f63 100644 --- a/tests/test_api_audit.py +++ b/tests/test_api_audit.py @@ -74,7 +74,7 @@ class APIAuditTestCase(MyApiTestCase): cols = json_response.get("result").get("value").get("auditcolumns") self.assertIn("number", cols) self.assertIn("serial", cols) - self.assertEqual(21, len(cols)) + self.assertEqual(22, len(cols)) def test_01_get_audit_csv(self): @contextmanager diff --git a/tests/testdata/audit.sqlite b/tests/testdata/audit.sqlite index 24167945e5abaad7652e0c1a7be6345f87c29338..042ec81840de2acb5dea3826f0a30e5ba7bb5875 100644 Binary files a/tests/testdata/audit.sqlite and b/tests/testdata/audit.sqlite differ