##// END OF EJS Templates
my-account-auth-tokens: moved into pyramid apps....
marcink -
r1505:0cb9b007 default
parent child Browse files
Show More
@@ -0,0 +1,39 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 from rhodecode.apps._base import ADMIN_PREFIX
23
24
25 def includeme(config):
26 config.add_route(
27 name='my_account_auth_tokens',
28 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
29 config.add_route(
30 name='my_account_auth_tokens_add',
31 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new',
32 )
33 config.add_route(
34 name='my_account_auth_tokens_delete',
35 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete',
36 )
37
38 # Scan module for configuration decorators.
39 config.scan()
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,111 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.model.db import User
25 from rhodecode.tests import (
26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
27 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import AssertResponse
30
31 fixture = Fixture()
32
33
34 def route_path(name, **kwargs):
35 return {
36 'my_account_auth_tokens':
37 ADMIN_PREFIX + '/my_account/auth_tokens',
38 'my_account_auth_tokens_add':
39 ADMIN_PREFIX + '/my_account/auth_tokens/new',
40 'my_account_auth_tokens_delete':
41 ADMIN_PREFIX + '/my_account/auth_tokens/delete',
42 }[name].format(**kwargs)
43
44
45 class TestMyAccountAuthTokens(TestController):
46
47 def test_my_account_auth_tokens(self):
48 usr = self.log_user('test_regular2', 'test12')
49 user = User.get(usr['user_id'])
50 response = self.app.get(route_path('my_account_auth_tokens'))
51 for token in user.auth_tokens:
52 response.mustcontain(token)
53 response.mustcontain('never')
54
55 def test_my_account_add_auth_tokens_wrong_csrf(self, user_util):
56 user = user_util.create_user(password='qweqwe')
57 self.log_user(user.username, 'qweqwe')
58
59 self.app.post(
60 route_path('my_account_auth_tokens_add'),
61 {'description': 'desc', 'lifetime': -1}, status=403)
62
63 @pytest.mark.parametrize("desc, lifetime", [
64 ('forever', -1),
65 ('5mins', 60*5),
66 ('30days', 60*60*24*30),
67 ])
68 def test_my_account_add_auth_tokens(self, desc, lifetime, user_util):
69 user = user_util.create_user(password='qweqwe')
70 user_id = user.user_id
71 self.log_user(user.username, 'qweqwe')
72
73 response = self.app.post(
74 route_path('my_account_auth_tokens_add'),
75 {'description': desc, 'lifetime': lifetime,
76 'csrf_token': self.csrf_token})
77 assert_session_flash(response, 'Auth token successfully created')
78
79 response = response.follow()
80 user = User.get(user_id)
81 for auth_token in user.auth_tokens:
82 response.mustcontain(auth_token)
83
84 def test_my_account_delete_auth_token(self, user_util):
85 user = user_util.create_user(password='qweqwe')
86 user_id = user.user_id
87 self.log_user(user.username, 'qweqwe')
88
89 user = User.get(user_id)
90 keys = user.extra_auth_tokens
91 assert 2 == len(keys)
92
93 response = self.app.post(
94 route_path('my_account_auth_tokens_add'),
95 {'description': 'desc', 'lifetime': -1,
96 'csrf_token': self.csrf_token})
97 assert_session_flash(response, 'Auth token successfully created')
98 response.follow()
99
100 user = User.get(user_id)
101 keys = user.extra_auth_tokens
102 assert 3 == len(keys)
103
104 response = self.app.post(
105 route_path('my_account_auth_tokens_delete'),
106 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
107 assert_session_flash(response, 'Auth token successfully deleted')
108
109 user = User.get(user_id)
110 keys = user.extra_auth_tokens
111 assert 2 == len(keys)
@@ -0,0 +1,111 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
28 from rhodecode.lib.utils2 import safe_int
29 from rhodecode.lib import helpers as h
30 from rhodecode.model.auth_token import AuthTokenModel
31 from rhodecode.model.meta import Session
32
33 log = logging.getLogger(__name__)
34
35
36 class MyAccountView(BaseAppView):
37
38 def load_default_context(self):
39 c = self._get_local_tmpl_context()
40
41 c.auth_user = self.request.user
42 c.user = c.auth_user.get_instance()
43
44 self._register_global_c(c)
45 return c
46
47 @LoginRequired()
48 @NotAnonymous()
49 @view_config(
50 route_name='my_account_auth_tokens', request_method='GET',
51 renderer='rhodecode:templates/admin/my_account/my_account.mako')
52 def my_account_auth_tokens(self):
53 _ = self.request.translate
54
55 c = self.load_default_context()
56 c.active = 'auth_tokens'
57
58 show_expired = True
59
60 c.lifetime_values = [
61 (str(-1), _('forever')),
62 (str(5), _('5 minutes')),
63 (str(60), _('1 hour')),
64 (str(60 * 24), _('1 day')),
65 (str(60 * 24 * 30), _('1 month')),
66 ]
67 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
68 c.role_values = [
69 (x, AuthTokenModel.cls._get_role_name(x))
70 for x in AuthTokenModel.cls.ROLES]
71 c.role_options = [(c.role_values, _("Role"))]
72 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
73 c.user.user_id, show_expired=show_expired)
74 return self._get_template_context(c)
75
76 @LoginRequired()
77 @NotAnonymous()
78 @CSRFRequired()
79 @view_config(
80 route_name='my_account_auth_tokens_add', request_method='POST')
81 def my_account_auth_tokens_add(self):
82 _ = self.request.translate
83 c = self.load_default_context()
84
85 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
86 description = self.request.POST.get('description')
87 role = self.request.POST.get('role')
88
89 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
90 Session().commit()
91 h.flash(_("Auth token successfully created"), category='success')
92
93 return HTTPFound(h.route_path('my_account_auth_tokens'))
94
95 @LoginRequired()
96 @NotAnonymous()
97 @CSRFRequired()
98 @view_config(
99 route_name='my_account_auth_tokens_delete', request_method='POST')
100 def my_account_auth_tokens_delete(self):
101 _ = self.request.translate
102 c = self.load_default_context()
103
104 del_auth_token = self.request.POST.get('del_auth_token')
105
106 if del_auth_token:
107 AuthTokenModel().delete(del_auth_token, c.user.user_id)
108 Session().commit()
109 h.flash(_("Auth token successfully deleted"), category='success')
110
111 return HTTPFound(h.route_path('my_account_auth_tokens'))
@@ -1,53 +1,64 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 from pylons import tmpl_context as c
22 from pylons import tmpl_context as c
23
23
24 from rhodecode.lib.utils2 import StrictAttributeDict
24 from rhodecode.lib.utils2 import StrictAttributeDict
25
25
26 log = logging.getLogger(__name__)
26 log = logging.getLogger(__name__)
27
27
28
28
29 ADMIN_PREFIX = '/_admin'
30 STATIC_FILE_PREFIX = '/_static'
31
32
29 class TemplateArgs(StrictAttributeDict):
33 class TemplateArgs(StrictAttributeDict):
30 pass
34 pass
31
35
32
36
33 class BaseAppView(object):
37 class BaseAppView(object):
34
38
35 def __init__(self, context, request):
39 def __init__(self, context, request):
36 self.request = request
40 self.request = request
37 self.context = context
41 self.context = context
38 self.session = request.session
42 self.session = request.session
39 self._rhodecode_user = request.user
43 self._rhodecode_user = request.user
40
44
41 def _get_local_tmpl_context(self):
45 def _get_local_tmpl_context(self):
42 return TemplateArgs()
46 return TemplateArgs()
43
47
48 def _register_global_c(self, tmpl_args):
49 """
50 Registers attributes to pylons global `c`
51 """
52 # TODO(marcink): remove once pyramid migration is finished
53 for k, v in tmpl_args.items():
54 setattr(c, k, v)
55
44 def _get_template_context(self, tmpl_args):
56 def _get_template_context(self, tmpl_args):
45
57
46 for k, v in tmpl_args.items():
58 self._register_global_c(tmpl_args)
47 setattr(c, k, v)
48
59
49 return {
60 return {
50 'defaults': {},
61 'defaults': {},
51 'errors': {},
62 'errors': {},
52 }
63 }
53
64
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,501 +1,502 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Pylons middleware initialization
22 Pylons middleware initialization
23 """
23 """
24 import logging
24 import logging
25 from collections import OrderedDict
25 from collections import OrderedDict
26
26
27 from paste.registry import RegistryManager
27 from paste.registry import RegistryManager
28 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
29 from pylons.wsgiapp import PylonsApp
29 from pylons.wsgiapp import PylonsApp
30 from pyramid.authorization import ACLAuthorizationPolicy
30 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.config import Configurator
31 from pyramid.config import Configurator
32 from pyramid.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
33 from pyramid.wsgi import wsgiapp
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
36 from pyramid.events import ApplicationCreated
36 from pyramid.events import ApplicationCreated
37 from pyramid.renderers import render_to_response
37 from pyramid.renderers import render_to_response
38 from routes.middleware import RoutesMiddleware
38 from routes.middleware import RoutesMiddleware
39 import routes.util
39 import routes.util
40
40
41 import rhodecode
41 import rhodecode
42 from rhodecode.model import meta
42 from rhodecode.model import meta
43 from rhodecode.config import patches
43 from rhodecode.config import patches
44 from rhodecode.config.routing import STATIC_FILE_PREFIX
44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 from rhodecode.config.environment import (
45 from rhodecode.config.environment import (
46 load_environment, load_pyramid_environment)
46 load_environment, load_pyramid_environment)
47 from rhodecode.lib.middleware import csrf
47 from rhodecode.lib.middleware import csrf
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.error_handling import (
49 from rhodecode.lib.middleware.error_handling import (
50 PylonsErrorHandlingMiddleware)
50 PylonsErrorHandlingMiddleware)
51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
52 from rhodecode.lib.middleware.vcs import VCSMiddleware
52 from rhodecode.lib.middleware.vcs import VCSMiddleware
53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
55 from rhodecode.subscribers import (
55 from rhodecode.subscribers import (
56 scan_repositories_if_enabled, write_metadata_if_needed)
56 scan_repositories_if_enabled, write_metadata_if_needed)
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 # this is used to avoid avoid the route lookup overhead in routesmiddleware
62 # this is used to avoid avoid the route lookup overhead in routesmiddleware
63 # for certain routes which won't go to pylons to - eg. static files, debugger
63 # for certain routes which won't go to pylons to - eg. static files, debugger
64 # it is only needed for the pylons migration and can be removed once complete
64 # it is only needed for the pylons migration and can be removed once complete
65 class SkippableRoutesMiddleware(RoutesMiddleware):
65 class SkippableRoutesMiddleware(RoutesMiddleware):
66 """ Routes middleware that allows you to skip prefixes """
66 """ Routes middleware that allows you to skip prefixes """
67
67
68 def __init__(self, *args, **kw):
68 def __init__(self, *args, **kw):
69 self.skip_prefixes = kw.pop('skip_prefixes', [])
69 self.skip_prefixes = kw.pop('skip_prefixes', [])
70 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
70 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
71
71
72 def __call__(self, environ, start_response):
72 def __call__(self, environ, start_response):
73 for prefix in self.skip_prefixes:
73 for prefix in self.skip_prefixes:
74 if environ['PATH_INFO'].startswith(prefix):
74 if environ['PATH_INFO'].startswith(prefix):
75 # added to avoid the case when a missing /_static route falls
75 # added to avoid the case when a missing /_static route falls
76 # through to pylons and causes an exception as pylons is
76 # through to pylons and causes an exception as pylons is
77 # expecting wsgiorg.routingargs to be set in the environ
77 # expecting wsgiorg.routingargs to be set in the environ
78 # by RoutesMiddleware.
78 # by RoutesMiddleware.
79 if 'wsgiorg.routing_args' not in environ:
79 if 'wsgiorg.routing_args' not in environ:
80 environ['wsgiorg.routing_args'] = (None, {})
80 environ['wsgiorg.routing_args'] = (None, {})
81 return self.app(environ, start_response)
81 return self.app(environ, start_response)
82
82
83 return super(SkippableRoutesMiddleware, self).__call__(
83 return super(SkippableRoutesMiddleware, self).__call__(
84 environ, start_response)
84 environ, start_response)
85
85
86
86
87 def make_app(global_conf, static_files=True, **app_conf):
87 def make_app(global_conf, static_files=True, **app_conf):
88 """Create a Pylons WSGI application and return it
88 """Create a Pylons WSGI application and return it
89
89
90 ``global_conf``
90 ``global_conf``
91 The inherited configuration for this application. Normally from
91 The inherited configuration for this application. Normally from
92 the [DEFAULT] section of the Paste ini file.
92 the [DEFAULT] section of the Paste ini file.
93
93
94 ``app_conf``
94 ``app_conf``
95 The application's local configuration. Normally specified in
95 The application's local configuration. Normally specified in
96 the [app:<name>] section of the Paste ini file (where <name>
96 the [app:<name>] section of the Paste ini file (where <name>
97 defaults to main).
97 defaults to main).
98
98
99 """
99 """
100 # Apply compatibility patches
100 # Apply compatibility patches
101 patches.kombu_1_5_1_python_2_7_11()
101 patches.kombu_1_5_1_python_2_7_11()
102 patches.inspect_getargspec()
102 patches.inspect_getargspec()
103
103
104 # Configure the Pylons environment
104 # Configure the Pylons environment
105 config = load_environment(global_conf, app_conf)
105 config = load_environment(global_conf, app_conf)
106
106
107 # The Pylons WSGI app
107 # The Pylons WSGI app
108 app = PylonsApp(config=config)
108 app = PylonsApp(config=config)
109 if rhodecode.is_test:
109 if rhodecode.is_test:
110 app = csrf.CSRFDetector(app)
110 app = csrf.CSRFDetector(app)
111
111
112 expected_origin = config.get('expected_origin')
112 expected_origin = config.get('expected_origin')
113 if expected_origin:
113 if expected_origin:
114 # The API can be accessed from other Origins.
114 # The API can be accessed from other Origins.
115 app = csrf.OriginChecker(app, expected_origin,
115 app = csrf.OriginChecker(app, expected_origin,
116 skip_urls=[routes.util.url_for('api')])
116 skip_urls=[routes.util.url_for('api')])
117
117
118 # Establish the Registry for this application
118 # Establish the Registry for this application
119 app = RegistryManager(app)
119 app = RegistryManager(app)
120
120
121 app.config = config
121 app.config = config
122
122
123 return app
123 return app
124
124
125
125
126 def make_pyramid_app(global_config, **settings):
126 def make_pyramid_app(global_config, **settings):
127 """
127 """
128 Constructs the WSGI application based on Pyramid and wraps the Pylons based
128 Constructs the WSGI application based on Pyramid and wraps the Pylons based
129 application.
129 application.
130
130
131 Specials:
131 Specials:
132
132
133 * We migrate from Pylons to Pyramid. While doing this, we keep both
133 * We migrate from Pylons to Pyramid. While doing this, we keep both
134 frameworks functional. This involves moving some WSGI middlewares around
134 frameworks functional. This involves moving some WSGI middlewares around
135 and providing access to some data internals, so that the old code is
135 and providing access to some data internals, so that the old code is
136 still functional.
136 still functional.
137
137
138 * The application can also be integrated like a plugin via the call to
138 * The application can also be integrated like a plugin via the call to
139 `includeme`. This is accompanied with the other utility functions which
139 `includeme`. This is accompanied with the other utility functions which
140 are called. Changing this should be done with great care to not break
140 are called. Changing this should be done with great care to not break
141 cases when these fragments are assembled from another place.
141 cases when these fragments are assembled from another place.
142
142
143 """
143 """
144 # The edition string should be available in pylons too, so we add it here
144 # The edition string should be available in pylons too, so we add it here
145 # before copying the settings.
145 # before copying the settings.
146 settings.setdefault('rhodecode.edition', 'Community Edition')
146 settings.setdefault('rhodecode.edition', 'Community Edition')
147
147
148 # As long as our Pylons application does expect "unprepared" settings, make
148 # As long as our Pylons application does expect "unprepared" settings, make
149 # sure that we keep an unmodified copy. This avoids unintentional change of
149 # sure that we keep an unmodified copy. This avoids unintentional change of
150 # behavior in the old application.
150 # behavior in the old application.
151 settings_pylons = settings.copy()
151 settings_pylons = settings.copy()
152
152
153 sanitize_settings_and_apply_defaults(settings)
153 sanitize_settings_and_apply_defaults(settings)
154 config = Configurator(settings=settings)
154 config = Configurator(settings=settings)
155 add_pylons_compat_data(config.registry, global_config, settings_pylons)
155 add_pylons_compat_data(config.registry, global_config, settings_pylons)
156
156
157 load_pyramid_environment(global_config, settings)
157 load_pyramid_environment(global_config, settings)
158
158
159 includeme_first(config)
159 includeme_first(config)
160 includeme(config)
160 includeme(config)
161 pyramid_app = config.make_wsgi_app()
161 pyramid_app = config.make_wsgi_app()
162 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
162 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
163 pyramid_app.config = config
163 pyramid_app.config = config
164
164
165 # creating the app uses a connection - return it after we are done
165 # creating the app uses a connection - return it after we are done
166 meta.Session.remove()
166 meta.Session.remove()
167
167
168 return pyramid_app
168 return pyramid_app
169
169
170
170
171 def make_not_found_view(config):
171 def make_not_found_view(config):
172 """
172 """
173 This creates the view which should be registered as not-found-view to
173 This creates the view which should be registered as not-found-view to
174 pyramid. Basically it contains of the old pylons app, converted to a view.
174 pyramid. Basically it contains of the old pylons app, converted to a view.
175 Additionally it is wrapped by some other middlewares.
175 Additionally it is wrapped by some other middlewares.
176 """
176 """
177 settings = config.registry.settings
177 settings = config.registry.settings
178 vcs_server_enabled = settings['vcs.server.enable']
178 vcs_server_enabled = settings['vcs.server.enable']
179
179
180 # Make pylons app from unprepared settings.
180 # Make pylons app from unprepared settings.
181 pylons_app = make_app(
181 pylons_app = make_app(
182 config.registry._pylons_compat_global_config,
182 config.registry._pylons_compat_global_config,
183 **config.registry._pylons_compat_settings)
183 **config.registry._pylons_compat_settings)
184 config.registry._pylons_compat_config = pylons_app.config
184 config.registry._pylons_compat_config = pylons_app.config
185
185
186 # Appenlight monitoring.
186 # Appenlight monitoring.
187 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
187 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
188 pylons_app, settings)
188 pylons_app, settings)
189
189
190 # The pylons app is executed inside of the pyramid 404 exception handler.
190 # The pylons app is executed inside of the pyramid 404 exception handler.
191 # Exceptions which are raised inside of it are not handled by pyramid
191 # Exceptions which are raised inside of it are not handled by pyramid
192 # again. Therefore we add a middleware that invokes the error handler in
192 # again. Therefore we add a middleware that invokes the error handler in
193 # case of an exception or error response. This way we return proper error
193 # case of an exception or error response. This way we return proper error
194 # HTML pages in case of an error.
194 # HTML pages in case of an error.
195 reraise = (settings.get('debugtoolbar.enabled', False) or
195 reraise = (settings.get('debugtoolbar.enabled', False) or
196 rhodecode.disable_error_handler)
196 rhodecode.disable_error_handler)
197 pylons_app = PylonsErrorHandlingMiddleware(
197 pylons_app = PylonsErrorHandlingMiddleware(
198 pylons_app, error_handler, reraise)
198 pylons_app, error_handler, reraise)
199
199
200 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
200 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
201 # view to handle the request. Therefore it is wrapped around the pylons
201 # view to handle the request. Therefore it is wrapped around the pylons
202 # app. It has to be outside of the error handling otherwise error responses
202 # app. It has to be outside of the error handling otherwise error responses
203 # from the vcsserver are converted to HTML error pages. This confuses the
203 # from the vcsserver are converted to HTML error pages. This confuses the
204 # command line tools and the user won't get a meaningful error message.
204 # command line tools and the user won't get a meaningful error message.
205 if vcs_server_enabled:
205 if vcs_server_enabled:
206 pylons_app = VCSMiddleware(
206 pylons_app = VCSMiddleware(
207 pylons_app, settings, appenlight_client, registry=config.registry)
207 pylons_app, settings, appenlight_client, registry=config.registry)
208
208
209 # Convert WSGI app to pyramid view and return it.
209 # Convert WSGI app to pyramid view and return it.
210 return wsgiapp(pylons_app)
210 return wsgiapp(pylons_app)
211
211
212
212
213 def add_pylons_compat_data(registry, global_config, settings):
213 def add_pylons_compat_data(registry, global_config, settings):
214 """
214 """
215 Attach data to the registry to support the Pylons integration.
215 Attach data to the registry to support the Pylons integration.
216 """
216 """
217 registry._pylons_compat_global_config = global_config
217 registry._pylons_compat_global_config = global_config
218 registry._pylons_compat_settings = settings
218 registry._pylons_compat_settings = settings
219
219
220
220
221 def error_handler(exception, request):
221 def error_handler(exception, request):
222 import rhodecode
222 import rhodecode
223 from rhodecode.lib.utils2 import AttributeDict
223 from rhodecode.lib.utils2 import AttributeDict
224
224
225 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
225 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
226
226
227 base_response = HTTPInternalServerError()
227 base_response = HTTPInternalServerError()
228 # prefer original exception for the response since it may have headers set
228 # prefer original exception for the response since it may have headers set
229 if isinstance(exception, HTTPException):
229 if isinstance(exception, HTTPException):
230 base_response = exception
230 base_response = exception
231
231
232 def is_http_error(response):
232 def is_http_error(response):
233 # error which should have traceback
233 # error which should have traceback
234 return response.status_code > 499
234 return response.status_code > 499
235
235
236 if is_http_error(base_response):
236 if is_http_error(base_response):
237 log.exception(
237 log.exception(
238 'error occurred handling this request for path: %s', request.path)
238 'error occurred handling this request for path: %s', request.path)
239
239
240 c = AttributeDict()
240 c = AttributeDict()
241 c.error_message = base_response.status
241 c.error_message = base_response.status
242 c.error_explanation = base_response.explanation or str(base_response)
242 c.error_explanation = base_response.explanation or str(base_response)
243 c.visual = AttributeDict()
243 c.visual = AttributeDict()
244
244
245 c.visual.rhodecode_support_url = (
245 c.visual.rhodecode_support_url = (
246 request.registry.settings.get('rhodecode_support_url') or
246 request.registry.settings.get('rhodecode_support_url') or
247 request.route_url('rhodecode_support')
247 request.route_url('rhodecode_support')
248 )
248 )
249 c.redirect_time = 0
249 c.redirect_time = 0
250 c.rhodecode_name = rhodecode_title
250 c.rhodecode_name = rhodecode_title
251 if not c.rhodecode_name:
251 if not c.rhodecode_name:
252 c.rhodecode_name = 'Rhodecode'
252 c.rhodecode_name = 'Rhodecode'
253
253
254 c.causes = []
254 c.causes = []
255 if hasattr(base_response, 'causes'):
255 if hasattr(base_response, 'causes'):
256 c.causes = base_response.causes
256 c.causes = base_response.causes
257
257
258 response = render_to_response(
258 response = render_to_response(
259 '/errors/error_document.mako', {'c': c}, request=request,
259 '/errors/error_document.mako', {'c': c}, request=request,
260 response=base_response)
260 response=base_response)
261
261
262 return response
262 return response
263
263
264
264
265 def includeme(config):
265 def includeme(config):
266 settings = config.registry.settings
266 settings = config.registry.settings
267
267
268 # plugin information
268 # plugin information
269 config.registry.rhodecode_plugins = OrderedDict()
269 config.registry.rhodecode_plugins = OrderedDict()
270
270
271 config.add_directive(
271 config.add_directive(
272 'register_rhodecode_plugin', register_rhodecode_plugin)
272 'register_rhodecode_plugin', register_rhodecode_plugin)
273
273
274 if asbool(settings.get('appenlight', 'false')):
274 if asbool(settings.get('appenlight', 'false')):
275 config.include('appenlight_client.ext.pyramid_tween')
275 config.include('appenlight_client.ext.pyramid_tween')
276
276
277 # Includes which are required. The application would fail without them.
277 # Includes which are required. The application would fail without them.
278 config.include('pyramid_mako')
278 config.include('pyramid_mako')
279 config.include('pyramid_beaker')
279 config.include('pyramid_beaker')
280
280
281 config.include('rhodecode.authentication')
281 config.include('rhodecode.authentication')
282 config.include('rhodecode.integrations')
282 config.include('rhodecode.integrations')
283
283
284 # apps
284 # apps
285 config.include('rhodecode.apps.admin')
285 config.include('rhodecode.apps.admin')
286 config.include('rhodecode.apps.channelstream')
286 config.include('rhodecode.apps.channelstream')
287 config.include('rhodecode.apps.login')
287 config.include('rhodecode.apps.login')
288 config.include('rhodecode.apps.user_profile')
288 config.include('rhodecode.apps.user_profile')
289 config.include('rhodecode.apps.my_account')
289
290
290 config.include('rhodecode.tweens')
291 config.include('rhodecode.tweens')
291 config.include('rhodecode.api')
292 config.include('rhodecode.api')
292 config.include('rhodecode.svn_support')
293 config.include('rhodecode.svn_support')
293
294
294 config.add_route(
295 config.add_route(
295 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
296 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
296
297
297 config.add_translation_dirs('rhodecode:i18n/')
298 config.add_translation_dirs('rhodecode:i18n/')
298 settings['default_locale_name'] = settings.get('lang', 'en')
299 settings['default_locale_name'] = settings.get('lang', 'en')
299
300
300 # Add subscribers.
301 # Add subscribers.
301 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
302 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
302 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
303 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
303
304
304 # Set the authorization policy.
305 # Set the authorization policy.
305 authz_policy = ACLAuthorizationPolicy()
306 authz_policy = ACLAuthorizationPolicy()
306 config.set_authorization_policy(authz_policy)
307 config.set_authorization_policy(authz_policy)
307
308
308 # Set the default renderer for HTML templates to mako.
309 # Set the default renderer for HTML templates to mako.
309 config.add_mako_renderer('.html')
310 config.add_mako_renderer('.html')
310
311
311 # include RhodeCode plugins
312 # include RhodeCode plugins
312 includes = aslist(settings.get('rhodecode.includes', []))
313 includes = aslist(settings.get('rhodecode.includes', []))
313 for inc in includes:
314 for inc in includes:
314 config.include(inc)
315 config.include(inc)
315
316
316 # This is the glue which allows us to migrate in chunks. By registering the
317 # This is the glue which allows us to migrate in chunks. By registering the
317 # pylons based application as the "Not Found" view in Pyramid, we will
318 # pylons based application as the "Not Found" view in Pyramid, we will
318 # fallback to the old application each time the new one does not yet know
319 # fallback to the old application each time the new one does not yet know
319 # how to handle a request.
320 # how to handle a request.
320 config.add_notfound_view(make_not_found_view(config))
321 config.add_notfound_view(make_not_found_view(config))
321
322
322 if not settings.get('debugtoolbar.enabled', False):
323 if not settings.get('debugtoolbar.enabled', False):
323 # if no toolbar, then any exception gets caught and rendered
324 # if no toolbar, then any exception gets caught and rendered
324 config.add_view(error_handler, context=Exception)
325 config.add_view(error_handler, context=Exception)
325
326
326 config.add_view(error_handler, context=HTTPError)
327 config.add_view(error_handler, context=HTTPError)
327
328
328
329
329 def includeme_first(config):
330 def includeme_first(config):
330 # redirect automatic browser favicon.ico requests to correct place
331 # redirect automatic browser favicon.ico requests to correct place
331 def favicon_redirect(context, request):
332 def favicon_redirect(context, request):
332 return HTTPFound(
333 return HTTPFound(
333 request.static_path('rhodecode:public/images/favicon.ico'))
334 request.static_path('rhodecode:public/images/favicon.ico'))
334
335
335 config.add_view(favicon_redirect, route_name='favicon')
336 config.add_view(favicon_redirect, route_name='favicon')
336 config.add_route('favicon', '/favicon.ico')
337 config.add_route('favicon', '/favicon.ico')
337
338
338 def robots_redirect(context, request):
339 def robots_redirect(context, request):
339 return HTTPFound(
340 return HTTPFound(
340 request.static_path('rhodecode:public/robots.txt'))
341 request.static_path('rhodecode:public/robots.txt'))
341
342
342 config.add_view(robots_redirect, route_name='robots')
343 config.add_view(robots_redirect, route_name='robots')
343 config.add_route('robots', '/robots.txt')
344 config.add_route('robots', '/robots.txt')
344
345
345 config.add_static_view(
346 config.add_static_view(
346 '_static/deform', 'deform:static')
347 '_static/deform', 'deform:static')
347 config.add_static_view(
348 config.add_static_view(
348 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
349 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
349
350
350
351
351 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
352 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
352 """
353 """
353 Apply outer WSGI middlewares around the application.
354 Apply outer WSGI middlewares around the application.
354
355
355 Part of this has been moved up from the Pylons layer, so that the
356 Part of this has been moved up from the Pylons layer, so that the
356 data is also available if old Pylons code is hit through an already ported
357 data is also available if old Pylons code is hit through an already ported
357 view.
358 view.
358 """
359 """
359 settings = config.registry.settings
360 settings = config.registry.settings
360
361
361 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
362 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
362 pyramid_app = HttpsFixup(pyramid_app, settings)
363 pyramid_app = HttpsFixup(pyramid_app, settings)
363
364
364 # Add RoutesMiddleware to support the pylons compatibility tween during
365 # Add RoutesMiddleware to support the pylons compatibility tween during
365 # migration to pyramid.
366 # migration to pyramid.
366 pyramid_app = SkippableRoutesMiddleware(
367 pyramid_app = SkippableRoutesMiddleware(
367 pyramid_app, config.registry._pylons_compat_config['routes.map'],
368 pyramid_app, config.registry._pylons_compat_config['routes.map'],
368 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
369 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
369
370
370 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
371 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
371
372
372 if settings['gzip_responses']:
373 if settings['gzip_responses']:
373 pyramid_app = make_gzip_middleware(
374 pyramid_app = make_gzip_middleware(
374 pyramid_app, settings, compress_level=1)
375 pyramid_app, settings, compress_level=1)
375
376
376 # this should be the outer most middleware in the wsgi stack since
377 # this should be the outer most middleware in the wsgi stack since
377 # middleware like Routes make database calls
378 # middleware like Routes make database calls
378 def pyramid_app_with_cleanup(environ, start_response):
379 def pyramid_app_with_cleanup(environ, start_response):
379 try:
380 try:
380 return pyramid_app(environ, start_response)
381 return pyramid_app(environ, start_response)
381 finally:
382 finally:
382 # Dispose current database session and rollback uncommitted
383 # Dispose current database session and rollback uncommitted
383 # transactions.
384 # transactions.
384 meta.Session.remove()
385 meta.Session.remove()
385
386
386 # In a single threaded mode server, on non sqlite db we should have
387 # In a single threaded mode server, on non sqlite db we should have
387 # '0 Current Checked out connections' at the end of a request,
388 # '0 Current Checked out connections' at the end of a request,
388 # if not, then something, somewhere is leaving a connection open
389 # if not, then something, somewhere is leaving a connection open
389 pool = meta.Base.metadata.bind.engine.pool
390 pool = meta.Base.metadata.bind.engine.pool
390 log.debug('sa pool status: %s', pool.status())
391 log.debug('sa pool status: %s', pool.status())
391
392
392
393
393 return pyramid_app_with_cleanup
394 return pyramid_app_with_cleanup
394
395
395
396
396 def sanitize_settings_and_apply_defaults(settings):
397 def sanitize_settings_and_apply_defaults(settings):
397 """
398 """
398 Applies settings defaults and does all type conversion.
399 Applies settings defaults and does all type conversion.
399
400
400 We would move all settings parsing and preparation into this place, so that
401 We would move all settings parsing and preparation into this place, so that
401 we have only one place left which deals with this part. The remaining parts
402 we have only one place left which deals with this part. The remaining parts
402 of the application would start to rely fully on well prepared settings.
403 of the application would start to rely fully on well prepared settings.
403
404
404 This piece would later be split up per topic to avoid a big fat monster
405 This piece would later be split up per topic to avoid a big fat monster
405 function.
406 function.
406 """
407 """
407
408
408 # Pyramid's mako renderer has to search in the templates folder so that the
409 # Pyramid's mako renderer has to search in the templates folder so that the
409 # old templates still work. Ported and new templates are expected to use
410 # old templates still work. Ported and new templates are expected to use
410 # real asset specifications for the includes.
411 # real asset specifications for the includes.
411 mako_directories = settings.setdefault('mako.directories', [
412 mako_directories = settings.setdefault('mako.directories', [
412 # Base templates of the original Pylons application
413 # Base templates of the original Pylons application
413 'rhodecode:templates',
414 'rhodecode:templates',
414 ])
415 ])
415 log.debug(
416 log.debug(
416 "Using the following Mako template directories: %s",
417 "Using the following Mako template directories: %s",
417 mako_directories)
418 mako_directories)
418
419
419 # Default includes, possible to change as a user
420 # Default includes, possible to change as a user
420 pyramid_includes = settings.setdefault('pyramid.includes', [
421 pyramid_includes = settings.setdefault('pyramid.includes', [
421 'rhodecode.lib.middleware.request_wrapper',
422 'rhodecode.lib.middleware.request_wrapper',
422 ])
423 ])
423 log.debug(
424 log.debug(
424 "Using the following pyramid.includes: %s",
425 "Using the following pyramid.includes: %s",
425 pyramid_includes)
426 pyramid_includes)
426
427
427 # TODO: johbo: Re-think this, usually the call to config.include
428 # TODO: johbo: Re-think this, usually the call to config.include
428 # should allow to pass in a prefix.
429 # should allow to pass in a prefix.
429 settings.setdefault('rhodecode.api.url', '/_admin/api')
430 settings.setdefault('rhodecode.api.url', '/_admin/api')
430
431
431 # Sanitize generic settings.
432 # Sanitize generic settings.
432 _list_setting(settings, 'default_encoding', 'UTF-8')
433 _list_setting(settings, 'default_encoding', 'UTF-8')
433 _bool_setting(settings, 'is_test', 'false')
434 _bool_setting(settings, 'is_test', 'false')
434 _bool_setting(settings, 'gzip_responses', 'false')
435 _bool_setting(settings, 'gzip_responses', 'false')
435
436
436 # Call split out functions that sanitize settings for each topic.
437 # Call split out functions that sanitize settings for each topic.
437 _sanitize_appenlight_settings(settings)
438 _sanitize_appenlight_settings(settings)
438 _sanitize_vcs_settings(settings)
439 _sanitize_vcs_settings(settings)
439
440
440 return settings
441 return settings
441
442
442
443
443 def _sanitize_appenlight_settings(settings):
444 def _sanitize_appenlight_settings(settings):
444 _bool_setting(settings, 'appenlight', 'false')
445 _bool_setting(settings, 'appenlight', 'false')
445
446
446
447
447 def _sanitize_vcs_settings(settings):
448 def _sanitize_vcs_settings(settings):
448 """
449 """
449 Applies settings defaults and does type conversion for all VCS related
450 Applies settings defaults and does type conversion for all VCS related
450 settings.
451 settings.
451 """
452 """
452 _string_setting(settings, 'vcs.svn.compatible_version', '')
453 _string_setting(settings, 'vcs.svn.compatible_version', '')
453 _string_setting(settings, 'git_rev_filter', '--all')
454 _string_setting(settings, 'git_rev_filter', '--all')
454 _string_setting(settings, 'vcs.hooks.protocol', 'http')
455 _string_setting(settings, 'vcs.hooks.protocol', 'http')
455 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
456 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
456 _string_setting(settings, 'vcs.server', '')
457 _string_setting(settings, 'vcs.server', '')
457 _string_setting(settings, 'vcs.server.log_level', 'debug')
458 _string_setting(settings, 'vcs.server.log_level', 'debug')
458 _string_setting(settings, 'vcs.server.protocol', 'http')
459 _string_setting(settings, 'vcs.server.protocol', 'http')
459 _bool_setting(settings, 'startup.import_repos', 'false')
460 _bool_setting(settings, 'startup.import_repos', 'false')
460 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
461 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
461 _bool_setting(settings, 'vcs.server.enable', 'true')
462 _bool_setting(settings, 'vcs.server.enable', 'true')
462 _bool_setting(settings, 'vcs.start_server', 'false')
463 _bool_setting(settings, 'vcs.start_server', 'false')
463 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
464 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
464 _int_setting(settings, 'vcs.connection_timeout', 3600)
465 _int_setting(settings, 'vcs.connection_timeout', 3600)
465
466
466 # Support legacy values of vcs.scm_app_implementation. Legacy
467 # Support legacy values of vcs.scm_app_implementation. Legacy
467 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
468 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
468 # which is now mapped to 'http'.
469 # which is now mapped to 'http'.
469 scm_app_impl = settings['vcs.scm_app_implementation']
470 scm_app_impl = settings['vcs.scm_app_implementation']
470 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
471 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
471 settings['vcs.scm_app_implementation'] = 'http'
472 settings['vcs.scm_app_implementation'] = 'http'
472
473
473
474
474 def _int_setting(settings, name, default):
475 def _int_setting(settings, name, default):
475 settings[name] = int(settings.get(name, default))
476 settings[name] = int(settings.get(name, default))
476
477
477
478
478 def _bool_setting(settings, name, default):
479 def _bool_setting(settings, name, default):
479 input = settings.get(name, default)
480 input = settings.get(name, default)
480 if isinstance(input, unicode):
481 if isinstance(input, unicode):
481 input = input.encode('utf8')
482 input = input.encode('utf8')
482 settings[name] = asbool(input)
483 settings[name] = asbool(input)
483
484
484
485
485 def _list_setting(settings, name, default):
486 def _list_setting(settings, name, default):
486 raw_value = settings.get(name, default)
487 raw_value = settings.get(name, default)
487
488
488 old_separator = ','
489 old_separator = ','
489 if old_separator in raw_value:
490 if old_separator in raw_value:
490 # If we get a comma separated list, pass it to our own function.
491 # If we get a comma separated list, pass it to our own function.
491 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
492 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
492 else:
493 else:
493 # Otherwise we assume it uses pyramids space/newline separation.
494 # Otherwise we assume it uses pyramids space/newline separation.
494 settings[name] = aslist(raw_value)
495 settings[name] = aslist(raw_value)
495
496
496
497
497 def _string_setting(settings, name, default, lower=True):
498 def _string_setting(settings, name, default, lower=True):
498 value = settings.get(name, default)
499 value = settings.get(name, default)
499 if lower:
500 if lower:
500 value = value.lower()
501 value = value.lower()
501 settings[name] = value
502 settings[name] = value
@@ -1,1169 +1,1163 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 from rhodecode.config import routing_links
35 from rhodecode.config import routing_links
36
36
37 # prefix for non repository related links needs to be prefixed with `/`
37 # prefix for non repository related links needs to be prefixed with `/`
38 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
39 STATIC_FILE_PREFIX = '/_static'
40
40
41 # Default requirements for URL parts
41 # Default requirements for URL parts
42 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
43 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
44 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
46 # repo names can have a slash in them, but they must not end with a slash
46 # repo names can have a slash in them, but they must not end with a slash
47 'repo_name': r'.*?[^/]',
47 'repo_name': r'.*?[^/]',
48 # file path eats up everything at the end
48 # file path eats up everything at the end
49 'f_path': r'.*',
49 'f_path': r'.*',
50 # reference types
50 # reference types
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 }
53 }
54
54
55
55
56 def add_route_requirements(route_path, requirements):
56 def add_route_requirements(route_path, requirements):
57 """
57 """
58 Adds regex requirements to pyramid routes using a mapping dict
58 Adds regex requirements to pyramid routes using a mapping dict
59
59
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
61 '/{action}/{id:\d+}'
61 '/{action}/{id:\d+}'
62
62
63 """
63 """
64 for key, regex in requirements.items():
64 for key, regex in requirements.items():
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
66 return route_path
66 return route_path
67
67
68
68
69 class JSRoutesMapper(Mapper):
69 class JSRoutesMapper(Mapper):
70 """
70 """
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
72 """
72 """
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
75 def __init__(self, *args, **kw):
75 def __init__(self, *args, **kw):
76 super(JSRoutesMapper, self).__init__(*args, **kw)
76 super(JSRoutesMapper, self).__init__(*args, **kw)
77 self._jsroutes = []
77 self._jsroutes = []
78
78
79 def connect(self, *args, **kw):
79 def connect(self, *args, **kw):
80 """
80 """
81 Wrapper for connect to take an extra argument jsroute=True
81 Wrapper for connect to take an extra argument jsroute=True
82
82
83 :param jsroute: boolean, if True will add the route to the pyroutes list
83 :param jsroute: boolean, if True will add the route to the pyroutes list
84 """
84 """
85 if kw.pop('jsroute', False):
85 if kw.pop('jsroute', False):
86 if not self._named_route_regex.match(args[0]):
86 if not self._named_route_regex.match(args[0]):
87 raise Exception('only named routes can be added to pyroutes')
87 raise Exception('only named routes can be added to pyroutes')
88 self._jsroutes.append(args[0])
88 self._jsroutes.append(args[0])
89
89
90 super(JSRoutesMapper, self).connect(*args, **kw)
90 super(JSRoutesMapper, self).connect(*args, **kw)
91
91
92 def _extract_route_information(self, route):
92 def _extract_route_information(self, route):
93 """
93 """
94 Convert a route into tuple(name, path, args), eg:
94 Convert a route into tuple(name, path, args), eg:
95 ('show_user', '/profile/%(username)s', ['username'])
95 ('show_user', '/profile/%(username)s', ['username'])
96 """
96 """
97 routepath = route.routepath
97 routepath = route.routepath
98 def replace(matchobj):
98 def replace(matchobj):
99 if matchobj.group(1):
99 if matchobj.group(1):
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
101 else:
101 else:
102 return "%%(%s)s" % matchobj.group(2)
102 return "%%(%s)s" % matchobj.group(2)
103
103
104 routepath = self._argument_prog.sub(replace, routepath)
104 routepath = self._argument_prog.sub(replace, routepath)
105 return (
105 return (
106 route.name,
106 route.name,
107 routepath,
107 routepath,
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
109 for arg in self._argument_prog.findall(route.routepath)]
109 for arg in self._argument_prog.findall(route.routepath)]
110 )
110 )
111
111
112 def jsroutes(self):
112 def jsroutes(self):
113 """
113 """
114 Return a list of pyroutes.js compatible routes
114 Return a list of pyroutes.js compatible routes
115 """
115 """
116 for route_name in self._jsroutes:
116 for route_name in self._jsroutes:
117 yield self._extract_route_information(self._routenames[route_name])
117 yield self._extract_route_information(self._routenames[route_name])
118
118
119
119
120 def make_map(config):
120 def make_map(config):
121 """Create, configure and return the routes Mapper"""
121 """Create, configure and return the routes Mapper"""
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
123 always_scan=config['debug'])
123 always_scan=config['debug'])
124 rmap.minimization = False
124 rmap.minimization = False
125 rmap.explicit = False
125 rmap.explicit = False
126
126
127 from rhodecode.lib.utils2 import str2bool
127 from rhodecode.lib.utils2 import str2bool
128 from rhodecode.model import repo, repo_group
128 from rhodecode.model import repo, repo_group
129
129
130 def check_repo(environ, match_dict):
130 def check_repo(environ, match_dict):
131 """
131 """
132 check for valid repository for proper 404 handling
132 check for valid repository for proper 404 handling
133
133
134 :param environ:
134 :param environ:
135 :param match_dict:
135 :param match_dict:
136 """
136 """
137 repo_name = match_dict.get('repo_name')
137 repo_name = match_dict.get('repo_name')
138
138
139 if match_dict.get('f_path'):
139 if match_dict.get('f_path'):
140 # fix for multiple initial slashes that causes errors
140 # fix for multiple initial slashes that causes errors
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
142 repo_model = repo.RepoModel()
142 repo_model = repo.RepoModel()
143 by_name_match = repo_model.get_by_repo_name(repo_name)
143 by_name_match = repo_model.get_by_repo_name(repo_name)
144 # if we match quickly from database, short circuit the operation,
144 # if we match quickly from database, short circuit the operation,
145 # and validate repo based on the type.
145 # and validate repo based on the type.
146 if by_name_match:
146 if by_name_match:
147 return True
147 return True
148
148
149 by_id_match = repo_model.get_repo_by_id(repo_name)
149 by_id_match = repo_model.get_repo_by_id(repo_name)
150 if by_id_match:
150 if by_id_match:
151 repo_name = by_id_match.repo_name
151 repo_name = by_id_match.repo_name
152 match_dict['repo_name'] = repo_name
152 match_dict['repo_name'] = repo_name
153 return True
153 return True
154
154
155 return False
155 return False
156
156
157 def check_group(environ, match_dict):
157 def check_group(environ, match_dict):
158 """
158 """
159 check for valid repository group path for proper 404 handling
159 check for valid repository group path for proper 404 handling
160
160
161 :param environ:
161 :param environ:
162 :param match_dict:
162 :param match_dict:
163 """
163 """
164 repo_group_name = match_dict.get('group_name')
164 repo_group_name = match_dict.get('group_name')
165 repo_group_model = repo_group.RepoGroupModel()
165 repo_group_model = repo_group.RepoGroupModel()
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
167 if by_name_match:
167 if by_name_match:
168 return True
168 return True
169
169
170 return False
170 return False
171
171
172 def check_user_group(environ, match_dict):
172 def check_user_group(environ, match_dict):
173 """
173 """
174 check for valid user group for proper 404 handling
174 check for valid user group for proper 404 handling
175
175
176 :param environ:
176 :param environ:
177 :param match_dict:
177 :param match_dict:
178 """
178 """
179 return True
179 return True
180
180
181 def check_int(environ, match_dict):
181 def check_int(environ, match_dict):
182 return match_dict.get('id').isdigit()
182 return match_dict.get('id').isdigit()
183
183
184
184
185 #==========================================================================
185 #==========================================================================
186 # CUSTOM ROUTES HERE
186 # CUSTOM ROUTES HERE
187 #==========================================================================
187 #==========================================================================
188
188
189 # MAIN PAGE
189 # MAIN PAGE
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
192 action='goto_switcher_data')
192 action='goto_switcher_data')
193 rmap.connect('repo_list_data', '/_repos', controller='home',
193 rmap.connect('repo_list_data', '/_repos', controller='home',
194 action='repo_list_data')
194 action='repo_list_data')
195
195
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
197 action='user_autocomplete_data', jsroute=True)
197 action='user_autocomplete_data', jsroute=True)
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 action='user_group_autocomplete_data', jsroute=True)
199 action='user_group_autocomplete_data', jsroute=True)
200
200
201 # TODO: johbo: Static links, to be replaced by our redirection mechanism
201 # TODO: johbo: Static links, to be replaced by our redirection mechanism
202 rmap.connect('rst_help',
202 rmap.connect('rst_help',
203 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
203 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
204 _static=True)
204 _static=True)
205 rmap.connect('markdown_help',
205 rmap.connect('markdown_help',
206 'http://daringfireball.net/projects/markdown/syntax',
206 'http://daringfireball.net/projects/markdown/syntax',
207 _static=True)
207 _static=True)
208 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
208 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
209 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
209 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
210 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
210 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
211 # TODO: anderson - making this a static link since redirect won't play
211 # TODO: anderson - making this a static link since redirect won't play
212 # nice with POST requests
212 # nice with POST requests
213 rmap.connect('enterprise_license_convert_from_old',
213 rmap.connect('enterprise_license_convert_from_old',
214 'https://rhodecode.com/u/license-upgrade',
214 'https://rhodecode.com/u/license-upgrade',
215 _static=True)
215 _static=True)
216
216
217 routing_links.connect_redirection_links(rmap)
217 routing_links.connect_redirection_links(rmap)
218
218
219 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
219 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
220 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
220 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
221
221
222 # ADMIN REPOSITORY ROUTES
222 # ADMIN REPOSITORY ROUTES
223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
224 controller='admin/repos') as m:
224 controller='admin/repos') as m:
225 m.connect('repos', '/repos',
225 m.connect('repos', '/repos',
226 action='create', conditions={'method': ['POST']})
226 action='create', conditions={'method': ['POST']})
227 m.connect('repos', '/repos',
227 m.connect('repos', '/repos',
228 action='index', conditions={'method': ['GET']})
228 action='index', conditions={'method': ['GET']})
229 m.connect('new_repo', '/create_repository', jsroute=True,
229 m.connect('new_repo', '/create_repository', jsroute=True,
230 action='create_repository', conditions={'method': ['GET']})
230 action='create_repository', conditions={'method': ['GET']})
231 m.connect('/repos/{repo_name}',
231 m.connect('/repos/{repo_name}',
232 action='update', conditions={'method': ['PUT'],
232 action='update', conditions={'method': ['PUT'],
233 'function': check_repo},
233 'function': check_repo},
234 requirements=URL_NAME_REQUIREMENTS)
234 requirements=URL_NAME_REQUIREMENTS)
235 m.connect('delete_repo', '/repos/{repo_name}',
235 m.connect('delete_repo', '/repos/{repo_name}',
236 action='delete', conditions={'method': ['DELETE']},
236 action='delete', conditions={'method': ['DELETE']},
237 requirements=URL_NAME_REQUIREMENTS)
237 requirements=URL_NAME_REQUIREMENTS)
238 m.connect('repo', '/repos/{repo_name}',
238 m.connect('repo', '/repos/{repo_name}',
239 action='show', conditions={'method': ['GET'],
239 action='show', conditions={'method': ['GET'],
240 'function': check_repo},
240 'function': check_repo},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242
242
243 # ADMIN REPOSITORY GROUPS ROUTES
243 # ADMIN REPOSITORY GROUPS ROUTES
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 controller='admin/repo_groups') as m:
245 controller='admin/repo_groups') as m:
246 m.connect('repo_groups', '/repo_groups',
246 m.connect('repo_groups', '/repo_groups',
247 action='create', conditions={'method': ['POST']})
247 action='create', conditions={'method': ['POST']})
248 m.connect('repo_groups', '/repo_groups',
248 m.connect('repo_groups', '/repo_groups',
249 action='index', conditions={'method': ['GET']})
249 action='index', conditions={'method': ['GET']})
250 m.connect('new_repo_group', '/repo_groups/new',
250 m.connect('new_repo_group', '/repo_groups/new',
251 action='new', conditions={'method': ['GET']})
251 action='new', conditions={'method': ['GET']})
252 m.connect('update_repo_group', '/repo_groups/{group_name}',
252 m.connect('update_repo_group', '/repo_groups/{group_name}',
253 action='update', conditions={'method': ['PUT'],
253 action='update', conditions={'method': ['PUT'],
254 'function': check_group},
254 'function': check_group},
255 requirements=URL_NAME_REQUIREMENTS)
255 requirements=URL_NAME_REQUIREMENTS)
256
256
257 # EXTRAS REPO GROUP ROUTES
257 # EXTRAS REPO GROUP ROUTES
258 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
258 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
259 action='edit',
259 action='edit',
260 conditions={'method': ['GET'], 'function': check_group},
260 conditions={'method': ['GET'], 'function': check_group},
261 requirements=URL_NAME_REQUIREMENTS)
261 requirements=URL_NAME_REQUIREMENTS)
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
263 action='edit',
263 action='edit',
264 conditions={'method': ['PUT'], 'function': check_group},
264 conditions={'method': ['PUT'], 'function': check_group},
265 requirements=URL_NAME_REQUIREMENTS)
265 requirements=URL_NAME_REQUIREMENTS)
266
266
267 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
267 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
268 action='edit_repo_group_advanced',
268 action='edit_repo_group_advanced',
269 conditions={'method': ['GET'], 'function': check_group},
269 conditions={'method': ['GET'], 'function': check_group},
270 requirements=URL_NAME_REQUIREMENTS)
270 requirements=URL_NAME_REQUIREMENTS)
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
272 action='edit_repo_group_advanced',
272 action='edit_repo_group_advanced',
273 conditions={'method': ['PUT'], 'function': check_group},
273 conditions={'method': ['PUT'], 'function': check_group},
274 requirements=URL_NAME_REQUIREMENTS)
274 requirements=URL_NAME_REQUIREMENTS)
275
275
276 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
276 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
277 action='edit_repo_group_perms',
277 action='edit_repo_group_perms',
278 conditions={'method': ['GET'], 'function': check_group},
278 conditions={'method': ['GET'], 'function': check_group},
279 requirements=URL_NAME_REQUIREMENTS)
279 requirements=URL_NAME_REQUIREMENTS)
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
281 action='update_perms',
281 action='update_perms',
282 conditions={'method': ['PUT'], 'function': check_group},
282 conditions={'method': ['PUT'], 'function': check_group},
283 requirements=URL_NAME_REQUIREMENTS)
283 requirements=URL_NAME_REQUIREMENTS)
284
284
285 m.connect('delete_repo_group', '/repo_groups/{group_name}',
285 m.connect('delete_repo_group', '/repo_groups/{group_name}',
286 action='delete', conditions={'method': ['DELETE'],
286 action='delete', conditions={'method': ['DELETE'],
287 'function': check_group},
287 'function': check_group},
288 requirements=URL_NAME_REQUIREMENTS)
288 requirements=URL_NAME_REQUIREMENTS)
289
289
290 # ADMIN USER ROUTES
290 # ADMIN USER ROUTES
291 with rmap.submapper(path_prefix=ADMIN_PREFIX,
291 with rmap.submapper(path_prefix=ADMIN_PREFIX,
292 controller='admin/users') as m:
292 controller='admin/users') as m:
293 m.connect('users', '/users',
293 m.connect('users', '/users',
294 action='create', conditions={'method': ['POST']})
294 action='create', conditions={'method': ['POST']})
295 m.connect('users', '/users',
295 m.connect('users', '/users',
296 action='index', conditions={'method': ['GET']})
296 action='index', conditions={'method': ['GET']})
297 m.connect('new_user', '/users/new',
297 m.connect('new_user', '/users/new',
298 action='new', conditions={'method': ['GET']})
298 action='new', conditions={'method': ['GET']})
299 m.connect('update_user', '/users/{user_id}',
299 m.connect('update_user', '/users/{user_id}',
300 action='update', conditions={'method': ['PUT']})
300 action='update', conditions={'method': ['PUT']})
301 m.connect('delete_user', '/users/{user_id}',
301 m.connect('delete_user', '/users/{user_id}',
302 action='delete', conditions={'method': ['DELETE']})
302 action='delete', conditions={'method': ['DELETE']})
303 m.connect('edit_user', '/users/{user_id}/edit',
303 m.connect('edit_user', '/users/{user_id}/edit',
304 action='edit', conditions={'method': ['GET']}, jsroute=True)
304 action='edit', conditions={'method': ['GET']}, jsroute=True)
305 m.connect('user', '/users/{user_id}',
305 m.connect('user', '/users/{user_id}',
306 action='show', conditions={'method': ['GET']})
306 action='show', conditions={'method': ['GET']})
307 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
307 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
308 action='reset_password', conditions={'method': ['POST']})
308 action='reset_password', conditions={'method': ['POST']})
309 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
309 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
310 action='create_personal_repo_group', conditions={'method': ['POST']})
310 action='create_personal_repo_group', conditions={'method': ['POST']})
311
311
312 # EXTRAS USER ROUTES
312 # EXTRAS USER ROUTES
313 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
313 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
314 action='edit_advanced', conditions={'method': ['GET']})
314 action='edit_advanced', conditions={'method': ['GET']})
315 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
315 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
316 action='update_advanced', conditions={'method': ['PUT']})
316 action='update_advanced', conditions={'method': ['PUT']})
317
317
318 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
318 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
319 action='edit_auth_tokens', conditions={'method': ['GET']})
319 action='edit_auth_tokens', conditions={'method': ['GET']})
320 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
320 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
321 action='add_auth_token', conditions={'method': ['PUT']})
321 action='add_auth_token', conditions={'method': ['PUT']})
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 action='delete_auth_token', conditions={'method': ['DELETE']})
323 action='delete_auth_token', conditions={'method': ['DELETE']})
324
324
325 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
325 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
326 action='edit_global_perms', conditions={'method': ['GET']})
326 action='edit_global_perms', conditions={'method': ['GET']})
327 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
327 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
328 action='update_global_perms', conditions={'method': ['PUT']})
328 action='update_global_perms', conditions={'method': ['PUT']})
329
329
330 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
330 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
331 action='edit_perms_summary', conditions={'method': ['GET']})
331 action='edit_perms_summary', conditions={'method': ['GET']})
332
332
333 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
333 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
334 action='edit_emails', conditions={'method': ['GET']})
334 action='edit_emails', conditions={'method': ['GET']})
335 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
335 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
336 action='add_email', conditions={'method': ['PUT']})
336 action='add_email', conditions={'method': ['PUT']})
337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
338 action='delete_email', conditions={'method': ['DELETE']})
338 action='delete_email', conditions={'method': ['DELETE']})
339
339
340 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
340 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
341 action='edit_ips', conditions={'method': ['GET']})
341 action='edit_ips', conditions={'method': ['GET']})
342 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
342 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
343 action='add_ip', conditions={'method': ['PUT']})
343 action='add_ip', conditions={'method': ['PUT']})
344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
345 action='delete_ip', conditions={'method': ['DELETE']})
345 action='delete_ip', conditions={'method': ['DELETE']})
346
346
347 # ADMIN USER GROUPS REST ROUTES
347 # ADMIN USER GROUPS REST ROUTES
348 with rmap.submapper(path_prefix=ADMIN_PREFIX,
348 with rmap.submapper(path_prefix=ADMIN_PREFIX,
349 controller='admin/user_groups') as m:
349 controller='admin/user_groups') as m:
350 m.connect('users_groups', '/user_groups',
350 m.connect('users_groups', '/user_groups',
351 action='create', conditions={'method': ['POST']})
351 action='create', conditions={'method': ['POST']})
352 m.connect('users_groups', '/user_groups',
352 m.connect('users_groups', '/user_groups',
353 action='index', conditions={'method': ['GET']})
353 action='index', conditions={'method': ['GET']})
354 m.connect('new_users_group', '/user_groups/new',
354 m.connect('new_users_group', '/user_groups/new',
355 action='new', conditions={'method': ['GET']})
355 action='new', conditions={'method': ['GET']})
356 m.connect('update_users_group', '/user_groups/{user_group_id}',
356 m.connect('update_users_group', '/user_groups/{user_group_id}',
357 action='update', conditions={'method': ['PUT']})
357 action='update', conditions={'method': ['PUT']})
358 m.connect('delete_users_group', '/user_groups/{user_group_id}',
358 m.connect('delete_users_group', '/user_groups/{user_group_id}',
359 action='delete', conditions={'method': ['DELETE']})
359 action='delete', conditions={'method': ['DELETE']})
360 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
360 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
361 action='edit', conditions={'method': ['GET']},
361 action='edit', conditions={'method': ['GET']},
362 function=check_user_group)
362 function=check_user_group)
363
363
364 # EXTRAS USER GROUP ROUTES
364 # EXTRAS USER GROUP ROUTES
365 m.connect('edit_user_group_global_perms',
365 m.connect('edit_user_group_global_perms',
366 '/user_groups/{user_group_id}/edit/global_permissions',
366 '/user_groups/{user_group_id}/edit/global_permissions',
367 action='edit_global_perms', conditions={'method': ['GET']})
367 action='edit_global_perms', conditions={'method': ['GET']})
368 m.connect('edit_user_group_global_perms',
368 m.connect('edit_user_group_global_perms',
369 '/user_groups/{user_group_id}/edit/global_permissions',
369 '/user_groups/{user_group_id}/edit/global_permissions',
370 action='update_global_perms', conditions={'method': ['PUT']})
370 action='update_global_perms', conditions={'method': ['PUT']})
371 m.connect('edit_user_group_perms_summary',
371 m.connect('edit_user_group_perms_summary',
372 '/user_groups/{user_group_id}/edit/permissions_summary',
372 '/user_groups/{user_group_id}/edit/permissions_summary',
373 action='edit_perms_summary', conditions={'method': ['GET']})
373 action='edit_perms_summary', conditions={'method': ['GET']})
374
374
375 m.connect('edit_user_group_perms',
375 m.connect('edit_user_group_perms',
376 '/user_groups/{user_group_id}/edit/permissions',
376 '/user_groups/{user_group_id}/edit/permissions',
377 action='edit_perms', conditions={'method': ['GET']})
377 action='edit_perms', conditions={'method': ['GET']})
378 m.connect('edit_user_group_perms',
378 m.connect('edit_user_group_perms',
379 '/user_groups/{user_group_id}/edit/permissions',
379 '/user_groups/{user_group_id}/edit/permissions',
380 action='update_perms', conditions={'method': ['PUT']})
380 action='update_perms', conditions={'method': ['PUT']})
381
381
382 m.connect('edit_user_group_advanced',
382 m.connect('edit_user_group_advanced',
383 '/user_groups/{user_group_id}/edit/advanced',
383 '/user_groups/{user_group_id}/edit/advanced',
384 action='edit_advanced', conditions={'method': ['GET']})
384 action='edit_advanced', conditions={'method': ['GET']})
385
385
386 m.connect('edit_user_group_members',
386 m.connect('edit_user_group_members',
387 '/user_groups/{user_group_id}/edit/members', jsroute=True,
387 '/user_groups/{user_group_id}/edit/members', jsroute=True,
388 action='user_group_members', conditions={'method': ['GET']})
388 action='user_group_members', conditions={'method': ['GET']})
389
389
390 # ADMIN PERMISSIONS ROUTES
390 # ADMIN PERMISSIONS ROUTES
391 with rmap.submapper(path_prefix=ADMIN_PREFIX,
391 with rmap.submapper(path_prefix=ADMIN_PREFIX,
392 controller='admin/permissions') as m:
392 controller='admin/permissions') as m:
393 m.connect('admin_permissions_application', '/permissions/application',
393 m.connect('admin_permissions_application', '/permissions/application',
394 action='permission_application_update', conditions={'method': ['POST']})
394 action='permission_application_update', conditions={'method': ['POST']})
395 m.connect('admin_permissions_application', '/permissions/application',
395 m.connect('admin_permissions_application', '/permissions/application',
396 action='permission_application', conditions={'method': ['GET']})
396 action='permission_application', conditions={'method': ['GET']})
397
397
398 m.connect('admin_permissions_global', '/permissions/global',
398 m.connect('admin_permissions_global', '/permissions/global',
399 action='permission_global_update', conditions={'method': ['POST']})
399 action='permission_global_update', conditions={'method': ['POST']})
400 m.connect('admin_permissions_global', '/permissions/global',
400 m.connect('admin_permissions_global', '/permissions/global',
401 action='permission_global', conditions={'method': ['GET']})
401 action='permission_global', conditions={'method': ['GET']})
402
402
403 m.connect('admin_permissions_object', '/permissions/object',
403 m.connect('admin_permissions_object', '/permissions/object',
404 action='permission_objects_update', conditions={'method': ['POST']})
404 action='permission_objects_update', conditions={'method': ['POST']})
405 m.connect('admin_permissions_object', '/permissions/object',
405 m.connect('admin_permissions_object', '/permissions/object',
406 action='permission_objects', conditions={'method': ['GET']})
406 action='permission_objects', conditions={'method': ['GET']})
407
407
408 m.connect('admin_permissions_ips', '/permissions/ips',
408 m.connect('admin_permissions_ips', '/permissions/ips',
409 action='permission_ips', conditions={'method': ['POST']})
409 action='permission_ips', conditions={'method': ['POST']})
410 m.connect('admin_permissions_ips', '/permissions/ips',
410 m.connect('admin_permissions_ips', '/permissions/ips',
411 action='permission_ips', conditions={'method': ['GET']})
411 action='permission_ips', conditions={'method': ['GET']})
412
412
413 m.connect('admin_permissions_overview', '/permissions/overview',
413 m.connect('admin_permissions_overview', '/permissions/overview',
414 action='permission_perms', conditions={'method': ['GET']})
414 action='permission_perms', conditions={'method': ['GET']})
415
415
416 # ADMIN DEFAULTS REST ROUTES
416 # ADMIN DEFAULTS REST ROUTES
417 with rmap.submapper(path_prefix=ADMIN_PREFIX,
417 with rmap.submapper(path_prefix=ADMIN_PREFIX,
418 controller='admin/defaults') as m:
418 controller='admin/defaults') as m:
419 m.connect('admin_defaults_repositories', '/defaults/repositories',
419 m.connect('admin_defaults_repositories', '/defaults/repositories',
420 action='update_repository_defaults', conditions={'method': ['POST']})
420 action='update_repository_defaults', conditions={'method': ['POST']})
421 m.connect('admin_defaults_repositories', '/defaults/repositories',
421 m.connect('admin_defaults_repositories', '/defaults/repositories',
422 action='index', conditions={'method': ['GET']})
422 action='index', conditions={'method': ['GET']})
423
423
424 # ADMIN DEBUG STYLE ROUTES
424 # ADMIN DEBUG STYLE ROUTES
425 if str2bool(config.get('debug_style')):
425 if str2bool(config.get('debug_style')):
426 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
426 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
427 controller='debug_style') as m:
427 controller='debug_style') as m:
428 m.connect('debug_style_home', '',
428 m.connect('debug_style_home', '',
429 action='index', conditions={'method': ['GET']})
429 action='index', conditions={'method': ['GET']})
430 m.connect('debug_style_template', '/t/{t_path}',
430 m.connect('debug_style_template', '/t/{t_path}',
431 action='template', conditions={'method': ['GET']})
431 action='template', conditions={'method': ['GET']})
432
432
433 # ADMIN SETTINGS ROUTES
433 # ADMIN SETTINGS ROUTES
434 with rmap.submapper(path_prefix=ADMIN_PREFIX,
434 with rmap.submapper(path_prefix=ADMIN_PREFIX,
435 controller='admin/settings') as m:
435 controller='admin/settings') as m:
436
436
437 # default
437 # default
438 m.connect('admin_settings', '/settings',
438 m.connect('admin_settings', '/settings',
439 action='settings_global_update',
439 action='settings_global_update',
440 conditions={'method': ['POST']})
440 conditions={'method': ['POST']})
441 m.connect('admin_settings', '/settings',
441 m.connect('admin_settings', '/settings',
442 action='settings_global', conditions={'method': ['GET']})
442 action='settings_global', conditions={'method': ['GET']})
443
443
444 m.connect('admin_settings_vcs', '/settings/vcs',
444 m.connect('admin_settings_vcs', '/settings/vcs',
445 action='settings_vcs_update',
445 action='settings_vcs_update',
446 conditions={'method': ['POST']})
446 conditions={'method': ['POST']})
447 m.connect('admin_settings_vcs', '/settings/vcs',
447 m.connect('admin_settings_vcs', '/settings/vcs',
448 action='settings_vcs',
448 action='settings_vcs',
449 conditions={'method': ['GET']})
449 conditions={'method': ['GET']})
450 m.connect('admin_settings_vcs', '/settings/vcs',
450 m.connect('admin_settings_vcs', '/settings/vcs',
451 action='delete_svn_pattern',
451 action='delete_svn_pattern',
452 conditions={'method': ['DELETE']})
452 conditions={'method': ['DELETE']})
453
453
454 m.connect('admin_settings_mapping', '/settings/mapping',
454 m.connect('admin_settings_mapping', '/settings/mapping',
455 action='settings_mapping_update',
455 action='settings_mapping_update',
456 conditions={'method': ['POST']})
456 conditions={'method': ['POST']})
457 m.connect('admin_settings_mapping', '/settings/mapping',
457 m.connect('admin_settings_mapping', '/settings/mapping',
458 action='settings_mapping', conditions={'method': ['GET']})
458 action='settings_mapping', conditions={'method': ['GET']})
459
459
460 m.connect('admin_settings_global', '/settings/global',
460 m.connect('admin_settings_global', '/settings/global',
461 action='settings_global_update',
461 action='settings_global_update',
462 conditions={'method': ['POST']})
462 conditions={'method': ['POST']})
463 m.connect('admin_settings_global', '/settings/global',
463 m.connect('admin_settings_global', '/settings/global',
464 action='settings_global', conditions={'method': ['GET']})
464 action='settings_global', conditions={'method': ['GET']})
465
465
466 m.connect('admin_settings_visual', '/settings/visual',
466 m.connect('admin_settings_visual', '/settings/visual',
467 action='settings_visual_update',
467 action='settings_visual_update',
468 conditions={'method': ['POST']})
468 conditions={'method': ['POST']})
469 m.connect('admin_settings_visual', '/settings/visual',
469 m.connect('admin_settings_visual', '/settings/visual',
470 action='settings_visual', conditions={'method': ['GET']})
470 action='settings_visual', conditions={'method': ['GET']})
471
471
472 m.connect('admin_settings_issuetracker',
472 m.connect('admin_settings_issuetracker',
473 '/settings/issue-tracker', action='settings_issuetracker',
473 '/settings/issue-tracker', action='settings_issuetracker',
474 conditions={'method': ['GET']})
474 conditions={'method': ['GET']})
475 m.connect('admin_settings_issuetracker_save',
475 m.connect('admin_settings_issuetracker_save',
476 '/settings/issue-tracker/save',
476 '/settings/issue-tracker/save',
477 action='settings_issuetracker_save',
477 action='settings_issuetracker_save',
478 conditions={'method': ['POST']})
478 conditions={'method': ['POST']})
479 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
479 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
480 action='settings_issuetracker_test',
480 action='settings_issuetracker_test',
481 conditions={'method': ['POST']})
481 conditions={'method': ['POST']})
482 m.connect('admin_issuetracker_delete',
482 m.connect('admin_issuetracker_delete',
483 '/settings/issue-tracker/delete',
483 '/settings/issue-tracker/delete',
484 action='settings_issuetracker_delete',
484 action='settings_issuetracker_delete',
485 conditions={'method': ['DELETE']})
485 conditions={'method': ['DELETE']})
486
486
487 m.connect('admin_settings_email', '/settings/email',
487 m.connect('admin_settings_email', '/settings/email',
488 action='settings_email_update',
488 action='settings_email_update',
489 conditions={'method': ['POST']})
489 conditions={'method': ['POST']})
490 m.connect('admin_settings_email', '/settings/email',
490 m.connect('admin_settings_email', '/settings/email',
491 action='settings_email', conditions={'method': ['GET']})
491 action='settings_email', conditions={'method': ['GET']})
492
492
493 m.connect('admin_settings_hooks', '/settings/hooks',
493 m.connect('admin_settings_hooks', '/settings/hooks',
494 action='settings_hooks_update',
494 action='settings_hooks_update',
495 conditions={'method': ['POST', 'DELETE']})
495 conditions={'method': ['POST', 'DELETE']})
496 m.connect('admin_settings_hooks', '/settings/hooks',
496 m.connect('admin_settings_hooks', '/settings/hooks',
497 action='settings_hooks', conditions={'method': ['GET']})
497 action='settings_hooks', conditions={'method': ['GET']})
498
498
499 m.connect('admin_settings_search', '/settings/search',
499 m.connect('admin_settings_search', '/settings/search',
500 action='settings_search', conditions={'method': ['GET']})
500 action='settings_search', conditions={'method': ['GET']})
501
501
502 m.connect('admin_settings_supervisor', '/settings/supervisor',
502 m.connect('admin_settings_supervisor', '/settings/supervisor',
503 action='settings_supervisor', conditions={'method': ['GET']})
503 action='settings_supervisor', conditions={'method': ['GET']})
504 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
504 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
505 action='settings_supervisor_log', conditions={'method': ['GET']})
505 action='settings_supervisor_log', conditions={'method': ['GET']})
506
506
507 m.connect('admin_settings_labs', '/settings/labs',
507 m.connect('admin_settings_labs', '/settings/labs',
508 action='settings_labs_update',
508 action='settings_labs_update',
509 conditions={'method': ['POST']})
509 conditions={'method': ['POST']})
510 m.connect('admin_settings_labs', '/settings/labs',
510 m.connect('admin_settings_labs', '/settings/labs',
511 action='settings_labs', conditions={'method': ['GET']})
511 action='settings_labs', conditions={'method': ['GET']})
512
512
513 # ADMIN MY ACCOUNT
513 # ADMIN MY ACCOUNT
514 with rmap.submapper(path_prefix=ADMIN_PREFIX,
514 with rmap.submapper(path_prefix=ADMIN_PREFIX,
515 controller='admin/my_account') as m:
515 controller='admin/my_account') as m:
516
516
517 m.connect('my_account', '/my_account',
517 m.connect('my_account', '/my_account',
518 action='my_account', conditions={'method': ['GET']})
518 action='my_account', conditions={'method': ['GET']})
519 m.connect('my_account_edit', '/my_account/edit',
519 m.connect('my_account_edit', '/my_account/edit',
520 action='my_account_edit', conditions={'method': ['GET']})
520 action='my_account_edit', conditions={'method': ['GET']})
521 m.connect('my_account', '/my_account',
521 m.connect('my_account', '/my_account',
522 action='my_account_update', conditions={'method': ['POST']})
522 action='my_account_update', conditions={'method': ['POST']})
523
523
524 m.connect('my_account_password', '/my_account/password',
524 m.connect('my_account_password', '/my_account/password',
525 action='my_account_password', conditions={'method': ['GET', 'POST']})
525 action='my_account_password', conditions={'method': ['GET', 'POST']})
526
526
527 m.connect('my_account_repos', '/my_account/repos',
527 m.connect('my_account_repos', '/my_account/repos',
528 action='my_account_repos', conditions={'method': ['GET']})
528 action='my_account_repos', conditions={'method': ['GET']})
529
529
530 m.connect('my_account_watched', '/my_account/watched',
530 m.connect('my_account_watched', '/my_account/watched',
531 action='my_account_watched', conditions={'method': ['GET']})
531 action='my_account_watched', conditions={'method': ['GET']})
532
532
533 m.connect('my_account_pullrequests', '/my_account/pull_requests',
533 m.connect('my_account_pullrequests', '/my_account/pull_requests',
534 action='my_account_pullrequests', conditions={'method': ['GET']})
534 action='my_account_pullrequests', conditions={'method': ['GET']})
535
535
536 m.connect('my_account_perms', '/my_account/perms',
536 m.connect('my_account_perms', '/my_account/perms',
537 action='my_account_perms', conditions={'method': ['GET']})
537 action='my_account_perms', conditions={'method': ['GET']})
538
538
539 m.connect('my_account_emails', '/my_account/emails',
539 m.connect('my_account_emails', '/my_account/emails',
540 action='my_account_emails', conditions={'method': ['GET']})
540 action='my_account_emails', conditions={'method': ['GET']})
541 m.connect('my_account_emails', '/my_account/emails',
541 m.connect('my_account_emails', '/my_account/emails',
542 action='my_account_emails_add', conditions={'method': ['POST']})
542 action='my_account_emails_add', conditions={'method': ['POST']})
543 m.connect('my_account_emails', '/my_account/emails',
543 m.connect('my_account_emails', '/my_account/emails',
544 action='my_account_emails_delete', conditions={'method': ['DELETE']})
544 action='my_account_emails_delete', conditions={'method': ['DELETE']})
545
545
546 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
547 action='my_account_auth_tokens', conditions={'method': ['GET']})
548 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
549 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
550 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
551 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
552 m.connect('my_account_notifications', '/my_account/notifications',
546 m.connect('my_account_notifications', '/my_account/notifications',
553 action='my_notifications',
547 action='my_notifications',
554 conditions={'method': ['GET']})
548 conditions={'method': ['GET']})
555 m.connect('my_account_notifications_toggle_visibility',
549 m.connect('my_account_notifications_toggle_visibility',
556 '/my_account/toggle_visibility',
550 '/my_account/toggle_visibility',
557 action='my_notifications_toggle_visibility',
551 action='my_notifications_toggle_visibility',
558 conditions={'method': ['POST']})
552 conditions={'method': ['POST']})
559 m.connect('my_account_notifications_test_channelstream',
553 m.connect('my_account_notifications_test_channelstream',
560 '/my_account/test_channelstream',
554 '/my_account/test_channelstream',
561 action='my_account_notifications_test_channelstream',
555 action='my_account_notifications_test_channelstream',
562 conditions={'method': ['POST']})
556 conditions={'method': ['POST']})
563
557
564 # NOTIFICATION REST ROUTES
558 # NOTIFICATION REST ROUTES
565 with rmap.submapper(path_prefix=ADMIN_PREFIX,
559 with rmap.submapper(path_prefix=ADMIN_PREFIX,
566 controller='admin/notifications') as m:
560 controller='admin/notifications') as m:
567 m.connect('notifications', '/notifications',
561 m.connect('notifications', '/notifications',
568 action='index', conditions={'method': ['GET']})
562 action='index', conditions={'method': ['GET']})
569 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
563 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
570 action='mark_all_read', conditions={'method': ['POST']})
564 action='mark_all_read', conditions={'method': ['POST']})
571 m.connect('/notifications/{notification_id}',
565 m.connect('/notifications/{notification_id}',
572 action='update', conditions={'method': ['PUT']})
566 action='update', conditions={'method': ['PUT']})
573 m.connect('/notifications/{notification_id}',
567 m.connect('/notifications/{notification_id}',
574 action='delete', conditions={'method': ['DELETE']})
568 action='delete', conditions={'method': ['DELETE']})
575 m.connect('notification', '/notifications/{notification_id}',
569 m.connect('notification', '/notifications/{notification_id}',
576 action='show', conditions={'method': ['GET']})
570 action='show', conditions={'method': ['GET']})
577
571
578 # ADMIN GIST
572 # ADMIN GIST
579 with rmap.submapper(path_prefix=ADMIN_PREFIX,
573 with rmap.submapper(path_prefix=ADMIN_PREFIX,
580 controller='admin/gists') as m:
574 controller='admin/gists') as m:
581 m.connect('gists', '/gists',
575 m.connect('gists', '/gists',
582 action='create', conditions={'method': ['POST']})
576 action='create', conditions={'method': ['POST']})
583 m.connect('gists', '/gists', jsroute=True,
577 m.connect('gists', '/gists', jsroute=True,
584 action='index', conditions={'method': ['GET']})
578 action='index', conditions={'method': ['GET']})
585 m.connect('new_gist', '/gists/new', jsroute=True,
579 m.connect('new_gist', '/gists/new', jsroute=True,
586 action='new', conditions={'method': ['GET']})
580 action='new', conditions={'method': ['GET']})
587
581
588 m.connect('/gists/{gist_id}',
582 m.connect('/gists/{gist_id}',
589 action='delete', conditions={'method': ['DELETE']})
583 action='delete', conditions={'method': ['DELETE']})
590 m.connect('edit_gist', '/gists/{gist_id}/edit',
584 m.connect('edit_gist', '/gists/{gist_id}/edit',
591 action='edit_form', conditions={'method': ['GET']})
585 action='edit_form', conditions={'method': ['GET']})
592 m.connect('edit_gist', '/gists/{gist_id}/edit',
586 m.connect('edit_gist', '/gists/{gist_id}/edit',
593 action='edit', conditions={'method': ['POST']})
587 action='edit', conditions={'method': ['POST']})
594 m.connect(
588 m.connect(
595 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
589 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
596 action='check_revision', conditions={'method': ['GET']})
590 action='check_revision', conditions={'method': ['GET']})
597
591
598 m.connect('gist', '/gists/{gist_id}',
592 m.connect('gist', '/gists/{gist_id}',
599 action='show', conditions={'method': ['GET']})
593 action='show', conditions={'method': ['GET']})
600 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
594 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
601 revision='tip',
595 revision='tip',
602 action='show', conditions={'method': ['GET']})
596 action='show', conditions={'method': ['GET']})
603 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
597 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
604 revision='tip',
598 revision='tip',
605 action='show', conditions={'method': ['GET']})
599 action='show', conditions={'method': ['GET']})
606 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
600 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
607 revision='tip',
601 revision='tip',
608 action='show', conditions={'method': ['GET']},
602 action='show', conditions={'method': ['GET']},
609 requirements=URL_NAME_REQUIREMENTS)
603 requirements=URL_NAME_REQUIREMENTS)
610
604
611 # ADMIN MAIN PAGES
605 # ADMIN MAIN PAGES
612 with rmap.submapper(path_prefix=ADMIN_PREFIX,
606 with rmap.submapper(path_prefix=ADMIN_PREFIX,
613 controller='admin/admin') as m:
607 controller='admin/admin') as m:
614 m.connect('admin_home', '', action='index')
608 m.connect('admin_home', '', action='index')
615 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
609 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
616 action='add_repo')
610 action='add_repo')
617 m.connect(
611 m.connect(
618 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
612 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
619 action='pull_requests')
613 action='pull_requests')
620 m.connect(
614 m.connect(
621 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
615 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
622 action='pull_requests')
616 action='pull_requests')
623 m.connect(
617 m.connect(
624 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
618 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
625 action='pull_requests')
619 action='pull_requests')
626
620
627 # USER JOURNAL
621 # USER JOURNAL
628 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
622 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
629 controller='journal', action='index')
623 controller='journal', action='index')
630 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
624 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
631 controller='journal', action='journal_rss')
625 controller='journal', action='journal_rss')
632 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
626 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
633 controller='journal', action='journal_atom')
627 controller='journal', action='journal_atom')
634
628
635 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
629 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
636 controller='journal', action='public_journal')
630 controller='journal', action='public_journal')
637
631
638 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
632 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
639 controller='journal', action='public_journal_rss')
633 controller='journal', action='public_journal_rss')
640
634
641 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
635 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
642 controller='journal', action='public_journal_rss')
636 controller='journal', action='public_journal_rss')
643
637
644 rmap.connect('public_journal_atom',
638 rmap.connect('public_journal_atom',
645 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
639 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
646 action='public_journal_atom')
640 action='public_journal_atom')
647
641
648 rmap.connect('public_journal_atom_old',
642 rmap.connect('public_journal_atom_old',
649 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
643 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
650 action='public_journal_atom')
644 action='public_journal_atom')
651
645
652 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
646 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
653 controller='journal', action='toggle_following', jsroute=True,
647 controller='journal', action='toggle_following', jsroute=True,
654 conditions={'method': ['POST']})
648 conditions={'method': ['POST']})
655
649
656 # FULL TEXT SEARCH
650 # FULL TEXT SEARCH
657 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
651 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
658 controller='search')
652 controller='search')
659 rmap.connect('search_repo_home', '/{repo_name}/search',
653 rmap.connect('search_repo_home', '/{repo_name}/search',
660 controller='search',
654 controller='search',
661 action='index',
655 action='index',
662 conditions={'function': check_repo},
656 conditions={'function': check_repo},
663 requirements=URL_NAME_REQUIREMENTS)
657 requirements=URL_NAME_REQUIREMENTS)
664
658
665 # FEEDS
659 # FEEDS
666 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
660 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
667 controller='feed', action='rss',
661 controller='feed', action='rss',
668 conditions={'function': check_repo},
662 conditions={'function': check_repo},
669 requirements=URL_NAME_REQUIREMENTS)
663 requirements=URL_NAME_REQUIREMENTS)
670
664
671 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
665 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
672 controller='feed', action='atom',
666 controller='feed', action='atom',
673 conditions={'function': check_repo},
667 conditions={'function': check_repo},
674 requirements=URL_NAME_REQUIREMENTS)
668 requirements=URL_NAME_REQUIREMENTS)
675
669
676 #==========================================================================
670 #==========================================================================
677 # REPOSITORY ROUTES
671 # REPOSITORY ROUTES
678 #==========================================================================
672 #==========================================================================
679
673
680 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
674 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
681 controller='admin/repos', action='repo_creating',
675 controller='admin/repos', action='repo_creating',
682 requirements=URL_NAME_REQUIREMENTS)
676 requirements=URL_NAME_REQUIREMENTS)
683 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
677 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
684 controller='admin/repos', action='repo_check',
678 controller='admin/repos', action='repo_check',
685 requirements=URL_NAME_REQUIREMENTS)
679 requirements=URL_NAME_REQUIREMENTS)
686
680
687 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
681 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
688 controller='summary', action='repo_stats',
682 controller='summary', action='repo_stats',
689 conditions={'function': check_repo},
683 conditions={'function': check_repo},
690 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
684 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
691
685
692 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
686 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
693 controller='summary', action='repo_refs_data',
687 controller='summary', action='repo_refs_data',
694 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
688 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
695 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
689 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
696 controller='summary', action='repo_refs_changelog_data',
690 controller='summary', action='repo_refs_changelog_data',
697 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
691 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
698 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
692 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
699 controller='summary', action='repo_default_reviewers_data',
693 controller='summary', action='repo_default_reviewers_data',
700 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
694 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
701
695
702 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
696 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
703 controller='changeset', revision='tip',
697 controller='changeset', revision='tip',
704 conditions={'function': check_repo},
698 conditions={'function': check_repo},
705 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
699 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
706 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
700 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
707 controller='changeset', revision='tip', action='changeset_children',
701 controller='changeset', revision='tip', action='changeset_children',
708 conditions={'function': check_repo},
702 conditions={'function': check_repo},
709 requirements=URL_NAME_REQUIREMENTS)
703 requirements=URL_NAME_REQUIREMENTS)
710 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
704 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
711 controller='changeset', revision='tip', action='changeset_parents',
705 controller='changeset', revision='tip', action='changeset_parents',
712 conditions={'function': check_repo},
706 conditions={'function': check_repo},
713 requirements=URL_NAME_REQUIREMENTS)
707 requirements=URL_NAME_REQUIREMENTS)
714
708
715 # repo edit options
709 # repo edit options
716 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
710 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
717 controller='admin/repos', action='edit',
711 controller='admin/repos', action='edit',
718 conditions={'method': ['GET'], 'function': check_repo},
712 conditions={'method': ['GET'], 'function': check_repo},
719 requirements=URL_NAME_REQUIREMENTS)
713 requirements=URL_NAME_REQUIREMENTS)
720
714
721 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
715 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
722 jsroute=True,
716 jsroute=True,
723 controller='admin/repos', action='edit_permissions',
717 controller='admin/repos', action='edit_permissions',
724 conditions={'method': ['GET'], 'function': check_repo},
718 conditions={'method': ['GET'], 'function': check_repo},
725 requirements=URL_NAME_REQUIREMENTS)
719 requirements=URL_NAME_REQUIREMENTS)
726 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
720 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
727 controller='admin/repos', action='edit_permissions_update',
721 controller='admin/repos', action='edit_permissions_update',
728 conditions={'method': ['PUT'], 'function': check_repo},
722 conditions={'method': ['PUT'], 'function': check_repo},
729 requirements=URL_NAME_REQUIREMENTS)
723 requirements=URL_NAME_REQUIREMENTS)
730
724
731 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
725 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
732 controller='admin/repos', action='edit_fields',
726 controller='admin/repos', action='edit_fields',
733 conditions={'method': ['GET'], 'function': check_repo},
727 conditions={'method': ['GET'], 'function': check_repo},
734 requirements=URL_NAME_REQUIREMENTS)
728 requirements=URL_NAME_REQUIREMENTS)
735 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
729 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
736 controller='admin/repos', action='create_repo_field',
730 controller='admin/repos', action='create_repo_field',
737 conditions={'method': ['PUT'], 'function': check_repo},
731 conditions={'method': ['PUT'], 'function': check_repo},
738 requirements=URL_NAME_REQUIREMENTS)
732 requirements=URL_NAME_REQUIREMENTS)
739 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
733 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
740 controller='admin/repos', action='delete_repo_field',
734 controller='admin/repos', action='delete_repo_field',
741 conditions={'method': ['DELETE'], 'function': check_repo},
735 conditions={'method': ['DELETE'], 'function': check_repo},
742 requirements=URL_NAME_REQUIREMENTS)
736 requirements=URL_NAME_REQUIREMENTS)
743
737
744 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
738 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
745 controller='admin/repos', action='edit_advanced',
739 controller='admin/repos', action='edit_advanced',
746 conditions={'method': ['GET'], 'function': check_repo},
740 conditions={'method': ['GET'], 'function': check_repo},
747 requirements=URL_NAME_REQUIREMENTS)
741 requirements=URL_NAME_REQUIREMENTS)
748
742
749 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
743 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
750 controller='admin/repos', action='edit_advanced_locking',
744 controller='admin/repos', action='edit_advanced_locking',
751 conditions={'method': ['PUT'], 'function': check_repo},
745 conditions={'method': ['PUT'], 'function': check_repo},
752 requirements=URL_NAME_REQUIREMENTS)
746 requirements=URL_NAME_REQUIREMENTS)
753 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
747 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
754 controller='admin/repos', action='toggle_locking',
748 controller='admin/repos', action='toggle_locking',
755 conditions={'method': ['GET'], 'function': check_repo},
749 conditions={'method': ['GET'], 'function': check_repo},
756 requirements=URL_NAME_REQUIREMENTS)
750 requirements=URL_NAME_REQUIREMENTS)
757
751
758 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
752 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
759 controller='admin/repos', action='edit_advanced_journal',
753 controller='admin/repos', action='edit_advanced_journal',
760 conditions={'method': ['PUT'], 'function': check_repo},
754 conditions={'method': ['PUT'], 'function': check_repo},
761 requirements=URL_NAME_REQUIREMENTS)
755 requirements=URL_NAME_REQUIREMENTS)
762
756
763 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
757 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
764 controller='admin/repos', action='edit_advanced_fork',
758 controller='admin/repos', action='edit_advanced_fork',
765 conditions={'method': ['PUT'], 'function': check_repo},
759 conditions={'method': ['PUT'], 'function': check_repo},
766 requirements=URL_NAME_REQUIREMENTS)
760 requirements=URL_NAME_REQUIREMENTS)
767
761
768 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
762 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
769 controller='admin/repos', action='edit_caches_form',
763 controller='admin/repos', action='edit_caches_form',
770 conditions={'method': ['GET'], 'function': check_repo},
764 conditions={'method': ['GET'], 'function': check_repo},
771 requirements=URL_NAME_REQUIREMENTS)
765 requirements=URL_NAME_REQUIREMENTS)
772 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
766 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
773 controller='admin/repos', action='edit_caches',
767 controller='admin/repos', action='edit_caches',
774 conditions={'method': ['PUT'], 'function': check_repo},
768 conditions={'method': ['PUT'], 'function': check_repo},
775 requirements=URL_NAME_REQUIREMENTS)
769 requirements=URL_NAME_REQUIREMENTS)
776
770
777 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
771 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
778 controller='admin/repos', action='edit_remote_form',
772 controller='admin/repos', action='edit_remote_form',
779 conditions={'method': ['GET'], 'function': check_repo},
773 conditions={'method': ['GET'], 'function': check_repo},
780 requirements=URL_NAME_REQUIREMENTS)
774 requirements=URL_NAME_REQUIREMENTS)
781 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
775 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
782 controller='admin/repos', action='edit_remote',
776 controller='admin/repos', action='edit_remote',
783 conditions={'method': ['PUT'], 'function': check_repo},
777 conditions={'method': ['PUT'], 'function': check_repo},
784 requirements=URL_NAME_REQUIREMENTS)
778 requirements=URL_NAME_REQUIREMENTS)
785
779
786 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
780 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
787 controller='admin/repos', action='edit_statistics_form',
781 controller='admin/repos', action='edit_statistics_form',
788 conditions={'method': ['GET'], 'function': check_repo},
782 conditions={'method': ['GET'], 'function': check_repo},
789 requirements=URL_NAME_REQUIREMENTS)
783 requirements=URL_NAME_REQUIREMENTS)
790 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
784 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
791 controller='admin/repos', action='edit_statistics',
785 controller='admin/repos', action='edit_statistics',
792 conditions={'method': ['PUT'], 'function': check_repo},
786 conditions={'method': ['PUT'], 'function': check_repo},
793 requirements=URL_NAME_REQUIREMENTS)
787 requirements=URL_NAME_REQUIREMENTS)
794 rmap.connect('repo_settings_issuetracker',
788 rmap.connect('repo_settings_issuetracker',
795 '/{repo_name}/settings/issue-tracker',
789 '/{repo_name}/settings/issue-tracker',
796 controller='admin/repos', action='repo_issuetracker',
790 controller='admin/repos', action='repo_issuetracker',
797 conditions={'method': ['GET'], 'function': check_repo},
791 conditions={'method': ['GET'], 'function': check_repo},
798 requirements=URL_NAME_REQUIREMENTS)
792 requirements=URL_NAME_REQUIREMENTS)
799 rmap.connect('repo_issuetracker_test',
793 rmap.connect('repo_issuetracker_test',
800 '/{repo_name}/settings/issue-tracker/test',
794 '/{repo_name}/settings/issue-tracker/test',
801 controller='admin/repos', action='repo_issuetracker_test',
795 controller='admin/repos', action='repo_issuetracker_test',
802 conditions={'method': ['POST'], 'function': check_repo},
796 conditions={'method': ['POST'], 'function': check_repo},
803 requirements=URL_NAME_REQUIREMENTS)
797 requirements=URL_NAME_REQUIREMENTS)
804 rmap.connect('repo_issuetracker_delete',
798 rmap.connect('repo_issuetracker_delete',
805 '/{repo_name}/settings/issue-tracker/delete',
799 '/{repo_name}/settings/issue-tracker/delete',
806 controller='admin/repos', action='repo_issuetracker_delete',
800 controller='admin/repos', action='repo_issuetracker_delete',
807 conditions={'method': ['DELETE'], 'function': check_repo},
801 conditions={'method': ['DELETE'], 'function': check_repo},
808 requirements=URL_NAME_REQUIREMENTS)
802 requirements=URL_NAME_REQUIREMENTS)
809 rmap.connect('repo_issuetracker_save',
803 rmap.connect('repo_issuetracker_save',
810 '/{repo_name}/settings/issue-tracker/save',
804 '/{repo_name}/settings/issue-tracker/save',
811 controller='admin/repos', action='repo_issuetracker_save',
805 controller='admin/repos', action='repo_issuetracker_save',
812 conditions={'method': ['POST'], 'function': check_repo},
806 conditions={'method': ['POST'], 'function': check_repo},
813 requirements=URL_NAME_REQUIREMENTS)
807 requirements=URL_NAME_REQUIREMENTS)
814 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
808 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
815 controller='admin/repos', action='repo_settings_vcs_update',
809 controller='admin/repos', action='repo_settings_vcs_update',
816 conditions={'method': ['POST'], 'function': check_repo},
810 conditions={'method': ['POST'], 'function': check_repo},
817 requirements=URL_NAME_REQUIREMENTS)
811 requirements=URL_NAME_REQUIREMENTS)
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
812 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
819 controller='admin/repos', action='repo_settings_vcs',
813 controller='admin/repos', action='repo_settings_vcs',
820 conditions={'method': ['GET'], 'function': check_repo},
814 conditions={'method': ['GET'], 'function': check_repo},
821 requirements=URL_NAME_REQUIREMENTS)
815 requirements=URL_NAME_REQUIREMENTS)
822 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
816 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
823 controller='admin/repos', action='repo_delete_svn_pattern',
817 controller='admin/repos', action='repo_delete_svn_pattern',
824 conditions={'method': ['DELETE'], 'function': check_repo},
818 conditions={'method': ['DELETE'], 'function': check_repo},
825 requirements=URL_NAME_REQUIREMENTS)
819 requirements=URL_NAME_REQUIREMENTS)
826 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
820 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
827 controller='admin/repos', action='repo_settings_pullrequest',
821 controller='admin/repos', action='repo_settings_pullrequest',
828 conditions={'method': ['GET', 'POST'], 'function': check_repo},
822 conditions={'method': ['GET', 'POST'], 'function': check_repo},
829 requirements=URL_NAME_REQUIREMENTS)
823 requirements=URL_NAME_REQUIREMENTS)
830
824
831 # still working url for backward compat.
825 # still working url for backward compat.
832 rmap.connect('raw_changeset_home_depraced',
826 rmap.connect('raw_changeset_home_depraced',
833 '/{repo_name}/raw-changeset/{revision}',
827 '/{repo_name}/raw-changeset/{revision}',
834 controller='changeset', action='changeset_raw',
828 controller='changeset', action='changeset_raw',
835 revision='tip', conditions={'function': check_repo},
829 revision='tip', conditions={'function': check_repo},
836 requirements=URL_NAME_REQUIREMENTS)
830 requirements=URL_NAME_REQUIREMENTS)
837
831
838 # new URLs
832 # new URLs
839 rmap.connect('changeset_raw_home',
833 rmap.connect('changeset_raw_home',
840 '/{repo_name}/changeset-diff/{revision}',
834 '/{repo_name}/changeset-diff/{revision}',
841 controller='changeset', action='changeset_raw',
835 controller='changeset', action='changeset_raw',
842 revision='tip', conditions={'function': check_repo},
836 revision='tip', conditions={'function': check_repo},
843 requirements=URL_NAME_REQUIREMENTS)
837 requirements=URL_NAME_REQUIREMENTS)
844
838
845 rmap.connect('changeset_patch_home',
839 rmap.connect('changeset_patch_home',
846 '/{repo_name}/changeset-patch/{revision}',
840 '/{repo_name}/changeset-patch/{revision}',
847 controller='changeset', action='changeset_patch',
841 controller='changeset', action='changeset_patch',
848 revision='tip', conditions={'function': check_repo},
842 revision='tip', conditions={'function': check_repo},
849 requirements=URL_NAME_REQUIREMENTS)
843 requirements=URL_NAME_REQUIREMENTS)
850
844
851 rmap.connect('changeset_download_home',
845 rmap.connect('changeset_download_home',
852 '/{repo_name}/changeset-download/{revision}',
846 '/{repo_name}/changeset-download/{revision}',
853 controller='changeset', action='changeset_download',
847 controller='changeset', action='changeset_download',
854 revision='tip', conditions={'function': check_repo},
848 revision='tip', conditions={'function': check_repo},
855 requirements=URL_NAME_REQUIREMENTS)
849 requirements=URL_NAME_REQUIREMENTS)
856
850
857 rmap.connect('changeset_comment',
851 rmap.connect('changeset_comment',
858 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
852 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
859 controller='changeset', revision='tip', action='comment',
853 controller='changeset', revision='tip', action='comment',
860 conditions={'function': check_repo},
854 conditions={'function': check_repo},
861 requirements=URL_NAME_REQUIREMENTS)
855 requirements=URL_NAME_REQUIREMENTS)
862
856
863 rmap.connect('changeset_comment_preview',
857 rmap.connect('changeset_comment_preview',
864 '/{repo_name}/changeset/comment/preview', jsroute=True,
858 '/{repo_name}/changeset/comment/preview', jsroute=True,
865 controller='changeset', action='preview_comment',
859 controller='changeset', action='preview_comment',
866 conditions={'function': check_repo, 'method': ['POST']},
860 conditions={'function': check_repo, 'method': ['POST']},
867 requirements=URL_NAME_REQUIREMENTS)
861 requirements=URL_NAME_REQUIREMENTS)
868
862
869 rmap.connect('changeset_comment_delete',
863 rmap.connect('changeset_comment_delete',
870 '/{repo_name}/changeset/comment/{comment_id}/delete',
864 '/{repo_name}/changeset/comment/{comment_id}/delete',
871 controller='changeset', action='delete_comment',
865 controller='changeset', action='delete_comment',
872 conditions={'function': check_repo, 'method': ['DELETE']},
866 conditions={'function': check_repo, 'method': ['DELETE']},
873 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
867 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
874
868
875 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
869 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
876 controller='changeset', action='changeset_info',
870 controller='changeset', action='changeset_info',
877 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
871 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
878
872
879 rmap.connect('compare_home',
873 rmap.connect('compare_home',
880 '/{repo_name}/compare',
874 '/{repo_name}/compare',
881 controller='compare', action='index',
875 controller='compare', action='index',
882 conditions={'function': check_repo},
876 conditions={'function': check_repo},
883 requirements=URL_NAME_REQUIREMENTS)
877 requirements=URL_NAME_REQUIREMENTS)
884
878
885 rmap.connect('compare_url',
879 rmap.connect('compare_url',
886 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
880 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
887 controller='compare', action='compare',
881 controller='compare', action='compare',
888 conditions={'function': check_repo},
882 conditions={'function': check_repo},
889 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
883 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
890
884
891 rmap.connect('pullrequest_home',
885 rmap.connect('pullrequest_home',
892 '/{repo_name}/pull-request/new', controller='pullrequests',
886 '/{repo_name}/pull-request/new', controller='pullrequests',
893 action='index', conditions={'function': check_repo,
887 action='index', conditions={'function': check_repo,
894 'method': ['GET']},
888 'method': ['GET']},
895 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
889 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
896
890
897 rmap.connect('pullrequest',
891 rmap.connect('pullrequest',
898 '/{repo_name}/pull-request/new', controller='pullrequests',
892 '/{repo_name}/pull-request/new', controller='pullrequests',
899 action='create', conditions={'function': check_repo,
893 action='create', conditions={'function': check_repo,
900 'method': ['POST']},
894 'method': ['POST']},
901 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
895 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
902
896
903 rmap.connect('pullrequest_repo_refs',
897 rmap.connect('pullrequest_repo_refs',
904 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
898 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
905 controller='pullrequests',
899 controller='pullrequests',
906 action='get_repo_refs',
900 action='get_repo_refs',
907 conditions={'function': check_repo, 'method': ['GET']},
901 conditions={'function': check_repo, 'method': ['GET']},
908 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
902 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
909
903
910 rmap.connect('pullrequest_repo_destinations',
904 rmap.connect('pullrequest_repo_destinations',
911 '/{repo_name}/pull-request/repo-destinations',
905 '/{repo_name}/pull-request/repo-destinations',
912 controller='pullrequests',
906 controller='pullrequests',
913 action='get_repo_destinations',
907 action='get_repo_destinations',
914 conditions={'function': check_repo, 'method': ['GET']},
908 conditions={'function': check_repo, 'method': ['GET']},
915 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
909 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
916
910
917 rmap.connect('pullrequest_show',
911 rmap.connect('pullrequest_show',
918 '/{repo_name}/pull-request/{pull_request_id}',
912 '/{repo_name}/pull-request/{pull_request_id}',
919 controller='pullrequests',
913 controller='pullrequests',
920 action='show', conditions={'function': check_repo,
914 action='show', conditions={'function': check_repo,
921 'method': ['GET']},
915 'method': ['GET']},
922 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
916 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
923
917
924 rmap.connect('pullrequest_update',
918 rmap.connect('pullrequest_update',
925 '/{repo_name}/pull-request/{pull_request_id}',
919 '/{repo_name}/pull-request/{pull_request_id}',
926 controller='pullrequests',
920 controller='pullrequests',
927 action='update', conditions={'function': check_repo,
921 action='update', conditions={'function': check_repo,
928 'method': ['PUT']},
922 'method': ['PUT']},
929 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
923 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
930
924
931 rmap.connect('pullrequest_merge',
925 rmap.connect('pullrequest_merge',
932 '/{repo_name}/pull-request/{pull_request_id}',
926 '/{repo_name}/pull-request/{pull_request_id}',
933 controller='pullrequests',
927 controller='pullrequests',
934 action='merge', conditions={'function': check_repo,
928 action='merge', conditions={'function': check_repo,
935 'method': ['POST']},
929 'method': ['POST']},
936 requirements=URL_NAME_REQUIREMENTS)
930 requirements=URL_NAME_REQUIREMENTS)
937
931
938 rmap.connect('pullrequest_delete',
932 rmap.connect('pullrequest_delete',
939 '/{repo_name}/pull-request/{pull_request_id}',
933 '/{repo_name}/pull-request/{pull_request_id}',
940 controller='pullrequests',
934 controller='pullrequests',
941 action='delete', conditions={'function': check_repo,
935 action='delete', conditions={'function': check_repo,
942 'method': ['DELETE']},
936 'method': ['DELETE']},
943 requirements=URL_NAME_REQUIREMENTS)
937 requirements=URL_NAME_REQUIREMENTS)
944
938
945 rmap.connect('pullrequest_show_all',
939 rmap.connect('pullrequest_show_all',
946 '/{repo_name}/pull-request',
940 '/{repo_name}/pull-request',
947 controller='pullrequests',
941 controller='pullrequests',
948 action='show_all', conditions={'function': check_repo,
942 action='show_all', conditions={'function': check_repo,
949 'method': ['GET']},
943 'method': ['GET']},
950 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
944 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
951
945
952 rmap.connect('pullrequest_comment',
946 rmap.connect('pullrequest_comment',
953 '/{repo_name}/pull-request-comment/{pull_request_id}',
947 '/{repo_name}/pull-request-comment/{pull_request_id}',
954 controller='pullrequests',
948 controller='pullrequests',
955 action='comment', conditions={'function': check_repo,
949 action='comment', conditions={'function': check_repo,
956 'method': ['POST']},
950 'method': ['POST']},
957 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
951 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
958
952
959 rmap.connect('pullrequest_comment_delete',
953 rmap.connect('pullrequest_comment_delete',
960 '/{repo_name}/pull-request-comment/{comment_id}/delete',
954 '/{repo_name}/pull-request-comment/{comment_id}/delete',
961 controller='pullrequests', action='delete_comment',
955 controller='pullrequests', action='delete_comment',
962 conditions={'function': check_repo, 'method': ['DELETE']},
956 conditions={'function': check_repo, 'method': ['DELETE']},
963 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
957 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
964
958
965 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
959 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
966 controller='summary', conditions={'function': check_repo},
960 controller='summary', conditions={'function': check_repo},
967 requirements=URL_NAME_REQUIREMENTS)
961 requirements=URL_NAME_REQUIREMENTS)
968
962
969 rmap.connect('branches_home', '/{repo_name}/branches',
963 rmap.connect('branches_home', '/{repo_name}/branches',
970 controller='branches', conditions={'function': check_repo},
964 controller='branches', conditions={'function': check_repo},
971 requirements=URL_NAME_REQUIREMENTS)
965 requirements=URL_NAME_REQUIREMENTS)
972
966
973 rmap.connect('tags_home', '/{repo_name}/tags',
967 rmap.connect('tags_home', '/{repo_name}/tags',
974 controller='tags', conditions={'function': check_repo},
968 controller='tags', conditions={'function': check_repo},
975 requirements=URL_NAME_REQUIREMENTS)
969 requirements=URL_NAME_REQUIREMENTS)
976
970
977 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
971 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
978 controller='bookmarks', conditions={'function': check_repo},
972 controller='bookmarks', conditions={'function': check_repo},
979 requirements=URL_NAME_REQUIREMENTS)
973 requirements=URL_NAME_REQUIREMENTS)
980
974
981 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
975 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
982 controller='changelog', conditions={'function': check_repo},
976 controller='changelog', conditions={'function': check_repo},
983 requirements=URL_NAME_REQUIREMENTS)
977 requirements=URL_NAME_REQUIREMENTS)
984
978
985 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
979 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
986 controller='changelog', action='changelog_summary',
980 controller='changelog', action='changelog_summary',
987 conditions={'function': check_repo},
981 conditions={'function': check_repo},
988 requirements=URL_NAME_REQUIREMENTS)
982 requirements=URL_NAME_REQUIREMENTS)
989
983
990 rmap.connect('changelog_file_home',
984 rmap.connect('changelog_file_home',
991 '/{repo_name}/changelog/{revision}/{f_path}',
985 '/{repo_name}/changelog/{revision}/{f_path}',
992 controller='changelog', f_path=None,
986 controller='changelog', f_path=None,
993 conditions={'function': check_repo},
987 conditions={'function': check_repo},
994 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
988 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
995
989
996 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
990 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
997 controller='changelog', action='changelog_elements',
991 controller='changelog', action='changelog_elements',
998 conditions={'function': check_repo},
992 conditions={'function': check_repo},
999 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
993 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1000
994
1001 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
995 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1002 controller='files', revision='tip', f_path='',
996 controller='files', revision='tip', f_path='',
1003 conditions={'function': check_repo},
997 conditions={'function': check_repo},
1004 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
998 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1005
999
1006 rmap.connect('files_home_simple_catchrev',
1000 rmap.connect('files_home_simple_catchrev',
1007 '/{repo_name}/files/{revision}',
1001 '/{repo_name}/files/{revision}',
1008 controller='files', revision='tip', f_path='',
1002 controller='files', revision='tip', f_path='',
1009 conditions={'function': check_repo},
1003 conditions={'function': check_repo},
1010 requirements=URL_NAME_REQUIREMENTS)
1004 requirements=URL_NAME_REQUIREMENTS)
1011
1005
1012 rmap.connect('files_home_simple_catchall',
1006 rmap.connect('files_home_simple_catchall',
1013 '/{repo_name}/files',
1007 '/{repo_name}/files',
1014 controller='files', revision='tip', f_path='',
1008 controller='files', revision='tip', f_path='',
1015 conditions={'function': check_repo},
1009 conditions={'function': check_repo},
1016 requirements=URL_NAME_REQUIREMENTS)
1010 requirements=URL_NAME_REQUIREMENTS)
1017
1011
1018 rmap.connect('files_history_home',
1012 rmap.connect('files_history_home',
1019 '/{repo_name}/history/{revision}/{f_path}',
1013 '/{repo_name}/history/{revision}/{f_path}',
1020 controller='files', action='history', revision='tip', f_path='',
1014 controller='files', action='history', revision='tip', f_path='',
1021 conditions={'function': check_repo},
1015 conditions={'function': check_repo},
1022 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1016 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1023
1017
1024 rmap.connect('files_authors_home',
1018 rmap.connect('files_authors_home',
1025 '/{repo_name}/authors/{revision}/{f_path}',
1019 '/{repo_name}/authors/{revision}/{f_path}',
1026 controller='files', action='authors', revision='tip', f_path='',
1020 controller='files', action='authors', revision='tip', f_path='',
1027 conditions={'function': check_repo},
1021 conditions={'function': check_repo},
1028 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1022 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1029
1023
1030 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1024 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1031 controller='files', action='diff', f_path='',
1025 controller='files', action='diff', f_path='',
1032 conditions={'function': check_repo},
1026 conditions={'function': check_repo},
1033 requirements=URL_NAME_REQUIREMENTS)
1027 requirements=URL_NAME_REQUIREMENTS)
1034
1028
1035 rmap.connect('files_diff_2way_home',
1029 rmap.connect('files_diff_2way_home',
1036 '/{repo_name}/diff-2way/{f_path}',
1030 '/{repo_name}/diff-2way/{f_path}',
1037 controller='files', action='diff_2way', f_path='',
1031 controller='files', action='diff_2way', f_path='',
1038 conditions={'function': check_repo},
1032 conditions={'function': check_repo},
1039 requirements=URL_NAME_REQUIREMENTS)
1033 requirements=URL_NAME_REQUIREMENTS)
1040
1034
1041 rmap.connect('files_rawfile_home',
1035 rmap.connect('files_rawfile_home',
1042 '/{repo_name}/rawfile/{revision}/{f_path}',
1036 '/{repo_name}/rawfile/{revision}/{f_path}',
1043 controller='files', action='rawfile', revision='tip',
1037 controller='files', action='rawfile', revision='tip',
1044 f_path='', conditions={'function': check_repo},
1038 f_path='', conditions={'function': check_repo},
1045 requirements=URL_NAME_REQUIREMENTS)
1039 requirements=URL_NAME_REQUIREMENTS)
1046
1040
1047 rmap.connect('files_raw_home',
1041 rmap.connect('files_raw_home',
1048 '/{repo_name}/raw/{revision}/{f_path}',
1042 '/{repo_name}/raw/{revision}/{f_path}',
1049 controller='files', action='raw', revision='tip', f_path='',
1043 controller='files', action='raw', revision='tip', f_path='',
1050 conditions={'function': check_repo},
1044 conditions={'function': check_repo},
1051 requirements=URL_NAME_REQUIREMENTS)
1045 requirements=URL_NAME_REQUIREMENTS)
1052
1046
1053 rmap.connect('files_render_home',
1047 rmap.connect('files_render_home',
1054 '/{repo_name}/render/{revision}/{f_path}',
1048 '/{repo_name}/render/{revision}/{f_path}',
1055 controller='files', action='index', revision='tip', f_path='',
1049 controller='files', action='index', revision='tip', f_path='',
1056 rendered=True, conditions={'function': check_repo},
1050 rendered=True, conditions={'function': check_repo},
1057 requirements=URL_NAME_REQUIREMENTS)
1051 requirements=URL_NAME_REQUIREMENTS)
1058
1052
1059 rmap.connect('files_annotate_home',
1053 rmap.connect('files_annotate_home',
1060 '/{repo_name}/annotate/{revision}/{f_path}',
1054 '/{repo_name}/annotate/{revision}/{f_path}',
1061 controller='files', action='index', revision='tip',
1055 controller='files', action='index', revision='tip',
1062 f_path='', annotate=True, conditions={'function': check_repo},
1056 f_path='', annotate=True, conditions={'function': check_repo},
1063 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1057 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1064
1058
1065 rmap.connect('files_annotate_previous',
1059 rmap.connect('files_annotate_previous',
1066 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1060 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1067 controller='files', action='annotate_previous', revision='tip',
1061 controller='files', action='annotate_previous', revision='tip',
1068 f_path='', annotate=True, conditions={'function': check_repo},
1062 f_path='', annotate=True, conditions={'function': check_repo},
1069 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1063 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1070
1064
1071 rmap.connect('files_edit',
1065 rmap.connect('files_edit',
1072 '/{repo_name}/edit/{revision}/{f_path}',
1066 '/{repo_name}/edit/{revision}/{f_path}',
1073 controller='files', action='edit', revision='tip',
1067 controller='files', action='edit', revision='tip',
1074 f_path='',
1068 f_path='',
1075 conditions={'function': check_repo, 'method': ['POST']},
1069 conditions={'function': check_repo, 'method': ['POST']},
1076 requirements=URL_NAME_REQUIREMENTS)
1070 requirements=URL_NAME_REQUIREMENTS)
1077
1071
1078 rmap.connect('files_edit_home',
1072 rmap.connect('files_edit_home',
1079 '/{repo_name}/edit/{revision}/{f_path}',
1073 '/{repo_name}/edit/{revision}/{f_path}',
1080 controller='files', action='edit_home', revision='tip',
1074 controller='files', action='edit_home', revision='tip',
1081 f_path='', conditions={'function': check_repo},
1075 f_path='', conditions={'function': check_repo},
1082 requirements=URL_NAME_REQUIREMENTS)
1076 requirements=URL_NAME_REQUIREMENTS)
1083
1077
1084 rmap.connect('files_add',
1078 rmap.connect('files_add',
1085 '/{repo_name}/add/{revision}/{f_path}',
1079 '/{repo_name}/add/{revision}/{f_path}',
1086 controller='files', action='add', revision='tip',
1080 controller='files', action='add', revision='tip',
1087 f_path='',
1081 f_path='',
1088 conditions={'function': check_repo, 'method': ['POST']},
1082 conditions={'function': check_repo, 'method': ['POST']},
1089 requirements=URL_NAME_REQUIREMENTS)
1083 requirements=URL_NAME_REQUIREMENTS)
1090
1084
1091 rmap.connect('files_add_home',
1085 rmap.connect('files_add_home',
1092 '/{repo_name}/add/{revision}/{f_path}',
1086 '/{repo_name}/add/{revision}/{f_path}',
1093 controller='files', action='add_home', revision='tip',
1087 controller='files', action='add_home', revision='tip',
1094 f_path='', conditions={'function': check_repo},
1088 f_path='', conditions={'function': check_repo},
1095 requirements=URL_NAME_REQUIREMENTS)
1089 requirements=URL_NAME_REQUIREMENTS)
1096
1090
1097 rmap.connect('files_delete',
1091 rmap.connect('files_delete',
1098 '/{repo_name}/delete/{revision}/{f_path}',
1092 '/{repo_name}/delete/{revision}/{f_path}',
1099 controller='files', action='delete', revision='tip',
1093 controller='files', action='delete', revision='tip',
1100 f_path='',
1094 f_path='',
1101 conditions={'function': check_repo, 'method': ['POST']},
1095 conditions={'function': check_repo, 'method': ['POST']},
1102 requirements=URL_NAME_REQUIREMENTS)
1096 requirements=URL_NAME_REQUIREMENTS)
1103
1097
1104 rmap.connect('files_delete_home',
1098 rmap.connect('files_delete_home',
1105 '/{repo_name}/delete/{revision}/{f_path}',
1099 '/{repo_name}/delete/{revision}/{f_path}',
1106 controller='files', action='delete_home', revision='tip',
1100 controller='files', action='delete_home', revision='tip',
1107 f_path='', conditions={'function': check_repo},
1101 f_path='', conditions={'function': check_repo},
1108 requirements=URL_NAME_REQUIREMENTS)
1102 requirements=URL_NAME_REQUIREMENTS)
1109
1103
1110 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1104 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1111 controller='files', action='archivefile',
1105 controller='files', action='archivefile',
1112 conditions={'function': check_repo},
1106 conditions={'function': check_repo},
1113 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1107 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1114
1108
1115 rmap.connect('files_nodelist_home',
1109 rmap.connect('files_nodelist_home',
1116 '/{repo_name}/nodelist/{revision}/{f_path}',
1110 '/{repo_name}/nodelist/{revision}/{f_path}',
1117 controller='files', action='nodelist',
1111 controller='files', action='nodelist',
1118 conditions={'function': check_repo},
1112 conditions={'function': check_repo},
1119 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1113 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1120
1114
1121 rmap.connect('files_nodetree_full',
1115 rmap.connect('files_nodetree_full',
1122 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1116 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1123 controller='files', action='nodetree_full',
1117 controller='files', action='nodetree_full',
1124 conditions={'function': check_repo},
1118 conditions={'function': check_repo},
1125 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1119 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1126
1120
1127 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1121 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1128 controller='forks', action='fork_create',
1122 controller='forks', action='fork_create',
1129 conditions={'function': check_repo, 'method': ['POST']},
1123 conditions={'function': check_repo, 'method': ['POST']},
1130 requirements=URL_NAME_REQUIREMENTS)
1124 requirements=URL_NAME_REQUIREMENTS)
1131
1125
1132 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1126 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1133 controller='forks', action='fork',
1127 controller='forks', action='fork',
1134 conditions={'function': check_repo},
1128 conditions={'function': check_repo},
1135 requirements=URL_NAME_REQUIREMENTS)
1129 requirements=URL_NAME_REQUIREMENTS)
1136
1130
1137 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1131 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1138 controller='forks', action='forks',
1132 controller='forks', action='forks',
1139 conditions={'function': check_repo},
1133 conditions={'function': check_repo},
1140 requirements=URL_NAME_REQUIREMENTS)
1134 requirements=URL_NAME_REQUIREMENTS)
1141
1135
1142 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1136 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1143 controller='followers', action='followers',
1137 controller='followers', action='followers',
1144 conditions={'function': check_repo},
1138 conditions={'function': check_repo},
1145 requirements=URL_NAME_REQUIREMENTS)
1139 requirements=URL_NAME_REQUIREMENTS)
1146
1140
1147 # must be here for proper group/repo catching pattern
1141 # must be here for proper group/repo catching pattern
1148 _connect_with_slash(
1142 _connect_with_slash(
1149 rmap, 'repo_group_home', '/{group_name}',
1143 rmap, 'repo_group_home', '/{group_name}',
1150 controller='home', action='index_repo_group',
1144 controller='home', action='index_repo_group',
1151 conditions={'function': check_group},
1145 conditions={'function': check_group},
1152 requirements=URL_NAME_REQUIREMENTS)
1146 requirements=URL_NAME_REQUIREMENTS)
1153
1147
1154 # catch all, at the end
1148 # catch all, at the end
1155 _connect_with_slash(
1149 _connect_with_slash(
1156 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1150 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1157 controller='summary', action='index',
1151 controller='summary', action='index',
1158 conditions={'function': check_repo},
1152 conditions={'function': check_repo},
1159 requirements=URL_NAME_REQUIREMENTS)
1153 requirements=URL_NAME_REQUIREMENTS)
1160
1154
1161 return rmap
1155 return rmap
1162
1156
1163
1157
1164 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1158 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1165 """
1159 """
1166 Connect a route with an optional trailing slash in `path`.
1160 Connect a route with an optional trailing slash in `path`.
1167 """
1161 """
1168 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1162 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1169 mapper.connect(name, path, *args, **kwargs)
1163 mapper.connect(name, path, *args, **kwargs)
@@ -1,461 +1,418 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 my account controller for RhodeCode admin
23 my account controller for RhodeCode admin
24 """
24 """
25
25
26 import logging
26 import logging
27 import datetime
27 import datetime
28
28
29 import formencode
29 import formencode
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pyramid.threadlocal import get_current_registry
31 from pyramid.threadlocal import get_current_registry
32 from pylons import request, tmpl_context as c, url, session
32 from pylons import request, tmpl_context as c, url, session
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm import joinedload
36 from webob.exc import HTTPBadGateway
37
36
38 from rhodecode import forms
37 from rhodecode import forms
39 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
40 from rhodecode.lib import auth
39 from rhodecode.lib import auth
41 from rhodecode.lib.auth import (
40 from rhodecode.lib.auth import (
42 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
41 LoginRequired, NotAnonymous, AuthUser)
43 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils import jsonify
45 from rhodecode.lib.utils2 import safe_int, md5, str2bool
44 from rhodecode.lib.utils2 import safe_int, md5, str2bool
46 from rhodecode.lib.ext_json import json
45 from rhodecode.lib.ext_json import json
47 from rhodecode.lib.channelstream import channelstream_request, \
46 from rhodecode.lib.channelstream import channelstream_request, \
48 ChannelstreamException
47 ChannelstreamException
49
48
50 from rhodecode.model.validation_schema.schemas import user_schema
49 from rhodecode.model.validation_schema.schemas import user_schema
51 from rhodecode.model.db import (
50 from rhodecode.model.db import (
52 Repository, PullRequest, UserEmailMap, User, UserFollowing)
51 Repository, PullRequest, UserEmailMap, User, UserFollowing)
53 from rhodecode.model.forms import UserForm
52 from rhodecode.model.forms import UserForm
54 from rhodecode.model.scm import RepoList
53 from rhodecode.model.scm import RepoList
55 from rhodecode.model.user import UserModel
54 from rhodecode.model.user import UserModel
56 from rhodecode.model.repo import RepoModel
55 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.auth_token import AuthTokenModel
58 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
59 from rhodecode.model.pull_request import PullRequestModel
57 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.comment import CommentsModel
58 from rhodecode.model.comment import CommentsModel
61
59
62 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
63
61
64
62
65 class MyAccountController(BaseController):
63 class MyAccountController(BaseController):
66 """REST Controller styled on the Atom Publishing Protocol"""
64 """REST Controller styled on the Atom Publishing Protocol"""
67 # To properly map this controller, ensure your config/routing.py
65 # To properly map this controller, ensure your config/routing.py
68 # file has a resource setup:
66 # file has a resource setup:
69 # map.resource('setting', 'settings', controller='admin/settings',
67 # map.resource('setting', 'settings', controller='admin/settings',
70 # path_prefix='/admin', name_prefix='admin_')
68 # path_prefix='/admin', name_prefix='admin_')
71
69
72 @LoginRequired()
70 @LoginRequired()
73 @NotAnonymous()
71 @NotAnonymous()
74 def __before__(self):
72 def __before__(self):
75 super(MyAccountController, self).__before__()
73 super(MyAccountController, self).__before__()
76
74
77 def __load_data(self):
75 def __load_data(self):
78 c.user = User.get(c.rhodecode_user.user_id)
76 c.user = User.get(c.rhodecode_user.user_id)
79 if c.user.username == User.DEFAULT_USER:
77 if c.user.username == User.DEFAULT_USER:
80 h.flash(_("You can't edit this user since it's"
78 h.flash(_("You can't edit this user since it's"
81 " crucial for entire application"), category='warning')
79 " crucial for entire application"), category='warning')
82 return redirect(url('users'))
80 return redirect(url('users'))
83
81
84 c.auth_user = AuthUser(
82 c.auth_user = AuthUser(
85 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
83 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
86
84
87 def _load_my_repos_data(self, watched=False):
85 def _load_my_repos_data(self, watched=False):
88 if watched:
86 if watched:
89 admin = False
87 admin = False
90 follows_repos = Session().query(UserFollowing)\
88 follows_repos = Session().query(UserFollowing)\
91 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
89 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
92 .options(joinedload(UserFollowing.follows_repository))\
90 .options(joinedload(UserFollowing.follows_repository))\
93 .all()
91 .all()
94 repo_list = [x.follows_repository for x in follows_repos]
92 repo_list = [x.follows_repository for x in follows_repos]
95 else:
93 else:
96 admin = True
94 admin = True
97 repo_list = Repository.get_all_repos(
95 repo_list = Repository.get_all_repos(
98 user_id=c.rhodecode_user.user_id)
96 user_id=c.rhodecode_user.user_id)
99 repo_list = RepoList(repo_list, perm_set=[
97 repo_list = RepoList(repo_list, perm_set=[
100 'repository.read', 'repository.write', 'repository.admin'])
98 'repository.read', 'repository.write', 'repository.admin'])
101
99
102 repos_data = RepoModel().get_repos_as_dict(
100 repos_data = RepoModel().get_repos_as_dict(
103 repo_list=repo_list, admin=admin)
101 repo_list=repo_list, admin=admin)
104 # json used to render the grid
102 # json used to render the grid
105 return json.dumps(repos_data)
103 return json.dumps(repos_data)
106
104
107 @auth.CSRFRequired()
105 @auth.CSRFRequired()
108 def my_account_update(self):
106 def my_account_update(self):
109 """
107 """
110 POST /_admin/my_account Updates info of my account
108 POST /_admin/my_account Updates info of my account
111 """
109 """
112 # url('my_account')
110 # url('my_account')
113 c.active = 'profile_edit'
111 c.active = 'profile_edit'
114 self.__load_data()
112 self.__load_data()
115 c.perm_user = c.auth_user
113 c.perm_user = c.auth_user
116 c.extern_type = c.user.extern_type
114 c.extern_type = c.user.extern_type
117 c.extern_name = c.user.extern_name
115 c.extern_name = c.user.extern_name
118
116
119 defaults = c.user.get_dict()
117 defaults = c.user.get_dict()
120 update = False
118 update = False
121 _form = UserForm(edit=True,
119 _form = UserForm(edit=True,
122 old_data={'user_id': c.rhodecode_user.user_id,
120 old_data={'user_id': c.rhodecode_user.user_id,
123 'email': c.rhodecode_user.email})()
121 'email': c.rhodecode_user.email})()
124 form_result = {}
122 form_result = {}
125 try:
123 try:
126 post_data = dict(request.POST)
124 post_data = dict(request.POST)
127 post_data['new_password'] = ''
125 post_data['new_password'] = ''
128 post_data['password_confirmation'] = ''
126 post_data['password_confirmation'] = ''
129 form_result = _form.to_python(post_data)
127 form_result = _form.to_python(post_data)
130 # skip updating those attrs for my account
128 # skip updating those attrs for my account
131 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
129 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
132 'new_password', 'password_confirmation']
130 'new_password', 'password_confirmation']
133 # TODO: plugin should define if username can be updated
131 # TODO: plugin should define if username can be updated
134 if c.extern_type != "rhodecode":
132 if c.extern_type != "rhodecode":
135 # forbid updating username for external accounts
133 # forbid updating username for external accounts
136 skip_attrs.append('username')
134 skip_attrs.append('username')
137
135
138 UserModel().update_user(
136 UserModel().update_user(
139 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
137 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
140 h.flash(_('Your account was updated successfully'),
138 h.flash(_('Your account was updated successfully'),
141 category='success')
139 category='success')
142 Session().commit()
140 Session().commit()
143 update = True
141 update = True
144
142
145 except formencode.Invalid as errors:
143 except formencode.Invalid as errors:
146 return htmlfill.render(
144 return htmlfill.render(
147 render('admin/my_account/my_account.mako'),
145 render('admin/my_account/my_account.mako'),
148 defaults=errors.value,
146 defaults=errors.value,
149 errors=errors.error_dict or {},
147 errors=errors.error_dict or {},
150 prefix_error=False,
148 prefix_error=False,
151 encoding="UTF-8",
149 encoding="UTF-8",
152 force_defaults=False)
150 force_defaults=False)
153 except Exception:
151 except Exception:
154 log.exception("Exception updating user")
152 log.exception("Exception updating user")
155 h.flash(_('Error occurred during update of user %s')
153 h.flash(_('Error occurred during update of user %s')
156 % form_result.get('username'), category='error')
154 % form_result.get('username'), category='error')
157
155
158 if update:
156 if update:
159 return redirect('my_account')
157 return redirect('my_account')
160
158
161 return htmlfill.render(
159 return htmlfill.render(
162 render('admin/my_account/my_account.mako'),
160 render('admin/my_account/my_account.mako'),
163 defaults=defaults,
161 defaults=defaults,
164 encoding="UTF-8",
162 encoding="UTF-8",
165 force_defaults=False
163 force_defaults=False
166 )
164 )
167
165
168 def my_account(self):
166 def my_account(self):
169 """
167 """
170 GET /_admin/my_account Displays info about my account
168 GET /_admin/my_account Displays info about my account
171 """
169 """
172 # url('my_account')
170 # url('my_account')
173 c.active = 'profile'
171 c.active = 'profile'
174 self.__load_data()
172 self.__load_data()
175
173
176 defaults = c.user.get_dict()
174 defaults = c.user.get_dict()
177 return htmlfill.render(
175 return htmlfill.render(
178 render('admin/my_account/my_account.mako'),
176 render('admin/my_account/my_account.mako'),
179 defaults=defaults, encoding="UTF-8", force_defaults=False)
177 defaults=defaults, encoding="UTF-8", force_defaults=False)
180
178
181 def my_account_edit(self):
179 def my_account_edit(self):
182 """
180 """
183 GET /_admin/my_account/edit Displays edit form of my account
181 GET /_admin/my_account/edit Displays edit form of my account
184 """
182 """
185 c.active = 'profile_edit'
183 c.active = 'profile_edit'
186 self.__load_data()
184 self.__load_data()
187 c.perm_user = c.auth_user
185 c.perm_user = c.auth_user
188 c.extern_type = c.user.extern_type
186 c.extern_type = c.user.extern_type
189 c.extern_name = c.user.extern_name
187 c.extern_name = c.user.extern_name
190
188
191 defaults = c.user.get_dict()
189 defaults = c.user.get_dict()
192 return htmlfill.render(
190 return htmlfill.render(
193 render('admin/my_account/my_account.mako'),
191 render('admin/my_account/my_account.mako'),
194 defaults=defaults,
192 defaults=defaults,
195 encoding="UTF-8",
193 encoding="UTF-8",
196 force_defaults=False
194 force_defaults=False
197 )
195 )
198
196
199 @auth.CSRFRequired(except_methods=['GET'])
197 @auth.CSRFRequired(except_methods=['GET'])
200 def my_account_password(self):
198 def my_account_password(self):
201 c.active = 'password'
199 c.active = 'password'
202 self.__load_data()
200 self.__load_data()
203 c.extern_type = c.user.extern_type
201 c.extern_type = c.user.extern_type
204
202
205 schema = user_schema.ChangePasswordSchema().bind(
203 schema = user_schema.ChangePasswordSchema().bind(
206 username=c.rhodecode_user.username)
204 username=c.rhodecode_user.username)
207
205
208 form = forms.Form(schema,
206 form = forms.Form(schema,
209 buttons=(forms.buttons.save, forms.buttons.reset))
207 buttons=(forms.buttons.save, forms.buttons.reset))
210
208
211 if request.method == 'POST' and c.extern_type == 'rhodecode':
209 if request.method == 'POST' and c.extern_type == 'rhodecode':
212 controls = request.POST.items()
210 controls = request.POST.items()
213 try:
211 try:
214 valid_data = form.validate(controls)
212 valid_data = form.validate(controls)
215 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
213 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
216 instance = c.rhodecode_user.get_instance()
214 instance = c.rhodecode_user.get_instance()
217 instance.update_userdata(force_password_change=False)
215 instance.update_userdata(force_password_change=False)
218 Session().commit()
216 Session().commit()
219 except forms.ValidationFailure as e:
217 except forms.ValidationFailure as e:
220 request.session.flash(
218 request.session.flash(
221 _('Error occurred during update of user password'),
219 _('Error occurred during update of user password'),
222 queue='error')
220 queue='error')
223 form = e
221 form = e
224 except Exception:
222 except Exception:
225 log.exception("Exception updating password")
223 log.exception("Exception updating password")
226 request.session.flash(
224 request.session.flash(
227 _('Error occurred during update of user password'),
225 _('Error occurred during update of user password'),
228 queue='error')
226 queue='error')
229 else:
227 else:
230 session.setdefault('rhodecode_user', {}).update(
228 session.setdefault('rhodecode_user', {}).update(
231 {'password': md5(instance.password)})
229 {'password': md5(instance.password)})
232 session.save()
230 session.save()
233 request.session.flash(
231 request.session.flash(
234 _("Successfully updated password"), queue='success')
232 _("Successfully updated password"), queue='success')
235 return redirect(url('my_account_password'))
233 return redirect(url('my_account_password'))
236
234
237 c.form = form
235 c.form = form
238 return render('admin/my_account/my_account.mako')
236 return render('admin/my_account/my_account.mako')
239
237
240 def my_account_repos(self):
238 def my_account_repos(self):
241 c.active = 'repos'
239 c.active = 'repos'
242 self.__load_data()
240 self.__load_data()
243
241
244 # json used to render the grid
242 # json used to render the grid
245 c.data = self._load_my_repos_data()
243 c.data = self._load_my_repos_data()
246 return render('admin/my_account/my_account.mako')
244 return render('admin/my_account/my_account.mako')
247
245
248 def my_account_watched(self):
246 def my_account_watched(self):
249 c.active = 'watched'
247 c.active = 'watched'
250 self.__load_data()
248 self.__load_data()
251
249
252 # json used to render the grid
250 # json used to render the grid
253 c.data = self._load_my_repos_data(watched=True)
251 c.data = self._load_my_repos_data(watched=True)
254 return render('admin/my_account/my_account.mako')
252 return render('admin/my_account/my_account.mako')
255
253
256 def my_account_perms(self):
254 def my_account_perms(self):
257 c.active = 'perms'
255 c.active = 'perms'
258 self.__load_data()
256 self.__load_data()
259 c.perm_user = c.auth_user
257 c.perm_user = c.auth_user
260
258
261 return render('admin/my_account/my_account.mako')
259 return render('admin/my_account/my_account.mako')
262
260
263 def my_account_emails(self):
261 def my_account_emails(self):
264 c.active = 'emails'
262 c.active = 'emails'
265 self.__load_data()
263 self.__load_data()
266
264
267 c.user_email_map = UserEmailMap.query()\
265 c.user_email_map = UserEmailMap.query()\
268 .filter(UserEmailMap.user == c.user).all()
266 .filter(UserEmailMap.user == c.user).all()
269 return render('admin/my_account/my_account.mako')
267 return render('admin/my_account/my_account.mako')
270
268
271 @auth.CSRFRequired()
269 @auth.CSRFRequired()
272 def my_account_emails_add(self):
270 def my_account_emails_add(self):
273 email = request.POST.get('new_email')
271 email = request.POST.get('new_email')
274
272
275 try:
273 try:
276 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
274 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
277 Session().commit()
275 Session().commit()
278 h.flash(_("Added new email address `%s` for user account") % email,
276 h.flash(_("Added new email address `%s` for user account") % email,
279 category='success')
277 category='success')
280 except formencode.Invalid as error:
278 except formencode.Invalid as error:
281 msg = error.error_dict['email']
279 msg = error.error_dict['email']
282 h.flash(msg, category='error')
280 h.flash(msg, category='error')
283 except Exception:
281 except Exception:
284 log.exception("Exception in my_account_emails")
282 log.exception("Exception in my_account_emails")
285 h.flash(_('An error occurred during email saving'),
283 h.flash(_('An error occurred during email saving'),
286 category='error')
284 category='error')
287 return redirect(url('my_account_emails'))
285 return redirect(url('my_account_emails'))
288
286
289 @auth.CSRFRequired()
287 @auth.CSRFRequired()
290 def my_account_emails_delete(self):
288 def my_account_emails_delete(self):
291 email_id = request.POST.get('del_email_id')
289 email_id = request.POST.get('del_email_id')
292 user_model = UserModel()
290 user_model = UserModel()
293 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
291 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
294 Session().commit()
292 Session().commit()
295 h.flash(_("Removed email address from user account"),
293 h.flash(_("Removed email address from user account"),
296 category='success')
294 category='success')
297 return redirect(url('my_account_emails'))
295 return redirect(url('my_account_emails'))
298
296
299 def _extract_ordering(self, request):
297 def _extract_ordering(self, request):
300 column_index = safe_int(request.GET.get('order[0][column]'))
298 column_index = safe_int(request.GET.get('order[0][column]'))
301 order_dir = request.GET.get('order[0][dir]', 'desc')
299 order_dir = request.GET.get('order[0][dir]', 'desc')
302 order_by = request.GET.get(
300 order_by = request.GET.get(
303 'columns[%s][data][sort]' % column_index, 'name_raw')
301 'columns[%s][data][sort]' % column_index, 'name_raw')
304 return order_by, order_dir
302 return order_by, order_dir
305
303
306 def _get_pull_requests_list(self, statuses):
304 def _get_pull_requests_list(self, statuses):
307 start = safe_int(request.GET.get('start'), 0)
305 start = safe_int(request.GET.get('start'), 0)
308 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
306 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
309 order_by, order_dir = self._extract_ordering(request)
307 order_by, order_dir = self._extract_ordering(request)
310
308
311 pull_requests = PullRequestModel().get_im_participating_in(
309 pull_requests = PullRequestModel().get_im_participating_in(
312 user_id=c.rhodecode_user.user_id,
310 user_id=c.rhodecode_user.user_id,
313 statuses=statuses,
311 statuses=statuses,
314 offset=start, length=length, order_by=order_by,
312 offset=start, length=length, order_by=order_by,
315 order_dir=order_dir)
313 order_dir=order_dir)
316
314
317 pull_requests_total_count = PullRequestModel().count_im_participating_in(
315 pull_requests_total_count = PullRequestModel().count_im_participating_in(
318 user_id=c.rhodecode_user.user_id, statuses=statuses)
316 user_id=c.rhodecode_user.user_id, statuses=statuses)
319
317
320 from rhodecode.lib.utils import PartialRenderer
318 from rhodecode.lib.utils import PartialRenderer
321 _render = PartialRenderer('data_table/_dt_elements.mako')
319 _render = PartialRenderer('data_table/_dt_elements.mako')
322 data = []
320 data = []
323 for pr in pull_requests:
321 for pr in pull_requests:
324 repo_id = pr.target_repo_id
322 repo_id = pr.target_repo_id
325 comments = CommentsModel().get_all_comments(
323 comments = CommentsModel().get_all_comments(
326 repo_id, pull_request=pr)
324 repo_id, pull_request=pr)
327 owned = pr.user_id == c.rhodecode_user.user_id
325 owned = pr.user_id == c.rhodecode_user.user_id
328 status = pr.calculated_review_status()
326 status = pr.calculated_review_status()
329
327
330 data.append({
328 data.append({
331 'target_repo': _render('pullrequest_target_repo',
329 'target_repo': _render('pullrequest_target_repo',
332 pr.target_repo.repo_name),
330 pr.target_repo.repo_name),
333 'name': _render('pullrequest_name',
331 'name': _render('pullrequest_name',
334 pr.pull_request_id, pr.target_repo.repo_name,
332 pr.pull_request_id, pr.target_repo.repo_name,
335 short=True),
333 short=True),
336 'name_raw': pr.pull_request_id,
334 'name_raw': pr.pull_request_id,
337 'status': _render('pullrequest_status', status),
335 'status': _render('pullrequest_status', status),
338 'title': _render(
336 'title': _render(
339 'pullrequest_title', pr.title, pr.description),
337 'pullrequest_title', pr.title, pr.description),
340 'description': h.escape(pr.description),
338 'description': h.escape(pr.description),
341 'updated_on': _render('pullrequest_updated_on',
339 'updated_on': _render('pullrequest_updated_on',
342 h.datetime_to_time(pr.updated_on)),
340 h.datetime_to_time(pr.updated_on)),
343 'updated_on_raw': h.datetime_to_time(pr.updated_on),
341 'updated_on_raw': h.datetime_to_time(pr.updated_on),
344 'created_on': _render('pullrequest_updated_on',
342 'created_on': _render('pullrequest_updated_on',
345 h.datetime_to_time(pr.created_on)),
343 h.datetime_to_time(pr.created_on)),
346 'created_on_raw': h.datetime_to_time(pr.created_on),
344 'created_on_raw': h.datetime_to_time(pr.created_on),
347 'author': _render('pullrequest_author',
345 'author': _render('pullrequest_author',
348 pr.author.full_contact, ),
346 pr.author.full_contact, ),
349 'author_raw': pr.author.full_name,
347 'author_raw': pr.author.full_name,
350 'comments': _render('pullrequest_comments', len(comments)),
348 'comments': _render('pullrequest_comments', len(comments)),
351 'comments_raw': len(comments),
349 'comments_raw': len(comments),
352 'closed': pr.is_closed(),
350 'closed': pr.is_closed(),
353 'owned': owned
351 'owned': owned
354 })
352 })
355 # json used to render the grid
353 # json used to render the grid
356 data = ({
354 data = ({
357 'data': data,
355 'data': data,
358 'recordsTotal': pull_requests_total_count,
356 'recordsTotal': pull_requests_total_count,
359 'recordsFiltered': pull_requests_total_count,
357 'recordsFiltered': pull_requests_total_count,
360 })
358 })
361 return data
359 return data
362
360
363 def my_account_pullrequests(self):
361 def my_account_pullrequests(self):
364 c.active = 'pullrequests'
362 c.active = 'pullrequests'
365 self.__load_data()
363 self.__load_data()
366 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
364 c.show_closed = str2bool(request.GET.get('pr_show_closed'))
367
365
368 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
366 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
369 if c.show_closed:
367 if c.show_closed:
370 statuses += [PullRequest.STATUS_CLOSED]
368 statuses += [PullRequest.STATUS_CLOSED]
371 data = self._get_pull_requests_list(statuses)
369 data = self._get_pull_requests_list(statuses)
372 if not request.is_xhr:
370 if not request.is_xhr:
373 c.data_participate = json.dumps(data['data'])
371 c.data_participate = json.dumps(data['data'])
374 c.records_total_participate = data['recordsTotal']
372 c.records_total_participate = data['recordsTotal']
375 return render('admin/my_account/my_account.mako')
373 return render('admin/my_account/my_account.mako')
376 else:
374 else:
377 return json.dumps(data)
375 return json.dumps(data)
378
376
379 def my_account_auth_tokens(self):
380 c.active = 'auth_tokens'
381 self.__load_data()
382 show_expired = True
383 c.lifetime_values = [
384 (str(-1), _('forever')),
385 (str(5), _('5 minutes')),
386 (str(60), _('1 hour')),
387 (str(60 * 24), _('1 day')),
388 (str(60 * 24 * 30), _('1 month')),
389 ]
390 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
391 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
392 for x in AuthTokenModel.cls.ROLES]
393 c.role_options = [(c.role_values, _("Role"))]
394 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
395 c.rhodecode_user.user_id, show_expired=show_expired)
396 return render('admin/my_account/my_account.mako')
397
398 @auth.CSRFRequired()
399 def my_account_auth_tokens_add(self):
400 lifetime = safe_int(request.POST.get('lifetime'), -1)
401 description = request.POST.get('description')
402 role = request.POST.get('role')
403 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
404 role)
405 Session().commit()
406 h.flash(_("Auth token successfully created"), category='success')
407 return redirect(url('my_account_auth_tokens'))
408
409 @auth.CSRFRequired()
410 def my_account_auth_tokens_delete(self):
411 del_auth_token = request.POST.get('del_auth_token')
412
413 if del_auth_token:
414 AuthTokenModel().delete(del_auth_token, c.rhodecode_user.user_id)
415 Session().commit()
416 h.flash(_("Auth token successfully deleted"), category='success')
417
418 return redirect(url('my_account_auth_tokens'))
419
420 def my_notifications(self):
377 def my_notifications(self):
421 c.active = 'notifications'
378 c.active = 'notifications'
422 return render('admin/my_account/my_account.mako')
379 return render('admin/my_account/my_account.mako')
423
380
424 @auth.CSRFRequired()
381 @auth.CSRFRequired()
425 @jsonify
382 @jsonify
426 def my_notifications_toggle_visibility(self):
383 def my_notifications_toggle_visibility(self):
427 user = c.rhodecode_user.get_instance()
384 user = c.rhodecode_user.get_instance()
428 new_status = not user.user_data.get('notification_status', True)
385 new_status = not user.user_data.get('notification_status', True)
429 user.update_userdata(notification_status=new_status)
386 user.update_userdata(notification_status=new_status)
430 Session().commit()
387 Session().commit()
431 return user.user_data['notification_status']
388 return user.user_data['notification_status']
432
389
433 @auth.CSRFRequired()
390 @auth.CSRFRequired()
434 @jsonify
391 @jsonify
435 def my_account_notifications_test_channelstream(self):
392 def my_account_notifications_test_channelstream(self):
436 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
393 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
437 c.rhodecode_user.username, datetime.datetime.now())
394 c.rhodecode_user.username, datetime.datetime.now())
438 payload = {
395 payload = {
439 'type': 'message',
396 'type': 'message',
440 'timestamp': datetime.datetime.utcnow(),
397 'timestamp': datetime.datetime.utcnow(),
441 'user': 'system',
398 'user': 'system',
442 #'channel': 'broadcast',
399 #'channel': 'broadcast',
443 'pm_users': [c.rhodecode_user.username],
400 'pm_users': [c.rhodecode_user.username],
444 'message': {
401 'message': {
445 'message': message,
402 'message': message,
446 'level': 'info',
403 'level': 'info',
447 'topic': '/notifications'
404 'topic': '/notifications'
448 }
405 }
449 }
406 }
450
407
451 registry = get_current_registry()
408 registry = get_current_registry()
452 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
409 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
453 channelstream_config = rhodecode_plugins.get('channelstream', {})
410 channelstream_config = rhodecode_plugins.get('channelstream', {})
454
411
455 try:
412 try:
456 channelstream_request(channelstream_config, [payload], '/message')
413 channelstream_request(channelstream_config, [payload], '/message')
457 except ChannelstreamException as e:
414 except ChannelstreamException as e:
458 log.exception('Failed to send channelstream data')
415 log.exception('Failed to send channelstream data')
459 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
416 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
460 return {"response": 'Channelstream data sent. '
417 return {"response": 'Channelstream data sent. '
461 'You should see a new live message now.'}
418 'You should see a new live message now.'}
@@ -1,52 +1,52 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('My account')} ${c.rhodecode_user.username}
5 ${_('My account')} ${c.rhodecode_user.username}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${_('My Account')}
12 ${_('My Account')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_nav()">
15 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='my_account')}
16 ${self.menu_items(active='my_account')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24
24
25 <div class="sidebar-col-wrapper scw-small">
25 <div class="sidebar-col-wrapper scw-small">
26 ##main
26 ##main
27 <div class="sidebar">
27 <div class="sidebar">
28 <ul class="nav nav-pills nav-stacked">
28 <ul class="nav nav-pills nav-stacked">
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.url('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
32 ## TODO: Find a better integration of oauth views into navigation.
32 ## TODO: Find a better integration of oauth views into navigation.
33 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
33 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
34 % if my_account_oauth_url:
34 % if my_account_oauth_url:
35 <li class="${'active' if c.active=='oauth' else ''}"><a href="${my_account_oauth_url}">${_('OAuth Identities')}</a></li>
35 <li class="${'active' if c.active=='oauth' else ''}"><a href="${my_account_oauth_url}">${_('OAuth Identities')}</a></li>
36 % endif
36 % endif
37 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('Emails')}</a></li>
37 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('Emails')}</a></li>
38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('Repositories')}</a></li>
38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('Repositories')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</a></li>
40 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.url('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
40 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.url('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('Permissions')}</a></li>
41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('Permissions')}</a></li>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.url('my_account_notifications')}">${_('Live Notifications')}</a></li>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.url('my_account_notifications')}">${_('Live Notifications')}</a></li>
43 </ul>
43 </ul>
44 </div>
44 </div>
45
45
46 <div class="main-content-full-width">
46 <div class="main-content-full-width">
47 <%include file="/admin/my_account/my_account_${c.active}.mako"/>
47 <%include file="/admin/my_account/my_account_${c.active}.mako"/>
48 </div>
48 </div>
49 </div>
49 </div>
50 </div>
50 </div>
51
51
52 </%def>
52 </%def>
@@ -1,95 +1,95 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <p>
6 <p>
7 ${_('Each token can have a role. Token with a role can be used only in given context, '
7 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
8 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 ${_('Additionally scope for VCS type token can narrow the use to chosen repository.')}
9 ${_('Additionally scope for VCS type token can narrow the use to chosen repository.')}
10 </p>
10 </p>
11 <table class="rctable auth_tokens">
11 <table class="rctable auth_tokens">
12 %if c.user_auth_tokens:
12 %if c.user_auth_tokens:
13 <tr>
13 <tr>
14 <th>${_('Token')}</th>
14 <th>${_('Token')}</th>
15 <th>${_('Scope')}</th>
15 <th>${_('Scope')}</th>
16 <th>${_('Description')}</th>
16 <th>${_('Description')}</th>
17 <th>${_('Role')}</th>
17 <th>${_('Role')}</th>
18 <th>${_('Expiration')}</th>
18 <th>${_('Expiration')}</th>
19 <th>${_('Action')}</th>
19 <th>${_('Action')}</th>
20 </tr>
20 </tr>
21 %for auth_token in c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
22 <tr class="${'expired' if auth_token.expired else ''}">
22 <tr class="${'expired' if auth_token.expired else ''}">
23 <td class="truncate-wrap td-authtoken">
23 <td class="truncate-wrap td-authtoken">
24 <div class="user_auth_tokens truncate autoexpand">
24 <div class="user_auth_tokens truncate autoexpand">
25 <code>${auth_token.api_key}</code>
25 <code>${auth_token.api_key}</code>
26 </div>
26 </div>
27 </td>
27 </td>
28 <td class="td">${auth_token.scope_humanized}</td>
28 <td class="td">${auth_token.scope_humanized}</td>
29 <td class="td-wrap">${auth_token.description}</td>
29 <td class="td-wrap">${auth_token.description}</td>
30 <td class="td-tags">
30 <td class="td-tags">
31 <span class="tag disabled">${auth_token.role_humanized}</span>
31 <span class="tag disabled">${auth_token.role_humanized}</span>
32 </td>
32 </td>
33 <td class="td-exp">
33 <td class="td-exp">
34 %if auth_token.expires == -1:
34 %if auth_token.expires == -1:
35 ${_('never')}
35 ${_('never')}
36 %else:
36 %else:
37 %if auth_token.expired:
37 %if auth_token.expired:
38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
39 %else:
39 %else:
40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
41 %endif
41 %endif
42 %endif
42 %endif
43 </td>
43 </td>
44 <td class="td-action">
44 <td class="td-action">
45 ${h.secure_form(url('my_account_auth_tokens'),method='delete')}
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), method='post')}
46 ${h.hidden('del_auth_token',auth_token.api_key)}
46 ${h.hidden('del_auth_token',auth_token.api_key)}
47 <button class="btn btn-link btn-danger" type="submit"
47 <button class="btn btn-link btn-danger" type="submit"
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
49 ${_('Delete')}
49 ${_('Delete')}
50 </button>
50 </button>
51 ${h.end_form()}
51 ${h.end_form()}
52 </td>
52 </td>
53 </tr>
53 </tr>
54 %endfor
54 %endfor
55 %else:
55 %else:
56 <tr><td><div class="ip">${_('No additional auth token specified')}</div></td></tr>
56 <tr><td><div class="ip">${_('No additional auth token specified')}</div></td></tr>
57 %endif
57 %endif
58 </table>
58 </table>
59
59
60 <div class="user_auth_tokens">
60 <div class="user_auth_tokens">
61 ${h.secure_form(url('my_account_auth_tokens'), method='post')}
61 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), method='post')}
62 <div class="form form-vertical">
62 <div class="form form-vertical">
63 <!-- fields -->
63 <!-- fields -->
64 <div class="fields">
64 <div class="fields">
65 <div class="field">
65 <div class="field">
66 <div class="label">
66 <div class="label">
67 <label for="new_email">${_('New authentication token')}:</label>
67 <label for="new_email">${_('New authentication token')}:</label>
68 </div>
68 </div>
69 <div class="input">
69 <div class="input">
70 ${h.text('description', placeholder=_('Description'))}
70 ${h.text('description', placeholder=_('Description'))}
71 ${h.select('lifetime', '', c.lifetime_options)}
71 ${h.select('lifetime', '', c.lifetime_options)}
72 ${h.select('role', '', c.role_options)}
72 ${h.select('role', '', c.role_options)}
73 </div>
73 </div>
74 </div>
74 </div>
75 <div class="buttons">
75 <div class="buttons">
76 ${h.submit('save',_('Add'),class_="btn")}
76 ${h.submit('save',_('Add'),class_="btn")}
77 ${h.reset('reset',_('Reset'),class_="btn")}
77 ${h.reset('reset',_('Reset'),class_="btn")}
78 </div>
78 </div>
79 </div>
79 </div>
80 </div>
80 </div>
81 ${h.end_form()}
81 ${h.end_form()}
82 </div>
82 </div>
83 </div>
83 </div>
84 </div>
84 </div>
85 <script>
85 <script>
86 $(document).ready(function(){
86 $(document).ready(function(){
87 var select2Options = {
87 var select2Options = {
88 'containerCssClass': "drop-menu",
88 'containerCssClass': "drop-menu",
89 'dropdownCssClass': "drop-menu-dropdown",
89 'dropdownCssClass': "drop-menu-dropdown",
90 'dropdownAutoWidth': true
90 'dropdownAutoWidth': true
91 };
91 };
92 $("#lifetime").select2(select2Options);
92 $("#lifetime").select2(select2Options);
93 $("#role").select2(select2Options);
93 $("#role").select2(select2Options);
94 });
94 });
95 </script>
95 </script>
@@ -1,383 +1,326 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib import helpers as h
23 from rhodecode.lib import helpers as h
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
25 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
28 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
29 assert_session_flash)
29 assert_session_flash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35
35
36 class TestMyAccountController(TestController):
36 class TestMyAccountController(TestController):
37 test_user_1 = 'testme'
37 test_user_1 = 'testme'
38 test_user_1_password = '0jd83nHNS/d23n'
38 test_user_1_password = '0jd83nHNS/d23n'
39 destroy_users = set()
39 destroy_users = set()
40
40
41 @classmethod
41 @classmethod
42 def teardown_class(cls):
42 def teardown_class(cls):
43 fixture.destroy_users(cls.destroy_users)
43 fixture.destroy_users(cls.destroy_users)
44
44
45 def test_my_account(self):
45 def test_my_account(self):
46 self.log_user()
46 self.log_user()
47 response = self.app.get(url('my_account'))
47 response = self.app.get(url('my_account'))
48
48
49 response.mustcontain('test_admin')
49 response.mustcontain('test_admin')
50 response.mustcontain('href="/_admin/my_account/edit"')
50 response.mustcontain('href="/_admin/my_account/edit"')
51
51
52 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
52 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
53 response = self.app.get(url('my_account'))
53 response = self.app.get(url('my_account'))
54 assert_response = AssertResponse(response)
54 assert_response = AssertResponse(response)
55 element = assert_response.get_element('.logout #csrf_token')
55 element = assert_response.get_element('.logout #csrf_token')
56 assert element.value == csrf_token
56 assert element.value == csrf_token
57
57
58 def test_my_account_edit(self):
58 def test_my_account_edit(self):
59 self.log_user()
59 self.log_user()
60 response = self.app.get(url('my_account_edit'))
60 response = self.app.get(url('my_account_edit'))
61
61
62 response.mustcontain('value="test_admin')
62 response.mustcontain('value="test_admin')
63
63
64 def test_my_account_my_repos(self):
64 def test_my_account_my_repos(self):
65 self.log_user()
65 self.log_user()
66 response = self.app.get(url('my_account_repos'))
66 response = self.app.get(url('my_account_repos'))
67 repos = Repository.query().filter(
67 repos = Repository.query().filter(
68 Repository.user == User.get_by_username(
68 Repository.user == User.get_by_username(
69 TEST_USER_ADMIN_LOGIN)).all()
69 TEST_USER_ADMIN_LOGIN)).all()
70 for repo in repos:
70 for repo in repos:
71 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
71 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
72
72
73 def test_my_account_my_watched(self):
73 def test_my_account_my_watched(self):
74 self.log_user()
74 self.log_user()
75 response = self.app.get(url('my_account_watched'))
75 response = self.app.get(url('my_account_watched'))
76
76
77 repos = UserFollowing.query().filter(
77 repos = UserFollowing.query().filter(
78 UserFollowing.user == User.get_by_username(
78 UserFollowing.user == User.get_by_username(
79 TEST_USER_ADMIN_LOGIN)).all()
79 TEST_USER_ADMIN_LOGIN)).all()
80 for repo in repos:
80 for repo in repos:
81 response.mustcontain(
81 response.mustcontain(
82 '"name_raw": "%s"' % repo.follows_repository.repo_name)
82 '"name_raw": "%s"' % repo.follows_repository.repo_name)
83
83
84 @pytest.mark.backends("git", "hg")
84 @pytest.mark.backends("git", "hg")
85 def test_my_account_my_pullrequests(self, pr_util):
85 def test_my_account_my_pullrequests(self, pr_util):
86 self.log_user()
86 self.log_user()
87 response = self.app.get(url('my_account_pullrequests'))
87 response = self.app.get(url('my_account_pullrequests'))
88 response.mustcontain('There are currently no open pull '
88 response.mustcontain('There are currently no open pull '
89 'requests requiring your participation.')
89 'requests requiring your participation.')
90
90
91 pr = pr_util.create_pull_request(title='TestMyAccountPR')
91 pr = pr_util.create_pull_request(title='TestMyAccountPR')
92 response = self.app.get(url('my_account_pullrequests'))
92 response = self.app.get(url('my_account_pullrequests'))
93 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
93 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
94 response.mustcontain('TestMyAccountPR')
94 response.mustcontain('TestMyAccountPR')
95
95
96 def test_my_account_my_emails(self):
96 def test_my_account_my_emails(self):
97 self.log_user()
97 self.log_user()
98 response = self.app.get(url('my_account_emails'))
98 response = self.app.get(url('my_account_emails'))
99 response.mustcontain('No additional emails specified')
99 response.mustcontain('No additional emails specified')
100
100
101 def test_my_account_my_emails_add_existing_email(self):
101 def test_my_account_my_emails_add_existing_email(self):
102 self.log_user()
102 self.log_user()
103 response = self.app.get(url('my_account_emails'))
103 response = self.app.get(url('my_account_emails'))
104 response.mustcontain('No additional emails specified')
104 response.mustcontain('No additional emails specified')
105 response = self.app.post(url('my_account_emails'),
105 response = self.app.post(url('my_account_emails'),
106 {'new_email': TEST_USER_REGULAR_EMAIL,
106 {'new_email': TEST_USER_REGULAR_EMAIL,
107 'csrf_token': self.csrf_token})
107 'csrf_token': self.csrf_token})
108 assert_session_flash(response, 'This e-mail address is already taken')
108 assert_session_flash(response, 'This e-mail address is already taken')
109
109
110 def test_my_account_my_emails_add_mising_email_in_form(self):
110 def test_my_account_my_emails_add_mising_email_in_form(self):
111 self.log_user()
111 self.log_user()
112 response = self.app.get(url('my_account_emails'))
112 response = self.app.get(url('my_account_emails'))
113 response.mustcontain('No additional emails specified')
113 response.mustcontain('No additional emails specified')
114 response = self.app.post(url('my_account_emails'),
114 response = self.app.post(url('my_account_emails'),
115 {'csrf_token': self.csrf_token})
115 {'csrf_token': self.csrf_token})
116 assert_session_flash(response, 'Please enter an email address')
116 assert_session_flash(response, 'Please enter an email address')
117
117
118 def test_my_account_my_emails_add_remove(self):
118 def test_my_account_my_emails_add_remove(self):
119 self.log_user()
119 self.log_user()
120 response = self.app.get(url('my_account_emails'))
120 response = self.app.get(url('my_account_emails'))
121 response.mustcontain('No additional emails specified')
121 response.mustcontain('No additional emails specified')
122
122
123 response = self.app.post(url('my_account_emails'),
123 response = self.app.post(url('my_account_emails'),
124 {'new_email': 'foo@barz.com',
124 {'new_email': 'foo@barz.com',
125 'csrf_token': self.csrf_token})
125 'csrf_token': self.csrf_token})
126
126
127 response = self.app.get(url('my_account_emails'))
127 response = self.app.get(url('my_account_emails'))
128
128
129 from rhodecode.model.db import UserEmailMap
129 from rhodecode.model.db import UserEmailMap
130 email_id = UserEmailMap.query().filter(
130 email_id = UserEmailMap.query().filter(
131 UserEmailMap.user == User.get_by_username(
131 UserEmailMap.user == User.get_by_username(
132 TEST_USER_ADMIN_LOGIN)).filter(
132 TEST_USER_ADMIN_LOGIN)).filter(
133 UserEmailMap.email == 'foo@barz.com').one().email_id
133 UserEmailMap.email == 'foo@barz.com').one().email_id
134
134
135 response.mustcontain('foo@barz.com')
135 response.mustcontain('foo@barz.com')
136 response.mustcontain('<input id="del_email_id" name="del_email_id" '
136 response.mustcontain('<input id="del_email_id" name="del_email_id" '
137 'type="hidden" value="%s" />' % email_id)
137 'type="hidden" value="%s" />' % email_id)
138
138
139 response = self.app.post(
139 response = self.app.post(
140 url('my_account_emails'), {
140 url('my_account_emails'), {
141 'del_email_id': email_id, '_method': 'delete',
141 'del_email_id': email_id, '_method': 'delete',
142 'csrf_token': self.csrf_token})
142 'csrf_token': self.csrf_token})
143 assert_session_flash(response, 'Removed email address from user account')
143 assert_session_flash(response, 'Removed email address from user account')
144 response = self.app.get(url('my_account_emails'))
144 response = self.app.get(url('my_account_emails'))
145 response.mustcontain('No additional emails specified')
145 response.mustcontain('No additional emails specified')
146
146
147 @pytest.mark.parametrize(
147 @pytest.mark.parametrize(
148 "name, attrs", [
148 "name, attrs", [
149 ('firstname', {'firstname': 'new_username'}),
149 ('firstname', {'firstname': 'new_username'}),
150 ('lastname', {'lastname': 'new_username'}),
150 ('lastname', {'lastname': 'new_username'}),
151 ('admin', {'admin': True}),
151 ('admin', {'admin': True}),
152 ('admin', {'admin': False}),
152 ('admin', {'admin': False}),
153 ('extern_type', {'extern_type': 'ldap'}),
153 ('extern_type', {'extern_type': 'ldap'}),
154 ('extern_type', {'extern_type': None}),
154 ('extern_type', {'extern_type': None}),
155 # ('extern_name', {'extern_name': 'test'}),
155 # ('extern_name', {'extern_name': 'test'}),
156 # ('extern_name', {'extern_name': None}),
156 # ('extern_name', {'extern_name': None}),
157 ('active', {'active': False}),
157 ('active', {'active': False}),
158 ('active', {'active': True}),
158 ('active', {'active': True}),
159 ('email', {'email': 'some@email.com'}),
159 ('email', {'email': 'some@email.com'}),
160 ])
160 ])
161 def test_my_account_update(self, name, attrs):
161 def test_my_account_update(self, name, attrs):
162 usr = fixture.create_user(self.test_user_1,
162 usr = fixture.create_user(self.test_user_1,
163 password=self.test_user_1_password,
163 password=self.test_user_1_password,
164 email='testme@rhodecode.org',
164 email='testme@rhodecode.org',
165 extern_type='rhodecode',
165 extern_type='rhodecode',
166 extern_name=self.test_user_1,
166 extern_name=self.test_user_1,
167 skip_if_exists=True)
167 skip_if_exists=True)
168 self.destroy_users.add(self.test_user_1)
168 self.destroy_users.add(self.test_user_1)
169
169
170 params = usr.get_api_data() # current user data
170 params = usr.get_api_data() # current user data
171 user_id = usr.user_id
171 user_id = usr.user_id
172 self.log_user(
172 self.log_user(
173 username=self.test_user_1, password=self.test_user_1_password)
173 username=self.test_user_1, password=self.test_user_1_password)
174
174
175 params.update({'password_confirmation': ''})
175 params.update({'password_confirmation': ''})
176 params.update({'new_password': ''})
176 params.update({'new_password': ''})
177 params.update({'extern_type': 'rhodecode'})
177 params.update({'extern_type': 'rhodecode'})
178 params.update({'extern_name': self.test_user_1})
178 params.update({'extern_name': self.test_user_1})
179 params.update({'csrf_token': self.csrf_token})
179 params.update({'csrf_token': self.csrf_token})
180
180
181 params.update(attrs)
181 params.update(attrs)
182 # my account page cannot set language param yet, only for admins
182 # my account page cannot set language param yet, only for admins
183 del params['language']
183 del params['language']
184 response = self.app.post(url('my_account'), params)
184 response = self.app.post(url('my_account'), params)
185
185
186 assert_session_flash(
186 assert_session_flash(
187 response, 'Your account was updated successfully')
187 response, 'Your account was updated successfully')
188
188
189 del params['csrf_token']
189 del params['csrf_token']
190
190
191 updated_user = User.get_by_username(self.test_user_1)
191 updated_user = User.get_by_username(self.test_user_1)
192 updated_params = updated_user.get_api_data()
192 updated_params = updated_user.get_api_data()
193 updated_params.update({'password_confirmation': ''})
193 updated_params.update({'password_confirmation': ''})
194 updated_params.update({'new_password': ''})
194 updated_params.update({'new_password': ''})
195
195
196 params['last_login'] = updated_params['last_login']
196 params['last_login'] = updated_params['last_login']
197 # my account page cannot set language param yet, only for admins
197 # my account page cannot set language param yet, only for admins
198 # but we get this info from API anyway
198 # but we get this info from API anyway
199 params['language'] = updated_params['language']
199 params['language'] = updated_params['language']
200
200
201 if name == 'email':
201 if name == 'email':
202 params['emails'] = [attrs['email']]
202 params['emails'] = [attrs['email']]
203 if name == 'extern_type':
203 if name == 'extern_type':
204 # cannot update this via form, expected value is original one
204 # cannot update this via form, expected value is original one
205 params['extern_type'] = "rhodecode"
205 params['extern_type'] = "rhodecode"
206 if name == 'extern_name':
206 if name == 'extern_name':
207 # cannot update this via form, expected value is original one
207 # cannot update this via form, expected value is original one
208 params['extern_name'] = str(user_id)
208 params['extern_name'] = str(user_id)
209 if name == 'active':
209 if name == 'active':
210 # my account cannot deactivate account
210 # my account cannot deactivate account
211 params['active'] = True
211 params['active'] = True
212 if name == 'admin':
212 if name == 'admin':
213 # my account cannot make you an admin !
213 # my account cannot make you an admin !
214 params['admin'] = False
214 params['admin'] = False
215
215
216 assert params == updated_params
216 assert params == updated_params
217
217
218 def test_my_account_update_err_email_exists(self):
218 def test_my_account_update_err_email_exists(self):
219 self.log_user()
219 self.log_user()
220
220
221 new_email = 'test_regular@mail.com' # already exisitn email
221 new_email = 'test_regular@mail.com' # already exisitn email
222 response = self.app.post(url('my_account'),
222 response = self.app.post(url('my_account'),
223 params={
223 params={
224 'username': 'test_admin',
224 'username': 'test_admin',
225 'new_password': 'test12',
225 'new_password': 'test12',
226 'password_confirmation': 'test122',
226 'password_confirmation': 'test122',
227 'firstname': 'NewName',
227 'firstname': 'NewName',
228 'lastname': 'NewLastname',
228 'lastname': 'NewLastname',
229 'email': new_email,
229 'email': new_email,
230 'csrf_token': self.csrf_token,
230 'csrf_token': self.csrf_token,
231 })
231 })
232
232
233 response.mustcontain('This e-mail address is already taken')
233 response.mustcontain('This e-mail address is already taken')
234
234
235 def test_my_account_update_err(self):
235 def test_my_account_update_err(self):
236 self.log_user('test_regular2', 'test12')
236 self.log_user('test_regular2', 'test12')
237
237
238 new_email = 'newmail.pl'
238 new_email = 'newmail.pl'
239 response = self.app.post(url('my_account'),
239 response = self.app.post(url('my_account'),
240 params={
240 params={
241 'username': 'test_admin',
241 'username': 'test_admin',
242 'new_password': 'test12',
242 'new_password': 'test12',
243 'password_confirmation': 'test122',
243 'password_confirmation': 'test122',
244 'firstname': 'NewName',
244 'firstname': 'NewName',
245 'lastname': 'NewLastname',
245 'lastname': 'NewLastname',
246 'email': new_email,
246 'email': new_email,
247 'csrf_token': self.csrf_token,
247 'csrf_token': self.csrf_token,
248 })
248 })
249
249
250 response.mustcontain('An email address must contain a single @')
250 response.mustcontain('An email address must contain a single @')
251 from rhodecode.model import validators
251 from rhodecode.model import validators
252 msg = validators.ValidUsername(
252 msg = validators.ValidUsername(
253 edit=False, old_data={})._messages['username_exists']
253 edit=False, old_data={})._messages['username_exists']
254 msg = h.html_escape(msg % {'username': 'test_admin'})
254 msg = h.html_escape(msg % {'username': 'test_admin'})
255 response.mustcontain(u"%s" % msg)
255 response.mustcontain(u"%s" % msg)
256
256
257 def test_my_account_auth_tokens(self):
258 usr = self.log_user('test_regular2', 'test12')
259 user = User.get(usr['user_id'])
260 response = self.app.get(url('my_account_auth_tokens'))
261 for token in user.auth_tokens:
262 response.mustcontain(token)
263 response.mustcontain('never')
264
265 @pytest.mark.parametrize("desc, lifetime", [
266 ('forever', -1),
267 ('5mins', 60*5),
268 ('30days', 60*60*24*30),
269 ])
270 def test_my_account_add_auth_tokens(self, desc, lifetime, user_util):
271 user = user_util.create_user(password='qweqwe')
272 user_id = user.user_id
273 self.log_user(user.username, 'qweqwe')
274
275 response = self.app.post(url('my_account_auth_tokens'),
276 {'description': desc, 'lifetime': lifetime,
277 'csrf_token': self.csrf_token})
278 assert_session_flash(response, 'Auth token successfully created')
279
280 response = response.follow()
281 user = User.get(user_id)
282 for auth_token in user.auth_tokens:
283 response.mustcontain(auth_token)
284
285 def test_my_account_remove_auth_token(self, user_util):
286 user = user_util.create_user(password='qweqwe')
287 user_id = user.user_id
288 self.log_user(user.username, 'qweqwe')
289
290 user = User.get(user_id)
291 keys = user.extra_auth_tokens
292 assert 2 == len(keys)
293
294 response = self.app.post(url('my_account_auth_tokens'),
295 {'description': 'desc', 'lifetime': -1,
296 'csrf_token': self.csrf_token})
297 assert_session_flash(response, 'Auth token successfully created')
298 response.follow()
299
300 user = User.get(user_id)
301 keys = user.extra_auth_tokens
302 assert 3 == len(keys)
303
304 response = self.app.post(
305 url('my_account_auth_tokens'),
306 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
307 'csrf_token': self.csrf_token})
308 assert_session_flash(response, 'Auth token successfully deleted')
309
310 user = User.get(user_id)
311 keys = user.extra_auth_tokens
312 assert 2 == len(keys)
313
314 def test_valid_change_password(self, user_util):
257 def test_valid_change_password(self, user_util):
315 new_password = 'my_new_valid_password'
258 new_password = 'my_new_valid_password'
316 user = user_util.create_user(password=self.test_user_1_password)
259 user = user_util.create_user(password=self.test_user_1_password)
317 session = self.log_user(user.username, self.test_user_1_password)
260 session = self.log_user(user.username, self.test_user_1_password)
318 form_data = [
261 form_data = [
319 ('current_password', self.test_user_1_password),
262 ('current_password', self.test_user_1_password),
320 ('__start__', 'new_password:mapping'),
263 ('__start__', 'new_password:mapping'),
321 ('new_password', new_password),
264 ('new_password', new_password),
322 ('new_password-confirm', new_password),
265 ('new_password-confirm', new_password),
323 ('__end__', 'new_password:mapping'),
266 ('__end__', 'new_password:mapping'),
324 ('csrf_token', self.csrf_token),
267 ('csrf_token', self.csrf_token),
325 ]
268 ]
326 response = self.app.post(url('my_account_password'), form_data).follow()
269 response = self.app.post(url('my_account_password'), form_data).follow()
327 assert 'Successfully updated password' in response
270 assert 'Successfully updated password' in response
328
271
329 # check_password depends on user being in session
272 # check_password depends on user being in session
330 Session().add(user)
273 Session().add(user)
331 try:
274 try:
332 assert check_password(new_password, user.password)
275 assert check_password(new_password, user.password)
333 finally:
276 finally:
334 Session().expunge(user)
277 Session().expunge(user)
335
278
336 @pytest.mark.parametrize('current_pw,new_pw,confirm_pw', [
279 @pytest.mark.parametrize('current_pw,new_pw,confirm_pw', [
337 ('', 'abcdef123', 'abcdef123'),
280 ('', 'abcdef123', 'abcdef123'),
338 ('wrong_pw', 'abcdef123', 'abcdef123'),
281 ('wrong_pw', 'abcdef123', 'abcdef123'),
339 (test_user_1_password, test_user_1_password, test_user_1_password),
282 (test_user_1_password, test_user_1_password, test_user_1_password),
340 (test_user_1_password, '', ''),
283 (test_user_1_password, '', ''),
341 (test_user_1_password, 'abcdef123', ''),
284 (test_user_1_password, 'abcdef123', ''),
342 (test_user_1_password, '', 'abcdef123'),
285 (test_user_1_password, '', 'abcdef123'),
343 (test_user_1_password, 'not_the', 'same_pw'),
286 (test_user_1_password, 'not_the', 'same_pw'),
344 (test_user_1_password, 'short', 'short'),
287 (test_user_1_password, 'short', 'short'),
345 ])
288 ])
346 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
289 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
347 user_util):
290 user_util):
348 user = user_util.create_user(password=self.test_user_1_password)
291 user = user_util.create_user(password=self.test_user_1_password)
349 session = self.log_user(user.username, self.test_user_1_password)
292 session = self.log_user(user.username, self.test_user_1_password)
350 old_password_hash = session['password']
293 old_password_hash = session['password']
351 form_data = [
294 form_data = [
352 ('current_password', current_pw),
295 ('current_password', current_pw),
353 ('__start__', 'new_password:mapping'),
296 ('__start__', 'new_password:mapping'),
354 ('new_password', new_pw),
297 ('new_password', new_pw),
355 ('new_password-confirm', confirm_pw),
298 ('new_password-confirm', confirm_pw),
356 ('__end__', 'new_password:mapping'),
299 ('__end__', 'new_password:mapping'),
357 ('csrf_token', self.csrf_token),
300 ('csrf_token', self.csrf_token),
358 ]
301 ]
359 response = self.app.post(url('my_account_password'), form_data)
302 response = self.app.post(url('my_account_password'), form_data)
360 assert 'Error occurred' in response
303 assert 'Error occurred' in response
361
304
362 def test_password_is_updated_in_session_on_password_change(self, user_util):
305 def test_password_is_updated_in_session_on_password_change(self, user_util):
363 old_password = 'abcdef123'
306 old_password = 'abcdef123'
364 new_password = 'abcdef124'
307 new_password = 'abcdef124'
365
308
366 user = user_util.create_user(password=old_password)
309 user = user_util.create_user(password=old_password)
367 session = self.log_user(user.username, old_password)
310 session = self.log_user(user.username, old_password)
368 old_password_hash = session['password']
311 old_password_hash = session['password']
369
312
370 form_data = [
313 form_data = [
371 ('current_password', old_password),
314 ('current_password', old_password),
372 ('__start__', 'new_password:mapping'),
315 ('__start__', 'new_password:mapping'),
373 ('new_password', new_password),
316 ('new_password', new_password),
374 ('new_password-confirm', new_password),
317 ('new_password-confirm', new_password),
375 ('__end__', 'new_password:mapping'),
318 ('__end__', 'new_password:mapping'),
376 ('csrf_token', self.csrf_token),
319 ('csrf_token', self.csrf_token),
377 ]
320 ]
378 self.app.post(url('my_account_password'), form_data)
321 self.app.post(url('my_account_password'), form_data)
379
322
380 response = self.app.get(url('home'))
323 response = self.app.get(url('home'))
381 new_password_hash = response.session['rhodecode_user']['password']
324 new_password_hash = response.session['rhodecode_user']['password']
382
325
383 assert old_password_hash != new_password_hash
326 assert old_password_hash != new_password_hash
General Comments 0
You need to be logged in to leave comments. Login now