diff --git a/rhodecode/api/tests/test_get_gist.py b/rhodecode/api/tests/test_get_gist.py
--- a/rhodecode/api/tests/test_get_gist.py
+++ b/rhodecode/api/tests/test_get_gist.py
@@ -28,7 +28,7 @@ from rhodecode.api.tests.utils import (
@pytest.mark.usefixtures("testuser_api", "app")
class TestApiGetGist(object):
- def test_api_get_gist(self, gist_util, http_host_stub):
+ def test_api_get_gist(self, gist_util, http_host_only_stub):
gist = gist_util.create_gist()
gist_id = gist.gist_access_id
gist_created_on = gist.created_on
@@ -45,14 +45,14 @@ class TestApiGetGist(object):
'expires': -1.0,
'gist_id': int(gist_id),
'type': 'public',
- 'url': 'http://%s/_admin/gists/%s' % (http_host_stub, gist_id,),
+ 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
'acl_level': Gist.ACL_LEVEL_PUBLIC,
'content': None,
}
assert_ok(id_, expected, given=response.body)
- def test_api_get_gist_with_content(self, gist_util, http_host_stub):
+ def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
mapping = {
u'filename1.txt': {'content': u'hello world'},
u'filename1ą.txt': {'content': u'hello worldę'}
@@ -73,7 +73,7 @@ class TestApiGetGist(object):
'expires': -1.0,
'gist_id': int(gist_id),
'type': 'public',
- 'url': 'http://%s/_admin/gists/%s' % (http_host_stub, gist_id,),
+ 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
'acl_level': Gist.ACL_LEVEL_PUBLIC,
'content': {
u'filename1.txt': u'hello world',
diff --git a/rhodecode/apps/gist/__init__.py b/rhodecode/apps/gist/__init__.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/gist/__init__.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-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 .
+#
+# 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/
+from rhodecode.apps._base import ADMIN_PREFIX
+
+
+def admin_routes(config):
+ config.add_route(
+ name='gists_show', pattern='/gists')
+ config.add_route(
+ name='gists_new', pattern='/gists/new')
+ config.add_route(
+ name='gists_create', pattern='/gists/create')
+
+ config.add_route(
+ name='gist_show', pattern='/gists/{gist_id}')
+
+ config.add_route(
+ name='gist_delete', pattern='/gists/{gist_id}/delete')
+
+ config.add_route(
+ name='gist_edit', pattern='/gists/{gist_id}/edit')
+
+ config.add_route(
+ name='gist_edit_check_revision',
+ pattern='/gists/{gist_id}/edit/check_revision')
+
+ config.add_route(
+ name='gist_update', pattern='/gists/{gist_id}/update')
+
+ config.add_route(
+ name='gist_show_rev',
+ pattern='/gists/{gist_id}/{revision}')
+ config.add_route(
+ name='gist_show_formatted',
+ pattern='/gists/{gist_id}/{revision}/{format}')
+
+ config.add_route(
+ name='gist_show_formatted_path',
+ pattern='/gists/{gist_id}/{revision}/{format}/{f_path:.*}')
+
+
+def includeme(config):
+ config.include(admin_routes, route_prefix=ADMIN_PREFIX)
+ # Scan module for configuration decorators.
+ config.scan()
diff --git a/rhodecode/apps/gist/tests/__init__.py b/rhodecode/apps/gist/tests/__init__.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/gist/tests/__init__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-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 .
+#
+# 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/
+
diff --git a/rhodecode/tests/functional/test_admin_gists.py b/rhodecode/apps/gist/tests/test_admin_gists.py
rename from rhodecode/tests/functional/test_admin_gists.py
rename to rhodecode/apps/gist/tests/test_admin_gists.py
--- a/rhodecode/tests/functional/test_admin_gists.py
+++ b/rhodecode/apps/gist/tests/test_admin_gists.py
@@ -27,7 +27,31 @@ from rhodecode.model.gist import GistMod
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, url)
+ TestController, assert_session_flash)
+
+
+def route_path(name, params=None, **kwargs):
+ import urllib
+ 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}/{revision}',
+ 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}',
+ 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}/{f_path}',
+
+ }[name].format(**kwargs)
+
+ if params:
+ base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
+ return base_url
class GistUtility(object):
@@ -70,7 +94,7 @@ class TestGistsController(TestController
def test_index_empty(self, create_gist):
self.log_user()
- response = self.app.get(url('gists'))
+ response = self.app.get(route_path('gists_show'))
response.mustcontain('data: [],')
def test_index(self, create_gist):
@@ -79,7 +103,7 @@ class TestGistsController(TestController
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(url('gists'))
+ response = self.app.get(route_path('gists_show'))
response.mustcontain('gist: %s' % g1.gist_access_id)
response.mustcontain('gist: %s' % g2.gist_access_id)
@@ -95,7 +119,7 @@ class TestGistsController(TestController
def test_index_private_gists(self, create_gist):
self.log_user()
gist = create_gist('gist5', gist_type='private')
- response = self.app.get(url('gists', private=1))
+ response = self.app.get(route_path('gists_show', params=dict(private=1)))
# and privates
response.mustcontain('gist: %s' % gist.gist_access_id)
@@ -107,7 +131,7 @@ class TestGistsController(TestController
create_gist('gist3', description='gist3-desc')
create_gist('gist4', gist_type='private')
- response = self.app.get(url('gists', all=1))
+ response = self.app.get(route_path('gists_show', params=dict(all=1)))
assert len(GistModel.get_all()) == 4
# and privates
@@ -120,7 +144,7 @@ class TestGistsController(TestController
create_gist('gist3', gist_type='private')
create_gist('gist4', gist_type='private')
- response = self.app.get(url('gists', all=1))
+ 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
@@ -131,7 +155,7 @@ class TestGistsController(TestController
def test_create(self):
self.log_user()
response = self.app.post(
- url('gists'),
+ route_path('gists_create'),
params={'lifetime': -1,
'content': 'gist test',
'filename': 'foo',
@@ -146,7 +170,7 @@ class TestGistsController(TestController
def test_create_with_path_with_dirs(self):
self.log_user()
response = self.app.post(
- url('gists'),
+ route_path('gists_create'),
params={'lifetime': -1,
'content': 'gist test',
'filename': '/home/foo',
@@ -163,12 +187,13 @@ class TestGistsController(TestController
Session().add(gist)
Session().commit()
- self.app.get(url('gist', gist_id=gist.gist_access_id), status=404)
+ 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(
- url('gists'),
+ route_path('gists_create'),
params={'lifetime': -1,
'content': 'private gist test',
'filename': 'private-foo',
@@ -187,7 +212,7 @@ class TestGistsController(TestController
def test_create_private_acl_private(self):
self.log_user()
response = self.app.post(
- url('gists'),
+ route_path('gists_create'),
params={'lifetime': -1,
'content': 'private gist test',
'filename': 'private-foo',
@@ -206,7 +231,7 @@ class TestGistsController(TestController
def test_create_with_description(self):
self.log_user()
response = self.app.post(
- url('gists'),
+ route_path('gists_create'),
params={'lifetime': -1,
'content': 'gist test',
'filename': 'foo-desc',
@@ -231,7 +256,8 @@ class TestGistsController(TestController
'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
'csrf_token': self.csrf_token
}
- response = self.app.post(url('gists'), params=params, status=302)
+ response = self.app.post(
+ route_path('gists_create'), params=params, status=302)
self.logout_user()
response = response.follow()
response.mustcontain('added file: foo-desc')
@@ -240,35 +266,36 @@ class TestGistsController(TestController
def test_new(self):
self.log_user()
- self.app.get(url('new_gist'))
+ 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(
- url('gist', gist_id=gist.gist_id),
- params={'_method': 'delete', 'csrf_token': self.csrf_token})
+ 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(
- url('gist', gist_id=gist.gist_id),
- params={'_method': 'delete', 'csrf_token': self.csrf_token})
+ 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')
+ gist = create_gist('delete-me-2')
+
self.app.post(
- url('gist', gist_id=gist.gist_id),
- params={'_method': 'delete', 'csrf_token': self.csrf_token},
- status=403)
+ 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(url('gist', gist_id=gist.gist_access_id))
+ response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id))
response.mustcontain('added file: gist-show-me<')
@@ -283,16 +310,19 @@ class TestGistsController(TestController
with mock.patch(
'rhodecode.lib.vcs.settings.ALIASES', ['git']):
gist = create_gist('gist-show-me-again')
- self.app.get(url('gist', gist_id=gist.gist_access_id), status=200)
+ 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(url('gist', gist_id=gist.gist_access_id), status=404)
+ 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(url('gist', gist_id=gist.gist_access_id))
+ 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()
@@ -303,36 +333,42 @@ class TestGistsController(TestController
def test_show_as_raw(self, create_gist):
gist = create_gist('gist-show-me', content='GIST CONTENT')
- response = self.app.get(url('formatted_gist',
- gist_id=gist.gist_access_id, format='raw'))
+ response = self.app.get(
+ route_path('gist_show_formatted',
+ gist_id=gist.gist_access_id, revision='tip',
+ format='raw'))
assert response.body == '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(url('formatted_gist_file',
- gist_id=gist.gist_access_id, format='raw',
- revision='tip', f_path='gist-show-me-raw'))
+ 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.body == '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(url('edit_gist', gist_id=gist.gist_access_id))
+ 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(url('edit_gist', gist_id=gist.gist_access_id), status=302)
+ 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(url('edit_gist', gist_id=gist.gist_access_id, status=200))
+ 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(url('edit_gist', gist_id=gist.gist_access_id), status=403)
+ 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 = '">'
@@ -341,7 +377,7 @@ class TestGistsController(TestController
user = user_util.create_user(
firstname=xss_atack_string, password=password)
create_gist('gist', gist_type='public', owner=user.username)
- response = self.app.get(url('gists'))
+ 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):
@@ -351,5 +387,5 @@ class TestGistsController(TestController
user = user_util.create_user(
lastname=xss_atack_string, password=password)
create_gist('gist', gist_type='public', owner=user.username)
- response = self.app.get(url('gists'))
+ response = self.app.get(route_path('gists_show'))
response.mustcontain(xss_escaped_string)
diff --git a/rhodecode/apps/gist/views.py b/rhodecode/apps/gist/views.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/gist/views.py
@@ -0,0 +1,412 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2013-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 .
+#
+# 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 time
+import logging
+
+import formencode
+import peppercorn
+
+from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden, HTTPFound
+from pyramid.view import view_config
+from pyramid.renderers import render
+from pyramid.response import Response
+
+from rhodecode.apps._base import BaseAppView
+from rhodecode.lib import helpers as h
+from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
+from rhodecode.lib.utils2 import time_to_datetime
+from rhodecode.lib.ext_json import json
+from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
+from rhodecode.model.gist import GistModel
+from rhodecode.model.meta import Session
+from rhodecode.model.db import Gist, User, or_
+from rhodecode.model import validation_schema
+from rhodecode.model.validation_schema.schemas import gist_schema
+
+
+log = logging.getLogger(__name__)
+
+
+class GistView(BaseAppView):
+
+ def load_default_context(self):
+ _ = self.request.translate
+ c = self._get_local_tmpl_context()
+ c.user = c.auth_user.get_instance()
+
+ c.lifetime_values = [
+ (-1, _('forever')),
+ (5, _('5 minutes')),
+ (60, _('1 hour')),
+ (60 * 24, _('1 day')),
+ (60 * 24 * 30, _('1 month')),
+ ]
+
+ c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
+ c.acl_options = [
+ (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
+ (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
+ ]
+
+ self._register_global_c(c)
+ return c
+
+ @LoginRequired()
+ @view_config(
+ route_name='gists_show', request_method='GET',
+ renderer='rhodecode:templates/admin/gists/index.mako')
+ def gist_show_all(self):
+ c = self.load_default_context()
+
+ not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
+ c.show_private = self.request.GET.get('private') and not_default_user
+ c.show_public = self.request.GET.get('public') and not_default_user
+ c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
+
+ gists = _gists = Gist().query()\
+ .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
+ .order_by(Gist.created_on.desc())
+
+ c.active = 'public'
+ # MY private
+ if c.show_private and not c.show_public:
+ gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
+ .filter(Gist.gist_owner == self._rhodecode_user.user_id)
+ c.active = 'my_private'
+ # MY public
+ elif c.show_public and not c.show_private:
+ gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
+ .filter(Gist.gist_owner == self._rhodecode_user.user_id)
+ c.active = 'my_public'
+ # MY public+private
+ elif c.show_private and c.show_public:
+ gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
+ Gist.gist_type == Gist.GIST_PRIVATE))\
+ .filter(Gist.gist_owner == self._rhodecode_user.user_id)
+ c.active = 'my_all'
+ # Show all by super-admin
+ elif c.show_all:
+ c.active = 'all'
+ gists = _gists
+
+ # default show ALL public gists
+ if not c.show_public and not c.show_private and not c.show_all:
+ gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
+ c.active = 'public'
+
+ from rhodecode.lib.utils import PartialRenderer
+ _render = PartialRenderer('data_table/_dt_elements.mako')
+
+ data = []
+
+ for gist in gists:
+ data.append({
+ 'created_on': _render('gist_created', gist.created_on),
+ 'created_on_raw': gist.created_on,
+ 'type': _render('gist_type', gist.gist_type),
+ 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
+ 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
+ 'author_raw': h.escape(gist.owner.full_contact),
+ 'expires': _render('gist_expires', gist.gist_expires),
+ 'description': _render('gist_description', gist.gist_description)
+ })
+ c.data = json.dumps(data)
+
+ return self._get_template_context(c)
+
+ @LoginRequired()
+ @NotAnonymous()
+ @view_config(
+ route_name='gists_new', request_method='GET',
+ renderer='rhodecode:templates/admin/gists/new.mako')
+ def gist_new(self):
+ c = self.load_default_context()
+ return self._get_template_context(c)
+
+ @LoginRequired()
+ @NotAnonymous()
+ @CSRFRequired()
+ @view_config(
+ route_name='gists_create', request_method='POST',
+ renderer='rhodecode:templates/admin/gists/new.mako')
+ def gist_create(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+
+ data = dict(self.request.POST)
+ data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
+ data['nodes'] = [{
+ 'filename': data['filename'],
+ 'content': data.get('content'),
+ 'mimetype': data.get('mimetype') # None is autodetect
+ }]
+
+ data['gist_type'] = (
+ Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
+ data['gist_acl_level'] = (
+ data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
+
+ schema = gist_schema.GistSchema().bind(
+ lifetime_options=[x[0] for x in c.lifetime_values])
+
+ try:
+
+ schema_data = schema.deserialize(data)
+ # convert to safer format with just KEYs so we sure no duplicates
+ schema_data['nodes'] = gist_schema.sequence_to_nodes(
+ schema_data['nodes'])
+
+ gist = GistModel().create(
+ gist_id=schema_data['gistid'], # custom access id not real ID
+ description=schema_data['description'],
+ owner=self._rhodecode_user.user_id,
+ gist_mapping=schema_data['nodes'],
+ gist_type=schema_data['gist_type'],
+ lifetime=schema_data['lifetime'],
+ gist_acl_level=schema_data['gist_acl_level']
+ )
+ Session().commit()
+ new_gist_id = gist.gist_access_id
+ except validation_schema.Invalid as errors:
+ defaults = data
+ errors = errors.asdict()
+
+ if 'nodes.0.content' in errors:
+ errors['content'] = errors['nodes.0.content']
+ del errors['nodes.0.content']
+ if 'nodes.0.filename' in errors:
+ errors['filename'] = errors['nodes.0.filename']
+ del errors['nodes.0.filename']
+
+ data = render('rhodecode:templates/admin/gists/new.mako',
+ self._get_template_context(c), self.request)
+ html = formencode.htmlfill.render(
+ data,
+ defaults=defaults,
+ errors=errors,
+ prefix_error=False,
+ encoding="UTF-8",
+ force_defaults=False
+ )
+ return Response(html)
+
+ except Exception:
+ log.exception("Exception while trying to create a gist")
+ h.flash(_('Error occurred during gist creation'), category='error')
+ raise HTTPFound(h.route_url('gists_new'))
+ raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
+
+ @LoginRequired()
+ @NotAnonymous()
+ @CSRFRequired()
+ @view_config(
+ route_name='gist_delete', request_method='POST')
+ def gist_delete(self):
+ _ = self.request.translate
+ gist_id = self.request.matchdict['gist_id']
+
+ c = self.load_default_context()
+ c.gist = Gist.get_or_404(gist_id)
+
+ owner = c.gist.gist_owner == self._rhodecode_user.user_id
+ if not (h.HasPermissionAny('hg.admin')() or owner):
+ log.warning('Deletion of Gist was forbidden '
+ 'by unauthorized user: `%s`', self._rhodecode_user)
+ raise HTTPNotFound()
+
+ GistModel().delete(c.gist)
+ Session().commit()
+ h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
+
+ raise HTTPFound(h.route_url('gists_show'))
+
+ def _get_gist(self, gist_id):
+
+ gist = Gist.get_or_404(gist_id)
+
+ # Check if this gist is expired
+ if gist.gist_expires != -1:
+ if time.time() > gist.gist_expires:
+ log.error(
+ 'Gist expired at %s', time_to_datetime(gist.gist_expires))
+ raise HTTPNotFound()
+
+ # check if this gist requires a login
+ is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
+ if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
+ log.error("Anonymous user %s tried to access protected gist `%s`",
+ self._rhodecode_user, gist_id)
+ raise HTTPNotFound()
+ return gist
+
+ @LoginRequired()
+ @view_config(
+ route_name='gist_show', request_method='GET',
+ renderer='rhodecode:templates/admin/gists/show.mako')
+ @view_config(
+ route_name='gist_show_rev', request_method='GET',
+ renderer='rhodecode:templates/admin/gists/show.mako')
+ @view_config(
+ route_name='gist_show_formatted', request_method='GET',
+ renderer=None)
+ @view_config(
+ route_name='gist_show_formatted_path', request_method='GET',
+ renderer=None)
+ def show(self):
+ gist_id = self.request.matchdict['gist_id']
+
+ # TODO(marcink): expose those via matching dict
+ revision = self.request.matchdict.get('revision', 'tip')
+ f_path = self.request.matchdict.get('f_path', None)
+ return_format = self.request.matchdict.get('format')
+
+ c = self.load_default_context()
+ c.gist = self._get_gist(gist_id)
+ c.render = not self.request.GET.get('no-render', False)
+
+ try:
+ c.file_last_commit, c.files = GistModel().get_gist_files(
+ gist_id, revision=revision)
+ except VCSError:
+ log.exception("Exception in gist show")
+ raise HTTPNotFound()
+
+ if return_format == 'raw':
+ content = '\n\n'.join([f.content for f in c.files
+ if (f_path is None or f.path == f_path)])
+ response = Response(content)
+ response.content_type = 'text/plain'
+ return response
+
+ return self._get_template_context(c)
+
+ @LoginRequired()
+ @NotAnonymous()
+ @view_config(
+ route_name='gist_edit', request_method='GET',
+ renderer='rhodecode:templates/admin/gists/edit.mako')
+ def gist_edit(self):
+ _ = self.request.translate
+ gist_id = self.request.matchdict['gist_id']
+ c = self.load_default_context()
+ c.gist = self._get_gist(gist_id)
+
+ owner = c.gist.gist_owner == self._rhodecode_user.user_id
+ if not (h.HasPermissionAny('hg.admin')() or owner):
+ raise HTTPNotFound()
+
+ try:
+ c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
+ except VCSError:
+ log.exception("Exception in gist edit")
+ raise HTTPNotFound()
+
+ if c.gist.gist_expires == -1:
+ expiry = _('never')
+ else:
+ # this cannot use timeago, since it's used in select2 as a value
+ expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
+
+ c.lifetime_values.append(
+ (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
+ )
+
+ return self._get_template_context(c)
+
+ @LoginRequired()
+ @NotAnonymous()
+ @CSRFRequired()
+ @view_config(
+ route_name='gist_update', request_method='POST',
+ renderer='rhodecode:templates/admin/gists/edit.mako')
+ def gist_update(self):
+ _ = self.request.translate
+ gist_id = self.request.matchdict['gist_id']
+ c = self.load_default_context()
+ c.gist = self._get_gist(gist_id)
+
+ owner = c.gist.gist_owner == self._rhodecode_user.user_id
+ if not (h.HasPermissionAny('hg.admin')() or owner):
+ raise HTTPNotFound()
+
+ data = peppercorn.parse(self.request.POST.items())
+
+ schema = gist_schema.GistSchema()
+ schema = schema.bind(
+ # '0' is special value to leave lifetime untouched
+ lifetime_options=[x[0] for x in c.lifetime_values] + [0],
+ )
+
+ try:
+ schema_data = schema.deserialize(data)
+ # convert to safer format with just KEYs so we sure no duplicates
+ schema_data['nodes'] = gist_schema.sequence_to_nodes(
+ schema_data['nodes'])
+
+ GistModel().update(
+ gist=c.gist,
+ description=schema_data['description'],
+ owner=c.gist.owner,
+ gist_mapping=schema_data['nodes'],
+ lifetime=schema_data['lifetime'],
+ gist_acl_level=schema_data['gist_acl_level']
+ )
+
+ Session().commit()
+ h.flash(_('Successfully updated gist content'), category='success')
+ except NodeNotChangedError:
+ # raised if nothing was changed in repo itself. We anyway then
+ # store only DB stuff for gist
+ Session().commit()
+ h.flash(_('Successfully updated gist data'), category='success')
+ except validation_schema.Invalid as errors:
+ errors = errors.asdict()
+ h.flash(_('Error occurred during update of gist {}: {}').format(
+ gist_id, errors), category='error')
+ except Exception:
+ log.exception("Exception in gist edit")
+ h.flash(_('Error occurred during update of gist %s') % gist_id,
+ category='error')
+
+ raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
+
+ @LoginRequired()
+ @NotAnonymous()
+ @view_config(
+ route_name='gist_edit_check_revision', request_method='GET',
+ renderer='json_ext')
+ def gist_edit_check_revision(self):
+ _ = self.request.translate
+ gist_id = self.request.matchdict['gist_id']
+ c = self.load_default_context()
+ c.gist = self._get_gist(gist_id)
+
+ last_rev = c.gist.scm_instance().get_commit()
+ success = True
+ revision = self.request.GET.get('revision')
+
+ if revision != last_rev.raw_id:
+ log.error('Last revision %s is different then submitted %s'
+ % (revision, last_rev))
+ # our gist has newer version than we
+ success = False
+
+ return {'success': success}
diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py
--- a/rhodecode/config/middleware.py
+++ b/rhodecode/config/middleware.py
@@ -303,6 +303,7 @@ def includeme(config):
config.include('rhodecode.apps.user_profile')
config.include('rhodecode.apps.my_account')
config.include('rhodecode.apps.svn_support')
+ config.include('rhodecode.apps.gist')
config.include('rhodecode.tweens')
config.include('rhodecode.api')
diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py
--- a/rhodecode/config/routing.py
+++ b/rhodecode/config/routing.py
@@ -489,39 +489,6 @@ def make_map(config):
m.connect('notification', '/notifications/{notification_id}',
action='show', conditions={'method': ['GET']})
- # ADMIN GIST
- with rmap.submapper(path_prefix=ADMIN_PREFIX,
- controller='admin/gists') as m:
- m.connect('gists', '/gists',
- action='create', conditions={'method': ['POST']})
- m.connect('gists', '/gists', jsroute=True,
- action='index', conditions={'method': ['GET']})
- m.connect('new_gist', '/gists/new', jsroute=True,
- action='new', conditions={'method': ['GET']})
-
- m.connect('/gists/{gist_id}',
- action='delete', conditions={'method': ['DELETE']})
- m.connect('edit_gist', '/gists/{gist_id}/edit',
- action='edit_form', conditions={'method': ['GET']})
- m.connect('edit_gist', '/gists/{gist_id}/edit',
- action='edit', conditions={'method': ['POST']})
- m.connect(
- 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
- action='check_revision', conditions={'method': ['GET']})
-
- m.connect('gist', '/gists/{gist_id}',
- action='show', conditions={'method': ['GET']})
- m.connect('gist_rev', '/gists/{gist_id}/{revision}',
- revision='tip',
- action='show', conditions={'method': ['GET']})
- m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
- revision='tip',
- action='show', conditions={'method': ['GET']})
- m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
- revision='tip',
- action='show', conditions={'method': ['GET']},
- requirements=URL_NAME_REQUIREMENTS)
-
# USER JOURNAL
rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
controller='journal', action='index')
diff --git a/rhodecode/controllers/admin/gists.py b/rhodecode/controllers/admin/gists.py
deleted file mode 100644
--- a/rhodecode/controllers/admin/gists.py
+++ /dev/null
@@ -1,369 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2013-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 .
-#
-# 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/
-
-
-"""
-gist controller for RhodeCode
-"""
-
-import time
-import logging
-
-import formencode
-import peppercorn
-
-from pylons import request, response, tmpl_context as c, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
-from webob.exc import HTTPNotFound, HTTPForbidden
-from sqlalchemy.sql.expression import or_
-
-
-from rhodecode.model.gist import GistModel
-from rhodecode.model.meta import Session
-from rhodecode.model.db import Gist, User
-from rhodecode.lib import auth
-from rhodecode.lib import helpers as h
-from rhodecode.lib.base import BaseController, render
-from rhodecode.lib.auth import LoginRequired, NotAnonymous
-from rhodecode.lib.utils import jsonify
-from rhodecode.lib.utils2 import time_to_datetime
-from rhodecode.lib.ext_json import json
-from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
-from rhodecode.model import validation_schema
-from rhodecode.model.validation_schema.schemas import gist_schema
-
-
-log = logging.getLogger(__name__)
-
-
-class GistsController(BaseController):
- """REST Controller styled on the Atom Publishing Protocol"""
-
- def __load_defaults(self, extra_values=None):
- c.lifetime_values = [
- (-1, _('forever')),
- (5, _('5 minutes')),
- (60, _('1 hour')),
- (60 * 24, _('1 day')),
- (60 * 24 * 30, _('1 month')),
- ]
- if extra_values:
- c.lifetime_values.append(extra_values)
- c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
- c.acl_options = [
- (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
- (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
- ]
-
- @LoginRequired()
- def index(self):
- """GET /admin/gists: All items in the collection"""
- # url('gists')
- not_default_user = c.rhodecode_user.username != User.DEFAULT_USER
- c.show_private = request.GET.get('private') and not_default_user
- c.show_public = request.GET.get('public') and not_default_user
- c.show_all = request.GET.get('all') and c.rhodecode_user.admin
-
- gists = _gists = Gist().query()\
- .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
- .order_by(Gist.created_on.desc())
-
- c.active = 'public'
- # MY private
- if c.show_private and not c.show_public:
- gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
- .filter(Gist.gist_owner == c.rhodecode_user.user_id)
- c.active = 'my_private'
- # MY public
- elif c.show_public and not c.show_private:
- gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
- .filter(Gist.gist_owner == c.rhodecode_user.user_id)
- c.active = 'my_public'
- # MY public+private
- elif c.show_private and c.show_public:
- gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
- Gist.gist_type == Gist.GIST_PRIVATE))\
- .filter(Gist.gist_owner == c.rhodecode_user.user_id)
- c.active = 'my_all'
- # Show all by super-admin
- elif c.show_all:
- c.active = 'all'
- gists = _gists
-
- # default show ALL public gists
- if not c.show_public and not c.show_private and not c.show_all:
- gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
- c.active = 'public'
-
- from rhodecode.lib.utils import PartialRenderer
- _render = PartialRenderer('data_table/_dt_elements.mako')
-
- data = []
-
- for gist in gists:
- data.append({
- 'created_on': _render('gist_created', gist.created_on),
- 'created_on_raw': gist.created_on,
- 'type': _render('gist_type', gist.gist_type),
- 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
- 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
- 'author_raw': h.escape(gist.owner.full_contact),
- 'expires': _render('gist_expires', gist.gist_expires),
- 'description': _render('gist_description', gist.gist_description)
- })
- c.data = json.dumps(data)
- return render('admin/gists/index.mako')
-
- @LoginRequired()
- @NotAnonymous()
- @auth.CSRFRequired()
- def create(self):
- """POST /admin/gists: Create a new item"""
- # url('gists')
- self.__load_defaults()
-
- data = dict(request.POST)
- data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
- data['nodes'] = [{
- 'filename': data['filename'],
- 'content': data.get('content'),
- 'mimetype': data.get('mimetype') # None is autodetect
- }]
-
- data['gist_type'] = (
- Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
- data['gist_acl_level'] = (
- data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
-
- schema = gist_schema.GistSchema().bind(
- lifetime_options=[x[0] for x in c.lifetime_values])
-
- try:
-
- schema_data = schema.deserialize(data)
- # convert to safer format with just KEYs so we sure no duplicates
- schema_data['nodes'] = gist_schema.sequence_to_nodes(
- schema_data['nodes'])
-
- gist = GistModel().create(
- gist_id=schema_data['gistid'], # custom access id not real ID
- description=schema_data['description'],
- owner=c.rhodecode_user.user_id,
- gist_mapping=schema_data['nodes'],
- gist_type=schema_data['gist_type'],
- lifetime=schema_data['lifetime'],
- gist_acl_level=schema_data['gist_acl_level']
- )
- Session().commit()
- new_gist_id = gist.gist_access_id
- except validation_schema.Invalid as errors:
- defaults = data
- errors = errors.asdict()
-
- if 'nodes.0.content' in errors:
- errors['content'] = errors['nodes.0.content']
- del errors['nodes.0.content']
- if 'nodes.0.filename' in errors:
- errors['filename'] = errors['nodes.0.filename']
- del errors['nodes.0.filename']
-
- return formencode.htmlfill.render(
- render('admin/gists/new.mako'),
- defaults=defaults,
- errors=errors,
- prefix_error=False,
- encoding="UTF-8",
- force_defaults=False
- )
-
- except Exception:
- log.exception("Exception while trying to create a gist")
- h.flash(_('Error occurred during gist creation'), category='error')
- return redirect(url('new_gist'))
- return redirect(url('gist', gist_id=new_gist_id))
-
- @LoginRequired()
- @NotAnonymous()
- def new(self):
- """GET /admin/gists/new: Form to create a new item"""
- # url('new_gist')
- self.__load_defaults()
- return render('admin/gists/new.mako')
-
- @LoginRequired()
- @NotAnonymous()
- @auth.CSRFRequired()
- def delete(self, gist_id):
- """DELETE /admin/gists/gist_id: Delete an existing item"""
- # Forms posted to this method should contain a hidden field:
- #
- # Or using helpers:
- # h.form(url('gist', gist_id=ID),
- # method='delete')
- # url('gist', gist_id=ID)
- c.gist = Gist.get_or_404(gist_id)
-
- owner = c.gist.gist_owner == c.rhodecode_user.user_id
- if not (h.HasPermissionAny('hg.admin')() or owner):
- raise HTTPForbidden()
-
- GistModel().delete(c.gist)
- Session().commit()
- h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
-
- return redirect(url('gists'))
-
- def _add_gist_to_context(self, gist_id):
- c.gist = Gist.get_or_404(gist_id)
-
- # Check if this gist is expired
- if c.gist.gist_expires != -1:
- if time.time() > c.gist.gist_expires:
- log.error(
- 'Gist expired at %s', time_to_datetime(c.gist.gist_expires))
- raise HTTPNotFound()
-
- # check if this gist requires a login
- is_default_user = c.rhodecode_user.username == User.DEFAULT_USER
- if c.gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
- log.error("Anonymous user %s tried to access protected gist `%s`",
- c.rhodecode_user, gist_id)
- raise HTTPNotFound()
-
- @LoginRequired()
- def show(self, gist_id, revision='tip', format='html', f_path=None):
- """GET /admin/gists/gist_id: Show a specific item"""
- # url('gist', gist_id=ID)
- self._add_gist_to_context(gist_id)
- c.render = not request.GET.get('no-render', False)
-
- try:
- c.file_last_commit, c.files = GistModel().get_gist_files(
- gist_id, revision=revision)
- except VCSError:
- log.exception("Exception in gist show")
- raise HTTPNotFound()
- if format == 'raw':
- content = '\n\n'.join([f.content for f in c.files
- if (f_path is None or f.path == f_path)])
- response.content_type = 'text/plain'
- return content
- return render('admin/gists/show.mako')
-
- @LoginRequired()
- @NotAnonymous()
- @auth.CSRFRequired()
- def edit(self, gist_id):
- self.__load_defaults()
- self._add_gist_to_context(gist_id)
-
- owner = c.gist.gist_owner == c.rhodecode_user.user_id
- if not (h.HasPermissionAny('hg.admin')() or owner):
- raise HTTPForbidden()
-
- data = peppercorn.parse(request.POST.items())
-
- schema = gist_schema.GistSchema()
- schema = schema.bind(
- # '0' is special value to leave lifetime untouched
- lifetime_options=[x[0] for x in c.lifetime_values] + [0],
- )
-
- try:
- schema_data = schema.deserialize(data)
- # convert to safer format with just KEYs so we sure no duplicates
- schema_data['nodes'] = gist_schema.sequence_to_nodes(
- schema_data['nodes'])
-
- GistModel().update(
- gist=c.gist,
- description=schema_data['description'],
- owner=c.gist.owner,
- gist_mapping=schema_data['nodes'],
- lifetime=schema_data['lifetime'],
- gist_acl_level=schema_data['gist_acl_level']
- )
-
- Session().commit()
- h.flash(_('Successfully updated gist content'), category='success')
- except NodeNotChangedError:
- # raised if nothing was changed in repo itself. We anyway then
- # store only DB stuff for gist
- Session().commit()
- h.flash(_('Successfully updated gist data'), category='success')
- except validation_schema.Invalid as errors:
- errors = errors.asdict()
- h.flash(_('Error occurred during update of gist {}: {}').format(
- gist_id, errors), category='error')
- except Exception:
- log.exception("Exception in gist edit")
- h.flash(_('Error occurred during update of gist %s') % gist_id,
- category='error')
-
- return redirect(url('gist', gist_id=gist_id))
-
- @LoginRequired()
- @NotAnonymous()
- def edit_form(self, gist_id):
- translate = _ = c.pyramid_request.translate
-
- """GET /admin/gists/gist_id/edit: Form to edit an existing item"""
- # url('edit_gist', gist_id=ID)
- self._add_gist_to_context(gist_id)
-
- owner = c.gist.gist_owner == c.rhodecode_user.user_id
- if not (h.HasPermissionAny('hg.admin')() or owner):
- raise HTTPForbidden()
-
- try:
- c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
- except VCSError:
- log.exception("Exception in gist edit")
- raise HTTPNotFound()
-
- if c.gist.gist_expires == -1:
- expiry = _('never')
- else:
- # this cannot use timeago, since it's used in select2 as a value
- expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
-
- expiry = translate(expiry)
- self.__load_defaults(
- extra_values=(0, _('%(expiry)s - current value') % {'expiry': expiry}))
- return render('admin/gists/edit.mako')
-
- @LoginRequired()
- @NotAnonymous()
- @jsonify
- def check_revision(self, gist_id):
- c.gist = Gist.get_or_404(gist_id)
- last_rev = c.gist.scm_instance().get_commit()
- success = True
- revision = request.GET.get('revision')
-
- ##TODO: maybe move this to model ?
- if revision != last_rev.raw_id:
- log.error('Last revision %s is different then submitted %s'
- % (revision, last_rev))
- # our gist has newer version than we
- success = False
-
- return {'success': success}
diff --git a/rhodecode/lib/middleware/csrf.py b/rhodecode/lib/middleware/csrf.py
--- a/rhodecode/lib/middleware/csrf.py
+++ b/rhodecode/lib/middleware/csrf.py
@@ -67,15 +67,25 @@ class CSRFDetector(object):
'/error/document',
))
+ _SKIP_PATTERN = frozenset((
+ '/_admin/gists/',
+ ))
+
def __init__(self, app):
self._app = app
def __call__(self, environ, start_response):
if environ['REQUEST_METHOD'].upper() not in ('GET', 'POST'):
raise Exception(self._PUT_DELETE_MESSAGE)
+ token_expected = environ['PATH_INFO'] not in self._PATHS_WITHOUT_TOKEN
+ allowed = True
+ for pattern in self._SKIP_PATTERN:
+ if environ['PATH_INFO'].startswith(pattern):
+ allowed = False
+ break
if (environ['REQUEST_METHOD'] == 'POST' and
- environ['PATH_INFO'] not in self._PATHS_WITHOUT_TOKEN and
+ token_expected and allowed and
routes.middleware.is_form_post(environ)):
body = environ['wsgi.input']
if body.seekable():
diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py
--- a/rhodecode/model/db.py
+++ b/rhodecode/model/db.py
@@ -3788,14 +3788,8 @@ class Gist(Base, BaseModel):
return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
def gist_url(self):
- import rhodecode
- from pylons import url
-
- alias_url = rhodecode.CONFIG.get('gist_alias_url')
- if alias_url:
- return alias_url.replace('{gistid}', self.gist_access_id)
-
- return url('gist', gist_id=self.gist_access_id, qualified=True)
+ from rhodecode.model.gist import GistModel
+ return GistModel().get_url(self)
@classmethod
def base_path(cls):
diff --git a/rhodecode/model/gist.py b/rhodecode/model/gist.py
--- a/rhodecode/model/gist.py
+++ b/rhodecode/model/gist.py
@@ -28,6 +28,8 @@ import logging
import traceback
import shutil
+from pyramid.threadlocal import get_current_request
+
from rhodecode.lib.utils2 import (
safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict)
from rhodecode.lib.ext_json import json
@@ -233,3 +235,16 @@ class GistModel(BaseModel):
)
return gist
+
+ def get_url(self, gist, request=None):
+ import rhodecode
+
+ if not request:
+ request = get_current_request()
+
+ alias_url = rhodecode.CONFIG.get('gist_alias_url')
+ if alias_url:
+ return alias_url.replace('{gistid}', gist.gist_access_id)
+
+ return request.route_url('gist_show', gist_id=gist.gist_access_id)
+
diff --git a/rhodecode/public/js/rhodecode/base/keyboard-bindings.js b/rhodecode/public/js/rhodecode/base/keyboard-bindings.js
--- a/rhodecode/public/js/rhodecode/base/keyboard-bindings.js
+++ b/rhodecode/public/js/rhodecode/base/keyboard-bindings.js
@@ -46,13 +46,13 @@ function setRCMouseBindings(repoName, re
window.location = pyroutes.url('home');
});
Mousetrap.bind(['g g'], function(e) {
- window.location = pyroutes.url('gists', {'private': 1});
+ window.location = pyroutes.url('gists_show', {'private': 1});
});
Mousetrap.bind(['g G'], function(e) {
- window.location = pyroutes.url('gists', {'public': 1});
+ window.location = pyroutes.url('gists_show', {'public': 1});
});
Mousetrap.bind(['n g'], function(e) {
- window.location = pyroutes.url('new_gist');
+ window.location = pyroutes.url('gists_new');
});
Mousetrap.bind(['n r'], function(e) {
window.location = pyroutes.url('new_repo');
diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js
--- a/rhodecode/public/js/rhodecode/routes.js
+++ b/rhodecode/public/js/rhodecode/routes.js
@@ -15,8 +15,6 @@ function registerRCRoutes() {
pyroutes.register('new_repo', '/_admin/create_repository', []);
pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
- pyroutes.register('gists', '/_admin/gists', []);
- pyroutes.register('new_gist', '/_admin/gists/new', []);
pyroutes.register('toggle_following', '/_admin/toggle_following', []);
pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
@@ -152,5 +150,16 @@ function registerRCRoutes() {
pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
+ pyroutes.register('gists_show', '/_admin/gists', []);
+ pyroutes.register('gists_new', '/_admin/gists/new', []);
+ pyroutes.register('gists_create', '/_admin/gists/create', []);
+ pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
+ pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
+ pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
+ pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
+ pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
+ pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
+ pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
+ pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
pyroutes.register('apiv2', '/_admin/api', []);
}
diff --git a/rhodecode/templates/admin/gists/edit.mako b/rhodecode/templates/admin/gists/edit.mako
--- a/rhodecode/templates/admin/gists/edit.mako
+++ b/rhodecode/templates/admin/gists/edit.mako
@@ -26,7 +26,7 @@
- ${h.secure_form(h.url('edit_gist', gist_id=c.gist.gist_access_id), method='post', id='eform')}
+ ${h.secure_form(h.route_path('gist_update', gist_id=c.gist.gist_access_id), id='eform', method='POST')}
${h.end_form()}
@@ -109,9 +109,12 @@