You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
5.5 KiB
149 lines
5.5 KiB
import sys
|
|
from dataclasses import asdict
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Any, Optional, List
|
|
|
|
from bitwarden_api import BitwardenApi
|
|
from models import (
|
|
BitwardenEventsRequest,
|
|
EventLogsCheckpoint,
|
|
SettingsConfig,
|
|
BitwardenEvent,
|
|
BitwardenEnhancedEvent,
|
|
BitwardenGroup,
|
|
BitwardenMember
|
|
)
|
|
from utils import get_logger, obj_to_json
|
|
|
|
|
|
class EventLogsWriter:
|
|
def __init__(self,
|
|
bitwarden_api: BitwardenApi,
|
|
checkpoint: EventLogsCheckpoint,
|
|
settings_config: SettingsConfig):
|
|
self.logger = get_logger()
|
|
self.bitwarden_api = bitwarden_api
|
|
self.checkpoint = checkpoint
|
|
self.settings_config = settings_config
|
|
self.bitwarden_groups = self.__get_bitwarden_groups()
|
|
self.bitwarden_members_id, self.bitwarden_members_user_id = self.__get_bitwarden_members()
|
|
|
|
def read_events(self):
|
|
self.logger.debug('reading events')
|
|
|
|
next_request = self.__get_first_request()
|
|
if next_request is None:
|
|
return
|
|
|
|
while True:
|
|
events_response = self.bitwarden_api.get_events(next_request)
|
|
if len(events_response.data) == 0:
|
|
break
|
|
|
|
next_request = BitwardenEventsRequest(next_request.start,
|
|
next_request.end,
|
|
events_response.continuationToken)
|
|
|
|
events = self.__enhance_bitwarden_events(events_response.data)
|
|
|
|
yield next_request, events
|
|
|
|
if events_response.continuationToken is None:
|
|
break
|
|
|
|
def write_events(self, events: List[Any]):
|
|
self.logger.info('writing %s events', len(events))
|
|
|
|
for event in events:
|
|
self.logger.debug('event %s', event)
|
|
self.__write_event(event)
|
|
|
|
# enforces checkpoint update after each batch of events
|
|
sys.stdout.flush()
|
|
|
|
def __get_first_request(self):
|
|
#
|
|
if self.checkpoint.next_request is not None:
|
|
self.logger.debug('using next request from checkpoint %s',
|
|
self.checkpoint.next_request)
|
|
return self.checkpoint.next_request
|
|
|
|
start_datetime = self.checkpoint.last_log_date
|
|
if start_datetime is None:
|
|
# First run..
|
|
if self.settings_config.start_date is not None:
|
|
# and start_date enforced via configuration file
|
|
start_datetime = self.settings_config.start_date
|
|
else:
|
|
# by default go back in time 1 year
|
|
start_datetime = datetime.now(tz=timezone.utc) - timedelta(days=365)
|
|
else:
|
|
# last run's end date + 1 microsecond to avoid duplicates
|
|
# (Bitwarden event api start and end dates are inclusive)
|
|
start_datetime = start_datetime + timedelta(microseconds=1)
|
|
|
|
# Bitwarden's events are stored in eventual consistent database
|
|
end_datetime = datetime.now(tz=timezone.utc) - timedelta(seconds=30)
|
|
|
|
if start_datetime.timestamp() >= end_datetime.timestamp():
|
|
self.logger.debug('start date %s is past end date %s',
|
|
start_datetime, end_datetime)
|
|
return None
|
|
|
|
return BitwardenEventsRequest(start_datetime, end_datetime)
|
|
|
|
def __write_event(self, obj: Any):
|
|
self.logger.debug('writing event log %s', obj)
|
|
|
|
obj_json = obj_to_json(obj)
|
|
|
|
self.logger.debug('writing event log json %s', obj_json)
|
|
|
|
print(obj_json)
|
|
|
|
def __get_bitwarden_groups(self):
|
|
response = self.bitwarden_api.get_groups()
|
|
|
|
return {group.id: group for group in response.data}
|
|
|
|
def __get_bitwarden_members(self):
|
|
response = self.bitwarden_api.get_members()
|
|
|
|
return ({member.id: member for member in response.data},
|
|
{member.userId: member for member in response.data})
|
|
|
|
def __enhance_bitwarden_events(self, bitwarden_events: List[BitwardenEvent]) -> List[BitwardenEnhancedEvent]:
|
|
|
|
events = []
|
|
|
|
for bitwarden_event in bitwarden_events:
|
|
group: Optional[BitwardenGroup] = None
|
|
member: Optional[BitwardenMember] = None
|
|
acting_user: Optional[BitwardenMember] = None
|
|
|
|
if bitwarden_event.groupId is not None:
|
|
group = self.bitwarden_groups.get(bitwarden_event.groupId, None)
|
|
|
|
if bitwarden_event.memberId is not None:
|
|
member = self.bitwarden_members_id.get(bitwarden_event.memberId, None)
|
|
|
|
if bitwarden_event.actingUserId is not None:
|
|
acting_user = self.bitwarden_members_user_id.get(bitwarden_event.actingUserId, None)
|
|
|
|
group_name = group.name if group is not None else None
|
|
member_name = member.name if member is not None else None
|
|
member_email = member.email if member is not None else None
|
|
acting_user_name = acting_user.name if acting_user is not None else None
|
|
acting_user_email = acting_user.email if acting_user is not None else None
|
|
|
|
event = BitwardenEnhancedEvent(**asdict(bitwarden_event),
|
|
groupName=group_name,
|
|
memberName=member_name,
|
|
memberEmail=member_email,
|
|
actingUserName=acting_user_name,
|
|
actingUserEmail=acting_user_email)
|
|
|
|
events.append(event)
|
|
|
|
return events
|