# Copyright (C) 2010-2020 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 . # # 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 mock import pytest from rhodecode.lib import helpers as h from rhodecode.model.db import User, Gist from rhodecode.model.gist import GistModel from rhodecode.model.meta import Session from rhodecode.tests import ( TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, TestController, assert_session_flash) def route_path(name, params=None, **kwargs): import urllib.request, urllib.parse, urllib.error from rhodecode.apps._base import ADMIN_PREFIX base_url = { 'gists_show': ADMIN_PREFIX + '/gists', 'gists_new': ADMIN_PREFIX + '/gists/new', 'gists_create': ADMIN_PREFIX + '/gists/create', 'gist_show': ADMIN_PREFIX + '/gists/{gist_id}', 'gist_delete': ADMIN_PREFIX + '/gists/{gist_id}/delete', 'gist_edit': ADMIN_PREFIX + '/gists/{gist_id}/edit', 'gist_edit_check_revision': ADMIN_PREFIX + '/gists/{gist_id}/edit/check_revision', 'gist_update': ADMIN_PREFIX + '/gists/{gist_id}/update', 'gist_show_rev': ADMIN_PREFIX + '/gists/{gist_id}/rev/{revision}', 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/rev/{revision}/{format}', 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/rev/{revision}/{format}/{f_path}', }[name].format(**kwargs) if params: base_url = '{}?{}'.format(base_url, urllib.parse.urlencode(params)) return base_url class GistUtility(object): def __init__(self): self._gist_ids = [] def __call__( self, f_name, content='some gist', lifetime=-1, description='gist-desc', gist_type='public', acl_level=Gist.GIST_PUBLIC, owner=TEST_USER_ADMIN_LOGIN): gist_mapping = { f_name: {'content': content} } user = User.get_by_username(owner) gist = GistModel().create( description, owner=user, gist_mapping=gist_mapping, gist_type=gist_type, lifetime=lifetime, gist_acl_level=acl_level) Session().commit() self._gist_ids.append(gist.gist_id) return gist def cleanup(self): for gist_id in self._gist_ids: gist = Gist.get(gist_id) if gist: Session().delete(gist) Session().commit() @pytest.fixture() def create_gist(request): gist_utility = GistUtility() request.addfinalizer(gist_utility.cleanup) return gist_utility class TestGistsController(TestController): def test_index_empty(self, create_gist): self.log_user() response = self.app.get(route_path('gists_show')) response.mustcontain('data: [],') def test_index(self, create_gist): self.log_user() g1 = create_gist('gist1') g2 = create_gist('gist2', lifetime=1400) g3 = create_gist('gist3', description='gist3-desc') g4 = create_gist('gist4', gist_type='private').gist_access_id response = self.app.get(route_path('gists_show')) response.mustcontain(g1.gist_access_id) response.mustcontain(g2.gist_access_id) response.mustcontain(g3.gist_access_id) response.mustcontain('gist3-desc') response.mustcontain(no=[g4]) # Expiration information should be visible expires_tag = '%s' % h.age_component( h.time_to_utcdatetime(g2.gist_expires)) response.mustcontain(expires_tag.replace('"', '\\"')) def test_index_private_gists(self, create_gist): self.log_user() gist = create_gist('gist5', gist_type='private') response = self.app.get(route_path('gists_show', params=dict(private=1))) # and privates response.mustcontain(gist.gist_access_id) def test_index_show_all(self, create_gist): self.log_user() create_gist('gist1') create_gist('gist2', lifetime=1400) create_gist('gist3', description='gist3-desc') create_gist('gist4', gist_type='private') response = self.app.get(route_path('gists_show', params=dict(all=1))) assert len(GistModel.get_all()) == 4 # and privates for gist in GistModel.get_all(): response.mustcontain(gist.gist_access_id) def test_index_show_all_hidden_from_regular(self, create_gist): self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) create_gist('gist2', gist_type='private') create_gist('gist3', gist_type='private') create_gist('gist4', gist_type='private') response = self.app.get(route_path('gists_show', params=dict(all=1))) assert len(GistModel.get_all()) == 3 # since we don't have access to private in this view, we # should see nothing for gist in GistModel.get_all(): response.mustcontain(no=[gist.gist_access_id]) def test_create(self): self.log_user() response = self.app.post( route_path('gists_create'), params={'lifetime': -1, 'content': 'gist test', 'filename': 'foo', 'gist_type': 'public', 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, 'csrf_token': self.csrf_token}, status=302) response = response.follow() response.mustcontain('added file: foo') response.mustcontain('gist test') def test_create_with_path_with_dirs(self): self.log_user() response = self.app.post( route_path('gists_create'), params={'lifetime': -1, 'content': 'gist test', 'filename': '/home/foo', 'gist_type': 'public', 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, 'csrf_token': self.csrf_token}, status=200) response.mustcontain('Filename /home/foo cannot be inside a directory') def test_access_expired_gist(self, create_gist): self.log_user() gist = create_gist('never-see-me') gist.gist_expires = 0 # 1970 Session().add(gist) Session().commit() self.app.get(route_path('gist_show', gist_id=gist.gist_access_id), status=404) def test_create_private(self): self.log_user() response = self.app.post( route_path('gists_create'), params={'lifetime': -1, 'content': 'private gist test', 'filename': 'private-foo', 'gist_type': 'private', 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, 'csrf_token': self.csrf_token}, status=302) response = response.follow() response.mustcontain('added file: private-foo<') response.mustcontain('private gist test') response.mustcontain('Private Gist') # Make sure private gists are not indexed by robots response.mustcontain( '') def test_create_private_acl_private(self): self.log_user() response = self.app.post( route_path('gists_create'), params={'lifetime': -1, 'content': 'private gist test', 'filename': 'private-foo', 'gist_type': 'private', 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE, 'csrf_token': self.csrf_token}, status=302) response = response.follow() response.mustcontain('added file: private-foo<') response.mustcontain('private gist test') response.mustcontain('Private Gist') # Make sure private gists are not indexed by robots response.mustcontain( '') def test_create_with_description(self): self.log_user() response = self.app.post( route_path('gists_create'), params={'lifetime': -1, 'content': 'gist test', 'filename': 'foo-desc', 'description': 'gist-desc', 'gist_type': 'public', 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, 'csrf_token': self.csrf_token}, status=302) response = response.follow() response.mustcontain('added file: foo-desc') response.mustcontain('gist test') response.mustcontain('gist-desc') def test_create_public_with_anonymous_access(self): self.log_user() params = { 'lifetime': -1, 'content': 'gist test', 'filename': 'foo-desc', 'description': 'gist-desc', 'gist_type': 'public', 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, 'csrf_token': self.csrf_token } response = self.app.post( route_path('gists_create'), params=params, status=302) self.logout_user() response = response.follow() response.mustcontain('added file: foo-desc') response.mustcontain('gist test') response.mustcontain('gist-desc') def test_new(self): self.log_user() self.app.get(route_path('gists_new')) def test_delete(self, create_gist): self.log_user() gist = create_gist('delete-me') response = self.app.post( route_path('gist_delete', gist_id=gist.gist_id), params={'csrf_token': self.csrf_token}) assert_session_flash(response, 'Deleted gist %s' % gist.gist_id) def test_delete_normal_user_his_gist(self, create_gist): self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN) response = self.app.post( route_path('gist_delete', gist_id=gist.gist_id), params={'csrf_token': self.csrf_token}) assert_session_flash(response, 'Deleted gist %s' % gist.gist_id) def test_delete_normal_user_not_his_own_gist(self, create_gist): self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) gist = create_gist('delete-me-2') self.app.post( route_path('gist_delete', gist_id=gist.gist_id), params={'csrf_token': self.csrf_token}, status=404) def test_show(self, create_gist): gist = create_gist('gist-show-me') response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id)) response.mustcontain('added file: gist-show-me<') assert_response = response.assert_response() assert_response.element_equals_to( 'div.rc-user span.user', 'test_admin') response.mustcontain('gist-desc') def test_show_without_hg(self, create_gist): with mock.patch( 'rhodecode.lib.vcs.settings.ALIASES', ['git']): gist = create_gist('gist-show-me-again') self.app.get( route_path('gist_show', gist_id=gist.gist_access_id), status=200) def test_show_acl_private(self, create_gist): gist = create_gist('gist-show-me-only-when-im-logged-in', acl_level=Gist.ACL_LEVEL_PRIVATE) self.app.get( route_path('gist_show', gist_id=gist.gist_access_id), status=404) # now we log-in we should see thi gist self.log_user() response = self.app.get( route_path('gist_show', gist_id=gist.gist_access_id)) response.mustcontain('added file: gist-show-me-only-when-im-logged-in') assert_response = response.assert_response() assert_response.element_equals_to( 'div.rc-user span.user', 'test_admin') response.mustcontain('gist-desc') def test_show_as_raw(self, create_gist): gist = create_gist('gist-show-me', content='GIST CONTENT') response = self.app.get( route_path('gist_show_formatted', gist_id=gist.gist_access_id, revision='tip', format='raw')) assert response.text == 'GIST CONTENT' def test_show_as_raw_individual_file(self, create_gist): gist = create_gist('gist-show-me-raw', content='GIST BODY') response = self.app.get( route_path('gist_show_formatted_path', gist_id=gist.gist_access_id, format='raw', revision='tip', f_path='gist-show-me-raw')) assert response.text == 'GIST BODY' def test_edit_page(self, create_gist): self.log_user() gist = create_gist('gist-for-edit', content='GIST EDIT BODY') response = self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id)) response.mustcontain('GIST EDIT BODY') def test_edit_page_non_logged_user(self, create_gist): gist = create_gist('gist-for-edit', content='GIST EDIT BODY') self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id), status=302) def test_edit_normal_user_his_gist(self, create_gist): self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN) self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id, status=200)) def test_edit_normal_user_not_his_own_gist(self, create_gist): self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) gist = create_gist('delete-me') self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id), status=404) def test_user_first_name_is_escaped(self, user_util, create_gist): xss_atack_string = '">' xss_escaped_string = h.html_escape(h.escape(xss_atack_string)) password = 'test' user = user_util.create_user( firstname=xss_atack_string, password=password) create_gist('gist', gist_type='public', owner=user.username) response = self.app.get(route_path('gists_show')) response.mustcontain(xss_escaped_string) def test_user_last_name_is_escaped(self, user_util, create_gist): xss_atack_string = '">' xss_escaped_string = h.html_escape(h.escape(xss_atack_string)) password = 'test' user = user_util.create_user( lastname=xss_atack_string, password=password) create_gist('gist', gist_type='public', owner=user.username) response = self.app.get(route_path('gists_show')) response.mustcontain(xss_escaped_string)