##// END OF EJS Templates
rcextensions: added example how to validate file size and name using rcextensions
marcink -
r3475:3b4cd059 default
parent child Browse files
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
@@ -1,92 +1,79 b''
1 # Example to validate commit message or author using some sort of rules
1 # Example to validate commit message or author using some sort of rules
2
2
3
3
4 @has_kwargs({
4 @has_kwargs({
5 'server_url': 'url of instance that triggered this hook',
5 'server_url': 'url of instance that triggered this hook',
6 'config': 'path to .ini config used',
6 'config': 'path to .ini config used',
7 'scm': 'type of version control "git", "hg", "svn"',
7 'scm': 'type of version control "git", "hg", "svn"',
8 'username': 'username of actor who triggered this event',
8 'username': 'username of actor who triggered this event',
9 'ip': 'ip address of actor who triggered this hook',
9 'ip': 'ip address of actor who triggered this hook',
10 'action': '',
10 'action': '',
11 'repository': 'repository name',
11 'repository': 'repository name',
12 'repo_store_path': 'full path to where repositories are stored',
12 'repo_store_path': 'full path to where repositories are stored',
13 'commit_ids': 'pre transaction metadata for commit ids',
13 'commit_ids': 'pre transaction metadata for commit ids',
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
33 To stop version control from storing the transaction and send a message to user
20 To stop version control from storing the transaction and send a message to user
34 use non-zero HookResponse with a message, e.g return HookResponse(1, 'Not allowed')
21 use non-zero HookResponse with a message, e.g return HookResponse(1, 'Not allowed')
35
22
36 This message will be shown back to client during PUSH operation
23 This message will be shown back to client during PUSH operation
37
24
38 Commit ids might look like that::
25 Commit ids might look like that::
39
26
40 [{u'hg_env|git_env': ...,
27 [{u'hg_env|git_env': ...,
41 u'multiple_heads': [],
28 u'multiple_heads': [],
42 u'name': u'default',
29 u'name': u'default',
43 u'new_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
30 u'new_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
44 u'old_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
31 u'old_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
45 u'ref': u'',
32 u'ref': u'',
46 u'total_commits': 2,
33 u'total_commits': 2,
47 u'type': u'branch'}]
34 u'type': u'branch'}]
48 """
35 """
49 import re
36 import re
50 from .helpers import extra_fields, extract_pre_commits
37 from .helpers import extra_fields, extract_pre_commits
51 from .utils import str2bool
38 from .utils import str2bool
52
39
53 # returns list of dicts with key-val fetched from extra fields
40 # returns list of dicts with key-val fetched from extra fields
54 repo_extra_fields = extra_fields.run(**kwargs)
41 repo_extra_fields = extra_fields.run(**kwargs)
55
42
56 # optionally use 'extra fields' to control the logic per repo
43 # optionally use 'extra fields' to control the logic per repo
57 validate_author = repo_extra_fields.get('validate_author', {}).get('field_value')
44 validate_author = repo_extra_fields.get('validate_author', {}).get('field_value')
58 should_validate = str2bool(validate_author)
45 should_validate = str2bool(validate_author)
59
46
60 # optionally store validation regex into extra fields
47 # optionally store validation regex into extra fields
61 validation_regex = repo_extra_fields.get('validation_regex', {}).get('field_value')
48 validation_regex = repo_extra_fields.get('validation_regex', {}).get('field_value')
62
49
63 def validate_commit_message(commit_message, message_regex=None):
50 def validate_commit_message(commit_message, message_regex=None):
64 """
51 """
65 This function validates commit_message against some sort of rules.
52 This function validates commit_message against some sort of rules.
66 It should return a valid boolean, and a reason for failure
53 It should return a valid boolean, and a reason for failure
67 """
54 """
68
55
69 if "secret_string" in commit_message:
56 if "secret_string" in commit_message:
70 msg = "!!Push forbidden: secret string found in commit messages"
57 msg = "!!Push forbidden: secret string found in commit messages"
71 return False, msg
58 return False, msg
72
59
73 if validation_regex:
60 if validation_regex:
74 regexp = re.compile(validation_regex)
61 regexp = re.compile(validation_regex)
75 if not regexp.match(message):
62 if not regexp.match(message):
76 msg = "!!Push forbidden: commit message does not match regexp"
63 msg = "!!Push forbidden: commit message does not match regexp"
77 return False, msg
64 return False, msg
78
65
79 return True, ''
66 return True, ''
80
67
81 if should_validate:
68 if should_validate:
82 # returns list of dicts with key-val fetched from extra fields
69 # returns list of dicts with key-val fetched from extra fields
83 commit_list = extract_pre_commits.run(**kwargs)
70 commit_list = extract_pre_commits.run(**kwargs)
84
71
85 for commit_data in commit_list:
72 for commit_data in commit_list:
86 message = commit_data['message']
73 message = commit_data['message']
87
74
88 message_valid, reason = validate_commit_message(message, validation_regex)
75 message_valid, reason = validate_commit_message(message, validation_regex)
89 if not message_valid:
76 if not message_valid:
90 return HookResponse(1, reason)
77 return HookResponse(1, reason)
91
78
92 return HookResponse(0, '')
79 return HookResponse(0, '')
General Comments 0
You need to be logged in to leave comments. Login now