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