repositories: ported repo_creating checks to pyramid.

- fixed security issues with resource discovery.
- ported repository creating check into base view for repos.
This commit is contained in:
Marcin Kuzminski 2017-07-28 15:56:06 +02:00
parent 21efeeab37
commit 3430eb74e5
14 changed files with 199 additions and 77 deletions

View file

@ -362,14 +362,22 @@ class RepoRoutePredicate(object):
repo_model = repo.RepoModel()
by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
def redirect_if_creating(db_repo):
if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
raise HTTPFound(
request.route_path('repo_creating',
repo_name=db_repo.repo_name))
if by_name_match:
# register this as request object we can re-use later
request.db_repo = by_name_match
redirect_if_creating(by_name_match)
return True
by_id_match = repo_model.get_repo_by_id(repo_name)
if by_id_match:
request.db_repo = by_id_match
redirect_if_creating(by_id_match)
return True
return False

View file

@ -22,6 +22,15 @@ from rhodecode.apps._base import add_route_with_slash
def includeme(config):
# repo creating checks, special cases that aren't repo routes
config.add_route(
name='repo_creating',
pattern='/{repo_name:.*?[^/]}/repo_creating')
config.add_route(
name='repo_creating_check',
pattern='/{repo_name:.*?[^/]}/repo_creating_check')
# Summary
# NOTE(marcink): one additional route is defined in very bottom, catch
# all pattern

View file

@ -47,8 +47,8 @@ def route_path(name, params=None, **kwargs):
'repo_summary': '/{repo_name}',
'repo_stats': '/{repo_name}/repo_stats/{commit_id}',
'repo_refs_data': '/{repo_name}/refs-data',
'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog'
'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog',
'repo_creating_check': '/{repo_name}/repo_creating_check',
}[name].format(**kwargs)
if params:

View file

@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2011-2017 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import logging
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from rhodecode.apps._base import BaseAppView
from rhodecode.lib import helpers as h
from rhodecode.lib.auth import (NotAnonymous, HasRepoPermissionAny)
from rhodecode.model.db import Repository
log = logging.getLogger(__name__)
class RepoChecksView(BaseAppView):
def load_default_context(self):
c = self._get_local_tmpl_context()
self._register_global_c(c)
return c
@NotAnonymous()
@view_config(
route_name='repo_creating', request_method='GET',
renderer='rhodecode:templates/admin/repos/repo_creating.mako')
def repo_creating(self):
c = self.load_default_context()
repo_name = self.request.matchdict['repo_name']
db_repo = Repository.get_by_repo_name(repo_name)
if not db_repo:
raise HTTPNotFound()
# check if maybe repo is already created
if db_repo.repo_state in [Repository.STATE_CREATED]:
# re-check permissions before redirecting to prevent resource
# discovery by checking the 302 code
perm_set = ['repository.read', 'repository.write', 'repository.admin']
has_perm = HasRepoPermissionAny(*perm_set)(
db_repo.repo_name, 'Repo Creating check')
if not has_perm:
raise HTTPNotFound()
raise HTTPFound(h.route_path(
'repo_summary', repo_name=db_repo.repo_name))
c.task_id = self.request.GET.get('task_id')
c.repo_name = repo_name
return self._get_template_context(c)
@NotAnonymous()
@view_config(
route_name='repo_creating_check', request_method='GET',
renderer='json_ext')
def repo_creating_check(self):
_ = self.request.translate
task_id = self.request.GET.get('task_id')
self.load_default_context()
repo_name = self.request.matchdict['repo_name']
if task_id and task_id not in ['None']:
import rhodecode
from celery.result import AsyncResult
if rhodecode.CELERY_ENABLED:
task = AsyncResult(task_id)
if task.failed():
msg = self._log_creation_exception(task.result, repo_name)
h.flash(msg, category='error')
raise HTTPFound(h.route_path('home'), code=501)
db_repo = Repository.get_by_repo_name(repo_name)
if db_repo and db_repo.repo_state == Repository.STATE_CREATED:
if db_repo.clone_uri:
clone_uri = db_repo.clone_uri_hidden
h.flash(_('Created repository %s from %s')
% (db_repo.repo_name, clone_uri), category='success')
else:
repo_url = h.link_to(
db_repo.repo_name,
h.route_path('repo_summary', repo_name=db_repo.repo_name))
fork = db_repo.fork
if fork:
fork_name = fork.repo_name
h.flash(h.literal(_('Forked repository %s as %s')
% (fork_name, repo_url)), category='success')
else:
h.flash(h.literal(_('Created repository %s') % repo_url),
category='success')
return {'result': True}
return {'result': False}

View file

@ -416,13 +416,6 @@ def make_map(config):
# REPOSITORY ROUTES
#==========================================================================
rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
controller='admin/repos', action='repo_creating',
requirements=URL_NAME_REQUIREMENTS)
rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
controller='admin/repos', action='repo_check',
requirements=URL_NAME_REQUIREMENTS)
# repo edit options
rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
controller='admin/repos', action='edit_fields',

View file

@ -31,8 +31,9 @@ from formencode import htmlfill
from pylons import request, tmpl_context as c, url
from pylons.controllers.util import redirect
from pylons.i18n.translation import _
from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
from webob.exc import HTTPForbidden, HTTPBadRequest
from pyramid.httpexceptions import HTTPFound
import rhodecode
from rhodecode.lib import auth, helpers as h
from rhodecode.lib.auth import (
@ -183,9 +184,10 @@ class ReposController(BaseRepoController):
h.flash(msg, category='error')
return redirect(h.route_path('home'))
return redirect(h.url('repo_creating_home',
repo_name=form_result['repo_name_full'],
task_id=task_id))
raise HTTPFound(
h.route_path('repo_creating',
repo_name=form_result['repo_name_full'],
_query=dict(task_id=task_id)))
# perms check inside
@NotAnonymous()
@ -239,51 +241,6 @@ class ReposController(BaseRepoController):
force_defaults=False
)
@NotAnonymous()
def repo_creating(self, repo_name):
c.repo = repo_name
c.task_id = request.GET.get('task_id')
if not c.repo:
raise HTTPNotFound()
return render('admin/repos/repo_creating.mako')
@NotAnonymous()
@jsonify
def repo_check(self, repo_name):
c.repo = repo_name
task_id = request.GET.get('task_id')
if task_id and task_id not in ['None']:
import rhodecode
from celery.result import AsyncResult
if rhodecode.CELERY_ENABLED:
task = AsyncResult(task_id)
if task.failed():
msg = self._log_creation_exception(task.result, c.repo)
h.flash(msg, category='error')
return redirect(h.route_path('home'), code=501)
repo = Repository.get_by_repo_name(repo_name)
if repo and repo.repo_state == Repository.STATE_CREATED:
if repo.clone_uri:
clone_uri = repo.clone_uri_hidden
h.flash(_('Created repository %s from %s')
% (repo.repo_name, clone_uri), category='success')
else:
repo_url = h.link_to(
repo.repo_name,
h.route_path('repo_summary',repo_name=repo.repo_name))
fork = repo.fork
if fork:
fork_name = fork.repo_name
h.flash(h.literal(_('Forked repository %s as %s')
% (fork_name, repo_url)), category='success')
else:
h.flash(h.literal(_('Created repository %s') % repo_url),
category='success')
return {'result': True}
return {'result': False}
@HasPermissionAllDecorator('hg.admin')
def show(self, repo_name, format='html'):
"""GET /repos/repo_name: Show a specific item"""

View file

@ -30,6 +30,7 @@ from pylons import tmpl_context as c, request, url
from pylons.controllers.util import redirect
from pylons.i18n.translation import _
from pyramid.httpexceptions import HTTPFound
import rhodecode.lib.helpers as h
from rhodecode.lib import auth
@ -191,6 +192,7 @@ class ForksController(BaseRepoController):
(repo_name, ))
h.flash(msg, category='error')
return redirect(h.url('repo_creating_home',
repo_name=form_result['repo_name_full'],
task_id=task_id))
raise HTTPFound(
h.route_path('repo_creating',
repo_name=form_result['repo_name_full'],
_query=dict(task_id=task_id)))

View file

@ -1833,6 +1833,11 @@ BIN_FILENODE = 7
}
}
.creation_in_progress {
color: @grey4
}
.status_box_menu {
margin: 0;
}

View file

@ -96,6 +96,8 @@ function registerRCRoutes() {
pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
pyroutes.register('toggle_following', '/_admin/toggle_following', []);
pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);

View file

@ -2,14 +2,14 @@
<%inherit file="/base/base.mako"/>
<%def name="title()">
${_('%s Creating repository') % c.repo_name}
${_('{} Creating repository').format(c.repo_name)}
%if c.rhodecode_name:
&middot; ${h.branding(c.rhodecode_name)}
%endif
</%def>
<%def name="breadcrumbs_links()">
${_('Creating repository')} ${c.repo}
${_('Creating repository')} ${c.repo_name}
</%def>
<%def name="menu_bar_nav()">
@ -38,7 +38,7 @@
<script>
(function worker() {
var skipCheck = false;
var url = "${h.url('repo_check_home', repo_name=c.repo_name, repo=c.repo, task_id=c.task_id)}";
var url = "${h.route_path('repo_creating_check', repo_name=c.repo_name, _query=dict(task_id=c.task_id))}";
$.ajax({
url: url,
complete: function(resp) {
@ -48,12 +48,12 @@
if (jsonResponse === undefined) {
setTimeout(function () {
// we might have a backend problem, try dashboard again
window.location = "${h.route_path('repo_summary', repo_name = c.repo)}";
window.location = "${h.route_path('repo_summary', repo_name = c.repo_name)}";
}, 3000);
} else {
if (skipCheck || jsonResponse.result === true) {
// success, means go to dashboard
window.location = "${h.route_path('repo_summary', repo_name = c.repo)}";
window.location = "${h.route_path('repo_summary', repo_name = c.repo_name)}";
} else {
// Schedule the next request when the current one's complete
setTimeout(worker, 1000);
@ -61,10 +61,17 @@
}
}
else {
window.location = "${h.route_path('home')}";
var payload = {
message: {
message: _gettext('Fetching repository state failed. Error code: {0} {1}. Try refreshing this page.').format(resp.status, resp.statusText),
level: 'error',
force: true
}
};
$.Topic('/notifications').publish(payload);
}
}
});
})();
</script>
</%def>
</%def>

View file

@ -67,7 +67,9 @@
<a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
%endif
%if rstate == 'repo_state_pending':
<i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
<span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
(${_('creating...')})
</span>
%endif
</div>
</%def>

View file

@ -122,8 +122,6 @@ class _BaseTest(TestController):
csrf_token=self.csrf_token))
# run the check page that triggers the flash message
# response = self.app.get(url('repo_check_home', repo_name=repo_name))
# assert response.json == {u'result': True}
repo_gr_url = h.route_path(
'repo_group_home', repo_group_name=repo_group_name)

View file

@ -35,14 +35,26 @@ from rhodecode.model.settings import SettingsModel, VcsSettingsModel
from rhodecode.model.user import UserModel
from rhodecode.tests import (
login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN,
TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, HG_REPO, GIT_REPO,
logout_user_session)
TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, logout_user_session)
from rhodecode.tests.fixture import Fixture, error_function
from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
fixture = Fixture()
def route_path(name, params=None, **kwargs):
import urllib
base_url = {
'repo_summary': '/{repo_name}',
'repo_creating_check': '/{repo_name}/repo_creating_check',
}[name].format(**kwargs)
if params:
base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
return base_url
@pytest.mark.usefixtures("app")
class TestAdminRepos(object):
@ -461,7 +473,8 @@ class TestAdminRepos(object):
repo_name_utf8 = safe_str(repo_name)
# run the check page that triggers the flash message
response = self.app.get(url('repo_check_home', repo_name=repo_name))
response = self.app.get(
route_path('repo_creating_check', repo_name=safe_str(repo_name)))
assert response.json == {u'result': True}
flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
@ -475,7 +488,8 @@ class TestAdminRepos(object):
assert new_repo.description == description
# test if the repository is visible in the list ?
response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
response = self.app.get(
h.route_path('repo_summary', repo_name=safe_str(repo_name)))
response.mustcontain(repo_name)
response.mustcontain(backend.alias)

View file

@ -32,6 +32,19 @@ from rhodecode.model.meta import Session
fixture = Fixture()
def route_path(name, params=None, **kwargs):
import urllib
base_url = {
'repo_summary': '/{repo_name}',
'repo_creating_check': '/{repo_name}/repo_creating_check',
}[name].format(**kwargs)
if params:
base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
return base_url
class _BaseTest(TestController):
REPO = None
@ -134,7 +147,8 @@ class _BaseTest(TestController):
assert repo.fork.repo_name == self.REPO
# run the check page that triggers the flash message
response = self.app.get(url('repo_check_home', repo_name=fork_name_full))
response = self.app.get(
route_path('repo_creating_check', repo_name=fork_name_full))
# test if we have a message that fork is ok
assert_session_flash(response,
'Forked repository %s as <a href="/%s">%s</a>'
@ -180,7 +194,8 @@ class _BaseTest(TestController):
assert repo.fork.repo_name == self.REPO
# run the check page that triggers the flash message
response = self.app.get(url('repo_check_home', repo_name=fork_name))
response = self.app.get(
route_path('repo_creating_check', repo_name=fork_name))
# test if we have a message that fork is ok
assert_session_flash(response,
'Forked repository %s as <a href="/%s">%s</a>'