Show More
@@ -0,0 +1,62 b'' | |||||
|
1 | <style> | |||
|
2 | .form-group { | |||
|
3 | margin-bottom: 15px; | |||
|
4 | } | |||
|
5 | ||||
|
6 | .form-group label { | |||
|
7 | display: flex; | |||
|
8 | align-items: left; | |||
|
9 | font-weight: bold; | |||
|
10 | } | |||
|
11 | ||||
|
12 | .form-control { | |||
|
13 | width: 60%; | |||
|
14 | padding: 10px; | |||
|
15 | font-size: 1rem; | |||
|
16 | line-height: 1.5; | |||
|
17 | border: 1px solid #ced4da; | |||
|
18 | border-radius: 4px; | |||
|
19 | box-sizing: border-box; | |||
|
20 | } | |||
|
21 | ||||
|
22 | .btn-primary { | |||
|
23 | background-color: #007bff; | |||
|
24 | border: none; | |||
|
25 | padding: 10px 20px; | |||
|
26 | color: white; | |||
|
27 | font-size: 1rem; | |||
|
28 | border-radius: 4px; | |||
|
29 | cursor: pointer; | |||
|
30 | } | |||
|
31 | ||||
|
32 | .btn-primary:hover { | |||
|
33 | background-color: #0056b3; | |||
|
34 | } | |||
|
35 | .form-group .help_block { | |||
|
36 | display: block; | |||
|
37 | width: 100%; | |||
|
38 | margin-top: 10px; | |||
|
39 | text-align: left; | |||
|
40 | font-size: 0.875rem; | |||
|
41 | } | |||
|
42 | </style> | |||
|
43 | ||||
|
44 | <div> | |||
|
45 | <div class="form-group"> | |||
|
46 | ${h.secure_form(h.route_path('check_2fa'), request=request, id='allowed_clients_form')} | |||
|
47 | <p><label for="git">${_('git')}:</label> | |||
|
48 | ${h.text('git', class_="form-control", value=initial_git)}</p> | |||
|
49 | <p><label for="hg">${_('hg')}:</label> | |||
|
50 | ${h.text('hg', class_="form-control", value=initial_hg)}</p> | |||
|
51 | <p><label for="svn">${_('svn')}:</label> | |||
|
52 | ${h.text('svn', class_="form-control", value=initial_svn)}</p> | |||
|
53 | %for k, v in errors.items(): | |||
|
54 | <span class="error-message">${k}: ${v}</span> | |||
|
55 | <br /> | |||
|
56 | %endfor | |||
|
57 | <p class="help_block">${_('Set rules for allowed git, hg or svn client versions. You can set exact version (for example 2.0.9) or use comparison operators to set earliest or latest version (>=2.6.0)')}</p> | |||
|
58 | ||||
|
59 | ${h.submit('send', _('Save'), class_="btn btn-primary")} | |||
|
60 | ${h.end_form()} | |||
|
61 | </div> | |||
|
62 | </div> |
@@ -49,7 +49,7 b' def admin_routes(config):' | |||||
49 |
|
49 | |||
50 | config.add_route( |
|
50 | config.add_route( | |
51 | 'admin_security', |
|
51 | 'admin_security', | |
52 |
pattern= |
|
52 | pattern='/security') | |
53 | config.add_view( |
|
53 | config.add_view( | |
54 | AdminSecurityView, |
|
54 | AdminSecurityView, | |
55 |
attr='security' |
|
55 | attr='security', | |
@@ -58,13 +58,22 b' def admin_routes(config):' | |||||
58 |
|
58 | |||
59 | config.add_route( |
|
59 | config.add_route( | |
60 | name='admin_security_update', |
|
60 | name='admin_security_update', | |
61 |
pattern= |
|
61 | pattern='/security/update') | |
62 | config.add_view( |
|
62 | config.add_view( | |
63 | AdminSecurityView, |
|
63 | AdminSecurityView, | |
64 | attr='security_update', |
|
64 | attr='security_update', | |
65 | route_name='admin_security_update', request_method='POST', |
|
65 | route_name='admin_security_update', request_method='POST', | |
66 | renderer='rhodecode:templates/admin/security/security.mako') |
|
66 | renderer='rhodecode:templates/admin/security/security.mako') | |
67 |
|
67 | |||
|
68 | config.add_route( | |||
|
69 | name='admin_security_modify_allowed_vcs_client_versions', | |||
|
70 | pattern='/security/modify/allowed_vcs_client_versions') | |||
|
71 | config.add_view( | |||
|
72 | AdminSecurityView, | |||
|
73 | attr='vcs_whitelisted_client_versions_edit', | |||
|
74 | route_name='admin_security_modify_allowed_vcs_client_versions', request_method=('GET', 'POST'), | |||
|
75 | renderer='rhodecode:templates/admin/security/edit_allowed_vcs_client_versions.mako') | |||
|
76 | ||||
68 |
|
77 | |||
69 | config.add_route( |
|
78 | config.add_route( | |
70 | name='admin_audit_logs', |
|
79 | name='admin_audit_logs', |
@@ -17,8 +17,13 b'' | |||||
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
18 |
|
18 | |||
19 | import logging |
|
19 | import logging | |
|
20 | import formencode | |||
20 |
|
21 | |||
|
22 | from rhodecode import BACKENDS | |||
21 | from rhodecode.apps._base import BaseAppView |
|
23 | from rhodecode.apps._base import BaseAppView | |
|
24 | from rhodecode.model.meta import Session | |||
|
25 | from rhodecode.model.settings import SettingsModel | |||
|
26 | from rhodecode.model.forms import WhitelistedVcsClientsForm | |||
22 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator |
|
27 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator | |
23 |
|
28 | |||
24 | log = logging.getLogger(__name__) |
|
29 | log = logging.getLogger(__name__) | |
@@ -37,3 +42,31 b' class AdminSecurityView(BaseAppView):' | |||||
37 | c.active = 'security' |
|
42 | c.active = 'security' | |
38 | return self._get_template_context(c) |
|
43 | return self._get_template_context(c) | |
39 |
|
44 | |||
|
45 | @LoginRequired() | |||
|
46 | @HasPermissionAllDecorator('hg.admin') | |||
|
47 | def vcs_whitelisted_client_versions_edit(self): | |||
|
48 | _ = self.request.translate | |||
|
49 | c = self.load_default_context() | |||
|
50 | render_ctx = {} | |||
|
51 | settings = SettingsModel() | |||
|
52 | form = WhitelistedVcsClientsForm(_, )() | |||
|
53 | if self.request.method == 'POST': | |||
|
54 | try: | |||
|
55 | result = form.to_python(self.request.POST) | |||
|
56 | for k, v in result.items(): | |||
|
57 | if v: | |||
|
58 | setting = settings.create_or_update_setting(name=f'{k}_allowed_clients', val=v) | |||
|
59 | Session().add(setting) | |||
|
60 | Session().commit() | |||
|
61 | ||||
|
62 | except formencode.Invalid as errors: | |||
|
63 | render_ctx.update({ | |||
|
64 | 'errors': errors.error_dict | |||
|
65 | }) | |||
|
66 | for key in BACKENDS.keys(): | |||
|
67 | verbose_name = f"initial_{key}" | |||
|
68 | if existing := settings.get_setting_by_name(name=f'{key}_allowed_clients'): | |||
|
69 | render_ctx[verbose_name] = existing.app_settings_value | |||
|
70 | else: | |||
|
71 | render_ctx[verbose_name] = '*' | |||
|
72 | return self._get_template_context(c, **render_ctx) |
@@ -102,6 +102,11 b' class HTTPRequirementError(HTTPClientErr' | |||||
102 | self.args = (message, ) |
|
102 | self.args = (message, ) | |
103 |
|
103 | |||
104 |
|
104 | |||
|
105 | class ClientNotSupportedError(HTTPRequirementError): | |||
|
106 | title = explanation = 'Client Not Supported' | |||
|
107 | reason = None | |||
|
108 | ||||
|
109 | ||||
105 | class HTTPLockedRC(HTTPClientError): |
|
110 | class HTTPLockedRC(HTTPClientError): | |
106 | """ |
|
111 | """ | |
107 | Special Exception For locked Repos in RhodeCode, the return code can |
|
112 | Special Exception For locked Repos in RhodeCode, the return code can |
@@ -30,7 +30,7 b' from rhodecode.lib import helpers as h' | |||||
30 | from rhodecode.lib import audit_logger |
|
30 | from rhodecode.lib import audit_logger | |
31 | from rhodecode.lib.utils2 import safe_str, user_agent_normalizer |
|
31 | from rhodecode.lib.utils2 import safe_str, user_agent_normalizer | |
32 | from rhodecode.lib.exceptions import ( |
|
32 | from rhodecode.lib.exceptions import ( | |
33 | HTTPLockedRC, HTTPBranchProtected, UserCreationError) |
|
33 | HTTPLockedRC, HTTPBranchProtected, UserCreationError, ClientNotSupportedError) | |
34 | from rhodecode.model.db import Repository, User |
|
34 | from rhodecode.model.db import Repository, User | |
35 | from rhodecode.lib.statsd_client import StatsdClient |
|
35 | from rhodecode.lib.statsd_client import StatsdClient | |
36 |
|
36 | |||
@@ -64,6 +64,18 b' def is_shadow_repo(extras):' | |||||
64 | return extras['is_shadow_repo'] |
|
64 | return extras['is_shadow_repo'] | |
65 |
|
65 | |||
66 |
|
66 | |||
|
67 | def check_vcs_client(extras): | |||
|
68 | """ | |||
|
69 | Checks if vcs client is allowed (Only works in enterprise edition) | |||
|
70 | """ | |||
|
71 | try: | |||
|
72 | from rc_ee.lib.security.utils import is_vcs_client_whitelisted | |||
|
73 | except ModuleNotFoundError: | |||
|
74 | is_vcs_client_whitelisted = lambda *x: True | |||
|
75 | backend = extras.get('scm') | |||
|
76 | if not is_vcs_client_whitelisted(extras.get('user_agent'), backend): | |||
|
77 | raise ClientNotSupportedError(f"Your {backend} client is forbidden") | |||
|
78 | ||||
67 | def _get_scm_size(alias, root_path): |
|
79 | def _get_scm_size(alias, root_path): | |
68 |
|
80 | |||
69 | if not alias.startswith('.'): |
|
81 | if not alias.startswith('.'): | |
@@ -108,6 +120,7 b' def pre_push(extras):' | |||||
108 | It bans pushing when the repository is locked. |
|
120 | It bans pushing when the repository is locked. | |
109 | """ |
|
121 | """ | |
110 |
|
122 | |||
|
123 | check_vcs_client(extras) | |||
111 | user = User.get_by_username(extras.username) |
|
124 | user = User.get_by_username(extras.username) | |
112 | output = '' |
|
125 | output = '' | |
113 | if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]): |
|
126 | if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]): | |
@@ -180,6 +193,7 b' def pre_pull(extras):' | |||||
180 | It bans pulling when the repository is locked. |
|
193 | It bans pulling when the repository is locked. | |
181 | """ |
|
194 | """ | |
182 |
|
195 | |||
|
196 | check_vcs_client(extras) | |||
183 | output = '' |
|
197 | output = '' | |
184 | if extras.locked_by[0]: |
|
198 | if extras.locked_by[0]: | |
185 | locked_by = User.get(extras.locked_by[0]).username |
|
199 | locked_by = User.get(extras.locked_by[0]).username |
@@ -84,8 +84,11 b' def adopt_for_celery(func):' | |||||
84 | @wraps(func) |
|
84 | @wraps(func) | |
85 | def wrapper(extras): |
|
85 | def wrapper(extras): | |
86 | extras = AttributeDict(extras) |
|
86 | extras = AttributeDict(extras) | |
|
87 | try: | |||
87 | # HooksResponse implements to_json method which must be used there. |
|
88 | # HooksResponse implements to_json method which must be used there. | |
88 | return func(extras).to_json() |
|
89 | return func(extras).to_json() | |
|
90 | except Exception as e: | |||
|
91 | return {'status': 128, 'exception': type(e).__name__, 'exception_args': e.args} | |||
89 | return wrapper |
|
92 | return wrapper | |
90 |
|
93 | |||
91 |
|
94 |
@@ -129,6 +129,20 b' def TOTPForm(localizer, user, allow_reco' | |||||
129 | return _TOTPForm |
|
129 | return _TOTPForm | |
130 |
|
130 | |||
131 |
|
131 | |||
|
132 | def WhitelistedVcsClientsForm(localizer): | |||
|
133 | _ = localizer | |||
|
134 | ||||
|
135 | class _WhitelistedVcsClientsForm(formencode.Schema): | |||
|
136 | regexp = r'^(?:\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\*)\s*(?:,\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\s*\*\s*)*$' | |||
|
137 | allow_extra_fields = True | |||
|
138 | filter_extra_fields = True | |||
|
139 | git = v.Regex(regexp) | |||
|
140 | hg = v.Regex(regexp) | |||
|
141 | svn = v.Regex(regexp) | |||
|
142 | ||||
|
143 | return _WhitelistedVcsClientsForm | |||
|
144 | ||||
|
145 | ||||
132 | def UserForm(localizer, edit=False, available_languages=None, old_data=None): |
|
146 | def UserForm(localizer, edit=False, available_languages=None, old_data=None): | |
133 | old_data = old_data or {} |
|
147 | old_data = old_data or {} | |
134 | available_languages = available_languages or [] |
|
148 | available_languages = available_languages or [] |
@@ -86,6 +86,7 b' function registerRCRoutes() {' | |||||
86 | pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []); |
|
86 | pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []); | |
87 | pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []); |
|
87 | pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []); | |
88 | pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []); |
|
88 | pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []); | |
|
89 | pyroutes.register('admin_security_modify_allowed_vcs_client_versions', '/_admin/security/modify/allowed_vcs_client_versions', []); | |||
89 | pyroutes.register('apiv2', '/_admin/api', []); |
|
90 | pyroutes.register('apiv2', '/_admin/api', []); | |
90 | pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']); |
|
91 | pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']); | |
91 | pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']); |
|
92 | pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']); |
@@ -28,13 +28,52 b'' | |||||
28 | <div class="panel-body"> |
|
28 | <div class="panel-body"> | |
29 | <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4> |
|
29 | <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4> | |
30 | <p> |
|
30 | <p> | |
31 | You can scan your repositories for exposed secrets, passwords, etc |
|
31 | ${_('You can scan your repositories for exposed secrets, passwords, etc')} | |
32 | </p> |
|
32 | </p> | |
33 | </div> |
|
33 | </div> | |
34 | </div> |
|
34 | </div> | |
35 |
|
35 | |||
|
36 | <div class="panel panel-default"> | |||
|
37 | <div class="panel-heading"> | |||
|
38 | <h3 class="panel-title">${_('Allowed client versions')}</h3> | |||
|
39 | </div> | |||
|
40 | <div class="panel-body"> | |||
|
41 | %if c.rhodecode_edition_id != 'EE': | |||
|
42 | <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4> | |||
|
43 | <p> | |||
|
44 | ${_('Some outdated client versions may have security vulnerabilities. This section have rules for whitelisting versions of clients for Git, Mercurial and SVN.')} | |||
|
45 | </p> | |||
|
46 | %else: | |||
|
47 | <div class="inner form" id="container"> | |||
|
48 | </div> | |||
|
49 | %endif | |||
|
50 | </div> | |||
|
51 | ||||
36 | </div> |
|
52 | </div> | |
37 |
|
53 | |||
|
54 | <script> | |||
|
55 | $(document).ready(function() { | |||
|
56 | $.ajax({ | |||
|
57 | url: pyroutes.url('admin_security_modify_allowed_vcs_client_versions'), | |||
|
58 | type: 'GET', | |||
|
59 | success: function(response) { | |||
|
60 | $('#container').html(response); | |||
|
61 | }, | |||
|
62 | }); | |||
|
63 | $(document).on('submit', '#allowed_clients_form', function(event) { | |||
|
64 | event.preventDefault(); | |||
|
65 | var formData = $(this).serialize(); | |||
|
66 | ||||
|
67 | $.ajax({ | |||
|
68 | url: pyroutes.url('admin_security_modify_allowed_vcs_client_versions'), | |||
|
69 | type: 'POST', | |||
|
70 | data: formData, | |||
|
71 | success: function(response) { | |||
|
72 | $('#container').html(response); | |||
|
73 | }, | |||
|
74 | }); | |||
|
75 | }); | |||
|
76 | }); | |||
|
77 | </script> | |||
38 |
|
78 | |||
39 | </%def> |
|
79 | </%def> | |
40 |
|
General Comments 0
You need to be logged in to leave comments.
Login now