From 7e4cb4c543c9e3368e43a6edb7a21ba1f1ca9617 Mon Sep 17 00:00:00 2001 From: Arik Fraimovich Date: Sun, 11 Feb 2018 11:56:10 +0200 Subject: [PATCH 1/3] Add: API to return events --- redash/handlers/events.py | 58 +++++++++++++++++++++++++++++++++++++-- requirements.txt | 3 ++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/redash/handlers/events.py b/redash/handlers/events.py index 83988c1ae..a74b3b4c3 100644 --- a/redash/handlers/events.py +++ b/redash/handlers/events.py @@ -1,11 +1,65 @@ from flask import request +from geoip import geolite2 +from user_agents import parse as parse_ua -from redash.handlers.base import BaseResource +from redash.handlers.base import BaseResource, paginate -class EventResource(BaseResource): +def get_location(ip): + if ip is None: + return "Unknown" + + match = geolite2.lookup(ip) + if match is None: + return "Unknown" + + return match.country + + +def event_details(event): + details = {} + if event.object_type == 'data_source' and event.action == 'execute_query': + details['query'] = event.additional_properties['query'] + details['data_source'] = event.object_id + elif event.object_type == 'page' and event.action =='view': + details['page'] = event.object_id + else: + details['object_id'] = event.object_id + details['object_type'] = event.object_type + + return details + + +def serialize_event(event): + d = { + 'org_id': event.org_id, + 'user_id': event.user_id, + 'action': event.action, + 'object_type': event.object_type, + 'object_id': event.object_id, + 'created_at': event.created_at + } + + if event.user_id: + d['user_name'] = event.additional_properties.get('user_name', 'User {}'.format(event.user_id)) + + if not event.user_id: + d['user_name'] = event.additional_properties.get('api_key', 'Unknown') + + d['browser'] = str(parse_ua(event.additional_properties.get('user_agent', ''))) + d['location'] = get_location(event.additional_properties.get('ip')) + d['details'] = event_details(event) + + return d + + +class EventsResource(BaseResource): def post(self): events_list = request.get_json(force=True) for event in events_list: self.record_event(event) + def get(self): + page = request.args.get('page', 1, type=int) + page_size = request.args.get('page_size', 25, type=int) + return paginate(self.current_org.events, page, page_size, serialize_event) diff --git a/requirements.txt b/requirements.txt index 3242ccea5..418866f4c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,6 +45,9 @@ pystache==0.5.4 parsedatetime==2.1 cryptography==2.0.2 simplejson==3.10.0 +ua-parser==0.7.3 +user-agents==1.1.0 +python-geoip-geolite2==2015.303 # Uncomment the requirement for ldap3 if using ldap. # It is not included by default because of the GPL license conflict. # ldap3==2.2.4 From f14388f770fdebb070a33cc466314499dcf04348 Mon Sep 17 00:00:00 2001 From: Arik Fraimovich Date: Sun, 11 Feb 2018 11:57:46 +0200 Subject: [PATCH 2/3] Require admin for events GET call. --- redash/handlers/events.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/redash/handlers/events.py b/redash/handlers/events.py index a74b3b4c3..ecef687c9 100644 --- a/redash/handlers/events.py +++ b/redash/handlers/events.py @@ -3,6 +3,7 @@ from geoip import geolite2 from user_agents import parse as parse_ua from redash.handlers.base import BaseResource, paginate +from redash.permissions import require_admin def get_location(ip): @@ -59,6 +60,7 @@ class EventsResource(BaseResource): for event in events_list: self.record_event(event) + @require_admin def get(self): page = request.args.get('page', 1, type=int) page_size = request.args.get('page_size', 25, type=int) From 2560d887f07693902ee49dbf71a2c7ffdaffa774 Mon Sep 17 00:00:00 2001 From: Arik Fraimovich Date: Sun, 11 Feb 2018 12:04:12 +0200 Subject: [PATCH 3/3] Fix import call --- redash/handlers/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/redash/handlers/api.py b/redash/handlers/api.py index 1ec8e38cd..9d5006bc2 100644 --- a/redash/handlers/api.py +++ b/redash/handlers/api.py @@ -8,7 +8,7 @@ from redash.handlers.permissions import ObjectPermissionsListResource, CheckPerm from redash.handlers.alerts import AlertResource, AlertListResource, AlertSubscriptionListResource, AlertSubscriptionResource from redash.handlers.dashboards import DashboardListResource, RecentDashboardsResource, DashboardResource, DashboardShareResource, PublicDashboardResource from redash.handlers.data_sources import DataSourceTypeListResource, DataSourceListResource, DataSourceSchemaResource, DataSourceResource, DataSourcePauseResource, DataSourceTestResource -from redash.handlers.events import EventResource +from redash.handlers.events import EventsResource from redash.handlers.queries import QueryForkResource, QueryRefreshResource, QueryListResource, QueryRecentResource, QuerySearchResource, QueryResource, MyQueriesResource from redash.handlers.query_results import QueryResultListResource, QueryResultResource, JobResource from redash.handlers.users import UserResource, UserListResource, UserInviteResource, UserResetPasswordResource @@ -65,7 +65,7 @@ api.add_org_resource(GroupMemberResource, '/api/groups//members//data_sources', endpoint='group_data_sources') api.add_org_resource(GroupDataSourceResource, '/api/groups//data_sources/', endpoint='group_data_source') -api.add_org_resource(EventResource, '/api/events', endpoint='events') +api.add_org_resource(EventsResource, '/api/events', endpoint='events') api.add_org_resource(QuerySearchResource, '/api/queries/search', endpoint='queries_search') api.add_org_resource(QueryRecentResource, '/api/queries/recent', endpoint='recent_queries')