Show More
@@ -0,0 +1,110 b'' | |||||
|
1 | # Example to validate pushed files names and size using some sort of rules | |||
|
2 | ||||
|
3 | ||||
|
4 | ||||
|
5 | @has_kwargs({ | |||
|
6 | 'server_url': 'url of instance that triggered this hook', | |||
|
7 | 'config': 'path to .ini config used', | |||
|
8 | 'scm': 'type of version control "git", "hg", "svn"', | |||
|
9 | 'username': 'username of actor who triggered this event', | |||
|
10 | 'ip': 'ip address of actor who triggered this hook', | |||
|
11 | 'action': '', | |||
|
12 | 'repository': 'repository name', | |||
|
13 | 'repo_store_path': 'full path to where repositories are stored', | |||
|
14 | 'commit_ids': 'pre transaction metadata for commit ids', | |||
|
15 | 'hook_type': '', | |||
|
16 | 'user_agent': 'Client user agent, e.g git or mercurial CLI version', | |||
|
17 | }) | |||
|
18 | def _pre_push_hook(*args, **kwargs): | |||
|
19 | """ | |||
|
20 | Post push hook | |||
|
21 | To stop version control from storing the transaction and send a message to user | |||
|
22 | use non-zero HookResponse with a message, e.g return HookResponse(1, 'Not allowed') | |||
|
23 | ||||
|
24 | This message will be shown back to client during PUSH operation | |||
|
25 | ||||
|
26 | Commit ids might look like that:: | |||
|
27 | ||||
|
28 | [{u'hg_env|git_env': ..., | |||
|
29 | u'multiple_heads': [], | |||
|
30 | u'name': u'default', | |||
|
31 | u'new_rev': u'd0befe0692e722e01d5677f27a104631cf798b69', | |||
|
32 | u'old_rev': u'd0befe0692e722e01d5677f27a104631cf798b69', | |||
|
33 | u'ref': u'', | |||
|
34 | u'total_commits': 2, | |||
|
35 | u'type': u'branch'}] | |||
|
36 | """ | |||
|
37 | import fnmatch | |||
|
38 | from .helpers import extra_fields, extract_pre_files | |||
|
39 | from .utils import str2bool, aslist | |||
|
40 | from rhodecode.lib.helpers import format_byte_size_binary | |||
|
41 | ||||
|
42 | # returns list of dicts with key-val fetched from extra fields | |||
|
43 | repo_extra_fields = extra_fields.run(**kwargs) | |||
|
44 | ||||
|
45 | # optionally use 'extra fields' to control the logic per repo | |||
|
46 | # e.g store a list of patterns to be forbidden e.g `*.exe, *.dump` | |||
|
47 | forbid_files = repo_extra_fields.get('forbid_files_glob', {}).get('field_value') | |||
|
48 | forbid_files = aslist(forbid_files) | |||
|
49 | ||||
|
50 | # optionally get bytes limit for a single file, e.g 1024 for 1KB | |||
|
51 | forbid_size_over = repo_extra_fields.get('forbid_size_over', {}).get('field_value') | |||
|
52 | forbid_size_over = int(forbid_size_over or 0) | |||
|
53 | ||||
|
54 | def validate_file_name_and_size(file_data, forbidden_files=None, size_limit=None): | |||
|
55 | """ | |||
|
56 | This function validates commited files against some sort of rules. | |||
|
57 | It should return a valid boolean, and a reason for failure | |||
|
58 | ||||
|
59 | file_data =[ | |||
|
60 | 'raw_diff', 'old_revision', 'stats', 'original_filename', 'is_limited_diff', | |||
|
61 | 'chunks', 'new_revision', 'operation', 'exceeds_limit', 'filename' | |||
|
62 | ] | |||
|
63 | file_data['ops'] = { | |||
|
64 | # is file binary | |||
|
65 | 'binary': False, | |||
|
66 | ||||
|
67 | # lines | |||
|
68 | 'added': 32, | |||
|
69 | 'deleted': 0 | |||
|
70 | ||||
|
71 | 'ops': {3: 'modified file'}, | |||
|
72 | 'new_mode': '100644', | |||
|
73 | 'old_mode': None | |||
|
74 | } | |||
|
75 | """ | |||
|
76 | file_name = file_data['filename'] | |||
|
77 | operation = file_data['operation'] # can be A(dded), M(odified), D(eleted) | |||
|
78 | ||||
|
79 | # check files names | |||
|
80 | if forbidden_files: | |||
|
81 | reason = 'File {} is forbidden to be pushed'.format(file_name) | |||
|
82 | for forbidden_pattern in forbid_files: | |||
|
83 | # here we can also filter for operation, e.g if check for only ADDED files | |||
|
84 | # if operation == 'A': | |||
|
85 | if fnmatch.fnmatch(file_name, forbidden_pattern): | |||
|
86 | return False, reason | |||
|
87 | ||||
|
88 | # validate A(dded) files and size | |||
|
89 | if size_limit and operation == 'A': | |||
|
90 | size = len(file_data['raw_diff']) | |||
|
91 | ||||
|
92 | reason = 'File {} size of {} bytes exceeds limit {}'.format( | |||
|
93 | file_name, format_byte_size_binary(size), | |||
|
94 | format_byte_size_binary(size_limit)) | |||
|
95 | if size > size_limit: | |||
|
96 | return False, reason | |||
|
97 | ||||
|
98 | return True, '' | |||
|
99 | ||||
|
100 | if forbid_files or forbid_size_over: | |||
|
101 | # returns list of dicts with key-val fetched from extra fields | |||
|
102 | file_list = extract_pre_files.run(**kwargs) | |||
|
103 | ||||
|
104 | for file_data in file_list: | |||
|
105 | file_valid, reason = validate_file_name_and_size( | |||
|
106 | file_data, forbid_files, forbid_size_over) | |||
|
107 | if not file_valid: | |||
|
108 | return HookResponse(1, reason) | |||
|
109 | ||||
|
110 | return HookResponse(0, '') |
@@ -0,0 +1,96 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | # Copyright (C) 2016-2019 RhodeCode GmbH | |||
|
3 | # | |||
|
4 | # This program is free software: you can redistribute it and/or modify | |||
|
5 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
6 | # (only), as published by the Free Software Foundation. | |||
|
7 | # | |||
|
8 | # This program is distributed in the hope that it will be useful, | |||
|
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
11 | # GNU General Public License for more details. | |||
|
12 | # | |||
|
13 | # You should have received a copy of the GNU Affero General Public License | |||
|
14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
15 | # | |||
|
16 | # This program is dual-licensed. If you wish to learn more about the | |||
|
17 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
19 | ||||
|
20 | """ | |||
|
21 | us in hooks:: | |||
|
22 | ||||
|
23 | from .helpers import extract_pre_files | |||
|
24 | # returns list of dicts with key-val fetched from extra fields | |||
|
25 | file_list = extract_pre_files.run(**kwargs) | |||
|
26 | ||||
|
27 | """ | |||
|
28 | import re | |||
|
29 | import collections | |||
|
30 | import json | |||
|
31 | ||||
|
32 | from rhodecode.lib import diffs | |||
|
33 | from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff | |||
|
34 | from rhodecode.lib.vcs.backends.git.diff import GitDiff | |||
|
35 | ||||
|
36 | ||||
|
37 | def get_hg_files(repo, refs): | |||
|
38 | files = [] | |||
|
39 | return files | |||
|
40 | ||||
|
41 | ||||
|
42 | def get_git_files(repo, refs): | |||
|
43 | files = [] | |||
|
44 | ||||
|
45 | for data in refs: | |||
|
46 | # we should now extract commit data | |||
|
47 | old_rev = data['old_rev'] | |||
|
48 | new_rev = data['new_rev'] | |||
|
49 | ||||
|
50 | if '00000000' in old_rev: | |||
|
51 | # new branch, we don't need to extract nothing | |||
|
52 | return files | |||
|
53 | ||||
|
54 | git_env = dict(data['git_env']) | |||
|
55 | ||||
|
56 | cmd = [ | |||
|
57 | 'diff', old_rev, new_rev | |||
|
58 | ] | |||
|
59 | ||||
|
60 | stdout, stderr = repo.run_git_command(cmd, extra_env=git_env) | |||
|
61 | vcs_diff = GitDiff(stdout) | |||
|
62 | ||||
|
63 | diff_processor = diffs.DiffProcessor(vcs_diff, format='newdiff') | |||
|
64 | # this is list of dicts with diff information | |||
|
65 | # _parsed[0].keys() | |||
|
66 | # ['raw_diff', 'old_revision', 'stats', 'original_filename', | |||
|
67 | # 'is_limited_diff', 'chunks', 'new_revision', 'operation', | |||
|
68 | # 'exceeds_limit', 'filename'] | |||
|
69 | files = _parsed = diff_processor.prepare() | |||
|
70 | ||||
|
71 | return files | |||
|
72 | ||||
|
73 | ||||
|
74 | def run(*args, **kwargs): | |||
|
75 | from rhodecode.model.db import Repository | |||
|
76 | ||||
|
77 | vcs_type = kwargs['scm'] | |||
|
78 | # use temp name then the main one propagated | |||
|
79 | repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository'] | |||
|
80 | ||||
|
81 | repo = Repository.get_by_repo_name(repo_name) | |||
|
82 | vcs_repo = repo.scm_instance(cache=False) | |||
|
83 | ||||
|
84 | files = [] | |||
|
85 | ||||
|
86 | if vcs_type == 'git': | |||
|
87 | for rev_data in kwargs['commit_ids']: | |||
|
88 | new_environ = dict((k, v) for k, v in rev_data['git_env']) | |||
|
89 | files = get_git_files(vcs_repo, kwargs['commit_ids']) | |||
|
90 | ||||
|
91 | if vcs_type == 'hg': | |||
|
92 | for rev_data in kwargs['commit_ids']: | |||
|
93 | new_environ = dict((k, v) for k, v in rev_data['hg_env']) | |||
|
94 | files = get_hg_files(vcs_repo, kwargs['commit_ids']) | |||
|
95 | ||||
|
96 | return files |
@@ -14,19 +14,6 b'' | |||||
14 | 'hook_type': '', |
|
14 | 'hook_type': '', | |
15 | 'user_agent': 'Client user agent, e.g git or mercurial CLI version', |
|
15 | 'user_agent': 'Client user agent, e.g git or mercurial CLI version', | |
16 | }) |
|
16 | }) | |
17 | @has_kwargs({ |
|
|||
18 | 'server_url': 'url of instance that triggered this hook', |
|
|||
19 | 'config': 'path to .ini config used', |
|
|||
20 | 'scm': 'type of version control "git", "hg", "svn"', |
|
|||
21 | 'username': 'username of actor who triggered this event', |
|
|||
22 | 'ip': 'ip address of actor who triggered this hook', |
|
|||
23 | 'action': '', |
|
|||
24 | 'repository': 'repository name', |
|
|||
25 | 'repo_store_path': 'full path to where repositories are stored', |
|
|||
26 | 'commit_ids': 'pre transaction metadata for commit ids', |
|
|||
27 | 'hook_type': '', |
|
|||
28 | 'user_agent': 'Client user agent, e.g git or mercurial CLI version', |
|
|||
29 | }) |
|
|||
30 | def _pre_push_hook(*args, **kwargs): |
|
17 | def _pre_push_hook(*args, **kwargs): | |
31 | """ |
|
18 | """ | |
32 | Post push hook |
|
19 | Post push hook |
General Comments 0
You need to be logged in to leave comments.
Login now