updated with a latest changes.

This commit is contained in:
Serhii Ilin 2024-10-02 12:22:06 +03:00
commit 0eeee78a52
21 changed files with 86 additions and 221 deletions

View file

@ -33,7 +33,7 @@ channelstream==0.7.1
gevent==24.2.1
greenlet==3.0.3
zope.event==5.0.0
zope.interface==6.4.post2
zope.interface==7.0.3
itsdangerous==1.1.0
marshmallow==2.18.0
pyramid==2.0.2
@ -46,7 +46,7 @@ channelstream==0.7.1
venusian==3.0.0
webob==1.8.7
zope.deprecation==5.0.0
zope.interface==6.4.post2
zope.interface==7.0.3
pyramid-jinja2==2.10
jinja2==3.1.2
markupsafe==2.1.2
@ -61,7 +61,7 @@ channelstream==0.7.1
venusian==3.0.0
webob==1.8.7
zope.deprecation==5.0.0
zope.interface==6.4.post2
zope.interface==7.0.3
zope.deprecation==5.0.0
python-dateutil==2.8.2
six==1.16.0
@ -87,13 +87,13 @@ dogpile.cache==1.3.3
pbr==5.11.1
formencode==2.1.0
six==1.16.0
fsspec==2024.6.0
fsspec==2024.9.0
gunicorn==23.0.0
packaging==24.1
gevent==24.2.1
greenlet==3.0.3
zope.event==5.0.0
zope.interface==6.4.post2
zope.interface==7.0.3
ipython==8.26.0
decorator==5.1.1
jedi==0.19.1
@ -167,7 +167,7 @@ nbconvert==7.7.3
tinycss2==1.2.1
webencodings==0.5.1
traitlets==5.14.3
orjson==3.10.6
orjson==3.10.7
paste==3.10.1
premailer==3.10.0
cachetools==5.3.3
@ -201,13 +201,13 @@ pyramid-mailer==0.15.1
venusian==3.0.0
webob==1.8.7
zope.deprecation==5.0.0
zope.interface==6.4.post2
zope.interface==7.0.3
repoze.sendmail==4.4.1
transaction==3.1.0
zope.interface==6.4.post2
zope.interface==6.4.post2
transaction==3.1.0
zope.interface==6.4.post2
transaction==5.0.0
zope.interface==7.0.3
zope.interface==7.0.3
transaction==5.0.0
zope.interface==7.0.3
pyramid-mako==1.1.0
mako==1.2.4
markupsafe==2.1.2
@ -221,7 +221,7 @@ pyramid-mako==1.1.0
venusian==3.0.0
webob==1.8.7
zope.deprecation==5.0.0
zope.interface==6.4.post2
zope.interface==7.0.3
python-ldap==3.4.3
pyasn1==0.4.8
pyasn1-modules==0.2.8
@ -236,13 +236,13 @@ python3-saml==1.16.0
xmlsec==1.3.14
lxml==5.3.0
pyyaml==6.0.1
redis==5.0.4
redis==5.1.0
async-timeout==4.0.3
regex==2022.10.31
routes==2.5.1
repoze.lru==0.7
six==1.16.0
s3fs==2024.6.0
s3fs==2024.9.0
aiobotocore==2.13.0
aiohttp==3.9.5
aiosignal==1.3.1
@ -269,7 +269,7 @@ s3fs==2024.6.0
yarl==1.9.4
idna==3.4
multidict==6.0.5
fsspec==2024.6.0
fsspec==2024.9.0
simplejson==3.19.2
sshpubkeys==3.3.1
cryptography==40.0.2

View file

@ -67,7 +67,7 @@ def admin_routes(config):
config.add_route(
name='admin_security_modify_allowed_vcs_client_versions',
pattern='/security/modify/allowed_vcs_client_versions')
pattern=ADMIN_PREFIX + '/security/modify/allowed_vcs_client_versions')
config.add_view(
AdminSecurityView,
attr='vcs_whitelisted_client_versions_edit',

View file

@ -17,13 +17,8 @@
# and proprietary license terms, please see https://rhodecode.com/licenses/
import logging
import formencode
from rhodecode import BACKENDS
from rhodecode.apps._base import BaseAppView
from rhodecode.model.meta import Session
from rhodecode.model.settings import SettingsModel
from rhodecode.model.forms import WhitelistedVcsClientsForm
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
log = logging.getLogger(__name__)
@ -42,31 +37,10 @@ class AdminSecurityView(BaseAppView):
c.active = 'security'
return self._get_template_context(c)
@LoginRequired()
@HasPermissionAllDecorator('hg.admin')
def vcs_whitelisted_client_versions_edit(self):
_ = self.request.translate
def admin_security_modify_allowed_vcs_client_versions(self):
c = self.load_default_context()
render_ctx = {}
settings = SettingsModel()
form = WhitelistedVcsClientsForm(_, )()
if self.request.method == 'POST':
try:
result = form.to_python(self.request.POST)
for k, v in result.items():
if v:
setting = settings.create_or_update_setting(name=f'{k}_allowed_clients', val=v)
Session().add(setting)
Session().commit()
except formencode.Invalid as errors:
render_ctx.update({
'errors': errors.error_dict
})
for key in BACKENDS.keys():
verbose_name = f"initial_{key}"
if existing := settings.get_setting_by_name(name=f'{key}_allowed_clients'):
render_ctx[verbose_name] = existing.app_settings_value
else:
render_ctx[verbose_name] = '*'
return self._get_template_context(c, **render_ctx)
c.active = 'security'
return self._get_template_context(c)

View file

@ -82,7 +82,7 @@ class AdminSettingsView(BaseAppView):
if k == '/':
k = 'root_path'
if k in ['push_ssl', 'publish', 'enabled']:
if k in ['publish', 'enabled']:
v = str2bool(v)
if k.find('.') != -1:
@ -164,7 +164,6 @@ class AdminSettingsView(BaseAppView):
return Response(html)
try:
model.update_global_ssl_setting(form_result['web_push_ssl'])
model.update_global_hook_settings(form_result)
model.create_or_update_global_svn_settings(form_result)

View file

@ -52,7 +52,8 @@ def sanitize_settings_and_apply_defaults(global_config, settings):
default=False,
parser='bool')
logging_conf = jn(os.path.dirname(global_config.get('__file__')), 'logging.ini')
ini_loc = os.path.dirname(global_config.get('__file__'))
logging_conf = jn(ini_loc, 'logging.ini')
settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG')
# Default includes, possible to change as a user
@ -95,6 +96,11 @@ def sanitize_settings_and_apply_defaults(global_config, settings):
settings_maker.make_setting('gzip_responses', False, parser='bool')
settings_maker.make_setting('startup.import_repos', 'false', parser='bool')
# License settings.
settings_maker.make_setting('license.hide_license_info', False, parser='bool')
settings_maker.make_setting('license.import_path', jn(ini_loc, 'rhodecode_enterprise.license'))
settings_maker.make_setting('license.import_path_mode', 'if-missing')
# statsd
settings_maker.make_setting('statsd.enabled', False, parser='bool')
settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')

View file

@ -50,7 +50,7 @@ from rhodecode.lib.utils2 import AttributeDict
from rhodecode.lib.exc_tracking import store_exception, format_exc
from rhodecode.subscribers import (
scan_repositories_if_enabled, write_js_routes_if_enabled,
write_metadata_if_needed, write_usage_data)
write_metadata_if_needed, write_usage_data, import_license_if_present)
from rhodecode.lib.statsd_client import StatsdClient
log = logging.getLogger(__name__)
@ -400,7 +400,8 @@ def includeme(config, auth_resources=None):
pyramid.events.ApplicationCreated)
config.add_subscriber(write_js_routes_if_enabled,
pyramid.events.ApplicationCreated)
config.add_subscriber(import_license_if_present,
pyramid.events.ApplicationCreated)
# Set the default renderer for HTML templates to mako.
config.add_mako_renderer('.html')

View file

@ -570,7 +570,6 @@ class DbManage(object):
self.create_ui_settings(path)
ui_config = [
('web', 'push_ssl', 'False'),
('web', 'allow_archive', 'gz zip bz2'),
('web', 'allow_push', '*'),
('web', 'baseurl', '/'),

View file

@ -339,21 +339,6 @@ class SimpleVCS(object):
log.exception('Failed to read http scheme')
return 'http'
def _check_ssl(self, environ, start_response):
"""
Checks the SSL check flag and returns False if SSL is not present
and required True otherwise
"""
org_proto = environ['wsgi._org_proto']
# check if we have SSL required ! if not it's a bad request !
require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
if require_ssl and org_proto == 'http':
log.debug(
'Bad request: detected protocol is `%s` and '
'SSL/HTTPS is required.', org_proto)
return False
return True
def _get_default_cache_ttl(self):
# take AUTH_CACHE_TTL from the `rhodecode` auth plugin
plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
@ -373,12 +358,6 @@ class SimpleVCS(object):
meta.Session.remove()
def _handle_request(self, environ, start_response):
if not self._check_ssl(environ, start_response):
reason = ('SSL required, while RhodeCode was unable '
'to detect this as SSL request')
log.debug('User not allowed to proceed, %s', reason)
return HTTPNotAcceptable(reason)(environ, start_response)
if not self.url_repo_name:
log.warning('Repository name is empty: %s', self.url_repo_name)
# failed to get repo name, we fail now

View file

@ -159,11 +159,18 @@ def detect_vcs_request(environ, backends):
# favicon often requested by browsers
'favicon.ico',
# static files no detection
'_static++',
# debug-toolbar
'_debug_toolbar++',
# e.g /_file_store/download
'_file_store++',
# login
"_admin/login",
f"{ADMIN_PREFIX}/login",
f"{ADMIN_PREFIX}/logout",
# 2fa
f"{ADMIN_PREFIX}/check_2fa",
@ -178,12 +185,6 @@ def detect_vcs_request(environ, backends):
# _admin/my_account is safe too
f'{ADMIN_PREFIX}/my_account++',
# static files no detection
'_static++',
# debug-toolbar
'_debug_toolbar++',
# skip ops ping, status
f'{ADMIN_PREFIX}/ops/ping',
f'{ADMIN_PREFIX}/ops/status',
@ -193,11 +194,14 @@ def detect_vcs_request(environ, backends):
'++/repo_creating_check'
]
path_info = get_path_info(environ)
path_url = path_info.lstrip('/')
req_method = environ.get('REQUEST_METHOD')
for item in white_list:
item = item.lstrip('/')
if item.endswith('++') and path_url.startswith(item[:-2]):
log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
return handler

View file

@ -108,11 +108,10 @@ def command(ini_path, force_yes, user, email, password, api_key, repos,
dbmanage.create_permissions()
dbmanage.populate_default_permissions()
if apply_license_key:
try:
from rc_license.models import apply_trial_license_if_missing
apply_trial_license_if_missing(force=True)
except ImportError:
pass
from rhodecode.model.license import apply_license_from_file
license_file_path = config.get('license.import_path')
if license_file_path:
apply_license_from_file(license_file_path, force=True)
Session().commit()

View file

@ -391,8 +391,7 @@ def prepare_config_data(clear_session=True, repo=None):
safe_str(setting.section), safe_str(setting.key),
safe_str(setting.value)))
if setting.key == 'push_ssl':
# force set push_ssl requirement to False, rhodecode
# handles that
# force set push_ssl requirement to False this is deprecated, and we must force it to False
config.append((
safe_str(setting.section), safe_str(setting.key), False))
config_getter = ConfigGet()

View file

@ -468,7 +468,6 @@ def ApplicationUiSettingsForm(localizer):
_ = localizer
class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
web_push_ssl = v.StringBoolean(if_missing=False)
extensions_hggit = v.StringBoolean(if_missing=False)
new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')

View file

@ -0,0 +1,17 @@
def apply_license(*args, **kwargs):
pass
try:
from rc_license.models import apply_license
except ImportError:
pass
def apply_license_from_file(*args, **kwargs):
pass
try:
from rc_license.models import apply_license_from_file
except ImportError:
pass

View file

@ -499,7 +499,6 @@ class VcsSettingsModel(object):
SVN_BRANCH_SECTION = 'vcs_svn_branch'
SVN_TAG_SECTION = 'vcs_svn_tag'
SSL_SETTING = ('web', 'push_ssl')
PATH_SETTING = ('paths', '/')
def __init__(self, sa=None, repo=None):
@ -704,10 +703,6 @@ class VcsSettingsModel(object):
# branch/tags patterns
self._create_svn_settings(self.global_settings, data)
def update_global_ssl_setting(self, value):
self._create_or_update_ui(
self.global_settings, *self.SSL_SETTING, value=value)
@assert_repo_settings
def delete_repo_svn_pattern(self, id_):
ui = self.repo_settings.UiDbModel.get(id_)

View file

@ -205,7 +205,7 @@ def write_usage_data(event):
return
def get_update_age(dest_file):
now = datetime.datetime.utcnow()
now = datetime.datetime.now(datetime.UTC)
with open(dest_file, 'rb') as f:
data = ext_json.json.loads(f.read())
@ -216,10 +216,9 @@ def write_usage_data(event):
return 0
utc_date = datetime.datetime.utcnow()
utc_date = datetime.datetime.now(datetime.UTC)
hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format(
date=utc_date, hour=hour_quarter)
fname = f'.rc_usage_{utc_date.year}{utc_date.month:02d}{utc_date.day:02d}_{hour_quarter}.json'
ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
usage_dir = os.path.join(ini_loc, '.rcusage')
@ -314,6 +313,22 @@ def write_js_routes_if_enabled(event):
log.exception('Failed to write routes.js into %s', jsroutes_file_path)
def import_license_if_present(event):
"""
This is subscribed to the `pyramid.events.ApplicationCreated` event. It
does a import license key based on a presence of the file.
"""
settings = event.app.registry.settings
license_file_path = settings.get('license.import_path')
force = settings.get('license.import_path_mode') == 'force'
if license_file_path:
from rhodecode.model.meta import Session
from rhodecode.model.license import apply_license_from_file
apply_license_from_file(license_file_path, force=force)
Session().commit()
class Subscriber(object):
"""
Base class for subscribers to the pyramid event system.

View file

@ -1,62 +0,0 @@
<style>
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: flex;
align-items: left;
font-weight: bold;
}
.form-control {
width: 60%;
padding: 10px;
font-size: 1rem;
line-height: 1.5;
border: 1px solid #ced4da;
border-radius: 4px;
box-sizing: border-box;
}
.btn-primary {
background-color: #007bff;
border: none;
padding: 10px 20px;
color: white;
font-size: 1rem;
border-radius: 4px;
cursor: pointer;
}
.btn-primary:hover {
background-color: #0056b3;
}
.form-group .help_block {
display: block;
width: 100%;
margin-top: 10px;
text-align: left;
font-size: 0.875rem;
}
</style>
<div>
<div class="form-group">
${h.secure_form(h.route_path('check_2fa'), request=request, id='allowed_clients_form')}
<p><label for="git">${_('git')}:</label>
${h.text('git', class_="form-control", value=initial_git)}</p>
<p><label for="hg">${_('hg')}:</label>
${h.text('hg', class_="form-control", value=initial_hg)}</p>
<p><label for="svn">${_('svn')}:</label>
${h.text('svn', class_="form-control", value=initial_svn)}</p>
%for k, v in errors.items():
<span class="error-message">${k}: ${v}</span>
<br />
%endfor
<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>
${h.submit('send', _('Save'), class_="btn btn-primary")}
${h.end_form()}
</div>
</div>

View file

@ -38,42 +38,13 @@
<h3 class="panel-title">${_('Allowed client versions')}</h3>
</div>
<div class="panel-body">
%if c.rhodecode_edition_id != 'EE':
<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>
<p>
${_('Some outdated client versions may have security vulnerabilities. This section have rules for whitelisting versions of clients for Git, Mercurial and SVN.')}
</p>
%else:
<div class="inner form" id="container">
</div>
%endif
</div>
</div>
<script>
$(document).ready(function() {
$.ajax({
url: pyroutes.url('admin_security_modify_allowed_vcs_client_versions'),
type: 'GET',
success: function(response) {
$('#container').html(response);
},
});
$(document).on('submit', '#allowed_clients_form', function(event) {
event.preventDefault();
var formData = $(this).serialize();
$.ajax({
url: pyroutes.url('admin_security_modify_allowed_vcs_client_versions'),
type: 'POST',
data: formData,
success: function(response) {
$('#container').html(response);
},
});
});
});
</script>
</%def>

View file

@ -5,22 +5,7 @@
<%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, **kwargs)">
% if display_globals:
<div class="panel panel-default">
<div class="panel-heading" id="general">
<h3 class="panel-title">${_('General')}<a class="permalink" href="#general"> ¶</a></h3>
</div>
<div class="panel-body">
<div class="field">
<div class="checkbox">
${h.checkbox('web_push_ssl' + suffix, 'True')}
<label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
</div>
<div class="label">
<span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
</div>
</div>
</div>
</div>
% endif
% if display_globals or repo_type in ['git', 'hg']:

View file

@ -120,7 +120,6 @@ def test_get_config(user_util, baseapp, request_stub):
expected_config = [
('vcs_svn_tag', 'ff89f8c714d135d865f44b90e5413b88de19a55f', '/tags/*'),
('web', 'push_ssl', 'False'),
('web', 'allow_push', '*'),
('web', 'allow_archive', 'gz zip bz2'),
('web', 'baseurl', '/'),

View file

@ -239,7 +239,6 @@ class TestShadowRepoExposure(object):
"""
controller = StubVCSController(
baseapp.config.get_settings(), request_stub.registry)
controller._check_ssl = mock.Mock()
controller.is_shadow_repo = True
controller._action = 'pull'
controller._is_shadow_repo_dir = True
@ -267,7 +266,6 @@ class TestShadowRepoExposure(object):
"""
controller = StubVCSController(
baseapp.config.get_settings(), request_stub.registry)
controller._check_ssl = mock.Mock()
controller.is_shadow_repo = True
controller._action = 'pull'
controller._is_shadow_repo_dir = False
@ -291,7 +289,6 @@ class TestShadowRepoExposure(object):
"""
controller = StubVCSController(
baseapp.config.get_settings(), request_stub.registry)
controller._check_ssl = mock.Mock()
controller.is_shadow_repo = True
controller._action = 'push'
controller.stub_response_body = (b'dummy body value',)

View file

@ -578,17 +578,6 @@ class TestCreateOrUpdateRepoHgSettings(object):
assert str(exc_info.value) == 'Repository is not specified'
class TestUpdateGlobalSslSetting(object):
def test_updates_global_hg_settings(self):
model = VcsSettingsModel()
with mock.patch.object(model, '_create_or_update_ui') as create_mock:
model.update_global_ssl_setting('False')
Session().commit()
create_mock.assert_called_once_with(
model.global_settings, 'web', 'push_ssl', value='False')
class TestCreateOrUpdateGlobalHgSettings(object):
FORM_DATA = {
'extensions_largefiles': False,