##// END OF EJS Templates
audit-logger: added entry for archive download.
marcink -
r1743:71b6a673 default
parent child Browse files
Show More
@@ -1,1102 +1,1114 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-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 """
22 22 Files controller for RhodeCode Enterprise
23 23 """
24 24
25 25 import itertools
26 26 import logging
27 27 import os
28 28 import shutil
29 29 import tempfile
30 30
31 31 from pylons import request, response, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from webob.exc import HTTPNotFound, HTTPBadRequest
35 35
36 36 from rhodecode.controllers.utils import parse_path_ref
37 37 from rhodecode.lib import diffs, helpers as h, caches
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib import audit_logger
39 39 from rhodecode.lib.codeblocks import (
40 40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 41 from rhodecode.lib.utils import jsonify, action_logger
42 42 from rhodecode.lib.utils2 import (
43 43 convert_line_endings, detect_mode, safe_str, str2bool)
44 44 from rhodecode.lib.auth import (
45 45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
46 46 from rhodecode.lib.base import BaseRepoController, render
47 47 from rhodecode.lib.vcs import path as vcspath
48 48 from rhodecode.lib.vcs.backends.base import EmptyCommit
49 49 from rhodecode.lib.vcs.conf import settings
50 50 from rhodecode.lib.vcs.exceptions import (
51 51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 53 NodeDoesNotExistError, CommitError, NodeError)
54 54 from rhodecode.lib.vcs.nodes import FileNode
55 55
56 56 from rhodecode.model.repo import RepoModel
57 57 from rhodecode.model.scm import ScmModel
58 58 from rhodecode.model.db import Repository
59 59
60 60 from rhodecode.controllers.changeset import (
61 61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
62 62 from rhodecode.lib.exceptions import NonRelativePathError
63 63
64 64 log = logging.getLogger(__name__)
65 65
66 66
67 67 class FilesController(BaseRepoController):
68 68
69 69 def __before__(self):
70 70 super(FilesController, self).__before__()
71 71 c.cut_off_limit = self.cut_off_limit_file
72 72
73 73 def _get_default_encoding(self):
74 74 enc_list = getattr(c, 'default_encodings', [])
75 75 return enc_list[0] if enc_list else 'UTF-8'
76 76
77 77 def __get_commit_or_redirect(self, commit_id, repo_name,
78 78 redirect_after=True):
79 79 """
80 80 This is a safe way to get commit. If an error occurs it redirects to
81 81 tip with proper message
82 82
83 83 :param commit_id: id of commit to fetch
84 84 :param repo_name: repo name to redirect after
85 85 :param redirect_after: toggle redirection
86 86 """
87 87 try:
88 88 return c.rhodecode_repo.get_commit(commit_id)
89 89 except EmptyRepositoryError:
90 90 if not redirect_after:
91 91 return None
92 92 url_ = url('files_add_home',
93 93 repo_name=c.repo_name,
94 94 revision=0, f_path='', anchor='edit')
95 95 if h.HasRepoPermissionAny(
96 96 'repository.write', 'repository.admin')(c.repo_name):
97 97 add_new = h.link_to(
98 98 _('Click here to add a new file.'),
99 99 url_, class_="alert-link")
100 100 else:
101 101 add_new = ""
102 102 h.flash(h.literal(
103 103 _('There are no files yet. %s') % add_new), category='warning')
104 104 redirect(h.url('summary_home', repo_name=repo_name))
105 105 except (CommitDoesNotExistError, LookupError):
106 106 msg = _('No such commit exists for this repository')
107 107 h.flash(msg, category='error')
108 108 raise HTTPNotFound()
109 109 except RepositoryError as e:
110 110 h.flash(safe_str(e), category='error')
111 111 raise HTTPNotFound()
112 112
113 113 def __get_filenode_or_redirect(self, repo_name, commit, path):
114 114 """
115 115 Returns file_node, if error occurs or given path is directory,
116 116 it'll redirect to top level path
117 117
118 118 :param repo_name: repo_name
119 119 :param commit: given commit
120 120 :param path: path to lookup
121 121 """
122 122 try:
123 123 file_node = commit.get_node(path)
124 124 if file_node.is_dir():
125 125 raise RepositoryError('The given path is a directory')
126 126 except CommitDoesNotExistError:
127 127 msg = _('No such commit exists for this repository')
128 128 log.exception(msg)
129 129 h.flash(msg, category='error')
130 130 raise HTTPNotFound()
131 131 except RepositoryError as e:
132 132 h.flash(safe_str(e), category='error')
133 133 raise HTTPNotFound()
134 134
135 135 return file_node
136 136
137 137 def __get_tree_cache_manager(self, repo_name, namespace_type):
138 138 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
139 139 return caches.get_cache_manager('repo_cache_long', _namespace)
140 140
141 141 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
142 142 full_load=False, force=False):
143 143 def _cached_tree():
144 144 log.debug('Generating cached file tree for %s, %s, %s',
145 145 repo_name, commit_id, f_path)
146 146 c.full_load = full_load
147 147 return render('files/files_browser_tree.mako')
148 148
149 149 cache_manager = self.__get_tree_cache_manager(
150 150 repo_name, caches.FILE_TREE)
151 151
152 152 cache_key = caches.compute_key_from_params(
153 153 repo_name, commit_id, f_path)
154 154
155 155 if force:
156 156 # we want to force recompute of caches
157 157 cache_manager.remove_value(cache_key)
158 158
159 159 return cache_manager.get(cache_key, createfunc=_cached_tree)
160 160
161 161 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
162 162 def _cached_nodes():
163 163 log.debug('Generating cached nodelist for %s, %s, %s',
164 164 repo_name, commit_id, f_path)
165 165 _d, _f = ScmModel().get_nodes(
166 166 repo_name, commit_id, f_path, flat=False)
167 167 return _d + _f
168 168
169 169 cache_manager = self.__get_tree_cache_manager(
170 170 repo_name, caches.FILE_SEARCH_TREE_META)
171 171
172 172 cache_key = caches.compute_key_from_params(
173 173 repo_name, commit_id, f_path)
174 174 return cache_manager.get(cache_key, createfunc=_cached_nodes)
175 175
176 176 @LoginRequired()
177 177 @HasRepoPermissionAnyDecorator(
178 178 'repository.read', 'repository.write', 'repository.admin')
179 179 def index(
180 180 self, repo_name, revision, f_path, annotate=False, rendered=False):
181 181 commit_id = revision
182 182
183 183 # redirect to given commit_id from form if given
184 184 get_commit_id = request.GET.get('at_rev', None)
185 185 if get_commit_id:
186 186 self.__get_commit_or_redirect(get_commit_id, repo_name)
187 187
188 188 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
189 189 c.branch = request.GET.get('branch', None)
190 190 c.f_path = f_path
191 191 c.annotate = annotate
192 192 # default is false, but .rst/.md files later are autorendered, we can
193 193 # overwrite autorendering by setting this GET flag
194 194 c.renderer = rendered or not request.GET.get('no-render', False)
195 195
196 196 # prev link
197 197 try:
198 198 prev_commit = c.commit.prev(c.branch)
199 199 c.prev_commit = prev_commit
200 200 c.url_prev = url('files_home', repo_name=c.repo_name,
201 201 revision=prev_commit.raw_id, f_path=f_path)
202 202 if c.branch:
203 203 c.url_prev += '?branch=%s' % c.branch
204 204 except (CommitDoesNotExistError, VCSError):
205 205 c.url_prev = '#'
206 206 c.prev_commit = EmptyCommit()
207 207
208 208 # next link
209 209 try:
210 210 next_commit = c.commit.next(c.branch)
211 211 c.next_commit = next_commit
212 212 c.url_next = url('files_home', repo_name=c.repo_name,
213 213 revision=next_commit.raw_id, f_path=f_path)
214 214 if c.branch:
215 215 c.url_next += '?branch=%s' % c.branch
216 216 except (CommitDoesNotExistError, VCSError):
217 217 c.url_next = '#'
218 218 c.next_commit = EmptyCommit()
219 219
220 220 # files or dirs
221 221 try:
222 222 c.file = c.commit.get_node(f_path)
223 223 c.file_author = True
224 224 c.file_tree = ''
225 225 if c.file.is_file():
226 226 c.lf_node = c.file.get_largefile_node()
227 227
228 228 c.file_source_page = 'true'
229 229 c.file_last_commit = c.file.last_commit
230 230 if c.file.size < self.cut_off_limit_file:
231 231 if c.annotate: # annotation has precedence over renderer
232 232 c.annotated_lines = filenode_as_annotated_lines_tokens(
233 233 c.file
234 234 )
235 235 else:
236 236 c.renderer = (
237 237 c.renderer and h.renderer_from_filename(c.file.path)
238 238 )
239 239 if not c.renderer:
240 240 c.lines = filenode_as_lines_tokens(c.file)
241 241
242 242 c.on_branch_head = self._is_valid_head(
243 243 commit_id, c.rhodecode_repo)
244 244
245 245 branch = c.commit.branch if (
246 246 c.commit.branch and '/' not in c.commit.branch) else None
247 247 c.branch_or_raw_id = branch or c.commit.raw_id
248 248 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
249 249
250 250 author = c.file_last_commit.author
251 251 c.authors = [(h.email(author),
252 252 h.person(author, 'username_or_name_or_email'))]
253 253 else:
254 254 c.file_source_page = 'false'
255 255 c.authors = []
256 256 c.file_tree = self._get_tree_at_commit(
257 257 repo_name, c.commit.raw_id, f_path)
258 258
259 259 except RepositoryError as e:
260 260 h.flash(safe_str(e), category='error')
261 261 raise HTTPNotFound()
262 262
263 263 if request.environ.get('HTTP_X_PJAX'):
264 264 return render('files/files_pjax.mako')
265 265
266 266 return render('files/files.mako')
267 267
268 268 @LoginRequired()
269 269 @HasRepoPermissionAnyDecorator(
270 270 'repository.read', 'repository.write', 'repository.admin')
271 271 def annotate_previous(self, repo_name, revision, f_path):
272 272
273 273 commit_id = revision
274 274 commit = self.__get_commit_or_redirect(commit_id, repo_name)
275 275 prev_commit_id = commit.raw_id
276 276
277 277 f_path = f_path
278 278 is_file = False
279 279 try:
280 280 _file = commit.get_node(f_path)
281 281 is_file = _file.is_file()
282 282 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
283 283 pass
284 284
285 285 if is_file:
286 286 history = commit.get_file_history(f_path)
287 287 prev_commit_id = history[1].raw_id \
288 288 if len(history) > 1 else prev_commit_id
289 289
290 290 return redirect(h.url(
291 291 'files_annotate_home', repo_name=repo_name,
292 292 revision=prev_commit_id, f_path=f_path))
293 293
294 294 @LoginRequired()
295 295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
296 296 'repository.admin')
297 297 @jsonify
298 298 def history(self, repo_name, revision, f_path):
299 299 commit = self.__get_commit_or_redirect(revision, repo_name)
300 300 f_path = f_path
301 301 _file = commit.get_node(f_path)
302 302 if _file.is_file():
303 303 file_history, _hist = self._get_node_history(commit, f_path)
304 304
305 305 res = []
306 306 for obj in file_history:
307 307 res.append({
308 308 'text': obj[1],
309 309 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
310 310 })
311 311
312 312 data = {
313 313 'more': False,
314 314 'results': res
315 315 }
316 316 return data
317 317
318 318 @LoginRequired()
319 319 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
320 320 'repository.admin')
321 321 def authors(self, repo_name, revision, f_path):
322 322 commit = self.__get_commit_or_redirect(revision, repo_name)
323 323 file_node = commit.get_node(f_path)
324 324 if file_node.is_file():
325 325 c.file_last_commit = file_node.last_commit
326 326 if request.GET.get('annotate') == '1':
327 327 # use _hist from annotation if annotation mode is on
328 328 commit_ids = set(x[1] for x in file_node.annotate)
329 329 _hist = (
330 330 c.rhodecode_repo.get_commit(commit_id)
331 331 for commit_id in commit_ids)
332 332 else:
333 333 _f_history, _hist = self._get_node_history(commit, f_path)
334 334 c.file_author = False
335 335 c.authors = []
336 336 for author in set(commit.author for commit in _hist):
337 337 c.authors.append((
338 338 h.email(author),
339 339 h.person(author, 'username_or_name_or_email')))
340 340 return render('files/file_authors_box.mako')
341 341
342 342 @LoginRequired()
343 343 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
344 344 'repository.admin')
345 345 def rawfile(self, repo_name, revision, f_path):
346 346 """
347 347 Action for download as raw
348 348 """
349 349 commit = self.__get_commit_or_redirect(revision, repo_name)
350 350 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
351 351
352 352 if request.GET.get('lf'):
353 353 # only if lf get flag is passed, we download this file
354 354 # as LFS/Largefile
355 355 lf_node = file_node.get_largefile_node()
356 356 if lf_node:
357 357 # overwrite our pointer with the REAL large-file
358 358 file_node = lf_node
359 359
360 360 response.content_disposition = 'attachment; filename=%s' % \
361 361 safe_str(f_path.split(Repository.NAME_SEP)[-1])
362 362
363 363 response.content_type = file_node.mimetype
364 364 charset = self._get_default_encoding()
365 365 if charset:
366 366 response.charset = charset
367 367
368 368 return file_node.content
369 369
370 370 @LoginRequired()
371 371 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
372 372 'repository.admin')
373 373 def raw(self, repo_name, revision, f_path):
374 374 """
375 375 Action for show as raw, some mimetypes are "rendered",
376 376 those include images, icons.
377 377 """
378 378 commit = self.__get_commit_or_redirect(revision, repo_name)
379 379 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
380 380
381 381 raw_mimetype_mapping = {
382 382 # map original mimetype to a mimetype used for "show as raw"
383 383 # you can also provide a content-disposition to override the
384 384 # default "attachment" disposition.
385 385 # orig_type: (new_type, new_dispo)
386 386
387 387 # show images inline:
388 388 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
389 389 # for example render an SVG with javascript inside or even render
390 390 # HTML.
391 391 'image/x-icon': ('image/x-icon', 'inline'),
392 392 'image/png': ('image/png', 'inline'),
393 393 'image/gif': ('image/gif', 'inline'),
394 394 'image/jpeg': ('image/jpeg', 'inline'),
395 395 'application/pdf': ('application/pdf', 'inline'),
396 396 }
397 397
398 398 mimetype = file_node.mimetype
399 399 try:
400 400 mimetype, dispo = raw_mimetype_mapping[mimetype]
401 401 except KeyError:
402 402 # we don't know anything special about this, handle it safely
403 403 if file_node.is_binary:
404 404 # do same as download raw for binary files
405 405 mimetype, dispo = 'application/octet-stream', 'attachment'
406 406 else:
407 407 # do not just use the original mimetype, but force text/plain,
408 408 # otherwise it would serve text/html and that might be unsafe.
409 409 # Note: underlying vcs library fakes text/plain mimetype if the
410 410 # mimetype can not be determined and it thinks it is not
411 411 # binary.This might lead to erroneous text display in some
412 412 # cases, but helps in other cases, like with text files
413 413 # without extension.
414 414 mimetype, dispo = 'text/plain', 'inline'
415 415
416 416 if dispo == 'attachment':
417 417 dispo = 'attachment; filename=%s' % safe_str(
418 418 f_path.split(os.sep)[-1])
419 419
420 420 response.content_disposition = dispo
421 421 response.content_type = mimetype
422 422 charset = self._get_default_encoding()
423 423 if charset:
424 424 response.charset = charset
425 425 return file_node.content
426 426
427 427 @CSRFRequired()
428 428 @LoginRequired()
429 429 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
430 430 def delete(self, repo_name, revision, f_path):
431 431 commit_id = revision
432 432
433 433 repo = c.rhodecode_db_repo
434 434 if repo.enable_locking and repo.locked[0]:
435 435 h.flash(_('This repository has been locked by %s on %s')
436 436 % (h.person_by_id(repo.locked[0]),
437 437 h.format_date(h.time_to_datetime(repo.locked[1]))),
438 438 'warning')
439 439 return redirect(h.url('files_home',
440 440 repo_name=repo_name, revision='tip'))
441 441
442 442 if not self._is_valid_head(commit_id, repo.scm_instance()):
443 443 h.flash(_('You can only delete files with revision '
444 444 'being a valid branch '), category='warning')
445 445 return redirect(h.url('files_home',
446 446 repo_name=repo_name, revision='tip',
447 447 f_path=f_path))
448 448
449 449 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
450 450 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
451 451
452 452 c.default_message = _(
453 453 'Deleted file %s via RhodeCode Enterprise') % (f_path)
454 454 c.f_path = f_path
455 455 node_path = f_path
456 456 author = c.rhodecode_user.full_contact
457 457 message = request.POST.get('message') or c.default_message
458 458 try:
459 459 nodes = {
460 460 node_path: {
461 461 'content': ''
462 462 }
463 463 }
464 464 self.scm_model.delete_nodes(
465 465 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
466 466 message=message,
467 467 nodes=nodes,
468 468 parent_commit=c.commit,
469 469 author=author,
470 470 )
471 471
472 472 h.flash(_('Successfully deleted file %s') % f_path,
473 473 category='success')
474 474 except Exception:
475 475 msg = _('Error occurred during commit')
476 476 log.exception(msg)
477 477 h.flash(msg, category='error')
478 478 return redirect(url('changeset_home',
479 479 repo_name=c.repo_name, revision='tip'))
480 480
481 481 @LoginRequired()
482 482 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
483 483 def delete_home(self, repo_name, revision, f_path):
484 484 commit_id = revision
485 485
486 486 repo = c.rhodecode_db_repo
487 487 if repo.enable_locking and repo.locked[0]:
488 488 h.flash(_('This repository has been locked by %s on %s')
489 489 % (h.person_by_id(repo.locked[0]),
490 490 h.format_date(h.time_to_datetime(repo.locked[1]))),
491 491 'warning')
492 492 return redirect(h.url('files_home',
493 493 repo_name=repo_name, revision='tip'))
494 494
495 495 if not self._is_valid_head(commit_id, repo.scm_instance()):
496 496 h.flash(_('You can only delete files with revision '
497 497 'being a valid branch '), category='warning')
498 498 return redirect(h.url('files_home',
499 499 repo_name=repo_name, revision='tip',
500 500 f_path=f_path))
501 501
502 502 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
503 503 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
504 504
505 505 c.default_message = _(
506 506 'Deleted file %s via RhodeCode Enterprise') % (f_path)
507 507 c.f_path = f_path
508 508
509 509 return render('files/files_delete.mako')
510 510
511 511 @CSRFRequired()
512 512 @LoginRequired()
513 513 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
514 514 def edit(self, repo_name, revision, f_path):
515 515 commit_id = revision
516 516
517 517 repo = c.rhodecode_db_repo
518 518 if repo.enable_locking and repo.locked[0]:
519 519 h.flash(_('This repository has been locked by %s on %s')
520 520 % (h.person_by_id(repo.locked[0]),
521 521 h.format_date(h.time_to_datetime(repo.locked[1]))),
522 522 'warning')
523 523 return redirect(h.url('files_home',
524 524 repo_name=repo_name, revision='tip'))
525 525
526 526 if not self._is_valid_head(commit_id, repo.scm_instance()):
527 527 h.flash(_('You can only edit files with revision '
528 528 'being a valid branch '), category='warning')
529 529 return redirect(h.url('files_home',
530 530 repo_name=repo_name, revision='tip',
531 531 f_path=f_path))
532 532
533 533 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
534 534 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
535 535
536 536 if c.file.is_binary:
537 537 return redirect(url('files_home', repo_name=c.repo_name,
538 538 revision=c.commit.raw_id, f_path=f_path))
539 539 c.default_message = _(
540 540 'Edited file %s via RhodeCode Enterprise') % (f_path)
541 541 c.f_path = f_path
542 542 old_content = c.file.content
543 543 sl = old_content.splitlines(1)
544 544 first_line = sl[0] if sl else ''
545 545
546 546 # modes: 0 - Unix, 1 - Mac, 2 - DOS
547 547 mode = detect_mode(first_line, 0)
548 548 content = convert_line_endings(request.POST.get('content', ''), mode)
549 549
550 550 message = request.POST.get('message') or c.default_message
551 551 org_f_path = c.file.unicode_path
552 552 filename = request.POST['filename']
553 553 org_filename = c.file.name
554 554
555 555 if content == old_content and filename == org_filename:
556 556 h.flash(_('No changes'), category='warning')
557 557 return redirect(url('changeset_home', repo_name=c.repo_name,
558 558 revision='tip'))
559 559 try:
560 560 mapping = {
561 561 org_f_path: {
562 562 'org_filename': org_f_path,
563 563 'filename': os.path.join(c.file.dir_path, filename),
564 564 'content': content,
565 565 'lexer': '',
566 566 'op': 'mod',
567 567 }
568 568 }
569 569
570 570 ScmModel().update_nodes(
571 571 user=c.rhodecode_user.user_id,
572 572 repo=c.rhodecode_db_repo,
573 573 message=message,
574 574 nodes=mapping,
575 575 parent_commit=c.commit,
576 576 )
577 577
578 578 h.flash(_('Successfully committed to %s') % f_path,
579 579 category='success')
580 580 except Exception:
581 581 msg = _('Error occurred during commit')
582 582 log.exception(msg)
583 583 h.flash(msg, category='error')
584 584 return redirect(url('changeset_home',
585 585 repo_name=c.repo_name, revision='tip'))
586 586
587 587 @LoginRequired()
588 588 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
589 589 def edit_home(self, repo_name, revision, f_path):
590 590 commit_id = revision
591 591
592 592 repo = c.rhodecode_db_repo
593 593 if repo.enable_locking and repo.locked[0]:
594 594 h.flash(_('This repository has been locked by %s on %s')
595 595 % (h.person_by_id(repo.locked[0]),
596 596 h.format_date(h.time_to_datetime(repo.locked[1]))),
597 597 'warning')
598 598 return redirect(h.url('files_home',
599 599 repo_name=repo_name, revision='tip'))
600 600
601 601 if not self._is_valid_head(commit_id, repo.scm_instance()):
602 602 h.flash(_('You can only edit files with revision '
603 603 'being a valid branch '), category='warning')
604 604 return redirect(h.url('files_home',
605 605 repo_name=repo_name, revision='tip',
606 606 f_path=f_path))
607 607
608 608 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
609 609 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
610 610
611 611 if c.file.is_binary:
612 612 return redirect(url('files_home', repo_name=c.repo_name,
613 613 revision=c.commit.raw_id, f_path=f_path))
614 614 c.default_message = _(
615 615 'Edited file %s via RhodeCode Enterprise') % (f_path)
616 616 c.f_path = f_path
617 617
618 618 return render('files/files_edit.mako')
619 619
620 620 def _is_valid_head(self, commit_id, repo):
621 621 # check if commit is a branch identifier- basically we cannot
622 622 # create multiple heads via file editing
623 623 valid_heads = repo.branches.keys() + repo.branches.values()
624 624
625 625 if h.is_svn(repo) and not repo.is_empty():
626 626 # Note: Subversion only has one head, we add it here in case there
627 627 # is no branch matched.
628 628 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
629 629
630 630 # check if commit is a branch name or branch hash
631 631 return commit_id in valid_heads
632 632
633 633 @CSRFRequired()
634 634 @LoginRequired()
635 635 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
636 636 def add(self, repo_name, revision, f_path):
637 637 repo = Repository.get_by_repo_name(repo_name)
638 638 if repo.enable_locking and repo.locked[0]:
639 639 h.flash(_('This repository has been locked by %s on %s')
640 640 % (h.person_by_id(repo.locked[0]),
641 641 h.format_date(h.time_to_datetime(repo.locked[1]))),
642 642 'warning')
643 643 return redirect(h.url('files_home',
644 644 repo_name=repo_name, revision='tip'))
645 645
646 646 r_post = request.POST
647 647
648 648 c.commit = self.__get_commit_or_redirect(
649 649 revision, repo_name, redirect_after=False)
650 650 if c.commit is None:
651 651 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
652 652 c.default_message = (_('Added file via RhodeCode Enterprise'))
653 653 c.f_path = f_path
654 654 unix_mode = 0
655 655 content = convert_line_endings(r_post.get('content', ''), unix_mode)
656 656
657 657 message = r_post.get('message') or c.default_message
658 658 filename = r_post.get('filename')
659 659 location = r_post.get('location', '') # dir location
660 660 file_obj = r_post.get('upload_file', None)
661 661
662 662 if file_obj is not None and hasattr(file_obj, 'filename'):
663 663 filename = r_post.get('filename_upload')
664 664 content = file_obj.file
665 665
666 666 if hasattr(content, 'file'):
667 667 # non posix systems store real file under file attr
668 668 content = content.file
669 669
670 670 # If there's no commit, redirect to repo summary
671 671 if type(c.commit) is EmptyCommit:
672 672 redirect_url = "summary_home"
673 673 else:
674 674 redirect_url = "changeset_home"
675 675
676 676 if not filename:
677 677 h.flash(_('No filename'), category='warning')
678 678 return redirect(url(redirect_url, repo_name=c.repo_name,
679 679 revision='tip'))
680 680
681 681 # extract the location from filename,
682 682 # allows using foo/bar.txt syntax to create subdirectories
683 683 subdir_loc = filename.rsplit('/', 1)
684 684 if len(subdir_loc) == 2:
685 685 location = os.path.join(location, subdir_loc[0])
686 686
687 687 # strip all crap out of file, just leave the basename
688 688 filename = os.path.basename(filename)
689 689 node_path = os.path.join(location, filename)
690 690 author = c.rhodecode_user.full_contact
691 691
692 692 try:
693 693 nodes = {
694 694 node_path: {
695 695 'content': content
696 696 }
697 697 }
698 698 self.scm_model.create_nodes(
699 699 user=c.rhodecode_user.user_id,
700 700 repo=c.rhodecode_db_repo,
701 701 message=message,
702 702 nodes=nodes,
703 703 parent_commit=c.commit,
704 704 author=author,
705 705 )
706 706
707 707 h.flash(_('Successfully committed to %s') % node_path,
708 708 category='success')
709 709 except NonRelativePathError as e:
710 710 h.flash(_(
711 711 'The location specified must be a relative path and must not '
712 712 'contain .. in the path'), category='warning')
713 713 return redirect(url('changeset_home', repo_name=c.repo_name,
714 714 revision='tip'))
715 715 except (NodeError, NodeAlreadyExistsError) as e:
716 716 h.flash(_(e), category='error')
717 717 except Exception:
718 718 msg = _('Error occurred during commit')
719 719 log.exception(msg)
720 720 h.flash(msg, category='error')
721 721 return redirect(url('changeset_home',
722 722 repo_name=c.repo_name, revision='tip'))
723 723
724 724 @LoginRequired()
725 725 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
726 726 def add_home(self, repo_name, revision, f_path):
727 727
728 728 repo = Repository.get_by_repo_name(repo_name)
729 729 if repo.enable_locking and repo.locked[0]:
730 730 h.flash(_('This repository has been locked by %s on %s')
731 731 % (h.person_by_id(repo.locked[0]),
732 732 h.format_date(h.time_to_datetime(repo.locked[1]))),
733 733 'warning')
734 734 return redirect(h.url('files_home',
735 735 repo_name=repo_name, revision='tip'))
736 736
737 737 c.commit = self.__get_commit_or_redirect(
738 738 revision, repo_name, redirect_after=False)
739 739 if c.commit is None:
740 740 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
741 741 c.default_message = (_('Added file via RhodeCode Enterprise'))
742 742 c.f_path = f_path
743 743
744 744 return render('files/files_add.mako')
745 745
746 746 @LoginRequired()
747 747 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
748 748 'repository.admin')
749 749 def archivefile(self, repo_name, fname):
750 750 fileformat = None
751 751 commit_id = None
752 752 ext = None
753 753 subrepos = request.GET.get('subrepos') == 'true'
754 754
755 755 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
756 756 archive_spec = fname.split(ext_data[1])
757 757 if len(archive_spec) == 2 and archive_spec[1] == '':
758 758 fileformat = a_type or ext_data[1]
759 759 commit_id = archive_spec[0]
760 760 ext = ext_data[1]
761 761
762 762 dbrepo = RepoModel().get_by_repo_name(repo_name)
763 763 if not dbrepo.enable_downloads:
764 764 return _('Downloads disabled')
765 765
766 766 try:
767 767 commit = c.rhodecode_repo.get_commit(commit_id)
768 768 content_type = settings.ARCHIVE_SPECS[fileformat][0]
769 769 except CommitDoesNotExistError:
770 770 return _('Unknown revision %s') % commit_id
771 771 except EmptyRepositoryError:
772 772 return _('Empty repository')
773 773 except KeyError:
774 774 return _('Unknown archive type')
775 775
776 776 # archive cache
777 777 from rhodecode import CONFIG
778 778
779 779 archive_name = '%s-%s%s%s' % (
780 780 safe_str(repo_name.replace('/', '_')),
781 781 '-sub' if subrepos else '',
782 782 safe_str(commit.short_id), ext)
783 783
784 784 use_cached_archive = False
785 785 archive_cache_enabled = CONFIG.get(
786 786 'archive_cache_dir') and not request.GET.get('no_cache')
787 787
788 788 if archive_cache_enabled:
789 789 # check if we it's ok to write
790 790 if not os.path.isdir(CONFIG['archive_cache_dir']):
791 791 os.makedirs(CONFIG['archive_cache_dir'])
792 792 cached_archive_path = os.path.join(
793 793 CONFIG['archive_cache_dir'], archive_name)
794 794 if os.path.isfile(cached_archive_path):
795 795 log.debug('Found cached archive in %s', cached_archive_path)
796 796 fd, archive = None, cached_archive_path
797 797 use_cached_archive = True
798 798 else:
799 799 log.debug('Archive %s is not yet cached', archive_name)
800 800
801 801 if not use_cached_archive:
802 802 # generate new archive
803 803 fd, archive = tempfile.mkstemp()
804 804 log.debug('Creating new temp archive in %s' % (archive,))
805 805 try:
806 806 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
807 807 except ImproperArchiveTypeError:
808 808 return _('Unknown archive type')
809 809 if archive_cache_enabled:
810 810 # if we generated the archive and we have cache enabled
811 811 # let's use this for future
812 812 log.debug('Storing new archive in %s' % (cached_archive_path,))
813 813 shutil.move(archive, cached_archive_path)
814 814 archive = cached_archive_path
815 815
816 # store download action
817 action_logger(user=c.rhodecode_user,
818 action='user_downloaded_archive:%s' % archive_name,
819 repo=repo_name, ipaddr=self.ip_addr, commit=True)
820
821 audit_logger.store(
822 action='repo.archive.download',
823 action_data={'user_agent': request.user_agent,
824 'archive_name': archive_name,
825 'archive_spec': fname,
826 'archive_cached': use_cached_archive},
827 user=c.rhodecode_user,
828 repo=dbrepo,
829 commit=True
830 )
831
832 response.content_disposition = str(
833 'attachment; filename=%s' % archive_name)
834 response.content_type = str(content_type)
835
816 836 def get_chunked_archive(archive):
817 837 with open(archive, 'rb') as stream:
818 838 while True:
819 839 data = stream.read(16 * 1024)
820 840 if not data:
821 841 if fd: # fd means we used temporary file
822 842 os.close(fd)
823 843 if not archive_cache_enabled:
824 844 log.debug('Destroying temp archive %s', archive)
825 845 os.remove(archive)
826 846 break
827 847 yield data
828 848
829 # store download action
830 action_logger(user=c.rhodecode_user,
831 action='user_downloaded_archive:%s' % archive_name,
832 repo=repo_name, ipaddr=self.ip_addr, commit=True)
833 response.content_disposition = str(
834 'attachment; filename=%s' % archive_name)
835 response.content_type = str(content_type)
836
837 849 return get_chunked_archive(archive)
838 850
839 851 @LoginRequired()
840 852 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
841 853 'repository.admin')
842 854 def diff(self, repo_name, f_path):
843 855
844 856 c.action = request.GET.get('diff')
845 857 diff1 = request.GET.get('diff1', '')
846 858 diff2 = request.GET.get('diff2', '')
847 859
848 860 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
849 861
850 862 ignore_whitespace = str2bool(request.GET.get('ignorews'))
851 863 line_context = request.GET.get('context', 3)
852 864
853 865 if not any((diff1, diff2)):
854 866 h.flash(
855 867 'Need query parameter "diff1" or "diff2" to generate a diff.',
856 868 category='error')
857 869 raise HTTPBadRequest()
858 870
859 871 if c.action not in ['download', 'raw']:
860 872 # redirect to new view if we render diff
861 873 return redirect(
862 874 url('compare_url', repo_name=repo_name,
863 875 source_ref_type='rev',
864 876 source_ref=diff1,
865 877 target_repo=c.repo_name,
866 878 target_ref_type='rev',
867 879 target_ref=diff2,
868 880 f_path=f_path))
869 881
870 882 try:
871 883 node1 = self._get_file_node(diff1, path1)
872 884 node2 = self._get_file_node(diff2, f_path)
873 885 except (RepositoryError, NodeError):
874 886 log.exception("Exception while trying to get node from repository")
875 887 return redirect(url(
876 888 'files_home', repo_name=c.repo_name, f_path=f_path))
877 889
878 890 if all(isinstance(node.commit, EmptyCommit)
879 891 for node in (node1, node2)):
880 892 raise HTTPNotFound
881 893
882 894 c.commit_1 = node1.commit
883 895 c.commit_2 = node2.commit
884 896
885 897 if c.action == 'download':
886 898 _diff = diffs.get_gitdiff(node1, node2,
887 899 ignore_whitespace=ignore_whitespace,
888 900 context=line_context)
889 901 diff = diffs.DiffProcessor(_diff, format='gitdiff')
890 902
891 903 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
892 904 response.content_type = 'text/plain'
893 905 response.content_disposition = (
894 906 'attachment; filename=%s' % (diff_name,)
895 907 )
896 908 charset = self._get_default_encoding()
897 909 if charset:
898 910 response.charset = charset
899 911 return diff.as_raw()
900 912
901 913 elif c.action == 'raw':
902 914 _diff = diffs.get_gitdiff(node1, node2,
903 915 ignore_whitespace=ignore_whitespace,
904 916 context=line_context)
905 917 diff = diffs.DiffProcessor(_diff, format='gitdiff')
906 918 response.content_type = 'text/plain'
907 919 charset = self._get_default_encoding()
908 920 if charset:
909 921 response.charset = charset
910 922 return diff.as_raw()
911 923
912 924 else:
913 925 return redirect(
914 926 url('compare_url', repo_name=repo_name,
915 927 source_ref_type='rev',
916 928 source_ref=diff1,
917 929 target_repo=c.repo_name,
918 930 target_ref_type='rev',
919 931 target_ref=diff2,
920 932 f_path=f_path))
921 933
922 934 @LoginRequired()
923 935 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
924 936 'repository.admin')
925 937 def diff_2way(self, repo_name, f_path):
926 938 """
927 939 Kept only to make OLD links work
928 940 """
929 941 diff1 = request.GET.get('diff1', '')
930 942 diff2 = request.GET.get('diff2', '')
931 943
932 944 if not any((diff1, diff2)):
933 945 h.flash(
934 946 'Need query parameter "diff1" or "diff2" to generate a diff.',
935 947 category='error')
936 948 raise HTTPBadRequest()
937 949
938 950 return redirect(
939 951 url('compare_url', repo_name=repo_name,
940 952 source_ref_type='rev',
941 953 source_ref=diff1,
942 954 target_repo=c.repo_name,
943 955 target_ref_type='rev',
944 956 target_ref=diff2,
945 957 f_path=f_path,
946 958 diffmode='sideside'))
947 959
948 960 def _get_file_node(self, commit_id, f_path):
949 961 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
950 962 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
951 963 try:
952 964 node = commit.get_node(f_path)
953 965 if node.is_dir():
954 966 raise NodeError('%s path is a %s not a file'
955 967 % (node, type(node)))
956 968 except NodeDoesNotExistError:
957 969 commit = EmptyCommit(
958 970 commit_id=commit_id,
959 971 idx=commit.idx,
960 972 repo=commit.repository,
961 973 alias=commit.repository.alias,
962 974 message=commit.message,
963 975 author=commit.author,
964 976 date=commit.date)
965 977 node = FileNode(f_path, '', commit=commit)
966 978 else:
967 979 commit = EmptyCommit(
968 980 repo=c.rhodecode_repo,
969 981 alias=c.rhodecode_repo.alias)
970 982 node = FileNode(f_path, '', commit=commit)
971 983 return node
972 984
973 985 def _get_node_history(self, commit, f_path, commits=None):
974 986 """
975 987 get commit history for given node
976 988
977 989 :param commit: commit to calculate history
978 990 :param f_path: path for node to calculate history for
979 991 :param commits: if passed don't calculate history and take
980 992 commits defined in this list
981 993 """
982 994 # calculate history based on tip
983 995 tip = c.rhodecode_repo.get_commit()
984 996 if commits is None:
985 997 pre_load = ["author", "branch"]
986 998 try:
987 999 commits = tip.get_file_history(f_path, pre_load=pre_load)
988 1000 except (NodeDoesNotExistError, CommitError):
989 1001 # this node is not present at tip!
990 1002 commits = commit.get_file_history(f_path, pre_load=pre_load)
991 1003
992 1004 history = []
993 1005 commits_group = ([], _("Changesets"))
994 1006 for commit in commits:
995 1007 branch = ' (%s)' % commit.branch if commit.branch else ''
996 1008 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
997 1009 commits_group[0].append((commit.raw_id, n_desc,))
998 1010 history.append(commits_group)
999 1011
1000 1012 symbolic_reference = self._symbolic_reference
1001 1013
1002 1014 if c.rhodecode_repo.alias == 'svn':
1003 1015 adjusted_f_path = self._adjust_file_path_for_svn(
1004 1016 f_path, c.rhodecode_repo)
1005 1017 if adjusted_f_path != f_path:
1006 1018 log.debug(
1007 1019 'Recognized svn tag or branch in file "%s", using svn '
1008 1020 'specific symbolic references', f_path)
1009 1021 f_path = adjusted_f_path
1010 1022 symbolic_reference = self._symbolic_reference_svn
1011 1023
1012 1024 branches = self._create_references(
1013 1025 c.rhodecode_repo.branches, symbolic_reference, f_path)
1014 1026 branches_group = (branches, _("Branches"))
1015 1027
1016 1028 tags = self._create_references(
1017 1029 c.rhodecode_repo.tags, symbolic_reference, f_path)
1018 1030 tags_group = (tags, _("Tags"))
1019 1031
1020 1032 history.append(branches_group)
1021 1033 history.append(tags_group)
1022 1034
1023 1035 return history, commits
1024 1036
1025 1037 def _adjust_file_path_for_svn(self, f_path, repo):
1026 1038 """
1027 1039 Computes the relative path of `f_path`.
1028 1040
1029 1041 This is mainly based on prefix matching of the recognized tags and
1030 1042 branches in the underlying repository.
1031 1043 """
1032 1044 tags_and_branches = itertools.chain(
1033 1045 repo.branches.iterkeys(),
1034 1046 repo.tags.iterkeys())
1035 1047 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1036 1048
1037 1049 for name in tags_and_branches:
1038 1050 if f_path.startswith(name + '/'):
1039 1051 f_path = vcspath.relpath(f_path, name)
1040 1052 break
1041 1053 return f_path
1042 1054
1043 1055 def _create_references(
1044 1056 self, branches_or_tags, symbolic_reference, f_path):
1045 1057 items = []
1046 1058 for name, commit_id in branches_or_tags.items():
1047 1059 sym_ref = symbolic_reference(commit_id, name, f_path)
1048 1060 items.append((sym_ref, name))
1049 1061 return items
1050 1062
1051 1063 def _symbolic_reference(self, commit_id, name, f_path):
1052 1064 return commit_id
1053 1065
1054 1066 def _symbolic_reference_svn(self, commit_id, name, f_path):
1055 1067 new_f_path = vcspath.join(name, f_path)
1056 1068 return u'%s@%s' % (new_f_path, commit_id)
1057 1069
1058 1070 @LoginRequired()
1059 1071 @XHRRequired()
1060 1072 @HasRepoPermissionAnyDecorator(
1061 1073 'repository.read', 'repository.write', 'repository.admin')
1062 1074 @jsonify
1063 1075 def nodelist(self, repo_name, revision, f_path):
1064 1076 commit = self.__get_commit_or_redirect(revision, repo_name)
1065 1077
1066 1078 metadata = self._get_nodelist_at_commit(
1067 1079 repo_name, commit.raw_id, f_path)
1068 1080 return {'nodes': metadata}
1069 1081
1070 1082 @LoginRequired()
1071 1083 @XHRRequired()
1072 1084 @HasRepoPermissionAnyDecorator(
1073 1085 'repository.read', 'repository.write', 'repository.admin')
1074 1086 def nodetree_full(self, repo_name, commit_id, f_path):
1075 1087 """
1076 1088 Returns rendered html of file tree that contains commit date,
1077 1089 author, revision for the specified combination of
1078 1090 repo, commit_id and file path
1079 1091
1080 1092 :param repo_name: name of the repository
1081 1093 :param commit_id: commit_id of file tree
1082 1094 :param f_path: file path of the requested directory
1083 1095 """
1084 1096
1085 1097 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1086 1098 try:
1087 1099 dir_node = commit.get_node(f_path)
1088 1100 except RepositoryError as e:
1089 1101 return 'error {}'.format(safe_str(e))
1090 1102
1091 1103 if dir_node.is_file():
1092 1104 return ''
1093 1105
1094 1106 c.file = dir_node
1095 1107 c.commit = commit
1096 1108
1097 1109 # using force=True here, make a little trick. We flush the cache and
1098 1110 # compute it using the same key as without full_load, so the fully
1099 1111 # loaded cached tree is now returned instead of partial
1100 1112 return self._get_tree_at_commit(
1101 1113 repo_name, commit.raw_id, dir_node.path, full_load=True,
1102 1114 force=True)
@@ -1,165 +1,183 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 24 from rhodecode.model import meta
25 25 from rhodecode.model.db import User, UserLog, Repository
26 26
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 ACTIONS = {
32 32 'user.login.success': {},
33 33 'user.login.failure': {},
34 34 'user.logout': {},
35 35 'user.password.reset_request': {},
36 36 'user.push': {},
37 37 'user.pull': {},
38 38
39 39 'repo.add': {},
40 40 'repo.edit': {},
41 41 'repo.edit.permissions': {},
42 'repo.commit.strip': {}
42 'repo.commit.strip': {},
43 'repo.archive.download': {},
43 44 }
44 45
45 46
46 47 class UserWrap(object):
47 48 """
48 49 Fake object used to imitate AuthUser
49 50 """
50 51
51 52 def __init__(self, user_id=None, username=None, ip_addr=None):
52 53 self.user_id = user_id
53 54 self.username = username
54 55 self.ip_addr = ip_addr
55 56
56 57
57 58 class RepoWrap(object):
58 59 """
59 60 Fake object used to imitate RepoObject that audit logger requires
60 61 """
61 62
62 63 def __init__(self, repo_id=None, repo_name=None):
63 64 self.repo_id = repo_id
64 65 self.repo_name = repo_name
65 66
66 67
67 68 def _store_log(action_name, action_data, user_id, username, user_data,
68 69 ip_address, repository_id, repository_name):
69 70 user_log = UserLog()
70 71 user_log.version = UserLog.VERSION_2
71 72
72 73 user_log.action = action_name
73 74 user_log.action_data = action_data
74 75
75 76 user_log.user_ip = ip_address
76 77
77 78 user_log.user_id = user_id
78 79 user_log.username = username
79 80 user_log.user_data = user_data
80 81
81 82 user_log.repository_id = repository_id
82 83 user_log.repository_name = repository_name
83 84
84 85 user_log.action_date = datetime.datetime.now()
85 86
86 87 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
87 88 action_name, user_id, username, ip_address)
88 89
89 90 return user_log
90 91
91 92
92 93 def store(
93 94 action, user, action_data=None, user_data=None, ip_addr=None,
94 95 repo=None, sa_session=None, commit=False):
95 96 """
96 97 Audit logger for various actions made by users, typically this results in a call such::
97 98
98 99 from rhodecode.lib import audit_logger
99 100
100 audit_logger.store(action='repo.edit', user=self._rhodecode_user)
101 audit_logger.store(action='repo.delete', user=audit_logger.UserWrap(username='itried-to-login', ip_addr='8.8.8.8'))
101 audit_logger.store(
102 action='repo.edit', user=self._rhodecode_user)
103 audit_logger.store(
104 action='repo.delete',
105 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
106
107 # repo action
108 audit_logger.store(
109 action='repo.delete',
110 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
111 repo=audit_logger.RepoWrap(repo_name='some-repo'))
112
113 # repo action, when we know and have the repository object already
114 audit_logger.store(
115 action='repo.delete',
116 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
117 repo=repo_object)
102 118
103 119 # without an user ?
104 audit_user = audit_logger.UserWrap(
105 username=self.request.params.get('username'),
106 ip_addr=self.request.remote_addr)
107 audit_logger.store(action='user.login.failure', user=audit_user)
120 audit_logger.store(
121 action='user.login.failure',
122 user=audit_logger.UserWrap(
123 username=self.request.params.get('username'),
124 ip_addr=self.request.remote_addr))
125
108 126 """
109 127 from rhodecode.lib.utils2 import safe_unicode
110 128 from rhodecode.lib.auth import AuthUser
111 129
112 130 if action not in ACTIONS:
113 131 raise ValueError('Action `{}` not in valid actions'.format(action))
114 132
115 133 if not sa_session:
116 134 sa_session = meta.Session()
117 135
118 136 try:
119 137 username = getattr(user, 'username', None)
120 138 if not username:
121 139 pass
122 140
123 141 user_id = getattr(user, 'user_id', None)
124 142 if not user_id:
125 143 # maybe we have username ? Try to figure user_id from username
126 144 if username:
127 145 user_id = getattr(
128 146 User.get_by_username(username), 'user_id', None)
129 147
130 148 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
131 149 if not ip_addr:
132 150 pass
133 151
134 152 if not user_data:
135 153 # try to get this from the auth user
136 154 if isinstance(user, AuthUser):
137 155 user_data = {
138 156 'username': user.username,
139 157 'email': user.email,
140 158 }
141 159
142 160 repository_name = getattr(repo, 'repo_name', None)
143 161 repository_id = getattr(repo, 'repo_id', None)
144 162 if not repository_id:
145 163 # maybe we have repo_name ? Try to figure repo_id from repo_name
146 164 if repository_name:
147 165 repository_id = getattr(
148 166 Repository.get_by_repo_name(repository_name), 'repo_id', None)
149 167
150 168 user_log = _store_log(
151 169 action_name=safe_unicode(action),
152 170 action_data=action_data or {},
153 171 user_id=user_id,
154 172 username=username,
155 173 user_data=user_data or {},
156 174 ip_address=safe_unicode(ip_addr),
157 175 repository_id=repository_id,
158 176 repository_name=repository_name
159 177 )
160 178 sa_session.add(user_log)
161 179 if commit:
162 180 sa_session.commit()
163 181
164 182 except Exception:
165 183 log.exception('AUDIT: failed to store audit log')
General Comments 0
You need to be logged in to leave comments. Login now