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