""" Access usage logs for organizations or repositories. """ import json from datetime import datetime, timedelta from endpoints.api import (resource, nickname, ApiResource, query_param, parse_args, RepositoryParamResource, require_repo_admin, related_user_resource, format_date, Unauthorized, NotFound, require_user_admin, internal_only, path_param, require_scope) from auth.permissions import AdministerOrganizationPermission, AdministerOrganizationPermission from auth.auth_context import get_authenticated_user from data import model from auth import scopes from app import avatar LOGS_PER_PAGE = 50 def log_view(log, kinds): view = { 'kind': kinds[log.kind_id], 'metadata': json.loads(log.metadata_json), 'ip': log.ip, 'datetime': format_date(log.datetime), } if log.performer and log.performer.username: view['performer'] = { 'kind': 'user', 'name': log.performer.username, 'is_robot': log.performer.robot, 'avatar': avatar.get_data_for_user(log.performer) } return view def aggregated_log_view(log, kinds, start_time): # Because we aggregate based on the day of the month in SQL, we only have that information. # Therefore, create a synthetic date based on the day and the month of the start time. # Logs are allowed for a maximum period of one week, so this calculation should always work. day = int(log.day) month = start_time.month if day < start_time.day: month = month + 1 view = { 'kind': kinds[log.kind_id], 'count': log.count, 'datetime': format_date(datetime(start_time.year, month, day)) } return view def _validate_logs_arguments(start_time, end_time, performer_name): performer = None if performer_name: performer = model.user.get_user(performer_name) if start_time: try: start_time = datetime.strptime(start_time + ' UTC', '%m/%d/%Y %Z') except ValueError: start_time = None if not start_time: start_time = datetime.today() - timedelta(7) # One week if end_time: try: end_time = datetime.strptime(end_time + ' UTC', '%m/%d/%Y %Z') end_time = end_time + timedelta(days=1) except ValueError: end_time = None if not end_time: end_time = datetime.today() return (start_time, end_time, performer) def get_logs(start_time, end_time, performer_name=None, repository=None, namespace=None, page=None): (start_time, end_time, performer) = _validate_logs_arguments(start_time, end_time, performer_name) page = page if page else 1 kinds = model.log.get_log_entry_kinds() logs = model.log.list_logs(start_time, end_time, performer=performer, repository=repository, namespace=namespace, page=page, count=LOGS_PER_PAGE + 1) return { 'start_time': format_date(start_time), 'end_time': format_date(end_time), 'logs': [log_view(log, kinds) for log in logs[0:LOGS_PER_PAGE]], 'page': page, 'has_additional': len(logs) > LOGS_PER_PAGE, } def get_aggregate_logs(start_time, end_time, performer_name=None, repository=None, namespace=None): (start_time, end_time, performer) = _validate_logs_arguments(start_time, end_time, performer_name) kinds = model.log.get_log_entry_kinds() aggregated_logs = model.log.get_aggregated_logs(start_time, end_time, performer=performer, repository=repository, namespace=namespace) return { 'aggregated': [aggregated_log_view(log, kinds, start_time) for log in aggregated_logs] } @resource('/v1/repository//logs') @path_param('repository', 'The full path of the repository. e.g. namespace/name') class RepositoryLogs(RepositoryParamResource): """ Resource for fetching logs for the specific repository. """ @require_repo_admin @nickname('listRepoLogs') @parse_args @query_param('starttime', 'Earliest time from which to get logs (%m/%d/%Y %Z)', type=str) @query_param('endtime', 'Latest time to which to get logs (%m/%d/%Y %Z)', type=str) @query_param('page', 'The page number for the logs', type=int, default=1) def get(self, args, namespace, repository): """ List the logs for the specified repository. """ repo = model.repository.get_repository(namespace, repository) if not repo: raise NotFound() start_time = args['starttime'] end_time = args['endtime'] return get_logs(start_time, end_time, repository=repo, page=args['page']) @resource('/v1/user/logs') @internal_only class UserLogs(ApiResource): """ Resource for fetching logs for the current user. """ @require_user_admin @nickname('listUserLogs') @parse_args @query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('performer', 'Username for which to filter logs.', type=str) @query_param('page', 'The page number for the logs', type=int, default=1) def get(self, args): """ List the logs for the current user. """ performer_name = args['performer'] start_time = args['starttime'] end_time = args['endtime'] user = get_authenticated_user() return get_logs(start_time, end_time, performer_name=performer_name, namespace=user.username, page=args['page']) @resource('/v1/organization//logs') @path_param('orgname', 'The name of the organization') @related_user_resource(UserLogs) class OrgLogs(ApiResource): """ Resource for fetching logs for the entire organization. """ @nickname('listOrgLogs') @parse_args @query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('performer', 'Username for which to filter logs.', type=str) @query_param('page', 'The page number for the logs', type=int, default=1) @require_scope(scopes.ORG_ADMIN) def get(self, args, orgname): """ List the logs for the specified organization. """ permission = AdministerOrganizationPermission(orgname) if permission.can(): performer_name = args['performer'] start_time = args['starttime'] end_time = args['endtime'] return get_logs(start_time, end_time, namespace=orgname, performer_name=performer_name, page=args['page']) raise Unauthorized() @resource('/v1/repository//aggregatelogs') @path_param('repository', 'The full path of the repository. e.g. namespace/name') class RepositoryAggregateLogs(RepositoryParamResource): """ Resource for fetching aggregated logs for the specific repository. """ @require_repo_admin @nickname('getAggregateRepoLogs') @parse_args @query_param('starttime', 'Earliest time from which to get logs (%m/%d/%Y %Z)', type=str) @query_param('endtime', 'Latest time to which to get logs (%m/%d/%Y %Z)', type=str) def get(self, args, namespace, repository): """ Returns the aggregated logs for the specified repository. """ repo = model.repository.get_repository(namespace, repository) if not repo: raise NotFound() start_time = args['starttime'] end_time = args['endtime'] return get_aggregate_logs(start_time, end_time, repository=repo) @resource('/v1/user/aggregatelogs') @internal_only class UserAggregateLogs(ApiResource): """ Resource for fetching aggregated logs for the current user. """ @require_user_admin @nickname('getAggregateUserLogs') @parse_args @query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('performer', 'Username for which to filter logs.', type=str) def get(self, args): """ Returns the aggregated logs for the current user. """ performer_name = args['performer'] start_time = args['starttime'] end_time = args['endtime'] user = get_authenticated_user() return get_aggregate_logs(start_time, end_time, performer_name=performer_name, namespace=user.username) @resource('/v1/organization//aggregatelogs') @path_param('orgname', 'The name of the organization') @related_user_resource(UserLogs) class OrgAggregateLogs(ApiResource): """ Resource for fetching aggregate logs for the entire organization. """ @nickname('getAggregateOrgLogs') @parse_args @query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str) @query_param('performer', 'Username for which to filter logs.', type=str) @require_scope(scopes.ORG_ADMIN) def get(self, args, orgname): """ Gets the aggregated logs for the specified organization. """ permission = AdministerOrganizationPermission(orgname) if permission.can(): performer_name = args['performer'] start_time = args['starttime'] end_time = args['endtime'] return get_aggregate_logs(start_time, end_time, namespace=orgname, performer_name=performer_name) raise Unauthorized()