##// END OF EJS Templates
vcs: Minimal change to expose the shadow repository...
vcs: Minimal change to expose the shadow repository Based on my original research, this was the "minimal" starting point. It shows that three concepts are needed for the "repo_name": * From the security standpoint we think of the shadow repository having the same ACL as the target repository of the pull request. This is because the pull request itself is considered to be a part of the target repository. Out of this thought, the variable "acl_repo_name" is used whenever we want to check permissions or when we need the database configuration of the repository. An alternative name would have been "db_repo_name", but the usage for ACL checking is the most important one. * From the web interaction perspective, we need the URL which was originally used to get to the repository. This is because based on this base URL commands can be identified. Especially for Git this is important, so that the commands are correctly recognized. Since the URL is in the focus, this is called "url_repo_name". * Finally we have to deal with the repository on the file system. This is what the VCS layer deal with normally, so this name is called "vcs_repo_name". The original repository interaction is a special case where all three names are the same. When interacting with a pull request, these three names are typically all different. This change is minimal in a sense that it just makes the interaction with a shadow repository barely work, without checking any special constraints yet. This was the starting point for further work on this topic.

File last commit:

r41:6cf4cae4 default
r887:175782be default
Show More
__init__.py
259 lines | 8.1 KiB | text/x-python | PythonLexer
# -*- coding: utf-8 -*-
# Copyright (C) 2010-2016 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 os
import time
import logging
import datetime
import hashlib
import tempfile
from os.path import join as jn
from tempfile import _RandomNameSequence
from paste.deploy import loadapp
from paste.script.appinstall import SetupCommand
import pylons
import pylons.test
from pylons import config, url
from pylons.i18n.translation import _get_translator
from pylons.util import ContextObj
from routes.util import URLGenerator
from webtest import TestApp
from nose.plugins.skip import SkipTest
import pytest
from rhodecode import is_windows
from rhodecode.config.routing import ADMIN_PREFIX
from rhodecode.model.meta import Session
from rhodecode.model.db import User
from rhodecode.lib import auth
from rhodecode.lib.helpers import flash, link_to
from rhodecode.lib.utils2 import safe_unicode, safe_str
from rhodecode.tests.utils import get_session_from_response
# TODO: johbo: Solve time zone related issues and remove this tweak
os.environ['TZ'] = 'UTC'
if not is_windows:
time.tzset()
log = logging.getLogger(__name__)
__all__ = [
'get_new_dir', 'TestController', 'SkipTest',
'url', 'link_to', 'ldap_lib_installed', 'clear_all_caches',
'assert_session_flash', 'login_user',
'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO',
'NEW_HG_REPO', 'NEW_GIT_REPO',
'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'SCM_TESTS',
]
# Invoke websetup with the current config file
# SetupCommand('setup-app').run([config_file])
# SOME GLOBALS FOR TESTS
TEST_DIR = tempfile.gettempdir()
TESTS_TMP_PATH = jn(TEST_DIR, 'rc_test_%s' % _RandomNameSequence().next())
TEST_USER_ADMIN_LOGIN = 'test_admin'
TEST_USER_ADMIN_PASS = 'test12'
TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
TEST_USER_REGULAR_LOGIN = 'test_regular'
TEST_USER_REGULAR_PASS = 'test12'
TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
TEST_USER_REGULAR2_LOGIN = 'test_regular2'
TEST_USER_REGULAR2_PASS = 'test12'
TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
HG_REPO = 'vcs_test_hg'
GIT_REPO = 'vcs_test_git'
SVN_REPO = 'vcs_test_svn'
NEW_HG_REPO = 'vcs_test_hg_new'
NEW_GIT_REPO = 'vcs_test_git_new'
HG_FORK = 'vcs_test_hg_fork'
GIT_FORK = 'vcs_test_git_fork'
## VCS
SCM_TESTS = ['hg', 'git']
uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)
TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)
TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix)
TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix)
TEST_REPO_PREFIX = 'vcs-test'
# skip ldap tests if LDAP lib is not installed
ldap_lib_installed = False
try:
import ldap
ldap_lib_installed = True
except ImportError:
# means that python-ldap is not installed
pass
def clear_all_caches():
from beaker.cache import cache_managers
for _cache in cache_managers.values():
_cache.clear()
def get_new_dir(title):
"""
Returns always new directory path.
"""
from rhodecode.tests.vcs.utils import get_normalized_path
name_parts = [TEST_REPO_PREFIX]
if title:
name_parts.append(title)
hex_str = hashlib.sha1('%s %s' % (os.getpid(), time.time())).hexdigest()
name_parts.append(hex_str)
name = '-'.join(name_parts)
path = os.path.join(TEST_DIR, name)
return get_normalized_path(path)
@pytest.mark.usefixtures('app', 'index_location')
class TestController(object):
maxDiff = None
def log_user(self, username=TEST_USER_ADMIN_LOGIN,
password=TEST_USER_ADMIN_PASS):
self._logged_username = username
self._session = login_user_session(self.app, username, password)
self.csrf_token = auth.get_csrf_token(self._session)
return self._session['rhodecode_user']
def logout_user(self):
logout_user_session(self.app, auth.get_csrf_token(self._session))
self.csrf_token = None
self._logged_username = None
self._session = None
def _get_logged_user(self):
return User.get_by_username(self._logged_username)
# TODO: remove, use plain assert in tests
def assertEqual(self, a, b, msg=None):
if msg:
assert a == b, msg
else:
assert a == b
def login_user_session(
app, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
from rhodecode.tests.functional.test_login import login_url
response = app.post(
login_url,
{'username': username, 'password': password})
if 'invalid user name' in response.body:
pytest.fail('could not login using %s %s' % (username, password))
assert response.status == '302 Found'
response = response.follow()
assert response.status == '200 OK'
session = get_session_from_response(response)
assert 'rhodecode_user' in session
rc_user = session['rhodecode_user']
assert rc_user.get('username') == username
assert rc_user.get('is_authenticated')
return session
def logout_user_session(app, csrf_token):
from rhodecode.tests.functional.test_login import logut_url
app.post(logut_url, {'csrf_token': csrf_token}, status=302)
def login_user(app, username=TEST_USER_ADMIN_LOGIN,
password=TEST_USER_ADMIN_PASS):
return login_user_session(app, username, password)['rhodecode_user']
def assert_session_flash(response=None, msg=None, category=None):
"""
Assert on a flash message in the current session.
:param msg: Required. The expected message. Will be evaluated if a
:class:`LazyString` is passed in.
:param response: Optional. For functional testing, pass in the response
object. Otherwise don't pass in any value.
:param category: Optional. If passed, the message category will be
checked as well.
"""
if msg is None:
raise ValueError("Parameter msg is required.")
messages = flash.pop_messages()
message = messages[0]
msg = _eval_if_lazy(msg)
message_text = _eval_if_lazy(message.message)
if msg not in message_text:
msg = u'msg `%s` not found in session flash: got `%s` instead' % (
msg, message_text)
pytest.fail(safe_str(msg))
if category:
assert category == message.category
def _eval_if_lazy(value):
return value.eval() if hasattr(value, 'eval') else value
def assert_not_in_session_flash(response, msg, category=None):
assert 'flash' in response.session, 'Response session has no flash key'
message_category, message_text = response.session['flash'][0]
if msg in message_text:
msg = u'msg `%s` found in session flash: got `%s` instead' % (
msg, message_text)
pytest.fail(safe_str(msg))
if category:
assert category == message_category
def assert_session_flash_is_empty(response):
if 'flash' in response.session:
msg = 'flash messages are present in session:%s' % \
response.session['flash'][0]
pytest.fail(safe_str(msg))