##// END OF EJS Templates
audit-logger: added convinience wrappers to store web or api action.
marcink -
r1800:4d68d81f default
parent child Browse files
Show More
@@ -1,193 +1,219 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23
23
24 from rhodecode.model import meta
24 from rhodecode.model import meta
25 from rhodecode.model.db import User, UserLog, Repository
25 from rhodecode.model.db import User, UserLog, Repository
26
26
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30
30
31 ACTIONS = {
31 ACTIONS = {
32 'user.login.success': {},
32 'user.login.success': {},
33 'user.login.failure': {},
33 'user.login.failure': {},
34 'user.logout': {},
34 'user.logout': {},
35 'user.password.reset_request': {},
35 'user.password.reset_request': {},
36 'user.push': {},
36 'user.push': {},
37 'user.pull': {},
37 'user.pull': {},
38
38
39 'repo.create': {},
39 'repo.create': {},
40 'repo.edit': {},
40 'repo.edit': {},
41 'repo.edit.permissions': {},
41 'repo.edit.permissions': {},
42 'repo.delete': {},
42 'repo.delete': {},
43 'repo.commit.strip': {},
43 'repo.commit.strip': {},
44 'repo.archive.download': {},
44 'repo.archive.download': {},
45
45
46 'repo_group.create': {},
46 'repo_group.create': {},
47 'repo_group.edit': {},
47 'repo_group.edit': {},
48 'repo_group.edit.permissions': {},
48 'repo_group.edit.permissions': {},
49 'repo_group.delete': {},
49 'repo_group.delete': {},
50 }
50 }
51
51
52 SOURCE_WEB = 'source_web'
52 SOURCE_WEB = 'source_web'
53 SOURCE_API = 'source_api'
53 SOURCE_API = 'source_api'
54
54
55
55
56 class UserWrap(object):
56 class UserWrap(object):
57 """
57 """
58 Fake object used to imitate AuthUser
58 Fake object used to imitate AuthUser
59 """
59 """
60
60
61 def __init__(self, user_id=None, username=None, ip_addr=None):
61 def __init__(self, user_id=None, username=None, ip_addr=None):
62 self.user_id = user_id
62 self.user_id = user_id
63 self.username = username
63 self.username = username
64 self.ip_addr = ip_addr
64 self.ip_addr = ip_addr
65
65
66
66
67 class RepoWrap(object):
67 class RepoWrap(object):
68 """
68 """
69 Fake object used to imitate RepoObject that audit logger requires
69 Fake object used to imitate RepoObject that audit logger requires
70 """
70 """
71
71
72 def __init__(self, repo_id=None, repo_name=None):
72 def __init__(self, repo_id=None, repo_name=None):
73 self.repo_id = repo_id
73 self.repo_id = repo_id
74 self.repo_name = repo_name
74 self.repo_name = repo_name
75
75
76
76
77 def _store_log(action_name, action_data, user_id, username, user_data,
77 def _store_log(action_name, action_data, user_id, username, user_data,
78 ip_address, repository_id, repository_name):
78 ip_address, repository_id, repository_name):
79 user_log = UserLog()
79 user_log = UserLog()
80 user_log.version = UserLog.VERSION_2
80 user_log.version = UserLog.VERSION_2
81
81
82 user_log.action = action_name
82 user_log.action = action_name
83 user_log.action_data = action_data
83 user_log.action_data = action_data
84
84
85 user_log.user_ip = ip_address
85 user_log.user_ip = ip_address
86
86
87 user_log.user_id = user_id
87 user_log.user_id = user_id
88 user_log.username = username
88 user_log.username = username
89 user_log.user_data = user_data
89 user_log.user_data = user_data
90
90
91 user_log.repository_id = repository_id
91 user_log.repository_id = repository_id
92 user_log.repository_name = repository_name
92 user_log.repository_name = repository_name
93
93
94 user_log.action_date = datetime.datetime.now()
94 user_log.action_date = datetime.datetime.now()
95
95
96 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
96 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
97 action_name, user_id, username, ip_address)
97 action_name, user_id, username, ip_address)
98
98
99 return user_log
99 return user_log
100
100
101
101
102 def store_web(*args, **kwargs):
103 if 'action_data' not in kwargs:
104 kwargs['action_data'] = {}
105 kwargs['action_data'].update({
106 'source': SOURCE_WEB
107 })
108 return store(*args, **kwargs)
109
110
111 def store_api(*args, **kwargs):
112 if 'action_data' not in kwargs:
113 kwargs['action_data'] = {}
114 kwargs['action_data'].update({
115 'source': SOURCE_API
116 })
117 return store(*args, **kwargs)
118
119
102 def store(
120 def store(
103 action, user, action_data=None, user_data=None, ip_addr=None,
121 action, user, action_data=None, user_data=None, ip_addr=None,
104 repo=None, sa_session=None, commit=False):
122 repo=None, sa_session=None, commit=False):
105 """
123 """
106 Audit logger for various actions made by users, typically this results in a call such::
124 Audit logger for various actions made by users, typically this
125 results in a call such::
107
126
108 from rhodecode.lib import audit_logger
127 from rhodecode.lib import audit_logger
109
128
110 audit_logger.store(
129 audit_logger.store(
111 action='repo.edit', user=self._rhodecode_user)
130 action='repo.edit', user=self._rhodecode_user)
112 audit_logger.store(
131 audit_logger.store(
113 action='repo.delete', action_data={'repo_data': repo_data},
132 action='repo.delete', action_data={'repo_data': repo_data},
114 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
133 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
115
134
116 # repo action
135 # repo action
117 audit_logger.store(
136 audit_logger.store(
118 action='repo.delete',
137 action='repo.delete',
119 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
138 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
120 repo=audit_logger.RepoWrap(repo_name='some-repo'))
139 repo=audit_logger.RepoWrap(repo_name='some-repo'))
121
140
122 # repo action, when we know and have the repository object already
141 # repo action, when we know and have the repository object already
123 audit_logger.store(
142 audit_logger.store(
124 action='repo.delete',
143 action='repo.delete',
125 action_data={'source': audit_logger.SOURCE_WEB, },
144 action_data={'source': audit_logger.SOURCE_WEB, },
126 user=self._rhodecode_user,
145 user=self._rhodecode_user,
127 repo=repo_object)
146 repo=repo_object)
128
147
148 # alternative wrapper to the above
149 audit_logger.store_web(
150 action='repo.delete',
151 action_data={},
152 user=self._rhodecode_user,
153 repo=repo_object)
154
129 # without an user ?
155 # without an user ?
130 audit_logger.store(
156 audit_logger.store(
131 action='user.login.failure',
157 action='user.login.failure',
132 user=audit_logger.UserWrap(
158 user=audit_logger.UserWrap(
133 username=self.request.params.get('username'),
159 username=self.request.params.get('username'),
134 ip_addr=self.request.remote_addr))
160 ip_addr=self.request.remote_addr))
135
161
136 """
162 """
137 from rhodecode.lib.utils2 import safe_unicode
163 from rhodecode.lib.utils2 import safe_unicode
138 from rhodecode.lib.auth import AuthUser
164 from rhodecode.lib.auth import AuthUser
139
165
140 if action not in ACTIONS:
166 if action not in ACTIONS:
141 raise ValueError('Action `{}` not in valid actions'.format(action))
167 raise ValueError('Action `{}` not in valid actions'.format(action))
142
168
143 if not sa_session:
169 if not sa_session:
144 sa_session = meta.Session()
170 sa_session = meta.Session()
145
171
146 try:
172 try:
147 username = getattr(user, 'username', None)
173 username = getattr(user, 'username', None)
148 if not username:
174 if not username:
149 pass
175 pass
150
176
151 user_id = getattr(user, 'user_id', None)
177 user_id = getattr(user, 'user_id', None)
152 if not user_id:
178 if not user_id:
153 # maybe we have username ? Try to figure user_id from username
179 # maybe we have username ? Try to figure user_id from username
154 if username:
180 if username:
155 user_id = getattr(
181 user_id = getattr(
156 User.get_by_username(username), 'user_id', None)
182 User.get_by_username(username), 'user_id', None)
157
183
158 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
184 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
159 if not ip_addr:
185 if not ip_addr:
160 pass
186 pass
161
187
162 if not user_data:
188 if not user_data:
163 # try to get this from the auth user
189 # try to get this from the auth user
164 if isinstance(user, AuthUser):
190 if isinstance(user, AuthUser):
165 user_data = {
191 user_data = {
166 'username': user.username,
192 'username': user.username,
167 'email': user.email,
193 'email': user.email,
168 }
194 }
169
195
170 repository_name = getattr(repo, 'repo_name', None)
196 repository_name = getattr(repo, 'repo_name', None)
171 repository_id = getattr(repo, 'repo_id', None)
197 repository_id = getattr(repo, 'repo_id', None)
172 if not repository_id:
198 if not repository_id:
173 # maybe we have repo_name ? Try to figure repo_id from repo_name
199 # maybe we have repo_name ? Try to figure repo_id from repo_name
174 if repository_name:
200 if repository_name:
175 repository_id = getattr(
201 repository_id = getattr(
176 Repository.get_by_repo_name(repository_name), 'repo_id', None)
202 Repository.get_by_repo_name(repository_name), 'repo_id', None)
177
203
178 user_log = _store_log(
204 user_log = _store_log(
179 action_name=safe_unicode(action),
205 action_name=safe_unicode(action),
180 action_data=action_data or {},
206 action_data=action_data or {},
181 user_id=user_id,
207 user_id=user_id,
182 username=username,
208 username=username,
183 user_data=user_data or {},
209 user_data=user_data or {},
184 ip_address=safe_unicode(ip_addr),
210 ip_address=safe_unicode(ip_addr),
185 repository_id=repository_id,
211 repository_id=repository_id,
186 repository_name=repository_name
212 repository_name=repository_name
187 )
213 )
188 sa_session.add(user_log)
214 sa_session.add(user_log)
189 if commit:
215 if commit:
190 sa_session.commit()
216 sa_session.commit()
191
217
192 except Exception:
218 except Exception:
193 log.exception('AUDIT: failed to store audit log')
219 log.exception('AUDIT: failed to store audit log')
General Comments 0
You need to be logged in to leave comments. Login now