##// END OF EJS Templates
account: convert change password form to colander schema and fix bug...
dan -
r665:a92da8f2 default
parent child Browse files
Show More
@@ -0,0 +1,33 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Base module for form rendering / validation - currently just a wrapper for
23 deform - later can be replaced with something custom.
24 """
25
26 from rhodecode.translation import _
27 from deform import Button, Form, widget, ValidationFailure
28
29
30 class buttons:
31 save = Button(name='Save', type='submit')
32 reset = Button(name=_('Reset'), type='reset')
33 delete = Button(name=_('Delete'), type='submit')
@@ -0,0 +1,61 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 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 colander
22
23 from rhodecode import forms
24 from rhodecode.model.db import User
25 from rhodecode.translation import _
26 from rhodecode.lib.auth import check_password
27
28
29 @colander.deferred
30 def deferred_user_password_validator(node, kw):
31 username = kw.get('username')
32 user = User.get_by_username(username)
33
34 def _user_password_validator(node, value):
35 if not check_password(value, user.password):
36 msg = _('Password is incorrect')
37 raise colander.Invalid(node, msg)
38 return _user_password_validator
39
40
41 class ChangePasswordSchema(colander.Schema):
42
43 current_password = colander.SchemaNode(
44 colander.String(),
45 missing=colander.required,
46 widget=forms.widget.PasswordWidget(redisplay=True),
47 validator=deferred_user_password_validator)
48
49 new_password = colander.SchemaNode(
50 colander.String(),
51 missing=colander.required,
52 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
53 validator=colander.Length(min=6))
54
55
56 def validator(self, form, values):
57 if values['current_password'] == values['new_password']:
58 exc = colander.Invalid(form)
59 exc['new_password'] = _('New password must be different '
60 'to old password')
61 raise exc
@@ -0,0 +1,10 b''
1 <%def name="panel(title, class_='default')">
2 <div class="panel panel-${class_}">
3 <div class="panel-heading">
4 <h3 class="panel-title">${title}</h3>
5 </div>
6 <div class="panel-body">
7 ${caller.body()}
8 </div>
9 </div>
10 </%def>
@@ -0,0 +1,72 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 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 colander
22 import pytest
23
24 from rhodecode.model import validation_schema
25 from rhodecode.model.validation_schema.schemas import user_schema
26
27
28 class TestChangePasswordSchema(object):
29 original_password = 'm092d903fnio0m'
30
31 def test_deserialize_bad_data(self, user_regular):
32 schema = user_schema.ChangePasswordSchema().bind(
33 username=user_regular.username)
34
35 with pytest.raises(validation_schema.Invalid) as exc_info:
36 schema.deserialize('err')
37 err = exc_info.value.asdict()
38 assert err[''] == '"err" is not a mapping type: ' \
39 'Does not implement dict-like functionality.'
40
41 def test_validate_valid_change_password_data(self, user_util):
42 user = user_util.create_user(password=self.original_password)
43 schema = user_schema.ChangePasswordSchema().bind(
44 username=user.username)
45
46 schema.deserialize({
47 'current_password': self.original_password,
48 'new_password': '23jf04rm04imr'
49 })
50
51 @pytest.mark.parametrize(
52 'current_password,new_password,key,message', [
53 ('', 'abcdef123', 'current_password', 'required'),
54 ('wrong_pw', 'abcdef123', 'current_password', 'incorrect'),
55 (original_password, original_password, 'new_password', 'different'),
56 (original_password, '', 'new_password', 'Required'),
57 (original_password, 'short', 'new_password', 'minimum'),
58 ])
59 def test_validate_invalid_change_password_data(self, current_password,
60 new_password, key, message,
61 user_util):
62 user = user_util.create_user(password=self.original_password)
63 schema = user_schema.ChangePasswordSchema().bind(
64 username=user.username)
65
66 with pytest.raises(validation_schema.Invalid) as exc_info:
67 schema.deserialize({
68 'current_password': current_password,
69 'new_password': new_password
70 })
71 err = exc_info.value.asdict()
72 assert message.lower() in err[key].lower()
@@ -1,1161 +1,1159 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 from rhodecode.config import routing_links
35 from rhodecode.config import routing_links
36
36
37 # prefix for non repository related links needs to be prefixed with `/`
37 # prefix for non repository related links needs to be prefixed with `/`
38 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
39 STATIC_FILE_PREFIX = '/_static'
40
40
41 # Default requirements for URL parts
41 # Default requirements for URL parts
42 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
43 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
44 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
45 # repo names can have a slash in them, but they must not end with a slash
45 # repo names can have a slash in them, but they must not end with a slash
46 'repo_name': r'.*?[^/]',
46 'repo_name': r'.*?[^/]',
47 # file path eats up everything at the end
47 # file path eats up everything at the end
48 'f_path': r'.*',
48 'f_path': r'.*',
49 # reference types
49 # reference types
50 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 }
52 }
53
53
54
54
55 def add_route_requirements(route_path, requirements):
55 def add_route_requirements(route_path, requirements):
56 """
56 """
57 Adds regex requirements to pyramid routes using a mapping dict
57 Adds regex requirements to pyramid routes using a mapping dict
58
58
59 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
59 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 '/{action}/{id:\d+}'
60 '/{action}/{id:\d+}'
61
61
62 """
62 """
63 for key, regex in requirements.items():
63 for key, regex in requirements.items():
64 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
64 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 return route_path
65 return route_path
66
66
67
67
68 class JSRoutesMapper(Mapper):
68 class JSRoutesMapper(Mapper):
69 """
69 """
70 Wrapper for routes.Mapper to make pyroutes compatible url definitions
70 Wrapper for routes.Mapper to make pyroutes compatible url definitions
71 """
71 """
72 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
72 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
73 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
73 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
74 def __init__(self, *args, **kw):
74 def __init__(self, *args, **kw):
75 super(JSRoutesMapper, self).__init__(*args, **kw)
75 super(JSRoutesMapper, self).__init__(*args, **kw)
76 self._jsroutes = []
76 self._jsroutes = []
77
77
78 def connect(self, *args, **kw):
78 def connect(self, *args, **kw):
79 """
79 """
80 Wrapper for connect to take an extra argument jsroute=True
80 Wrapper for connect to take an extra argument jsroute=True
81
81
82 :param jsroute: boolean, if True will add the route to the pyroutes list
82 :param jsroute: boolean, if True will add the route to the pyroutes list
83 """
83 """
84 if kw.pop('jsroute', False):
84 if kw.pop('jsroute', False):
85 if not self._named_route_regex.match(args[0]):
85 if not self._named_route_regex.match(args[0]):
86 raise Exception('only named routes can be added to pyroutes')
86 raise Exception('only named routes can be added to pyroutes')
87 self._jsroutes.append(args[0])
87 self._jsroutes.append(args[0])
88
88
89 super(JSRoutesMapper, self).connect(*args, **kw)
89 super(JSRoutesMapper, self).connect(*args, **kw)
90
90
91 def _extract_route_information(self, route):
91 def _extract_route_information(self, route):
92 """
92 """
93 Convert a route into tuple(name, path, args), eg:
93 Convert a route into tuple(name, path, args), eg:
94 ('user_profile', '/profile/%(username)s', ['username'])
94 ('user_profile', '/profile/%(username)s', ['username'])
95 """
95 """
96 routepath = route.routepath
96 routepath = route.routepath
97 def replace(matchobj):
97 def replace(matchobj):
98 if matchobj.group(1):
98 if matchobj.group(1):
99 return "%%(%s)s" % matchobj.group(1).split(':')[0]
99 return "%%(%s)s" % matchobj.group(1).split(':')[0]
100 else:
100 else:
101 return "%%(%s)s" % matchobj.group(2)
101 return "%%(%s)s" % matchobj.group(2)
102
102
103 routepath = self._argument_prog.sub(replace, routepath)
103 routepath = self._argument_prog.sub(replace, routepath)
104 return (
104 return (
105 route.name,
105 route.name,
106 routepath,
106 routepath,
107 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
107 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
108 for arg in self._argument_prog.findall(route.routepath)]
108 for arg in self._argument_prog.findall(route.routepath)]
109 )
109 )
110
110
111 def jsroutes(self):
111 def jsroutes(self):
112 """
112 """
113 Return a list of pyroutes.js compatible routes
113 Return a list of pyroutes.js compatible routes
114 """
114 """
115 for route_name in self._jsroutes:
115 for route_name in self._jsroutes:
116 yield self._extract_route_information(self._routenames[route_name])
116 yield self._extract_route_information(self._routenames[route_name])
117
117
118
118
119 def make_map(config):
119 def make_map(config):
120 """Create, configure and return the routes Mapper"""
120 """Create, configure and return the routes Mapper"""
121 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
121 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
122 always_scan=config['debug'])
122 always_scan=config['debug'])
123 rmap.minimization = False
123 rmap.minimization = False
124 rmap.explicit = False
124 rmap.explicit = False
125
125
126 from rhodecode.lib.utils2 import str2bool
126 from rhodecode.lib.utils2 import str2bool
127 from rhodecode.model import repo, repo_group
127 from rhodecode.model import repo, repo_group
128
128
129 def check_repo(environ, match_dict):
129 def check_repo(environ, match_dict):
130 """
130 """
131 check for valid repository for proper 404 handling
131 check for valid repository for proper 404 handling
132
132
133 :param environ:
133 :param environ:
134 :param match_dict:
134 :param match_dict:
135 """
135 """
136 repo_name = match_dict.get('repo_name')
136 repo_name = match_dict.get('repo_name')
137
137
138 if match_dict.get('f_path'):
138 if match_dict.get('f_path'):
139 # fix for multiple initial slashes that causes errors
139 # fix for multiple initial slashes that causes errors
140 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
140 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 repo_model = repo.RepoModel()
141 repo_model = repo.RepoModel()
142 by_name_match = repo_model.get_by_repo_name(repo_name)
142 by_name_match = repo_model.get_by_repo_name(repo_name)
143 # if we match quickly from database, short circuit the operation,
143 # if we match quickly from database, short circuit the operation,
144 # and validate repo based on the type.
144 # and validate repo based on the type.
145 if by_name_match:
145 if by_name_match:
146 return True
146 return True
147
147
148 by_id_match = repo_model.get_repo_by_id(repo_name)
148 by_id_match = repo_model.get_repo_by_id(repo_name)
149 if by_id_match:
149 if by_id_match:
150 repo_name = by_id_match.repo_name
150 repo_name = by_id_match.repo_name
151 match_dict['repo_name'] = repo_name
151 match_dict['repo_name'] = repo_name
152 return True
152 return True
153
153
154 return False
154 return False
155
155
156 def check_group(environ, match_dict):
156 def check_group(environ, match_dict):
157 """
157 """
158 check for valid repository group path for proper 404 handling
158 check for valid repository group path for proper 404 handling
159
159
160 :param environ:
160 :param environ:
161 :param match_dict:
161 :param match_dict:
162 """
162 """
163 repo_group_name = match_dict.get('group_name')
163 repo_group_name = match_dict.get('group_name')
164 repo_group_model = repo_group.RepoGroupModel()
164 repo_group_model = repo_group.RepoGroupModel()
165 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
165 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 if by_name_match:
166 if by_name_match:
167 return True
167 return True
168
168
169 return False
169 return False
170
170
171 def check_user_group(environ, match_dict):
171 def check_user_group(environ, match_dict):
172 """
172 """
173 check for valid user group for proper 404 handling
173 check for valid user group for proper 404 handling
174
174
175 :param environ:
175 :param environ:
176 :param match_dict:
176 :param match_dict:
177 """
177 """
178 return True
178 return True
179
179
180 def check_int(environ, match_dict):
180 def check_int(environ, match_dict):
181 return match_dict.get('id').isdigit()
181 return match_dict.get('id').isdigit()
182
182
183
183
184 #==========================================================================
184 #==========================================================================
185 # CUSTOM ROUTES HERE
185 # CUSTOM ROUTES HERE
186 #==========================================================================
186 #==========================================================================
187
187
188 # MAIN PAGE
188 # MAIN PAGE
189 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
189 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
190 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
190 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
191 action='goto_switcher_data')
191 action='goto_switcher_data')
192 rmap.connect('repo_list_data', '/_repos', controller='home',
192 rmap.connect('repo_list_data', '/_repos', controller='home',
193 action='repo_list_data')
193 action='repo_list_data')
194
194
195 rmap.connect('user_autocomplete_data', '/_users', controller='home',
195 rmap.connect('user_autocomplete_data', '/_users', controller='home',
196 action='user_autocomplete_data', jsroute=True)
196 action='user_autocomplete_data', jsroute=True)
197 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
197 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 action='user_group_autocomplete_data')
198 action='user_group_autocomplete_data')
199
199
200 rmap.connect(
200 rmap.connect(
201 'user_profile', '/_profiles/{username}', controller='users',
201 'user_profile', '/_profiles/{username}', controller='users',
202 action='user_profile')
202 action='user_profile')
203
203
204 # TODO: johbo: Static links, to be replaced by our redirection mechanism
204 # TODO: johbo: Static links, to be replaced by our redirection mechanism
205 rmap.connect('rst_help',
205 rmap.connect('rst_help',
206 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
206 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
207 _static=True)
207 _static=True)
208 rmap.connect('markdown_help',
208 rmap.connect('markdown_help',
209 'http://daringfireball.net/projects/markdown/syntax',
209 'http://daringfireball.net/projects/markdown/syntax',
210 _static=True)
210 _static=True)
211 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
211 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
212 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
212 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
213 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
213 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
214 # TODO: anderson - making this a static link since redirect won't play
214 # TODO: anderson - making this a static link since redirect won't play
215 # nice with POST requests
215 # nice with POST requests
216 rmap.connect('enterprise_license_convert_from_old',
216 rmap.connect('enterprise_license_convert_from_old',
217 'https://rhodecode.com/u/license-upgrade',
217 'https://rhodecode.com/u/license-upgrade',
218 _static=True)
218 _static=True)
219
219
220 routing_links.connect_redirection_links(rmap)
220 routing_links.connect_redirection_links(rmap)
221
221
222 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
222 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
223 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
223 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
224
224
225 # ADMIN REPOSITORY ROUTES
225 # ADMIN REPOSITORY ROUTES
226 with rmap.submapper(path_prefix=ADMIN_PREFIX,
226 with rmap.submapper(path_prefix=ADMIN_PREFIX,
227 controller='admin/repos') as m:
227 controller='admin/repos') as m:
228 m.connect('repos', '/repos',
228 m.connect('repos', '/repos',
229 action='create', conditions={'method': ['POST']})
229 action='create', conditions={'method': ['POST']})
230 m.connect('repos', '/repos',
230 m.connect('repos', '/repos',
231 action='index', conditions={'method': ['GET']})
231 action='index', conditions={'method': ['GET']})
232 m.connect('new_repo', '/create_repository', jsroute=True,
232 m.connect('new_repo', '/create_repository', jsroute=True,
233 action='create_repository', conditions={'method': ['GET']})
233 action='create_repository', conditions={'method': ['GET']})
234 m.connect('/repos/{repo_name}',
234 m.connect('/repos/{repo_name}',
235 action='update', conditions={'method': ['PUT'],
235 action='update', conditions={'method': ['PUT'],
236 'function': check_repo},
236 'function': check_repo},
237 requirements=URL_NAME_REQUIREMENTS)
237 requirements=URL_NAME_REQUIREMENTS)
238 m.connect('delete_repo', '/repos/{repo_name}',
238 m.connect('delete_repo', '/repos/{repo_name}',
239 action='delete', conditions={'method': ['DELETE']},
239 action='delete', conditions={'method': ['DELETE']},
240 requirements=URL_NAME_REQUIREMENTS)
240 requirements=URL_NAME_REQUIREMENTS)
241 m.connect('repo', '/repos/{repo_name}',
241 m.connect('repo', '/repos/{repo_name}',
242 action='show', conditions={'method': ['GET'],
242 action='show', conditions={'method': ['GET'],
243 'function': check_repo},
243 'function': check_repo},
244 requirements=URL_NAME_REQUIREMENTS)
244 requirements=URL_NAME_REQUIREMENTS)
245
245
246 # ADMIN REPOSITORY GROUPS ROUTES
246 # ADMIN REPOSITORY GROUPS ROUTES
247 with rmap.submapper(path_prefix=ADMIN_PREFIX,
247 with rmap.submapper(path_prefix=ADMIN_PREFIX,
248 controller='admin/repo_groups') as m:
248 controller='admin/repo_groups') as m:
249 m.connect('repo_groups', '/repo_groups',
249 m.connect('repo_groups', '/repo_groups',
250 action='create', conditions={'method': ['POST']})
250 action='create', conditions={'method': ['POST']})
251 m.connect('repo_groups', '/repo_groups',
251 m.connect('repo_groups', '/repo_groups',
252 action='index', conditions={'method': ['GET']})
252 action='index', conditions={'method': ['GET']})
253 m.connect('new_repo_group', '/repo_groups/new',
253 m.connect('new_repo_group', '/repo_groups/new',
254 action='new', conditions={'method': ['GET']})
254 action='new', conditions={'method': ['GET']})
255 m.connect('update_repo_group', '/repo_groups/{group_name}',
255 m.connect('update_repo_group', '/repo_groups/{group_name}',
256 action='update', conditions={'method': ['PUT'],
256 action='update', conditions={'method': ['PUT'],
257 'function': check_group},
257 'function': check_group},
258 requirements=URL_NAME_REQUIREMENTS)
258 requirements=URL_NAME_REQUIREMENTS)
259
259
260 # EXTRAS REPO GROUP ROUTES
260 # EXTRAS REPO GROUP ROUTES
261 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
261 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
262 action='edit',
262 action='edit',
263 conditions={'method': ['GET'], 'function': check_group},
263 conditions={'method': ['GET'], 'function': check_group},
264 requirements=URL_NAME_REQUIREMENTS)
264 requirements=URL_NAME_REQUIREMENTS)
265 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
265 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
266 action='edit',
266 action='edit',
267 conditions={'method': ['PUT'], 'function': check_group},
267 conditions={'method': ['PUT'], 'function': check_group},
268 requirements=URL_NAME_REQUIREMENTS)
268 requirements=URL_NAME_REQUIREMENTS)
269
269
270 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
270 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
271 action='edit_repo_group_advanced',
271 action='edit_repo_group_advanced',
272 conditions={'method': ['GET'], 'function': check_group},
272 conditions={'method': ['GET'], 'function': check_group},
273 requirements=URL_NAME_REQUIREMENTS)
273 requirements=URL_NAME_REQUIREMENTS)
274 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
274 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
275 action='edit_repo_group_advanced',
275 action='edit_repo_group_advanced',
276 conditions={'method': ['PUT'], 'function': check_group},
276 conditions={'method': ['PUT'], 'function': check_group},
277 requirements=URL_NAME_REQUIREMENTS)
277 requirements=URL_NAME_REQUIREMENTS)
278
278
279 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
279 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
280 action='edit_repo_group_perms',
280 action='edit_repo_group_perms',
281 conditions={'method': ['GET'], 'function': check_group},
281 conditions={'method': ['GET'], 'function': check_group},
282 requirements=URL_NAME_REQUIREMENTS)
282 requirements=URL_NAME_REQUIREMENTS)
283 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
283 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
284 action='update_perms',
284 action='update_perms',
285 conditions={'method': ['PUT'], 'function': check_group},
285 conditions={'method': ['PUT'], 'function': check_group},
286 requirements=URL_NAME_REQUIREMENTS)
286 requirements=URL_NAME_REQUIREMENTS)
287
287
288 m.connect('delete_repo_group', '/repo_groups/{group_name}',
288 m.connect('delete_repo_group', '/repo_groups/{group_name}',
289 action='delete', conditions={'method': ['DELETE'],
289 action='delete', conditions={'method': ['DELETE'],
290 'function': check_group},
290 'function': check_group},
291 requirements=URL_NAME_REQUIREMENTS)
291 requirements=URL_NAME_REQUIREMENTS)
292
292
293 # ADMIN USER ROUTES
293 # ADMIN USER ROUTES
294 with rmap.submapper(path_prefix=ADMIN_PREFIX,
294 with rmap.submapper(path_prefix=ADMIN_PREFIX,
295 controller='admin/users') as m:
295 controller='admin/users') as m:
296 m.connect('users', '/users',
296 m.connect('users', '/users',
297 action='create', conditions={'method': ['POST']})
297 action='create', conditions={'method': ['POST']})
298 m.connect('users', '/users',
298 m.connect('users', '/users',
299 action='index', conditions={'method': ['GET']})
299 action='index', conditions={'method': ['GET']})
300 m.connect('new_user', '/users/new',
300 m.connect('new_user', '/users/new',
301 action='new', conditions={'method': ['GET']})
301 action='new', conditions={'method': ['GET']})
302 m.connect('update_user', '/users/{user_id}',
302 m.connect('update_user', '/users/{user_id}',
303 action='update', conditions={'method': ['PUT']})
303 action='update', conditions={'method': ['PUT']})
304 m.connect('delete_user', '/users/{user_id}',
304 m.connect('delete_user', '/users/{user_id}',
305 action='delete', conditions={'method': ['DELETE']})
305 action='delete', conditions={'method': ['DELETE']})
306 m.connect('edit_user', '/users/{user_id}/edit',
306 m.connect('edit_user', '/users/{user_id}/edit',
307 action='edit', conditions={'method': ['GET']})
307 action='edit', conditions={'method': ['GET']})
308 m.connect('user', '/users/{user_id}',
308 m.connect('user', '/users/{user_id}',
309 action='show', conditions={'method': ['GET']})
309 action='show', conditions={'method': ['GET']})
310 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
310 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
311 action='reset_password', conditions={'method': ['POST']})
311 action='reset_password', conditions={'method': ['POST']})
312 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
312 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
313 action='create_personal_repo_group', conditions={'method': ['POST']})
313 action='create_personal_repo_group', conditions={'method': ['POST']})
314
314
315 # EXTRAS USER ROUTES
315 # EXTRAS USER ROUTES
316 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
316 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
317 action='edit_advanced', conditions={'method': ['GET']})
317 action='edit_advanced', conditions={'method': ['GET']})
318 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
318 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
319 action='update_advanced', conditions={'method': ['PUT']})
319 action='update_advanced', conditions={'method': ['PUT']})
320
320
321 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
321 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
322 action='edit_auth_tokens', conditions={'method': ['GET']})
322 action='edit_auth_tokens', conditions={'method': ['GET']})
323 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
324 action='add_auth_token', conditions={'method': ['PUT']})
324 action='add_auth_token', conditions={'method': ['PUT']})
325 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
325 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
326 action='delete_auth_token', conditions={'method': ['DELETE']})
326 action='delete_auth_token', conditions={'method': ['DELETE']})
327
327
328 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
328 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
329 action='edit_global_perms', conditions={'method': ['GET']})
329 action='edit_global_perms', conditions={'method': ['GET']})
330 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
330 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
331 action='update_global_perms', conditions={'method': ['PUT']})
331 action='update_global_perms', conditions={'method': ['PUT']})
332
332
333 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
333 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
334 action='edit_perms_summary', conditions={'method': ['GET']})
334 action='edit_perms_summary', conditions={'method': ['GET']})
335
335
336 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
336 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
337 action='edit_emails', conditions={'method': ['GET']})
337 action='edit_emails', conditions={'method': ['GET']})
338 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
338 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
339 action='add_email', conditions={'method': ['PUT']})
339 action='add_email', conditions={'method': ['PUT']})
340 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
340 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
341 action='delete_email', conditions={'method': ['DELETE']})
341 action='delete_email', conditions={'method': ['DELETE']})
342
342
343 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
343 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
344 action='edit_ips', conditions={'method': ['GET']})
344 action='edit_ips', conditions={'method': ['GET']})
345 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
345 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
346 action='add_ip', conditions={'method': ['PUT']})
346 action='add_ip', conditions={'method': ['PUT']})
347 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
347 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
348 action='delete_ip', conditions={'method': ['DELETE']})
348 action='delete_ip', conditions={'method': ['DELETE']})
349
349
350 # ADMIN USER GROUPS REST ROUTES
350 # ADMIN USER GROUPS REST ROUTES
351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 controller='admin/user_groups') as m:
352 controller='admin/user_groups') as m:
353 m.connect('users_groups', '/user_groups',
353 m.connect('users_groups', '/user_groups',
354 action='create', conditions={'method': ['POST']})
354 action='create', conditions={'method': ['POST']})
355 m.connect('users_groups', '/user_groups',
355 m.connect('users_groups', '/user_groups',
356 action='index', conditions={'method': ['GET']})
356 action='index', conditions={'method': ['GET']})
357 m.connect('new_users_group', '/user_groups/new',
357 m.connect('new_users_group', '/user_groups/new',
358 action='new', conditions={'method': ['GET']})
358 action='new', conditions={'method': ['GET']})
359 m.connect('update_users_group', '/user_groups/{user_group_id}',
359 m.connect('update_users_group', '/user_groups/{user_group_id}',
360 action='update', conditions={'method': ['PUT']})
360 action='update', conditions={'method': ['PUT']})
361 m.connect('delete_users_group', '/user_groups/{user_group_id}',
361 m.connect('delete_users_group', '/user_groups/{user_group_id}',
362 action='delete', conditions={'method': ['DELETE']})
362 action='delete', conditions={'method': ['DELETE']})
363 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
363 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
364 action='edit', conditions={'method': ['GET']},
364 action='edit', conditions={'method': ['GET']},
365 function=check_user_group)
365 function=check_user_group)
366
366
367 # EXTRAS USER GROUP ROUTES
367 # EXTRAS USER GROUP ROUTES
368 m.connect('edit_user_group_global_perms',
368 m.connect('edit_user_group_global_perms',
369 '/user_groups/{user_group_id}/edit/global_permissions',
369 '/user_groups/{user_group_id}/edit/global_permissions',
370 action='edit_global_perms', conditions={'method': ['GET']})
370 action='edit_global_perms', conditions={'method': ['GET']})
371 m.connect('edit_user_group_global_perms',
371 m.connect('edit_user_group_global_perms',
372 '/user_groups/{user_group_id}/edit/global_permissions',
372 '/user_groups/{user_group_id}/edit/global_permissions',
373 action='update_global_perms', conditions={'method': ['PUT']})
373 action='update_global_perms', conditions={'method': ['PUT']})
374 m.connect('edit_user_group_perms_summary',
374 m.connect('edit_user_group_perms_summary',
375 '/user_groups/{user_group_id}/edit/permissions_summary',
375 '/user_groups/{user_group_id}/edit/permissions_summary',
376 action='edit_perms_summary', conditions={'method': ['GET']})
376 action='edit_perms_summary', conditions={'method': ['GET']})
377
377
378 m.connect('edit_user_group_perms',
378 m.connect('edit_user_group_perms',
379 '/user_groups/{user_group_id}/edit/permissions',
379 '/user_groups/{user_group_id}/edit/permissions',
380 action='edit_perms', conditions={'method': ['GET']})
380 action='edit_perms', conditions={'method': ['GET']})
381 m.connect('edit_user_group_perms',
381 m.connect('edit_user_group_perms',
382 '/user_groups/{user_group_id}/edit/permissions',
382 '/user_groups/{user_group_id}/edit/permissions',
383 action='update_perms', conditions={'method': ['PUT']})
383 action='update_perms', conditions={'method': ['PUT']})
384
384
385 m.connect('edit_user_group_advanced',
385 m.connect('edit_user_group_advanced',
386 '/user_groups/{user_group_id}/edit/advanced',
386 '/user_groups/{user_group_id}/edit/advanced',
387 action='edit_advanced', conditions={'method': ['GET']})
387 action='edit_advanced', conditions={'method': ['GET']})
388
388
389 m.connect('edit_user_group_members',
389 m.connect('edit_user_group_members',
390 '/user_groups/{user_group_id}/edit/members', jsroute=True,
390 '/user_groups/{user_group_id}/edit/members', jsroute=True,
391 action='edit_members', conditions={'method': ['GET']})
391 action='edit_members', conditions={'method': ['GET']})
392
392
393 # ADMIN PERMISSIONS ROUTES
393 # ADMIN PERMISSIONS ROUTES
394 with rmap.submapper(path_prefix=ADMIN_PREFIX,
394 with rmap.submapper(path_prefix=ADMIN_PREFIX,
395 controller='admin/permissions') as m:
395 controller='admin/permissions') as m:
396 m.connect('admin_permissions_application', '/permissions/application',
396 m.connect('admin_permissions_application', '/permissions/application',
397 action='permission_application_update', conditions={'method': ['POST']})
397 action='permission_application_update', conditions={'method': ['POST']})
398 m.connect('admin_permissions_application', '/permissions/application',
398 m.connect('admin_permissions_application', '/permissions/application',
399 action='permission_application', conditions={'method': ['GET']})
399 action='permission_application', conditions={'method': ['GET']})
400
400
401 m.connect('admin_permissions_global', '/permissions/global',
401 m.connect('admin_permissions_global', '/permissions/global',
402 action='permission_global_update', conditions={'method': ['POST']})
402 action='permission_global_update', conditions={'method': ['POST']})
403 m.connect('admin_permissions_global', '/permissions/global',
403 m.connect('admin_permissions_global', '/permissions/global',
404 action='permission_global', conditions={'method': ['GET']})
404 action='permission_global', conditions={'method': ['GET']})
405
405
406 m.connect('admin_permissions_object', '/permissions/object',
406 m.connect('admin_permissions_object', '/permissions/object',
407 action='permission_objects_update', conditions={'method': ['POST']})
407 action='permission_objects_update', conditions={'method': ['POST']})
408 m.connect('admin_permissions_object', '/permissions/object',
408 m.connect('admin_permissions_object', '/permissions/object',
409 action='permission_objects', conditions={'method': ['GET']})
409 action='permission_objects', conditions={'method': ['GET']})
410
410
411 m.connect('admin_permissions_ips', '/permissions/ips',
411 m.connect('admin_permissions_ips', '/permissions/ips',
412 action='permission_ips', conditions={'method': ['POST']})
412 action='permission_ips', conditions={'method': ['POST']})
413 m.connect('admin_permissions_ips', '/permissions/ips',
413 m.connect('admin_permissions_ips', '/permissions/ips',
414 action='permission_ips', conditions={'method': ['GET']})
414 action='permission_ips', conditions={'method': ['GET']})
415
415
416 m.connect('admin_permissions_overview', '/permissions/overview',
416 m.connect('admin_permissions_overview', '/permissions/overview',
417 action='permission_perms', conditions={'method': ['GET']})
417 action='permission_perms', conditions={'method': ['GET']})
418
418
419 # ADMIN DEFAULTS REST ROUTES
419 # ADMIN DEFAULTS REST ROUTES
420 with rmap.submapper(path_prefix=ADMIN_PREFIX,
420 with rmap.submapper(path_prefix=ADMIN_PREFIX,
421 controller='admin/defaults') as m:
421 controller='admin/defaults') as m:
422 m.connect('admin_defaults_repositories', '/defaults/repositories',
422 m.connect('admin_defaults_repositories', '/defaults/repositories',
423 action='update_repository_defaults', conditions={'method': ['POST']})
423 action='update_repository_defaults', conditions={'method': ['POST']})
424 m.connect('admin_defaults_repositories', '/defaults/repositories',
424 m.connect('admin_defaults_repositories', '/defaults/repositories',
425 action='index', conditions={'method': ['GET']})
425 action='index', conditions={'method': ['GET']})
426
426
427 # ADMIN DEBUG STYLE ROUTES
427 # ADMIN DEBUG STYLE ROUTES
428 if str2bool(config.get('debug_style')):
428 if str2bool(config.get('debug_style')):
429 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
429 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
430 controller='debug_style') as m:
430 controller='debug_style') as m:
431 m.connect('debug_style_home', '',
431 m.connect('debug_style_home', '',
432 action='index', conditions={'method': ['GET']})
432 action='index', conditions={'method': ['GET']})
433 m.connect('debug_style_template', '/t/{t_path}',
433 m.connect('debug_style_template', '/t/{t_path}',
434 action='template', conditions={'method': ['GET']})
434 action='template', conditions={'method': ['GET']})
435
435
436 # ADMIN SETTINGS ROUTES
436 # ADMIN SETTINGS ROUTES
437 with rmap.submapper(path_prefix=ADMIN_PREFIX,
437 with rmap.submapper(path_prefix=ADMIN_PREFIX,
438 controller='admin/settings') as m:
438 controller='admin/settings') as m:
439
439
440 # default
440 # default
441 m.connect('admin_settings', '/settings',
441 m.connect('admin_settings', '/settings',
442 action='settings_global_update',
442 action='settings_global_update',
443 conditions={'method': ['POST']})
443 conditions={'method': ['POST']})
444 m.connect('admin_settings', '/settings',
444 m.connect('admin_settings', '/settings',
445 action='settings_global', conditions={'method': ['GET']})
445 action='settings_global', conditions={'method': ['GET']})
446
446
447 m.connect('admin_settings_vcs', '/settings/vcs',
447 m.connect('admin_settings_vcs', '/settings/vcs',
448 action='settings_vcs_update',
448 action='settings_vcs_update',
449 conditions={'method': ['POST']})
449 conditions={'method': ['POST']})
450 m.connect('admin_settings_vcs', '/settings/vcs',
450 m.connect('admin_settings_vcs', '/settings/vcs',
451 action='settings_vcs',
451 action='settings_vcs',
452 conditions={'method': ['GET']})
452 conditions={'method': ['GET']})
453 m.connect('admin_settings_vcs', '/settings/vcs',
453 m.connect('admin_settings_vcs', '/settings/vcs',
454 action='delete_svn_pattern',
454 action='delete_svn_pattern',
455 conditions={'method': ['DELETE']})
455 conditions={'method': ['DELETE']})
456
456
457 m.connect('admin_settings_mapping', '/settings/mapping',
457 m.connect('admin_settings_mapping', '/settings/mapping',
458 action='settings_mapping_update',
458 action='settings_mapping_update',
459 conditions={'method': ['POST']})
459 conditions={'method': ['POST']})
460 m.connect('admin_settings_mapping', '/settings/mapping',
460 m.connect('admin_settings_mapping', '/settings/mapping',
461 action='settings_mapping', conditions={'method': ['GET']})
461 action='settings_mapping', conditions={'method': ['GET']})
462
462
463 m.connect('admin_settings_global', '/settings/global',
463 m.connect('admin_settings_global', '/settings/global',
464 action='settings_global_update',
464 action='settings_global_update',
465 conditions={'method': ['POST']})
465 conditions={'method': ['POST']})
466 m.connect('admin_settings_global', '/settings/global',
466 m.connect('admin_settings_global', '/settings/global',
467 action='settings_global', conditions={'method': ['GET']})
467 action='settings_global', conditions={'method': ['GET']})
468
468
469 m.connect('admin_settings_visual', '/settings/visual',
469 m.connect('admin_settings_visual', '/settings/visual',
470 action='settings_visual_update',
470 action='settings_visual_update',
471 conditions={'method': ['POST']})
471 conditions={'method': ['POST']})
472 m.connect('admin_settings_visual', '/settings/visual',
472 m.connect('admin_settings_visual', '/settings/visual',
473 action='settings_visual', conditions={'method': ['GET']})
473 action='settings_visual', conditions={'method': ['GET']})
474
474
475 m.connect('admin_settings_issuetracker',
475 m.connect('admin_settings_issuetracker',
476 '/settings/issue-tracker', action='settings_issuetracker',
476 '/settings/issue-tracker', action='settings_issuetracker',
477 conditions={'method': ['GET']})
477 conditions={'method': ['GET']})
478 m.connect('admin_settings_issuetracker_save',
478 m.connect('admin_settings_issuetracker_save',
479 '/settings/issue-tracker/save',
479 '/settings/issue-tracker/save',
480 action='settings_issuetracker_save',
480 action='settings_issuetracker_save',
481 conditions={'method': ['POST']})
481 conditions={'method': ['POST']})
482 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
482 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
483 action='settings_issuetracker_test',
483 action='settings_issuetracker_test',
484 conditions={'method': ['POST']})
484 conditions={'method': ['POST']})
485 m.connect('admin_issuetracker_delete',
485 m.connect('admin_issuetracker_delete',
486 '/settings/issue-tracker/delete',
486 '/settings/issue-tracker/delete',
487 action='settings_issuetracker_delete',
487 action='settings_issuetracker_delete',
488 conditions={'method': ['DELETE']})
488 conditions={'method': ['DELETE']})
489
489
490 m.connect('admin_settings_email', '/settings/email',
490 m.connect('admin_settings_email', '/settings/email',
491 action='settings_email_update',
491 action='settings_email_update',
492 conditions={'method': ['POST']})
492 conditions={'method': ['POST']})
493 m.connect('admin_settings_email', '/settings/email',
493 m.connect('admin_settings_email', '/settings/email',
494 action='settings_email', conditions={'method': ['GET']})
494 action='settings_email', conditions={'method': ['GET']})
495
495
496 m.connect('admin_settings_hooks', '/settings/hooks',
496 m.connect('admin_settings_hooks', '/settings/hooks',
497 action='settings_hooks_update',
497 action='settings_hooks_update',
498 conditions={'method': ['POST', 'DELETE']})
498 conditions={'method': ['POST', 'DELETE']})
499 m.connect('admin_settings_hooks', '/settings/hooks',
499 m.connect('admin_settings_hooks', '/settings/hooks',
500 action='settings_hooks', conditions={'method': ['GET']})
500 action='settings_hooks', conditions={'method': ['GET']})
501
501
502 m.connect('admin_settings_search', '/settings/search',
502 m.connect('admin_settings_search', '/settings/search',
503 action='settings_search', conditions={'method': ['GET']})
503 action='settings_search', conditions={'method': ['GET']})
504
504
505 m.connect('admin_settings_system', '/settings/system',
505 m.connect('admin_settings_system', '/settings/system',
506 action='settings_system', conditions={'method': ['GET']})
506 action='settings_system', conditions={'method': ['GET']})
507
507
508 m.connect('admin_settings_system_update', '/settings/system/updates',
508 m.connect('admin_settings_system_update', '/settings/system/updates',
509 action='settings_system_update', conditions={'method': ['GET']})
509 action='settings_system_update', conditions={'method': ['GET']})
510
510
511 m.connect('admin_settings_supervisor', '/settings/supervisor',
511 m.connect('admin_settings_supervisor', '/settings/supervisor',
512 action='settings_supervisor', conditions={'method': ['GET']})
512 action='settings_supervisor', conditions={'method': ['GET']})
513 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
513 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
514 action='settings_supervisor_log', conditions={'method': ['GET']})
514 action='settings_supervisor_log', conditions={'method': ['GET']})
515
515
516 m.connect('admin_settings_labs', '/settings/labs',
516 m.connect('admin_settings_labs', '/settings/labs',
517 action='settings_labs_update',
517 action='settings_labs_update',
518 conditions={'method': ['POST']})
518 conditions={'method': ['POST']})
519 m.connect('admin_settings_labs', '/settings/labs',
519 m.connect('admin_settings_labs', '/settings/labs',
520 action='settings_labs', conditions={'method': ['GET']})
520 action='settings_labs', conditions={'method': ['GET']})
521
521
522 # ADMIN MY ACCOUNT
522 # ADMIN MY ACCOUNT
523 with rmap.submapper(path_prefix=ADMIN_PREFIX,
523 with rmap.submapper(path_prefix=ADMIN_PREFIX,
524 controller='admin/my_account') as m:
524 controller='admin/my_account') as m:
525
525
526 m.connect('my_account', '/my_account',
526 m.connect('my_account', '/my_account',
527 action='my_account', conditions={'method': ['GET']})
527 action='my_account', conditions={'method': ['GET']})
528 m.connect('my_account_edit', '/my_account/edit',
528 m.connect('my_account_edit', '/my_account/edit',
529 action='my_account_edit', conditions={'method': ['GET']})
529 action='my_account_edit', conditions={'method': ['GET']})
530 m.connect('my_account', '/my_account',
530 m.connect('my_account', '/my_account',
531 action='my_account_update', conditions={'method': ['POST']})
531 action='my_account_update', conditions={'method': ['POST']})
532
532
533 m.connect('my_account_password', '/my_account/password',
533 m.connect('my_account_password', '/my_account/password',
534 action='my_account_password', conditions={'method': ['GET']})
534 action='my_account_password', conditions={'method': ['GET', 'POST']})
535 m.connect('my_account_password', '/my_account/password',
536 action='my_account_password_update', conditions={'method': ['POST']})
537
535
538 m.connect('my_account_repos', '/my_account/repos',
536 m.connect('my_account_repos', '/my_account/repos',
539 action='my_account_repos', conditions={'method': ['GET']})
537 action='my_account_repos', conditions={'method': ['GET']})
540
538
541 m.connect('my_account_watched', '/my_account/watched',
539 m.connect('my_account_watched', '/my_account/watched',
542 action='my_account_watched', conditions={'method': ['GET']})
540 action='my_account_watched', conditions={'method': ['GET']})
543
541
544 m.connect('my_account_pullrequests', '/my_account/pull_requests',
542 m.connect('my_account_pullrequests', '/my_account/pull_requests',
545 action='my_account_pullrequests', conditions={'method': ['GET']})
543 action='my_account_pullrequests', conditions={'method': ['GET']})
546
544
547 m.connect('my_account_perms', '/my_account/perms',
545 m.connect('my_account_perms', '/my_account/perms',
548 action='my_account_perms', conditions={'method': ['GET']})
546 action='my_account_perms', conditions={'method': ['GET']})
549
547
550 m.connect('my_account_emails', '/my_account/emails',
548 m.connect('my_account_emails', '/my_account/emails',
551 action='my_account_emails', conditions={'method': ['GET']})
549 action='my_account_emails', conditions={'method': ['GET']})
552 m.connect('my_account_emails', '/my_account/emails',
550 m.connect('my_account_emails', '/my_account/emails',
553 action='my_account_emails_add', conditions={'method': ['POST']})
551 action='my_account_emails_add', conditions={'method': ['POST']})
554 m.connect('my_account_emails', '/my_account/emails',
552 m.connect('my_account_emails', '/my_account/emails',
555 action='my_account_emails_delete', conditions={'method': ['DELETE']})
553 action='my_account_emails_delete', conditions={'method': ['DELETE']})
556
554
557 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
555 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
558 action='my_account_auth_tokens', conditions={'method': ['GET']})
556 action='my_account_auth_tokens', conditions={'method': ['GET']})
559 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
557 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
560 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
558 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
561 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
559 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
562 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
560 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
563 m.connect('my_account_notifications', '/my_account/notifications',
561 m.connect('my_account_notifications', '/my_account/notifications',
564 action='my_notifications',
562 action='my_notifications',
565 conditions={'method': ['GET']})
563 conditions={'method': ['GET']})
566 m.connect('my_account_notifications_toggle_visibility',
564 m.connect('my_account_notifications_toggle_visibility',
567 '/my_account/toggle_visibility',
565 '/my_account/toggle_visibility',
568 action='my_notifications_toggle_visibility',
566 action='my_notifications_toggle_visibility',
569 conditions={'method': ['POST']})
567 conditions={'method': ['POST']})
570
568
571 # NOTIFICATION REST ROUTES
569 # NOTIFICATION REST ROUTES
572 with rmap.submapper(path_prefix=ADMIN_PREFIX,
570 with rmap.submapper(path_prefix=ADMIN_PREFIX,
573 controller='admin/notifications') as m:
571 controller='admin/notifications') as m:
574 m.connect('notifications', '/notifications',
572 m.connect('notifications', '/notifications',
575 action='index', conditions={'method': ['GET']})
573 action='index', conditions={'method': ['GET']})
576 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
574 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
577 action='mark_all_read', conditions={'method': ['POST']})
575 action='mark_all_read', conditions={'method': ['POST']})
578 m.connect('/notifications/{notification_id}',
576 m.connect('/notifications/{notification_id}',
579 action='update', conditions={'method': ['PUT']})
577 action='update', conditions={'method': ['PUT']})
580 m.connect('/notifications/{notification_id}',
578 m.connect('/notifications/{notification_id}',
581 action='delete', conditions={'method': ['DELETE']})
579 action='delete', conditions={'method': ['DELETE']})
582 m.connect('notification', '/notifications/{notification_id}',
580 m.connect('notification', '/notifications/{notification_id}',
583 action='show', conditions={'method': ['GET']})
581 action='show', conditions={'method': ['GET']})
584
582
585 # ADMIN GIST
583 # ADMIN GIST
586 with rmap.submapper(path_prefix=ADMIN_PREFIX,
584 with rmap.submapper(path_prefix=ADMIN_PREFIX,
587 controller='admin/gists') as m:
585 controller='admin/gists') as m:
588 m.connect('gists', '/gists',
586 m.connect('gists', '/gists',
589 action='create', conditions={'method': ['POST']})
587 action='create', conditions={'method': ['POST']})
590 m.connect('gists', '/gists', jsroute=True,
588 m.connect('gists', '/gists', jsroute=True,
591 action='index', conditions={'method': ['GET']})
589 action='index', conditions={'method': ['GET']})
592 m.connect('new_gist', '/gists/new', jsroute=True,
590 m.connect('new_gist', '/gists/new', jsroute=True,
593 action='new', conditions={'method': ['GET']})
591 action='new', conditions={'method': ['GET']})
594
592
595 m.connect('/gists/{gist_id}',
593 m.connect('/gists/{gist_id}',
596 action='delete', conditions={'method': ['DELETE']})
594 action='delete', conditions={'method': ['DELETE']})
597 m.connect('edit_gist', '/gists/{gist_id}/edit',
595 m.connect('edit_gist', '/gists/{gist_id}/edit',
598 action='edit_form', conditions={'method': ['GET']})
596 action='edit_form', conditions={'method': ['GET']})
599 m.connect('edit_gist', '/gists/{gist_id}/edit',
597 m.connect('edit_gist', '/gists/{gist_id}/edit',
600 action='edit', conditions={'method': ['POST']})
598 action='edit', conditions={'method': ['POST']})
601 m.connect(
599 m.connect(
602 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
600 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
603 action='check_revision', conditions={'method': ['GET']})
601 action='check_revision', conditions={'method': ['GET']})
604
602
605 m.connect('gist', '/gists/{gist_id}',
603 m.connect('gist', '/gists/{gist_id}',
606 action='show', conditions={'method': ['GET']})
604 action='show', conditions={'method': ['GET']})
607 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
605 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
608 revision='tip',
606 revision='tip',
609 action='show', conditions={'method': ['GET']})
607 action='show', conditions={'method': ['GET']})
610 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
608 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
611 revision='tip',
609 revision='tip',
612 action='show', conditions={'method': ['GET']})
610 action='show', conditions={'method': ['GET']})
613 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
611 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
614 revision='tip',
612 revision='tip',
615 action='show', conditions={'method': ['GET']},
613 action='show', conditions={'method': ['GET']},
616 requirements=URL_NAME_REQUIREMENTS)
614 requirements=URL_NAME_REQUIREMENTS)
617
615
618 # ADMIN MAIN PAGES
616 # ADMIN MAIN PAGES
619 with rmap.submapper(path_prefix=ADMIN_PREFIX,
617 with rmap.submapper(path_prefix=ADMIN_PREFIX,
620 controller='admin/admin') as m:
618 controller='admin/admin') as m:
621 m.connect('admin_home', '', action='index')
619 m.connect('admin_home', '', action='index')
622 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
620 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
623 action='add_repo')
621 action='add_repo')
624 m.connect(
622 m.connect(
625 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
623 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
626 action='pull_requests')
624 action='pull_requests')
627 m.connect(
625 m.connect(
628 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
626 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
629 action='pull_requests')
627 action='pull_requests')
630
628
631
629
632 # USER JOURNAL
630 # USER JOURNAL
633 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
631 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
634 controller='journal', action='index')
632 controller='journal', action='index')
635 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
633 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
636 controller='journal', action='journal_rss')
634 controller='journal', action='journal_rss')
637 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
635 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
638 controller='journal', action='journal_atom')
636 controller='journal', action='journal_atom')
639
637
640 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
638 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
641 controller='journal', action='public_journal')
639 controller='journal', action='public_journal')
642
640
643 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
641 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
644 controller='journal', action='public_journal_rss')
642 controller='journal', action='public_journal_rss')
645
643
646 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
644 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
647 controller='journal', action='public_journal_rss')
645 controller='journal', action='public_journal_rss')
648
646
649 rmap.connect('public_journal_atom',
647 rmap.connect('public_journal_atom',
650 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
648 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
651 action='public_journal_atom')
649 action='public_journal_atom')
652
650
653 rmap.connect('public_journal_atom_old',
651 rmap.connect('public_journal_atom_old',
654 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
652 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
655 action='public_journal_atom')
653 action='public_journal_atom')
656
654
657 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
655 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
658 controller='journal', action='toggle_following', jsroute=True,
656 controller='journal', action='toggle_following', jsroute=True,
659 conditions={'method': ['POST']})
657 conditions={'method': ['POST']})
660
658
661 # FULL TEXT SEARCH
659 # FULL TEXT SEARCH
662 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
660 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
663 controller='search')
661 controller='search')
664 rmap.connect('search_repo_home', '/{repo_name}/search',
662 rmap.connect('search_repo_home', '/{repo_name}/search',
665 controller='search',
663 controller='search',
666 action='index',
664 action='index',
667 conditions={'function': check_repo},
665 conditions={'function': check_repo},
668 requirements=URL_NAME_REQUIREMENTS)
666 requirements=URL_NAME_REQUIREMENTS)
669
667
670 # FEEDS
668 # FEEDS
671 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
669 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
672 controller='feed', action='rss',
670 controller='feed', action='rss',
673 conditions={'function': check_repo},
671 conditions={'function': check_repo},
674 requirements=URL_NAME_REQUIREMENTS)
672 requirements=URL_NAME_REQUIREMENTS)
675
673
676 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
674 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
677 controller='feed', action='atom',
675 controller='feed', action='atom',
678 conditions={'function': check_repo},
676 conditions={'function': check_repo},
679 requirements=URL_NAME_REQUIREMENTS)
677 requirements=URL_NAME_REQUIREMENTS)
680
678
681 #==========================================================================
679 #==========================================================================
682 # REPOSITORY ROUTES
680 # REPOSITORY ROUTES
683 #==========================================================================
681 #==========================================================================
684
682
685 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
683 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
686 controller='admin/repos', action='repo_creating',
684 controller='admin/repos', action='repo_creating',
687 requirements=URL_NAME_REQUIREMENTS)
685 requirements=URL_NAME_REQUIREMENTS)
688 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
686 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
689 controller='admin/repos', action='repo_check',
687 controller='admin/repos', action='repo_check',
690 requirements=URL_NAME_REQUIREMENTS)
688 requirements=URL_NAME_REQUIREMENTS)
691
689
692 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
690 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
693 controller='summary', action='repo_stats',
691 controller='summary', action='repo_stats',
694 conditions={'function': check_repo},
692 conditions={'function': check_repo},
695 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
693 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
696
694
697 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
695 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
698 controller='summary', action='repo_refs_data', jsroute=True,
696 controller='summary', action='repo_refs_data', jsroute=True,
699 requirements=URL_NAME_REQUIREMENTS)
697 requirements=URL_NAME_REQUIREMENTS)
700 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
698 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
701 controller='summary', action='repo_refs_changelog_data',
699 controller='summary', action='repo_refs_changelog_data',
702 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
700 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
703
701
704 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
702 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
705 controller='changeset', revision='tip', jsroute=True,
703 controller='changeset', revision='tip', jsroute=True,
706 conditions={'function': check_repo},
704 conditions={'function': check_repo},
707 requirements=URL_NAME_REQUIREMENTS)
705 requirements=URL_NAME_REQUIREMENTS)
708 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
706 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
709 controller='changeset', revision='tip', action='changeset_children',
707 controller='changeset', revision='tip', action='changeset_children',
710 conditions={'function': check_repo},
708 conditions={'function': check_repo},
711 requirements=URL_NAME_REQUIREMENTS)
709 requirements=URL_NAME_REQUIREMENTS)
712 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
710 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
713 controller='changeset', revision='tip', action='changeset_parents',
711 controller='changeset', revision='tip', action='changeset_parents',
714 conditions={'function': check_repo},
712 conditions={'function': check_repo},
715 requirements=URL_NAME_REQUIREMENTS)
713 requirements=URL_NAME_REQUIREMENTS)
716
714
717 # repo edit options
715 # repo edit options
718 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
716 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
719 controller='admin/repos', action='edit',
717 controller='admin/repos', action='edit',
720 conditions={'method': ['GET'], 'function': check_repo},
718 conditions={'method': ['GET'], 'function': check_repo},
721 requirements=URL_NAME_REQUIREMENTS)
719 requirements=URL_NAME_REQUIREMENTS)
722
720
723 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
721 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
724 jsroute=True,
722 jsroute=True,
725 controller='admin/repos', action='edit_permissions',
723 controller='admin/repos', action='edit_permissions',
726 conditions={'method': ['GET'], 'function': check_repo},
724 conditions={'method': ['GET'], 'function': check_repo},
727 requirements=URL_NAME_REQUIREMENTS)
725 requirements=URL_NAME_REQUIREMENTS)
728 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
726 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
729 controller='admin/repos', action='edit_permissions_update',
727 controller='admin/repos', action='edit_permissions_update',
730 conditions={'method': ['PUT'], 'function': check_repo},
728 conditions={'method': ['PUT'], 'function': check_repo},
731 requirements=URL_NAME_REQUIREMENTS)
729 requirements=URL_NAME_REQUIREMENTS)
732
730
733 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
731 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
734 controller='admin/repos', action='edit_fields',
732 controller='admin/repos', action='edit_fields',
735 conditions={'method': ['GET'], 'function': check_repo},
733 conditions={'method': ['GET'], 'function': check_repo},
736 requirements=URL_NAME_REQUIREMENTS)
734 requirements=URL_NAME_REQUIREMENTS)
737 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
735 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
738 controller='admin/repos', action='create_repo_field',
736 controller='admin/repos', action='create_repo_field',
739 conditions={'method': ['PUT'], 'function': check_repo},
737 conditions={'method': ['PUT'], 'function': check_repo},
740 requirements=URL_NAME_REQUIREMENTS)
738 requirements=URL_NAME_REQUIREMENTS)
741 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
739 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
742 controller='admin/repos', action='delete_repo_field',
740 controller='admin/repos', action='delete_repo_field',
743 conditions={'method': ['DELETE'], 'function': check_repo},
741 conditions={'method': ['DELETE'], 'function': check_repo},
744 requirements=URL_NAME_REQUIREMENTS)
742 requirements=URL_NAME_REQUIREMENTS)
745
743
746 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
744 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
747 controller='admin/repos', action='edit_advanced',
745 controller='admin/repos', action='edit_advanced',
748 conditions={'method': ['GET'], 'function': check_repo},
746 conditions={'method': ['GET'], 'function': check_repo},
749 requirements=URL_NAME_REQUIREMENTS)
747 requirements=URL_NAME_REQUIREMENTS)
750
748
751 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
749 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
752 controller='admin/repos', action='edit_advanced_locking',
750 controller='admin/repos', action='edit_advanced_locking',
753 conditions={'method': ['PUT'], 'function': check_repo},
751 conditions={'method': ['PUT'], 'function': check_repo},
754 requirements=URL_NAME_REQUIREMENTS)
752 requirements=URL_NAME_REQUIREMENTS)
755 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
753 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
756 controller='admin/repos', action='toggle_locking',
754 controller='admin/repos', action='toggle_locking',
757 conditions={'method': ['GET'], 'function': check_repo},
755 conditions={'method': ['GET'], 'function': check_repo},
758 requirements=URL_NAME_REQUIREMENTS)
756 requirements=URL_NAME_REQUIREMENTS)
759
757
760 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
758 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
761 controller='admin/repos', action='edit_advanced_journal',
759 controller='admin/repos', action='edit_advanced_journal',
762 conditions={'method': ['PUT'], 'function': check_repo},
760 conditions={'method': ['PUT'], 'function': check_repo},
763 requirements=URL_NAME_REQUIREMENTS)
761 requirements=URL_NAME_REQUIREMENTS)
764
762
765 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
763 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
766 controller='admin/repos', action='edit_advanced_fork',
764 controller='admin/repos', action='edit_advanced_fork',
767 conditions={'method': ['PUT'], 'function': check_repo},
765 conditions={'method': ['PUT'], 'function': check_repo},
768 requirements=URL_NAME_REQUIREMENTS)
766 requirements=URL_NAME_REQUIREMENTS)
769
767
770 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
768 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
771 controller='admin/repos', action='edit_caches_form',
769 controller='admin/repos', action='edit_caches_form',
772 conditions={'method': ['GET'], 'function': check_repo},
770 conditions={'method': ['GET'], 'function': check_repo},
773 requirements=URL_NAME_REQUIREMENTS)
771 requirements=URL_NAME_REQUIREMENTS)
774 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
772 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
775 controller='admin/repos', action='edit_caches',
773 controller='admin/repos', action='edit_caches',
776 conditions={'method': ['PUT'], 'function': check_repo},
774 conditions={'method': ['PUT'], 'function': check_repo},
777 requirements=URL_NAME_REQUIREMENTS)
775 requirements=URL_NAME_REQUIREMENTS)
778
776
779 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
777 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
780 controller='admin/repos', action='edit_remote_form',
778 controller='admin/repos', action='edit_remote_form',
781 conditions={'method': ['GET'], 'function': check_repo},
779 conditions={'method': ['GET'], 'function': check_repo},
782 requirements=URL_NAME_REQUIREMENTS)
780 requirements=URL_NAME_REQUIREMENTS)
783 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
781 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
784 controller='admin/repos', action='edit_remote',
782 controller='admin/repos', action='edit_remote',
785 conditions={'method': ['PUT'], 'function': check_repo},
783 conditions={'method': ['PUT'], 'function': check_repo},
786 requirements=URL_NAME_REQUIREMENTS)
784 requirements=URL_NAME_REQUIREMENTS)
787
785
788 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
786 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
789 controller='admin/repos', action='edit_statistics_form',
787 controller='admin/repos', action='edit_statistics_form',
790 conditions={'method': ['GET'], 'function': check_repo},
788 conditions={'method': ['GET'], 'function': check_repo},
791 requirements=URL_NAME_REQUIREMENTS)
789 requirements=URL_NAME_REQUIREMENTS)
792 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
790 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
793 controller='admin/repos', action='edit_statistics',
791 controller='admin/repos', action='edit_statistics',
794 conditions={'method': ['PUT'], 'function': check_repo},
792 conditions={'method': ['PUT'], 'function': check_repo},
795 requirements=URL_NAME_REQUIREMENTS)
793 requirements=URL_NAME_REQUIREMENTS)
796 rmap.connect('repo_settings_issuetracker',
794 rmap.connect('repo_settings_issuetracker',
797 '/{repo_name}/settings/issue-tracker',
795 '/{repo_name}/settings/issue-tracker',
798 controller='admin/repos', action='repo_issuetracker',
796 controller='admin/repos', action='repo_issuetracker',
799 conditions={'method': ['GET'], 'function': check_repo},
797 conditions={'method': ['GET'], 'function': check_repo},
800 requirements=URL_NAME_REQUIREMENTS)
798 requirements=URL_NAME_REQUIREMENTS)
801 rmap.connect('repo_issuetracker_test',
799 rmap.connect('repo_issuetracker_test',
802 '/{repo_name}/settings/issue-tracker/test',
800 '/{repo_name}/settings/issue-tracker/test',
803 controller='admin/repos', action='repo_issuetracker_test',
801 controller='admin/repos', action='repo_issuetracker_test',
804 conditions={'method': ['POST'], 'function': check_repo},
802 conditions={'method': ['POST'], 'function': check_repo},
805 requirements=URL_NAME_REQUIREMENTS)
803 requirements=URL_NAME_REQUIREMENTS)
806 rmap.connect('repo_issuetracker_delete',
804 rmap.connect('repo_issuetracker_delete',
807 '/{repo_name}/settings/issue-tracker/delete',
805 '/{repo_name}/settings/issue-tracker/delete',
808 controller='admin/repos', action='repo_issuetracker_delete',
806 controller='admin/repos', action='repo_issuetracker_delete',
809 conditions={'method': ['DELETE'], 'function': check_repo},
807 conditions={'method': ['DELETE'], 'function': check_repo},
810 requirements=URL_NAME_REQUIREMENTS)
808 requirements=URL_NAME_REQUIREMENTS)
811 rmap.connect('repo_issuetracker_save',
809 rmap.connect('repo_issuetracker_save',
812 '/{repo_name}/settings/issue-tracker/save',
810 '/{repo_name}/settings/issue-tracker/save',
813 controller='admin/repos', action='repo_issuetracker_save',
811 controller='admin/repos', action='repo_issuetracker_save',
814 conditions={'method': ['POST'], 'function': check_repo},
812 conditions={'method': ['POST'], 'function': check_repo},
815 requirements=URL_NAME_REQUIREMENTS)
813 requirements=URL_NAME_REQUIREMENTS)
816 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
814 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
817 controller='admin/repos', action='repo_settings_vcs_update',
815 controller='admin/repos', action='repo_settings_vcs_update',
818 conditions={'method': ['POST'], 'function': check_repo},
816 conditions={'method': ['POST'], 'function': check_repo},
819 requirements=URL_NAME_REQUIREMENTS)
817 requirements=URL_NAME_REQUIREMENTS)
820 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
821 controller='admin/repos', action='repo_settings_vcs',
819 controller='admin/repos', action='repo_settings_vcs',
822 conditions={'method': ['GET'], 'function': check_repo},
820 conditions={'method': ['GET'], 'function': check_repo},
823 requirements=URL_NAME_REQUIREMENTS)
821 requirements=URL_NAME_REQUIREMENTS)
824 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
822 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
825 controller='admin/repos', action='repo_delete_svn_pattern',
823 controller='admin/repos', action='repo_delete_svn_pattern',
826 conditions={'method': ['DELETE'], 'function': check_repo},
824 conditions={'method': ['DELETE'], 'function': check_repo},
827 requirements=URL_NAME_REQUIREMENTS)
825 requirements=URL_NAME_REQUIREMENTS)
828
826
829 # still working url for backward compat.
827 # still working url for backward compat.
830 rmap.connect('raw_changeset_home_depraced',
828 rmap.connect('raw_changeset_home_depraced',
831 '/{repo_name}/raw-changeset/{revision}',
829 '/{repo_name}/raw-changeset/{revision}',
832 controller='changeset', action='changeset_raw',
830 controller='changeset', action='changeset_raw',
833 revision='tip', conditions={'function': check_repo},
831 revision='tip', conditions={'function': check_repo},
834 requirements=URL_NAME_REQUIREMENTS)
832 requirements=URL_NAME_REQUIREMENTS)
835
833
836 # new URLs
834 # new URLs
837 rmap.connect('changeset_raw_home',
835 rmap.connect('changeset_raw_home',
838 '/{repo_name}/changeset-diff/{revision}',
836 '/{repo_name}/changeset-diff/{revision}',
839 controller='changeset', action='changeset_raw',
837 controller='changeset', action='changeset_raw',
840 revision='tip', conditions={'function': check_repo},
838 revision='tip', conditions={'function': check_repo},
841 requirements=URL_NAME_REQUIREMENTS)
839 requirements=URL_NAME_REQUIREMENTS)
842
840
843 rmap.connect('changeset_patch_home',
841 rmap.connect('changeset_patch_home',
844 '/{repo_name}/changeset-patch/{revision}',
842 '/{repo_name}/changeset-patch/{revision}',
845 controller='changeset', action='changeset_patch',
843 controller='changeset', action='changeset_patch',
846 revision='tip', conditions={'function': check_repo},
844 revision='tip', conditions={'function': check_repo},
847 requirements=URL_NAME_REQUIREMENTS)
845 requirements=URL_NAME_REQUIREMENTS)
848
846
849 rmap.connect('changeset_download_home',
847 rmap.connect('changeset_download_home',
850 '/{repo_name}/changeset-download/{revision}',
848 '/{repo_name}/changeset-download/{revision}',
851 controller='changeset', action='changeset_download',
849 controller='changeset', action='changeset_download',
852 revision='tip', conditions={'function': check_repo},
850 revision='tip', conditions={'function': check_repo},
853 requirements=URL_NAME_REQUIREMENTS)
851 requirements=URL_NAME_REQUIREMENTS)
854
852
855 rmap.connect('changeset_comment',
853 rmap.connect('changeset_comment',
856 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
854 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
857 controller='changeset', revision='tip', action='comment',
855 controller='changeset', revision='tip', action='comment',
858 conditions={'function': check_repo},
856 conditions={'function': check_repo},
859 requirements=URL_NAME_REQUIREMENTS)
857 requirements=URL_NAME_REQUIREMENTS)
860
858
861 rmap.connect('changeset_comment_preview',
859 rmap.connect('changeset_comment_preview',
862 '/{repo_name}/changeset/comment/preview', jsroute=True,
860 '/{repo_name}/changeset/comment/preview', jsroute=True,
863 controller='changeset', action='preview_comment',
861 controller='changeset', action='preview_comment',
864 conditions={'function': check_repo, 'method': ['POST']},
862 conditions={'function': check_repo, 'method': ['POST']},
865 requirements=URL_NAME_REQUIREMENTS)
863 requirements=URL_NAME_REQUIREMENTS)
866
864
867 rmap.connect('changeset_comment_delete',
865 rmap.connect('changeset_comment_delete',
868 '/{repo_name}/changeset/comment/{comment_id}/delete',
866 '/{repo_name}/changeset/comment/{comment_id}/delete',
869 controller='changeset', action='delete_comment',
867 controller='changeset', action='delete_comment',
870 conditions={'function': check_repo, 'method': ['DELETE']},
868 conditions={'function': check_repo, 'method': ['DELETE']},
871 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
869 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
872
870
873 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
871 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
874 controller='changeset', action='changeset_info',
872 controller='changeset', action='changeset_info',
875 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
873 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
876
874
877 rmap.connect('compare_home',
875 rmap.connect('compare_home',
878 '/{repo_name}/compare',
876 '/{repo_name}/compare',
879 controller='compare', action='index',
877 controller='compare', action='index',
880 conditions={'function': check_repo},
878 conditions={'function': check_repo},
881 requirements=URL_NAME_REQUIREMENTS)
879 requirements=URL_NAME_REQUIREMENTS)
882
880
883 rmap.connect('compare_url',
881 rmap.connect('compare_url',
884 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
882 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
885 controller='compare', action='compare',
883 controller='compare', action='compare',
886 conditions={'function': check_repo},
884 conditions={'function': check_repo},
887 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
885 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
888
886
889 rmap.connect('pullrequest_home',
887 rmap.connect('pullrequest_home',
890 '/{repo_name}/pull-request/new', controller='pullrequests',
888 '/{repo_name}/pull-request/new', controller='pullrequests',
891 action='index', conditions={'function': check_repo,
889 action='index', conditions={'function': check_repo,
892 'method': ['GET']},
890 'method': ['GET']},
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
891 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
894
892
895 rmap.connect('pullrequest',
893 rmap.connect('pullrequest',
896 '/{repo_name}/pull-request/new', controller='pullrequests',
894 '/{repo_name}/pull-request/new', controller='pullrequests',
897 action='create', conditions={'function': check_repo,
895 action='create', conditions={'function': check_repo,
898 'method': ['POST']},
896 'method': ['POST']},
899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
897 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
900
898
901 rmap.connect('pullrequest_repo_refs',
899 rmap.connect('pullrequest_repo_refs',
902 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
900 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
903 controller='pullrequests',
901 controller='pullrequests',
904 action='get_repo_refs',
902 action='get_repo_refs',
905 conditions={'function': check_repo, 'method': ['GET']},
903 conditions={'function': check_repo, 'method': ['GET']},
906 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
904 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
907
905
908 rmap.connect('pullrequest_repo_destinations',
906 rmap.connect('pullrequest_repo_destinations',
909 '/{repo_name}/pull-request/repo-destinations',
907 '/{repo_name}/pull-request/repo-destinations',
910 controller='pullrequests',
908 controller='pullrequests',
911 action='get_repo_destinations',
909 action='get_repo_destinations',
912 conditions={'function': check_repo, 'method': ['GET']},
910 conditions={'function': check_repo, 'method': ['GET']},
913 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
911 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
914
912
915 rmap.connect('pullrequest_show',
913 rmap.connect('pullrequest_show',
916 '/{repo_name}/pull-request/{pull_request_id}',
914 '/{repo_name}/pull-request/{pull_request_id}',
917 controller='pullrequests',
915 controller='pullrequests',
918 action='show', conditions={'function': check_repo,
916 action='show', conditions={'function': check_repo,
919 'method': ['GET']},
917 'method': ['GET']},
920 requirements=URL_NAME_REQUIREMENTS)
918 requirements=URL_NAME_REQUIREMENTS)
921
919
922 rmap.connect('pullrequest_update',
920 rmap.connect('pullrequest_update',
923 '/{repo_name}/pull-request/{pull_request_id}',
921 '/{repo_name}/pull-request/{pull_request_id}',
924 controller='pullrequests',
922 controller='pullrequests',
925 action='update', conditions={'function': check_repo,
923 action='update', conditions={'function': check_repo,
926 'method': ['PUT']},
924 'method': ['PUT']},
927 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
925 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
928
926
929 rmap.connect('pullrequest_merge',
927 rmap.connect('pullrequest_merge',
930 '/{repo_name}/pull-request/{pull_request_id}',
928 '/{repo_name}/pull-request/{pull_request_id}',
931 controller='pullrequests',
929 controller='pullrequests',
932 action='merge', conditions={'function': check_repo,
930 action='merge', conditions={'function': check_repo,
933 'method': ['POST']},
931 'method': ['POST']},
934 requirements=URL_NAME_REQUIREMENTS)
932 requirements=URL_NAME_REQUIREMENTS)
935
933
936 rmap.connect('pullrequest_delete',
934 rmap.connect('pullrequest_delete',
937 '/{repo_name}/pull-request/{pull_request_id}',
935 '/{repo_name}/pull-request/{pull_request_id}',
938 controller='pullrequests',
936 controller='pullrequests',
939 action='delete', conditions={'function': check_repo,
937 action='delete', conditions={'function': check_repo,
940 'method': ['DELETE']},
938 'method': ['DELETE']},
941 requirements=URL_NAME_REQUIREMENTS)
939 requirements=URL_NAME_REQUIREMENTS)
942
940
943 rmap.connect('pullrequest_show_all',
941 rmap.connect('pullrequest_show_all',
944 '/{repo_name}/pull-request',
942 '/{repo_name}/pull-request',
945 controller='pullrequests',
943 controller='pullrequests',
946 action='show_all', conditions={'function': check_repo,
944 action='show_all', conditions={'function': check_repo,
947 'method': ['GET']},
945 'method': ['GET']},
948 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
946 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
949
947
950 rmap.connect('pullrequest_comment',
948 rmap.connect('pullrequest_comment',
951 '/{repo_name}/pull-request-comment/{pull_request_id}',
949 '/{repo_name}/pull-request-comment/{pull_request_id}',
952 controller='pullrequests',
950 controller='pullrequests',
953 action='comment', conditions={'function': check_repo,
951 action='comment', conditions={'function': check_repo,
954 'method': ['POST']},
952 'method': ['POST']},
955 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
953 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
956
954
957 rmap.connect('pullrequest_comment_delete',
955 rmap.connect('pullrequest_comment_delete',
958 '/{repo_name}/pull-request-comment/{comment_id}/delete',
956 '/{repo_name}/pull-request-comment/{comment_id}/delete',
959 controller='pullrequests', action='delete_comment',
957 controller='pullrequests', action='delete_comment',
960 conditions={'function': check_repo, 'method': ['DELETE']},
958 conditions={'function': check_repo, 'method': ['DELETE']},
961 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
959 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
962
960
963 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
961 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
964 controller='summary', conditions={'function': check_repo},
962 controller='summary', conditions={'function': check_repo},
965 requirements=URL_NAME_REQUIREMENTS)
963 requirements=URL_NAME_REQUIREMENTS)
966
964
967 rmap.connect('branches_home', '/{repo_name}/branches',
965 rmap.connect('branches_home', '/{repo_name}/branches',
968 controller='branches', conditions={'function': check_repo},
966 controller='branches', conditions={'function': check_repo},
969 requirements=URL_NAME_REQUIREMENTS)
967 requirements=URL_NAME_REQUIREMENTS)
970
968
971 rmap.connect('tags_home', '/{repo_name}/tags',
969 rmap.connect('tags_home', '/{repo_name}/tags',
972 controller='tags', conditions={'function': check_repo},
970 controller='tags', conditions={'function': check_repo},
973 requirements=URL_NAME_REQUIREMENTS)
971 requirements=URL_NAME_REQUIREMENTS)
974
972
975 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
973 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
976 controller='bookmarks', conditions={'function': check_repo},
974 controller='bookmarks', conditions={'function': check_repo},
977 requirements=URL_NAME_REQUIREMENTS)
975 requirements=URL_NAME_REQUIREMENTS)
978
976
979 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
977 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
980 controller='changelog', conditions={'function': check_repo},
978 controller='changelog', conditions={'function': check_repo},
981 requirements=URL_NAME_REQUIREMENTS)
979 requirements=URL_NAME_REQUIREMENTS)
982
980
983 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
981 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
984 controller='changelog', action='changelog_summary',
982 controller='changelog', action='changelog_summary',
985 conditions={'function': check_repo},
983 conditions={'function': check_repo},
986 requirements=URL_NAME_REQUIREMENTS)
984 requirements=URL_NAME_REQUIREMENTS)
987
985
988 rmap.connect('changelog_file_home',
986 rmap.connect('changelog_file_home',
989 '/{repo_name}/changelog/{revision}/{f_path}',
987 '/{repo_name}/changelog/{revision}/{f_path}',
990 controller='changelog', f_path=None,
988 controller='changelog', f_path=None,
991 conditions={'function': check_repo},
989 conditions={'function': check_repo},
992 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
990 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
993
991
994 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
992 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
995 controller='changelog', action='changelog_details',
993 controller='changelog', action='changelog_details',
996 conditions={'function': check_repo},
994 conditions={'function': check_repo},
997 requirements=URL_NAME_REQUIREMENTS)
995 requirements=URL_NAME_REQUIREMENTS)
998
996
999 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
997 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1000 controller='files', revision='tip', f_path='',
998 controller='files', revision='tip', f_path='',
1001 conditions={'function': check_repo},
999 conditions={'function': check_repo},
1002 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1000 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1003
1001
1004 rmap.connect('files_home_simple_catchrev',
1002 rmap.connect('files_home_simple_catchrev',
1005 '/{repo_name}/files/{revision}',
1003 '/{repo_name}/files/{revision}',
1006 controller='files', revision='tip', f_path='',
1004 controller='files', revision='tip', f_path='',
1007 conditions={'function': check_repo},
1005 conditions={'function': check_repo},
1008 requirements=URL_NAME_REQUIREMENTS)
1006 requirements=URL_NAME_REQUIREMENTS)
1009
1007
1010 rmap.connect('files_home_simple_catchall',
1008 rmap.connect('files_home_simple_catchall',
1011 '/{repo_name}/files',
1009 '/{repo_name}/files',
1012 controller='files', revision='tip', f_path='',
1010 controller='files', revision='tip', f_path='',
1013 conditions={'function': check_repo},
1011 conditions={'function': check_repo},
1014 requirements=URL_NAME_REQUIREMENTS)
1012 requirements=URL_NAME_REQUIREMENTS)
1015
1013
1016 rmap.connect('files_history_home',
1014 rmap.connect('files_history_home',
1017 '/{repo_name}/history/{revision}/{f_path}',
1015 '/{repo_name}/history/{revision}/{f_path}',
1018 controller='files', action='history', revision='tip', f_path='',
1016 controller='files', action='history', revision='tip', f_path='',
1019 conditions={'function': check_repo},
1017 conditions={'function': check_repo},
1020 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1018 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1021
1019
1022 rmap.connect('files_authors_home',
1020 rmap.connect('files_authors_home',
1023 '/{repo_name}/authors/{revision}/{f_path}',
1021 '/{repo_name}/authors/{revision}/{f_path}',
1024 controller='files', action='authors', revision='tip', f_path='',
1022 controller='files', action='authors', revision='tip', f_path='',
1025 conditions={'function': check_repo},
1023 conditions={'function': check_repo},
1026 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1024 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1027
1025
1028 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1026 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1029 controller='files', action='diff', f_path='',
1027 controller='files', action='diff', f_path='',
1030 conditions={'function': check_repo},
1028 conditions={'function': check_repo},
1031 requirements=URL_NAME_REQUIREMENTS)
1029 requirements=URL_NAME_REQUIREMENTS)
1032
1030
1033 rmap.connect('files_diff_2way_home',
1031 rmap.connect('files_diff_2way_home',
1034 '/{repo_name}/diff-2way/{f_path}',
1032 '/{repo_name}/diff-2way/{f_path}',
1035 controller='files', action='diff_2way', f_path='',
1033 controller='files', action='diff_2way', f_path='',
1036 conditions={'function': check_repo},
1034 conditions={'function': check_repo},
1037 requirements=URL_NAME_REQUIREMENTS)
1035 requirements=URL_NAME_REQUIREMENTS)
1038
1036
1039 rmap.connect('files_rawfile_home',
1037 rmap.connect('files_rawfile_home',
1040 '/{repo_name}/rawfile/{revision}/{f_path}',
1038 '/{repo_name}/rawfile/{revision}/{f_path}',
1041 controller='files', action='rawfile', revision='tip',
1039 controller='files', action='rawfile', revision='tip',
1042 f_path='', conditions={'function': check_repo},
1040 f_path='', conditions={'function': check_repo},
1043 requirements=URL_NAME_REQUIREMENTS)
1041 requirements=URL_NAME_REQUIREMENTS)
1044
1042
1045 rmap.connect('files_raw_home',
1043 rmap.connect('files_raw_home',
1046 '/{repo_name}/raw/{revision}/{f_path}',
1044 '/{repo_name}/raw/{revision}/{f_path}',
1047 controller='files', action='raw', revision='tip', f_path='',
1045 controller='files', action='raw', revision='tip', f_path='',
1048 conditions={'function': check_repo},
1046 conditions={'function': check_repo},
1049 requirements=URL_NAME_REQUIREMENTS)
1047 requirements=URL_NAME_REQUIREMENTS)
1050
1048
1051 rmap.connect('files_render_home',
1049 rmap.connect('files_render_home',
1052 '/{repo_name}/render/{revision}/{f_path}',
1050 '/{repo_name}/render/{revision}/{f_path}',
1053 controller='files', action='index', revision='tip', f_path='',
1051 controller='files', action='index', revision='tip', f_path='',
1054 rendered=True, conditions={'function': check_repo},
1052 rendered=True, conditions={'function': check_repo},
1055 requirements=URL_NAME_REQUIREMENTS)
1053 requirements=URL_NAME_REQUIREMENTS)
1056
1054
1057 rmap.connect('files_annotate_home',
1055 rmap.connect('files_annotate_home',
1058 '/{repo_name}/annotate/{revision}/{f_path}',
1056 '/{repo_name}/annotate/{revision}/{f_path}',
1059 controller='files', action='index', revision='tip',
1057 controller='files', action='index', revision='tip',
1060 f_path='', annotate=True, conditions={'function': check_repo},
1058 f_path='', annotate=True, conditions={'function': check_repo},
1061 requirements=URL_NAME_REQUIREMENTS)
1059 requirements=URL_NAME_REQUIREMENTS)
1062
1060
1063 rmap.connect('files_edit',
1061 rmap.connect('files_edit',
1064 '/{repo_name}/edit/{revision}/{f_path}',
1062 '/{repo_name}/edit/{revision}/{f_path}',
1065 controller='files', action='edit', revision='tip',
1063 controller='files', action='edit', revision='tip',
1066 f_path='',
1064 f_path='',
1067 conditions={'function': check_repo, 'method': ['POST']},
1065 conditions={'function': check_repo, 'method': ['POST']},
1068 requirements=URL_NAME_REQUIREMENTS)
1066 requirements=URL_NAME_REQUIREMENTS)
1069
1067
1070 rmap.connect('files_edit_home',
1068 rmap.connect('files_edit_home',
1071 '/{repo_name}/edit/{revision}/{f_path}',
1069 '/{repo_name}/edit/{revision}/{f_path}',
1072 controller='files', action='edit_home', revision='tip',
1070 controller='files', action='edit_home', revision='tip',
1073 f_path='', conditions={'function': check_repo},
1071 f_path='', conditions={'function': check_repo},
1074 requirements=URL_NAME_REQUIREMENTS)
1072 requirements=URL_NAME_REQUIREMENTS)
1075
1073
1076 rmap.connect('files_add',
1074 rmap.connect('files_add',
1077 '/{repo_name}/add/{revision}/{f_path}',
1075 '/{repo_name}/add/{revision}/{f_path}',
1078 controller='files', action='add', revision='tip',
1076 controller='files', action='add', revision='tip',
1079 f_path='',
1077 f_path='',
1080 conditions={'function': check_repo, 'method': ['POST']},
1078 conditions={'function': check_repo, 'method': ['POST']},
1081 requirements=URL_NAME_REQUIREMENTS)
1079 requirements=URL_NAME_REQUIREMENTS)
1082
1080
1083 rmap.connect('files_add_home',
1081 rmap.connect('files_add_home',
1084 '/{repo_name}/add/{revision}/{f_path}',
1082 '/{repo_name}/add/{revision}/{f_path}',
1085 controller='files', action='add_home', revision='tip',
1083 controller='files', action='add_home', revision='tip',
1086 f_path='', conditions={'function': check_repo},
1084 f_path='', conditions={'function': check_repo},
1087 requirements=URL_NAME_REQUIREMENTS)
1085 requirements=URL_NAME_REQUIREMENTS)
1088
1086
1089 rmap.connect('files_delete',
1087 rmap.connect('files_delete',
1090 '/{repo_name}/delete/{revision}/{f_path}',
1088 '/{repo_name}/delete/{revision}/{f_path}',
1091 controller='files', action='delete', revision='tip',
1089 controller='files', action='delete', revision='tip',
1092 f_path='',
1090 f_path='',
1093 conditions={'function': check_repo, 'method': ['POST']},
1091 conditions={'function': check_repo, 'method': ['POST']},
1094 requirements=URL_NAME_REQUIREMENTS)
1092 requirements=URL_NAME_REQUIREMENTS)
1095
1093
1096 rmap.connect('files_delete_home',
1094 rmap.connect('files_delete_home',
1097 '/{repo_name}/delete/{revision}/{f_path}',
1095 '/{repo_name}/delete/{revision}/{f_path}',
1098 controller='files', action='delete_home', revision='tip',
1096 controller='files', action='delete_home', revision='tip',
1099 f_path='', conditions={'function': check_repo},
1097 f_path='', conditions={'function': check_repo},
1100 requirements=URL_NAME_REQUIREMENTS)
1098 requirements=URL_NAME_REQUIREMENTS)
1101
1099
1102 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1100 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1103 controller='files', action='archivefile',
1101 controller='files', action='archivefile',
1104 conditions={'function': check_repo},
1102 conditions={'function': check_repo},
1105 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1103 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1106
1104
1107 rmap.connect('files_nodelist_home',
1105 rmap.connect('files_nodelist_home',
1108 '/{repo_name}/nodelist/{revision}/{f_path}',
1106 '/{repo_name}/nodelist/{revision}/{f_path}',
1109 controller='files', action='nodelist',
1107 controller='files', action='nodelist',
1110 conditions={'function': check_repo},
1108 conditions={'function': check_repo},
1111 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1109 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1112
1110
1113 rmap.connect('files_nodetree_full',
1111 rmap.connect('files_nodetree_full',
1114 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1112 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1115 controller='files', action='nodetree_full',
1113 controller='files', action='nodetree_full',
1116 conditions={'function': check_repo},
1114 conditions={'function': check_repo},
1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1115 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1118
1116
1119 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1117 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1120 controller='forks', action='fork_create',
1118 controller='forks', action='fork_create',
1121 conditions={'function': check_repo, 'method': ['POST']},
1119 conditions={'function': check_repo, 'method': ['POST']},
1122 requirements=URL_NAME_REQUIREMENTS)
1120 requirements=URL_NAME_REQUIREMENTS)
1123
1121
1124 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1122 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1125 controller='forks', action='fork',
1123 controller='forks', action='fork',
1126 conditions={'function': check_repo},
1124 conditions={'function': check_repo},
1127 requirements=URL_NAME_REQUIREMENTS)
1125 requirements=URL_NAME_REQUIREMENTS)
1128
1126
1129 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1127 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1130 controller='forks', action='forks',
1128 controller='forks', action='forks',
1131 conditions={'function': check_repo},
1129 conditions={'function': check_repo},
1132 requirements=URL_NAME_REQUIREMENTS)
1130 requirements=URL_NAME_REQUIREMENTS)
1133
1131
1134 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1132 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1135 controller='followers', action='followers',
1133 controller='followers', action='followers',
1136 conditions={'function': check_repo},
1134 conditions={'function': check_repo},
1137 requirements=URL_NAME_REQUIREMENTS)
1135 requirements=URL_NAME_REQUIREMENTS)
1138
1136
1139 # must be here for proper group/repo catching pattern
1137 # must be here for proper group/repo catching pattern
1140 _connect_with_slash(
1138 _connect_with_slash(
1141 rmap, 'repo_group_home', '/{group_name}',
1139 rmap, 'repo_group_home', '/{group_name}',
1142 controller='home', action='index_repo_group',
1140 controller='home', action='index_repo_group',
1143 conditions={'function': check_group},
1141 conditions={'function': check_group},
1144 requirements=URL_NAME_REQUIREMENTS)
1142 requirements=URL_NAME_REQUIREMENTS)
1145
1143
1146 # catch all, at the end
1144 # catch all, at the end
1147 _connect_with_slash(
1145 _connect_with_slash(
1148 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1146 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1149 controller='summary', action='index',
1147 controller='summary', action='index',
1150 conditions={'function': check_repo},
1148 conditions={'function': check_repo},
1151 requirements=URL_NAME_REQUIREMENTS)
1149 requirements=URL_NAME_REQUIREMENTS)
1152
1150
1153 return rmap
1151 return rmap
1154
1152
1155
1153
1156 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1154 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1157 """
1155 """
1158 Connect a route with an optional trailing slash in `path`.
1156 Connect a route with an optional trailing slash in `path`.
1159 """
1157 """
1160 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1158 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1161 mapper.connect(name, path, *args, **kwargs)
1159 mapper.connect(name, path, *args, **kwargs)
@@ -1,362 +1,371 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2016 RhodeCode GmbH
3 # Copyright (C) 2013-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 my account controller for RhodeCode admin
23 my account controller for RhodeCode admin
24 """
24 """
25
25
26 import logging
26 import logging
27
27
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30 from pylons import request, tmpl_context as c, url, session
30 from pylons import request, tmpl_context as c, url, session
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from sqlalchemy.orm import joinedload
33 from sqlalchemy.orm import joinedload
34
34
35 from rhodecode import forms
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import auth
37 from rhodecode.lib import auth
37 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
38 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
39 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.utils2 import safe_int, md5
41 from rhodecode.lib.utils2 import safe_int, md5
41 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.ext_json import json
43
44 from rhodecode.model.validation_schema.schemas import user_schema
42 from rhodecode.model.db import (
45 from rhodecode.model.db import (
43 Repository, PullRequest, PullRequestReviewers, UserEmailMap, User,
46 Repository, PullRequest, PullRequestReviewers, UserEmailMap, User,
44 UserFollowing)
47 UserFollowing)
45 from rhodecode.model.forms import UserForm, PasswordChangeForm
48 from rhodecode.model.forms import UserForm
46 from rhodecode.model.scm import RepoList
49 from rhodecode.model.scm import RepoList
47 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
48 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.auth_token import AuthTokenModel
52 from rhodecode.model.auth_token import AuthTokenModel
50 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
51
54
52 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
53
56
54
57
55 class MyAccountController(BaseController):
58 class MyAccountController(BaseController):
56 """REST Controller styled on the Atom Publishing Protocol"""
59 """REST Controller styled on the Atom Publishing Protocol"""
57 # To properly map this controller, ensure your config/routing.py
60 # To properly map this controller, ensure your config/routing.py
58 # file has a resource setup:
61 # file has a resource setup:
59 # map.resource('setting', 'settings', controller='admin/settings',
62 # map.resource('setting', 'settings', controller='admin/settings',
60 # path_prefix='/admin', name_prefix='admin_')
63 # path_prefix='/admin', name_prefix='admin_')
61
64
62 @LoginRequired()
65 @LoginRequired()
63 @NotAnonymous()
66 @NotAnonymous()
64 def __before__(self):
67 def __before__(self):
65 super(MyAccountController, self).__before__()
68 super(MyAccountController, self).__before__()
66
69
67 def __load_data(self):
70 def __load_data(self):
68 c.user = User.get(c.rhodecode_user.user_id)
71 c.user = User.get(c.rhodecode_user.user_id)
69 if c.user.username == User.DEFAULT_USER:
72 if c.user.username == User.DEFAULT_USER:
70 h.flash(_("You can't edit this user since it's"
73 h.flash(_("You can't edit this user since it's"
71 " crucial for entire application"), category='warning')
74 " crucial for entire application"), category='warning')
72 return redirect(url('users'))
75 return redirect(url('users'))
73
76
74 def _load_my_repos_data(self, watched=False):
77 def _load_my_repos_data(self, watched=False):
75 if watched:
78 if watched:
76 admin = False
79 admin = False
77 follows_repos = Session().query(UserFollowing)\
80 follows_repos = Session().query(UserFollowing)\
78 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
81 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
79 .options(joinedload(UserFollowing.follows_repository))\
82 .options(joinedload(UserFollowing.follows_repository))\
80 .all()
83 .all()
81 repo_list = [x.follows_repository for x in follows_repos]
84 repo_list = [x.follows_repository for x in follows_repos]
82 else:
85 else:
83 admin = True
86 admin = True
84 repo_list = Repository.get_all_repos(
87 repo_list = Repository.get_all_repos(
85 user_id=c.rhodecode_user.user_id)
88 user_id=c.rhodecode_user.user_id)
86 repo_list = RepoList(repo_list, perm_set=[
89 repo_list = RepoList(repo_list, perm_set=[
87 'repository.read', 'repository.write', 'repository.admin'])
90 'repository.read', 'repository.write', 'repository.admin'])
88
91
89 repos_data = RepoModel().get_repos_as_dict(
92 repos_data = RepoModel().get_repos_as_dict(
90 repo_list=repo_list, admin=admin)
93 repo_list=repo_list, admin=admin)
91 # json used to render the grid
94 # json used to render the grid
92 return json.dumps(repos_data)
95 return json.dumps(repos_data)
93
96
94 @auth.CSRFRequired()
97 @auth.CSRFRequired()
95 def my_account_update(self):
98 def my_account_update(self):
96 """
99 """
97 POST /_admin/my_account Updates info of my account
100 POST /_admin/my_account Updates info of my account
98 """
101 """
99 # url('my_account')
102 # url('my_account')
100 c.active = 'profile_edit'
103 c.active = 'profile_edit'
101 self.__load_data()
104 self.__load_data()
102 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
105 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
103 ip_addr=self.ip_addr)
106 ip_addr=self.ip_addr)
104 c.extern_type = c.user.extern_type
107 c.extern_type = c.user.extern_type
105 c.extern_name = c.user.extern_name
108 c.extern_name = c.user.extern_name
106
109
107 defaults = c.user.get_dict()
110 defaults = c.user.get_dict()
108 update = False
111 update = False
109 _form = UserForm(edit=True,
112 _form = UserForm(edit=True,
110 old_data={'user_id': c.rhodecode_user.user_id,
113 old_data={'user_id': c.rhodecode_user.user_id,
111 'email': c.rhodecode_user.email})()
114 'email': c.rhodecode_user.email})()
112 form_result = {}
115 form_result = {}
113 try:
116 try:
114 post_data = dict(request.POST)
117 post_data = dict(request.POST)
115 post_data['new_password'] = ''
118 post_data['new_password'] = ''
116 post_data['password_confirmation'] = ''
119 post_data['password_confirmation'] = ''
117 form_result = _form.to_python(post_data)
120 form_result = _form.to_python(post_data)
118 # skip updating those attrs for my account
121 # skip updating those attrs for my account
119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
122 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
120 'new_password', 'password_confirmation']
123 'new_password', 'password_confirmation']
121 # TODO: plugin should define if username can be updated
124 # TODO: plugin should define if username can be updated
122 if c.extern_type != "rhodecode":
125 if c.extern_type != "rhodecode":
123 # forbid updating username for external accounts
126 # forbid updating username for external accounts
124 skip_attrs.append('username')
127 skip_attrs.append('username')
125
128
126 UserModel().update_user(
129 UserModel().update_user(
127 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
130 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
128 h.flash(_('Your account was updated successfully'),
131 h.flash(_('Your account was updated successfully'),
129 category='success')
132 category='success')
130 Session().commit()
133 Session().commit()
131 update = True
134 update = True
132
135
133 except formencode.Invalid as errors:
136 except formencode.Invalid as errors:
134 return htmlfill.render(
137 return htmlfill.render(
135 render('admin/my_account/my_account.html'),
138 render('admin/my_account/my_account.html'),
136 defaults=errors.value,
139 defaults=errors.value,
137 errors=errors.error_dict or {},
140 errors=errors.error_dict or {},
138 prefix_error=False,
141 prefix_error=False,
139 encoding="UTF-8",
142 encoding="UTF-8",
140 force_defaults=False)
143 force_defaults=False)
141 except Exception:
144 except Exception:
142 log.exception("Exception updating user")
145 log.exception("Exception updating user")
143 h.flash(_('Error occurred during update of user %s')
146 h.flash(_('Error occurred during update of user %s')
144 % form_result.get('username'), category='error')
147 % form_result.get('username'), category='error')
145
148
146 if update:
149 if update:
147 return redirect('my_account')
150 return redirect('my_account')
148
151
149 return htmlfill.render(
152 return htmlfill.render(
150 render('admin/my_account/my_account.html'),
153 render('admin/my_account/my_account.html'),
151 defaults=defaults,
154 defaults=defaults,
152 encoding="UTF-8",
155 encoding="UTF-8",
153 force_defaults=False
156 force_defaults=False
154 )
157 )
155
158
156 def my_account(self):
159 def my_account(self):
157 """
160 """
158 GET /_admin/my_account Displays info about my account
161 GET /_admin/my_account Displays info about my account
159 """
162 """
160 # url('my_account')
163 # url('my_account')
161 c.active = 'profile'
164 c.active = 'profile'
162 self.__load_data()
165 self.__load_data()
163
166
164 defaults = c.user.get_dict()
167 defaults = c.user.get_dict()
165 return htmlfill.render(
168 return htmlfill.render(
166 render('admin/my_account/my_account.html'),
169 render('admin/my_account/my_account.html'),
167 defaults=defaults, encoding="UTF-8", force_defaults=False)
170 defaults=defaults, encoding="UTF-8", force_defaults=False)
168
171
169 def my_account_edit(self):
172 def my_account_edit(self):
170 """
173 """
171 GET /_admin/my_account/edit Displays edit form of my account
174 GET /_admin/my_account/edit Displays edit form of my account
172 """
175 """
173 c.active = 'profile_edit'
176 c.active = 'profile_edit'
174 self.__load_data()
177 self.__load_data()
175 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
178 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
176 ip_addr=self.ip_addr)
179 ip_addr=self.ip_addr)
177 c.extern_type = c.user.extern_type
180 c.extern_type = c.user.extern_type
178 c.extern_name = c.user.extern_name
181 c.extern_name = c.user.extern_name
179
182
180 defaults = c.user.get_dict()
183 defaults = c.user.get_dict()
181 return htmlfill.render(
184 return htmlfill.render(
182 render('admin/my_account/my_account.html'),
185 render('admin/my_account/my_account.html'),
183 defaults=defaults,
186 defaults=defaults,
184 encoding="UTF-8",
187 encoding="UTF-8",
185 force_defaults=False
188 force_defaults=False
186 )
189 )
187
190
188 @auth.CSRFRequired()
191 @auth.CSRFRequired(except_methods=['GET'])
189 def my_account_password_update(self):
190 c.active = 'password'
191 self.__load_data()
192 _form = PasswordChangeForm(c.rhodecode_user.username)()
193 try:
194 form_result = _form.to_python(request.POST)
195 UserModel().update_user(c.rhodecode_user.user_id, **form_result)
196 instance = c.rhodecode_user.get_instance()
197 instance.update_userdata(force_password_change=False)
198 Session().commit()
199 session.setdefault('rhodecode_user', {}).update(
200 {'password': md5(instance.password)})
201 session.save()
202 h.flash(_("Successfully updated password"), category='success')
203 except formencode.Invalid as errors:
204 return htmlfill.render(
205 render('admin/my_account/my_account.html'),
206 defaults=errors.value,
207 errors=errors.error_dict or {},
208 prefix_error=False,
209 encoding="UTF-8",
210 force_defaults=False)
211 except Exception:
212 log.exception("Exception updating password")
213 h.flash(_('Error occurred during update of user password'),
214 category='error')
215 return render('admin/my_account/my_account.html')
216
217 def my_account_password(self):
192 def my_account_password(self):
218 c.active = 'password'
193 c.active = 'password'
219 self.__load_data()
194 self.__load_data()
195
196 schema = user_schema.ChangePasswordSchema().bind(
197 username=c.rhodecode_user.username)
198
199 form = forms.Form(schema,
200 buttons=(forms.buttons.save, forms.buttons.reset))
201
202 if request.method == 'POST':
203 controls = request.POST.items()
204 try:
205 valid_data = form.validate(controls)
206 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
207 instance = c.rhodecode_user.get_instance()
208 instance.update_userdata(force_password_change=False)
209 Session().commit()
210 except forms.ValidationFailure as e:
211 request.session.flash(
212 _('Error occurred during update of user password'),
213 queue='error')
214 form = e
215 except Exception:
216 log.exception("Exception updating password")
217 request.session.flash(
218 _('Error occurred during update of user password'),
219 queue='error')
220 else:
221 session.setdefault('rhodecode_user', {}).update(
222 {'password': md5(instance.password)})
223 session.save()
224 request.session.flash(
225 _("Successfully updated password"), queue='success')
226 return redirect(url('my_account_password'))
227
228 c.form = form
220 return render('admin/my_account/my_account.html')
229 return render('admin/my_account/my_account.html')
221
230
222 def my_account_repos(self):
231 def my_account_repos(self):
223 c.active = 'repos'
232 c.active = 'repos'
224 self.__load_data()
233 self.__load_data()
225
234
226 # json used to render the grid
235 # json used to render the grid
227 c.data = self._load_my_repos_data()
236 c.data = self._load_my_repos_data()
228 return render('admin/my_account/my_account.html')
237 return render('admin/my_account/my_account.html')
229
238
230 def my_account_watched(self):
239 def my_account_watched(self):
231 c.active = 'watched'
240 c.active = 'watched'
232 self.__load_data()
241 self.__load_data()
233
242
234 # json used to render the grid
243 # json used to render the grid
235 c.data = self._load_my_repos_data(watched=True)
244 c.data = self._load_my_repos_data(watched=True)
236 return render('admin/my_account/my_account.html')
245 return render('admin/my_account/my_account.html')
237
246
238 def my_account_perms(self):
247 def my_account_perms(self):
239 c.active = 'perms'
248 c.active = 'perms'
240 self.__load_data()
249 self.__load_data()
241 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
250 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
242 ip_addr=self.ip_addr)
251 ip_addr=self.ip_addr)
243
252
244 return render('admin/my_account/my_account.html')
253 return render('admin/my_account/my_account.html')
245
254
246 def my_account_emails(self):
255 def my_account_emails(self):
247 c.active = 'emails'
256 c.active = 'emails'
248 self.__load_data()
257 self.__load_data()
249
258
250 c.user_email_map = UserEmailMap.query()\
259 c.user_email_map = UserEmailMap.query()\
251 .filter(UserEmailMap.user == c.user).all()
260 .filter(UserEmailMap.user == c.user).all()
252 return render('admin/my_account/my_account.html')
261 return render('admin/my_account/my_account.html')
253
262
254 @auth.CSRFRequired()
263 @auth.CSRFRequired()
255 def my_account_emails_add(self):
264 def my_account_emails_add(self):
256 email = request.POST.get('new_email')
265 email = request.POST.get('new_email')
257
266
258 try:
267 try:
259 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
268 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
260 Session().commit()
269 Session().commit()
261 h.flash(_("Added new email address `%s` for user account") % email,
270 h.flash(_("Added new email address `%s` for user account") % email,
262 category='success')
271 category='success')
263 except formencode.Invalid as error:
272 except formencode.Invalid as error:
264 msg = error.error_dict['email']
273 msg = error.error_dict['email']
265 h.flash(msg, category='error')
274 h.flash(msg, category='error')
266 except Exception:
275 except Exception:
267 log.exception("Exception in my_account_emails")
276 log.exception("Exception in my_account_emails")
268 h.flash(_('An error occurred during email saving'),
277 h.flash(_('An error occurred during email saving'),
269 category='error')
278 category='error')
270 return redirect(url('my_account_emails'))
279 return redirect(url('my_account_emails'))
271
280
272 @auth.CSRFRequired()
281 @auth.CSRFRequired()
273 def my_account_emails_delete(self):
282 def my_account_emails_delete(self):
274 email_id = request.POST.get('del_email_id')
283 email_id = request.POST.get('del_email_id')
275 user_model = UserModel()
284 user_model = UserModel()
276 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
285 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
277 Session().commit()
286 Session().commit()
278 h.flash(_("Removed email address from user account"),
287 h.flash(_("Removed email address from user account"),
279 category='success')
288 category='success')
280 return redirect(url('my_account_emails'))
289 return redirect(url('my_account_emails'))
281
290
282 def my_account_pullrequests(self):
291 def my_account_pullrequests(self):
283 c.active = 'pullrequests'
292 c.active = 'pullrequests'
284 self.__load_data()
293 self.__load_data()
285 c.show_closed = request.GET.get('pr_show_closed')
294 c.show_closed = request.GET.get('pr_show_closed')
286
295
287 def _filter(pr):
296 def _filter(pr):
288 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
297 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
289 if not c.show_closed:
298 if not c.show_closed:
290 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
299 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
291 return s
300 return s
292
301
293 c.my_pull_requests = _filter(
302 c.my_pull_requests = _filter(
294 PullRequest.query().filter(
303 PullRequest.query().filter(
295 PullRequest.user_id == c.rhodecode_user.user_id).all())
304 PullRequest.user_id == c.rhodecode_user.user_id).all())
296 my_prs = [
305 my_prs = [
297 x.pull_request for x in PullRequestReviewers.query().filter(
306 x.pull_request for x in PullRequestReviewers.query().filter(
298 PullRequestReviewers.user_id == c.rhodecode_user.user_id).all()]
307 PullRequestReviewers.user_id == c.rhodecode_user.user_id).all()]
299 c.participate_in_pull_requests = _filter(my_prs)
308 c.participate_in_pull_requests = _filter(my_prs)
300 return render('admin/my_account/my_account.html')
309 return render('admin/my_account/my_account.html')
301
310
302 def my_account_auth_tokens(self):
311 def my_account_auth_tokens(self):
303 c.active = 'auth_tokens'
312 c.active = 'auth_tokens'
304 self.__load_data()
313 self.__load_data()
305 show_expired = True
314 show_expired = True
306 c.lifetime_values = [
315 c.lifetime_values = [
307 (str(-1), _('forever')),
316 (str(-1), _('forever')),
308 (str(5), _('5 minutes')),
317 (str(5), _('5 minutes')),
309 (str(60), _('1 hour')),
318 (str(60), _('1 hour')),
310 (str(60 * 24), _('1 day')),
319 (str(60 * 24), _('1 day')),
311 (str(60 * 24 * 30), _('1 month')),
320 (str(60 * 24 * 30), _('1 month')),
312 ]
321 ]
313 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
322 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
314 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
323 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
315 for x in AuthTokenModel.cls.ROLES]
324 for x in AuthTokenModel.cls.ROLES]
316 c.role_options = [(c.role_values, _("Role"))]
325 c.role_options = [(c.role_values, _("Role"))]
317 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
326 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
318 c.rhodecode_user.user_id, show_expired=show_expired)
327 c.rhodecode_user.user_id, show_expired=show_expired)
319 return render('admin/my_account/my_account.html')
328 return render('admin/my_account/my_account.html')
320
329
321 @auth.CSRFRequired()
330 @auth.CSRFRequired()
322 def my_account_auth_tokens_add(self):
331 def my_account_auth_tokens_add(self):
323 lifetime = safe_int(request.POST.get('lifetime'), -1)
332 lifetime = safe_int(request.POST.get('lifetime'), -1)
324 description = request.POST.get('description')
333 description = request.POST.get('description')
325 role = request.POST.get('role')
334 role = request.POST.get('role')
326 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
335 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
327 role)
336 role)
328 Session().commit()
337 Session().commit()
329 h.flash(_("Auth token successfully created"), category='success')
338 h.flash(_("Auth token successfully created"), category='success')
330 return redirect(url('my_account_auth_tokens'))
339 return redirect(url('my_account_auth_tokens'))
331
340
332 @auth.CSRFRequired()
341 @auth.CSRFRequired()
333 def my_account_auth_tokens_delete(self):
342 def my_account_auth_tokens_delete(self):
334 auth_token = request.POST.get('del_auth_token')
343 auth_token = request.POST.get('del_auth_token')
335 user_id = c.rhodecode_user.user_id
344 user_id = c.rhodecode_user.user_id
336 if request.POST.get('del_auth_token_builtin'):
345 if request.POST.get('del_auth_token_builtin'):
337 user = User.get(user_id)
346 user = User.get(user_id)
338 if user:
347 if user:
339 user.api_key = generate_auth_token(user.username)
348 user.api_key = generate_auth_token(user.username)
340 Session().add(user)
349 Session().add(user)
341 Session().commit()
350 Session().commit()
342 h.flash(_("Auth token successfully reset"), category='success')
351 h.flash(_("Auth token successfully reset"), category='success')
343 elif auth_token:
352 elif auth_token:
344 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
353 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
345 Session().commit()
354 Session().commit()
346 h.flash(_("Auth token successfully deleted"), category='success')
355 h.flash(_("Auth token successfully deleted"), category='success')
347
356
348 return redirect(url('my_account_auth_tokens'))
357 return redirect(url('my_account_auth_tokens'))
349
358
350 def my_notifications(self):
359 def my_notifications(self):
351 c.active = 'notifications'
360 c.active = 'notifications'
352 return render('admin/my_account/my_account.html')
361 return render('admin/my_account/my_account.html')
353
362
354 @auth.CSRFRequired()
363 @auth.CSRFRequired()
355 def my_notifications_toggle_visibility(self):
364 def my_notifications_toggle_visibility(self):
356 user = c.rhodecode_user.get_instance()
365 user = c.rhodecode_user.get_instance()
357 user_data = user.user_data
366 user_data = user.user_data
358 status = user_data.get('notification_status', False)
367 status = user_data.get('notification_status', False)
359 user_data['notification_status'] = not status
368 user_data['notification_status'] = not status
360 user.user_data = user_data
369 user.user_data = user_data
361 Session().commit()
370 Session().commit()
362 return redirect(url('my_account_notifications'))
371 return redirect(url('my_account_notifications'))
@@ -1,1898 +1,1903 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import inspect
25 import inspect
26 import collections
26 import collections
27 import fnmatch
27 import fnmatch
28 import hashlib
28 import hashlib
29 import itertools
29 import itertools
30 import logging
30 import logging
31 import os
31 import os
32 import random
32 import random
33 import time
33 import time
34 import traceback
34 import traceback
35 from functools import wraps
35 from functools import wraps
36
36
37 import ipaddress
37 import ipaddress
38 from pylons import url, request
38 from pylons import url, request
39 from pylons.controllers.util import abort, redirect
39 from pylons.controllers.util import abort, redirect
40 from pylons.i18n.translation import _
40 from pylons.i18n.translation import _
41 from sqlalchemy import or_
41 from sqlalchemy import or_
42 from sqlalchemy.orm.exc import ObjectDeletedError
42 from sqlalchemy.orm.exc import ObjectDeletedError
43 from sqlalchemy.orm import joinedload
43 from sqlalchemy.orm import joinedload
44 from zope.cachedescriptors.property import Lazy as LazyProperty
44 from zope.cachedescriptors.property import Lazy as LazyProperty
45
45
46 import rhodecode
46 import rhodecode
47 from rhodecode.model import meta
47 from rhodecode.model import meta
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.db import (
50 from rhodecode.model.db import (
51 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
51 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
52 UserIpMap, UserApiKeys)
52 UserIpMap, UserApiKeys)
53 from rhodecode.lib import caches
53 from rhodecode.lib import caches
54 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
54 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
55 from rhodecode.lib.utils import (
55 from rhodecode.lib.utils import (
56 get_repo_slug, get_repo_group_slug, get_user_group_slug)
56 get_repo_slug, get_repo_group_slug, get_user_group_slug)
57 from rhodecode.lib.caching_query import FromCache
57 from rhodecode.lib.caching_query import FromCache
58
58
59
59
60 if rhodecode.is_unix:
60 if rhodecode.is_unix:
61 import bcrypt
61 import bcrypt
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65 csrf_token_key = "csrf_token"
65 csrf_token_key = "csrf_token"
66
66
67
67
68 class PasswordGenerator(object):
68 class PasswordGenerator(object):
69 """
69 """
70 This is a simple class for generating password from different sets of
70 This is a simple class for generating password from different sets of
71 characters
71 characters
72 usage::
72 usage::
73
73
74 passwd_gen = PasswordGenerator()
74 passwd_gen = PasswordGenerator()
75 #print 8-letter password containing only big and small letters
75 #print 8-letter password containing only big and small letters
76 of alphabet
76 of alphabet
77 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
77 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
78 """
78 """
79 ALPHABETS_NUM = r'''1234567890'''
79 ALPHABETS_NUM = r'''1234567890'''
80 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
80 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
81 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
81 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
82 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
82 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
83 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
83 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
84 + ALPHABETS_NUM + ALPHABETS_SPECIAL
84 + ALPHABETS_NUM + ALPHABETS_SPECIAL
85 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
86 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
86 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
87 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
87 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
88 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
88 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
89
89
90 def __init__(self, passwd=''):
90 def __init__(self, passwd=''):
91 self.passwd = passwd
91 self.passwd = passwd
92
92
93 def gen_password(self, length, type_=None):
93 def gen_password(self, length, type_=None):
94 if type_ is None:
94 if type_ is None:
95 type_ = self.ALPHABETS_FULL
95 type_ = self.ALPHABETS_FULL
96 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
96 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
97 return self.passwd
97 return self.passwd
98
98
99
99
100 class _RhodeCodeCryptoBase(object):
100 class _RhodeCodeCryptoBase(object):
101
101
102 def hash_create(self, str_):
102 def hash_create(self, str_):
103 """
103 """
104 hash the string using
104 hash the string using
105
105
106 :param str_: password to hash
106 :param str_: password to hash
107 """
107 """
108 raise NotImplementedError
108 raise NotImplementedError
109
109
110 def hash_check_with_upgrade(self, password, hashed):
110 def hash_check_with_upgrade(self, password, hashed):
111 """
111 """
112 Returns tuple in which first element is boolean that states that
112 Returns tuple in which first element is boolean that states that
113 given password matches it's hashed version, and the second is new hash
113 given password matches it's hashed version, and the second is new hash
114 of the password, in case this password should be migrated to new
114 of the password, in case this password should be migrated to new
115 cipher.
115 cipher.
116 """
116 """
117 checked_hash = self.hash_check(password, hashed)
117 checked_hash = self.hash_check(password, hashed)
118 return checked_hash, None
118 return checked_hash, None
119
119
120 def hash_check(self, password, hashed):
120 def hash_check(self, password, hashed):
121 """
121 """
122 Checks matching password with it's hashed value.
122 Checks matching password with it's hashed value.
123
123
124 :param password: password
124 :param password: password
125 :param hashed: password in hashed form
125 :param hashed: password in hashed form
126 """
126 """
127 raise NotImplementedError
127 raise NotImplementedError
128
128
129 def _assert_bytes(self, value):
129 def _assert_bytes(self, value):
130 """
130 """
131 Passing in an `unicode` object can lead to hard to detect issues
131 Passing in an `unicode` object can lead to hard to detect issues
132 if passwords contain non-ascii characters. Doing a type check
132 if passwords contain non-ascii characters. Doing a type check
133 during runtime, so that such mistakes are detected early on.
133 during runtime, so that such mistakes are detected early on.
134 """
134 """
135 if not isinstance(value, str):
135 if not isinstance(value, str):
136 raise TypeError(
136 raise TypeError(
137 "Bytestring required as input, got %r." % (value, ))
137 "Bytestring required as input, got %r." % (value, ))
138
138
139
139
140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
141
141
142 def hash_create(self, str_):
142 def hash_create(self, str_):
143 self._assert_bytes(str_)
143 self._assert_bytes(str_)
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
145
145
146 def hash_check_with_upgrade(self, password, hashed):
146 def hash_check_with_upgrade(self, password, hashed):
147 """
147 """
148 Returns tuple in which first element is boolean that states that
148 Returns tuple in which first element is boolean that states that
149 given password matches it's hashed version, and the second is new hash
149 given password matches it's hashed version, and the second is new hash
150 of the password, in case this password should be migrated to new
150 of the password, in case this password should be migrated to new
151 cipher.
151 cipher.
152
152
153 This implements special upgrade logic which works like that:
153 This implements special upgrade logic which works like that:
154 - check if the given password == bcrypted hash, if yes then we
154 - check if the given password == bcrypted hash, if yes then we
155 properly used password and it was already in bcrypt. Proceed
155 properly used password and it was already in bcrypt. Proceed
156 without any changes
156 without any changes
157 - if bcrypt hash check is not working try with sha256. If hash compare
157 - if bcrypt hash check is not working try with sha256. If hash compare
158 is ok, it means we using correct but old hashed password. indicate
158 is ok, it means we using correct but old hashed password. indicate
159 hash change and proceed
159 hash change and proceed
160 """
160 """
161
161
162 new_hash = None
162 new_hash = None
163
163
164 # regular pw check
164 # regular pw check
165 password_match_bcrypt = self.hash_check(password, hashed)
165 password_match_bcrypt = self.hash_check(password, hashed)
166
166
167 # now we want to know if the password was maybe from sha256
167 # now we want to know if the password was maybe from sha256
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
169 if not password_match_bcrypt:
169 if not password_match_bcrypt:
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
171 new_hash = self.hash_create(password) # make new bcrypt hash
171 new_hash = self.hash_create(password) # make new bcrypt hash
172 password_match_bcrypt = True
172 password_match_bcrypt = True
173
173
174 return password_match_bcrypt, new_hash
174 return password_match_bcrypt, new_hash
175
175
176 def hash_check(self, password, hashed):
176 def hash_check(self, password, hashed):
177 """
177 """
178 Checks matching password with it's hashed value.
178 Checks matching password with it's hashed value.
179
179
180 :param password: password
180 :param password: password
181 :param hashed: password in hashed form
181 :param hashed: password in hashed form
182 """
182 """
183 self._assert_bytes(password)
183 self._assert_bytes(password)
184 try:
184 try:
185 return bcrypt.hashpw(password, hashed) == hashed
185 return bcrypt.hashpw(password, hashed) == hashed
186 except ValueError as e:
186 except ValueError as e:
187 # we're having a invalid salt here probably, we should not crash
187 # we're having a invalid salt here probably, we should not crash
188 # just return with False as it would be a wrong password.
188 # just return with False as it would be a wrong password.
189 log.debug('Failed to check password hash using bcrypt %s',
189 log.debug('Failed to check password hash using bcrypt %s',
190 safe_str(e))
190 safe_str(e))
191
191
192 return False
192 return False
193
193
194
194
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
196
196
197 def hash_create(self, str_):
197 def hash_create(self, str_):
198 self._assert_bytes(str_)
198 self._assert_bytes(str_)
199 return hashlib.sha256(str_).hexdigest()
199 return hashlib.sha256(str_).hexdigest()
200
200
201 def hash_check(self, password, hashed):
201 def hash_check(self, password, hashed):
202 """
202 """
203 Checks matching password with it's hashed value.
203 Checks matching password with it's hashed value.
204
204
205 :param password: password
205 :param password: password
206 :param hashed: password in hashed form
206 :param hashed: password in hashed form
207 """
207 """
208 self._assert_bytes(password)
208 self._assert_bytes(password)
209 return hashlib.sha256(password).hexdigest() == hashed
209 return hashlib.sha256(password).hexdigest() == hashed
210
210
211
211
212 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
212 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
213
213
214 def hash_create(self, str_):
214 def hash_create(self, str_):
215 self._assert_bytes(str_)
215 self._assert_bytes(str_)
216 return hashlib.md5(str_).hexdigest()
216 return hashlib.md5(str_).hexdigest()
217
217
218 def hash_check(self, password, hashed):
218 def hash_check(self, password, hashed):
219 """
219 """
220 Checks matching password with it's hashed value.
220 Checks matching password with it's hashed value.
221
221
222 :param password: password
222 :param password: password
223 :param hashed: password in hashed form
223 :param hashed: password in hashed form
224 """
224 """
225 self._assert_bytes(password)
225 self._assert_bytes(password)
226 return hashlib.md5(password).hexdigest() == hashed
226 return hashlib.md5(password).hexdigest() == hashed
227
227
228
228
229 def crypto_backend():
229 def crypto_backend():
230 """
230 """
231 Return the matching crypto backend.
231 Return the matching crypto backend.
232
232
233 Selection is based on if we run tests or not, we pick md5 backend to run
233 Selection is based on if we run tests or not, we pick md5 backend to run
234 tests faster since BCRYPT is expensive to calculate
234 tests faster since BCRYPT is expensive to calculate
235 """
235 """
236 if rhodecode.is_test:
236 if rhodecode.is_test:
237 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
237 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
238 else:
238 else:
239 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
239 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
240
240
241 return RhodeCodeCrypto
241 return RhodeCodeCrypto
242
242
243
243
244 def get_crypt_password(password):
244 def get_crypt_password(password):
245 """
245 """
246 Create the hash of `password` with the active crypto backend.
246 Create the hash of `password` with the active crypto backend.
247
247
248 :param password: The cleartext password.
248 :param password: The cleartext password.
249 :type password: unicode
249 :type password: unicode
250 """
250 """
251 password = safe_str(password)
251 password = safe_str(password)
252 return crypto_backend().hash_create(password)
252 return crypto_backend().hash_create(password)
253
253
254
254
255 def check_password(password, hashed):
255 def check_password(password, hashed):
256 """
256 """
257 Check if the value in `password` matches the hash in `hashed`.
257 Check if the value in `password` matches the hash in `hashed`.
258
258
259 :param password: The cleartext password.
259 :param password: The cleartext password.
260 :type password: unicode
260 :type password: unicode
261
261
262 :param hashed: The expected hashed version of the password.
262 :param hashed: The expected hashed version of the password.
263 :type hashed: The hash has to be passed in in text representation.
263 :type hashed: The hash has to be passed in in text representation.
264 """
264 """
265 password = safe_str(password)
265 password = safe_str(password)
266 return crypto_backend().hash_check(password, hashed)
266 return crypto_backend().hash_check(password, hashed)
267
267
268
268
269 def generate_auth_token(data, salt=None):
269 def generate_auth_token(data, salt=None):
270 """
270 """
271 Generates API KEY from given string
271 Generates API KEY from given string
272 """
272 """
273
273
274 if salt is None:
274 if salt is None:
275 salt = os.urandom(16)
275 salt = os.urandom(16)
276 return hashlib.sha1(safe_str(data) + salt).hexdigest()
276 return hashlib.sha1(safe_str(data) + salt).hexdigest()
277
277
278
278
279 class CookieStoreWrapper(object):
279 class CookieStoreWrapper(object):
280
280
281 def __init__(self, cookie_store):
281 def __init__(self, cookie_store):
282 self.cookie_store = cookie_store
282 self.cookie_store = cookie_store
283
283
284 def __repr__(self):
284 def __repr__(self):
285 return 'CookieStore<%s>' % (self.cookie_store)
285 return 'CookieStore<%s>' % (self.cookie_store)
286
286
287 def get(self, key, other=None):
287 def get(self, key, other=None):
288 if isinstance(self.cookie_store, dict):
288 if isinstance(self.cookie_store, dict):
289 return self.cookie_store.get(key, other)
289 return self.cookie_store.get(key, other)
290 elif isinstance(self.cookie_store, AuthUser):
290 elif isinstance(self.cookie_store, AuthUser):
291 return self.cookie_store.__dict__.get(key, other)
291 return self.cookie_store.__dict__.get(key, other)
292
292
293
293
294 def _cached_perms_data(user_id, scope, user_is_admin,
294 def _cached_perms_data(user_id, scope, user_is_admin,
295 user_inherit_default_permissions, explicit, algo):
295 user_inherit_default_permissions, explicit, algo):
296
296
297 permissions = PermissionCalculator(
297 permissions = PermissionCalculator(
298 user_id, scope, user_is_admin, user_inherit_default_permissions,
298 user_id, scope, user_is_admin, user_inherit_default_permissions,
299 explicit, algo)
299 explicit, algo)
300 return permissions.calculate()
300 return permissions.calculate()
301
301
302 class PermOrigin:
302 class PermOrigin:
303 ADMIN = 'superadmin'
303 ADMIN = 'superadmin'
304
304
305 REPO_USER = 'user:%s'
305 REPO_USER = 'user:%s'
306 REPO_USERGROUP = 'usergroup:%s'
306 REPO_USERGROUP = 'usergroup:%s'
307 REPO_OWNER = 'repo.owner'
307 REPO_OWNER = 'repo.owner'
308 REPO_DEFAULT = 'repo.default'
308 REPO_DEFAULT = 'repo.default'
309 REPO_PRIVATE = 'repo.private'
309 REPO_PRIVATE = 'repo.private'
310
310
311 REPOGROUP_USER = 'user:%s'
311 REPOGROUP_USER = 'user:%s'
312 REPOGROUP_USERGROUP = 'usergroup:%s'
312 REPOGROUP_USERGROUP = 'usergroup:%s'
313 REPOGROUP_OWNER = 'group.owner'
313 REPOGROUP_OWNER = 'group.owner'
314 REPOGROUP_DEFAULT = 'group.default'
314 REPOGROUP_DEFAULT = 'group.default'
315
315
316 USERGROUP_USER = 'user:%s'
316 USERGROUP_USER = 'user:%s'
317 USERGROUP_USERGROUP = 'usergroup:%s'
317 USERGROUP_USERGROUP = 'usergroup:%s'
318 USERGROUP_OWNER = 'usergroup.owner'
318 USERGROUP_OWNER = 'usergroup.owner'
319 USERGROUP_DEFAULT = 'usergroup.default'
319 USERGROUP_DEFAULT = 'usergroup.default'
320
320
321
321
322 class PermOriginDict(dict):
322 class PermOriginDict(dict):
323 """
323 """
324 A special dict used for tracking permissions along with their origins.
324 A special dict used for tracking permissions along with their origins.
325
325
326 `__setitem__` has been overridden to expect a tuple(perm, origin)
326 `__setitem__` has been overridden to expect a tuple(perm, origin)
327 `__getitem__` will return only the perm
327 `__getitem__` will return only the perm
328 `.perm_origin_stack` will return the stack of (perm, origin) set per key
328 `.perm_origin_stack` will return the stack of (perm, origin) set per key
329
329
330 >>> perms = PermOriginDict()
330 >>> perms = PermOriginDict()
331 >>> perms['resource'] = 'read', 'default'
331 >>> perms['resource'] = 'read', 'default'
332 >>> perms['resource']
332 >>> perms['resource']
333 'read'
333 'read'
334 >>> perms['resource'] = 'write', 'admin'
334 >>> perms['resource'] = 'write', 'admin'
335 >>> perms['resource']
335 >>> perms['resource']
336 'write'
336 'write'
337 >>> perms.perm_origin_stack
337 >>> perms.perm_origin_stack
338 {'resource': [('read', 'default'), ('write', 'admin')]}
338 {'resource': [('read', 'default'), ('write', 'admin')]}
339 """
339 """
340
340
341
341
342 def __init__(self, *args, **kw):
342 def __init__(self, *args, **kw):
343 dict.__init__(self, *args, **kw)
343 dict.__init__(self, *args, **kw)
344 self.perm_origin_stack = {}
344 self.perm_origin_stack = {}
345
345
346 def __setitem__(self, key, (perm, origin)):
346 def __setitem__(self, key, (perm, origin)):
347 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
347 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
348 dict.__setitem__(self, key, perm)
348 dict.__setitem__(self, key, perm)
349
349
350
350
351 class PermissionCalculator(object):
351 class PermissionCalculator(object):
352
352
353 def __init__(
353 def __init__(
354 self, user_id, scope, user_is_admin,
354 self, user_id, scope, user_is_admin,
355 user_inherit_default_permissions, explicit, algo):
355 user_inherit_default_permissions, explicit, algo):
356 self.user_id = user_id
356 self.user_id = user_id
357 self.user_is_admin = user_is_admin
357 self.user_is_admin = user_is_admin
358 self.inherit_default_permissions = user_inherit_default_permissions
358 self.inherit_default_permissions = user_inherit_default_permissions
359 self.explicit = explicit
359 self.explicit = explicit
360 self.algo = algo
360 self.algo = algo
361
361
362 scope = scope or {}
362 scope = scope or {}
363 self.scope_repo_id = scope.get('repo_id')
363 self.scope_repo_id = scope.get('repo_id')
364 self.scope_repo_group_id = scope.get('repo_group_id')
364 self.scope_repo_group_id = scope.get('repo_group_id')
365 self.scope_user_group_id = scope.get('user_group_id')
365 self.scope_user_group_id = scope.get('user_group_id')
366
366
367 self.default_user_id = User.get_default_user(cache=True).user_id
367 self.default_user_id = User.get_default_user(cache=True).user_id
368
368
369 self.permissions_repositories = PermOriginDict()
369 self.permissions_repositories = PermOriginDict()
370 self.permissions_repository_groups = PermOriginDict()
370 self.permissions_repository_groups = PermOriginDict()
371 self.permissions_user_groups = PermOriginDict()
371 self.permissions_user_groups = PermOriginDict()
372 self.permissions_global = set()
372 self.permissions_global = set()
373
373
374 self.default_repo_perms = Permission.get_default_repo_perms(
374 self.default_repo_perms = Permission.get_default_repo_perms(
375 self.default_user_id, self.scope_repo_id)
375 self.default_user_id, self.scope_repo_id)
376 self.default_repo_groups_perms = Permission.get_default_group_perms(
376 self.default_repo_groups_perms = Permission.get_default_group_perms(
377 self.default_user_id, self.scope_repo_group_id)
377 self.default_user_id, self.scope_repo_group_id)
378 self.default_user_group_perms = \
378 self.default_user_group_perms = \
379 Permission.get_default_user_group_perms(
379 Permission.get_default_user_group_perms(
380 self.default_user_id, self.scope_user_group_id)
380 self.default_user_id, self.scope_user_group_id)
381
381
382 def calculate(self):
382 def calculate(self):
383 if self.user_is_admin:
383 if self.user_is_admin:
384 return self._admin_permissions()
384 return self._admin_permissions()
385
385
386 self._calculate_global_default_permissions()
386 self._calculate_global_default_permissions()
387 self._calculate_global_permissions()
387 self._calculate_global_permissions()
388 self._calculate_default_permissions()
388 self._calculate_default_permissions()
389 self._calculate_repository_permissions()
389 self._calculate_repository_permissions()
390 self._calculate_repository_group_permissions()
390 self._calculate_repository_group_permissions()
391 self._calculate_user_group_permissions()
391 self._calculate_user_group_permissions()
392 return self._permission_structure()
392 return self._permission_structure()
393
393
394 def _admin_permissions(self):
394 def _admin_permissions(self):
395 """
395 """
396 admin user have all default rights for repositories
396 admin user have all default rights for repositories
397 and groups set to admin
397 and groups set to admin
398 """
398 """
399 self.permissions_global.add('hg.admin')
399 self.permissions_global.add('hg.admin')
400 self.permissions_global.add('hg.create.write_on_repogroup.true')
400 self.permissions_global.add('hg.create.write_on_repogroup.true')
401
401
402 # repositories
402 # repositories
403 for perm in self.default_repo_perms:
403 for perm in self.default_repo_perms:
404 r_k = perm.UserRepoToPerm.repository.repo_name
404 r_k = perm.UserRepoToPerm.repository.repo_name
405 p = 'repository.admin'
405 p = 'repository.admin'
406 self.permissions_repositories[r_k] = p, PermOrigin.ADMIN
406 self.permissions_repositories[r_k] = p, PermOrigin.ADMIN
407
407
408 # repository groups
408 # repository groups
409 for perm in self.default_repo_groups_perms:
409 for perm in self.default_repo_groups_perms:
410 rg_k = perm.UserRepoGroupToPerm.group.group_name
410 rg_k = perm.UserRepoGroupToPerm.group.group_name
411 p = 'group.admin'
411 p = 'group.admin'
412 self.permissions_repository_groups[rg_k] = p, PermOrigin.ADMIN
412 self.permissions_repository_groups[rg_k] = p, PermOrigin.ADMIN
413
413
414 # user groups
414 # user groups
415 for perm in self.default_user_group_perms:
415 for perm in self.default_user_group_perms:
416 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
416 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
417 p = 'usergroup.admin'
417 p = 'usergroup.admin'
418 self.permissions_user_groups[u_k] = p, PermOrigin.ADMIN
418 self.permissions_user_groups[u_k] = p, PermOrigin.ADMIN
419
419
420 return self._permission_structure()
420 return self._permission_structure()
421
421
422 def _calculate_global_default_permissions(self):
422 def _calculate_global_default_permissions(self):
423 """
423 """
424 global permissions taken from the default user
424 global permissions taken from the default user
425 """
425 """
426 default_global_perms = UserToPerm.query()\
426 default_global_perms = UserToPerm.query()\
427 .filter(UserToPerm.user_id == self.default_user_id)\
427 .filter(UserToPerm.user_id == self.default_user_id)\
428 .options(joinedload(UserToPerm.permission))
428 .options(joinedload(UserToPerm.permission))
429
429
430 for perm in default_global_perms:
430 for perm in default_global_perms:
431 self.permissions_global.add(perm.permission.permission_name)
431 self.permissions_global.add(perm.permission.permission_name)
432
432
433 def _calculate_global_permissions(self):
433 def _calculate_global_permissions(self):
434 """
434 """
435 Set global system permissions with user permissions or permissions
435 Set global system permissions with user permissions or permissions
436 taken from the user groups of the current user.
436 taken from the user groups of the current user.
437
437
438 The permissions include repo creating, repo group creating, forking
438 The permissions include repo creating, repo group creating, forking
439 etc.
439 etc.
440 """
440 """
441
441
442 # now we read the defined permissions and overwrite what we have set
442 # now we read the defined permissions and overwrite what we have set
443 # before those can be configured from groups or users explicitly.
443 # before those can be configured from groups or users explicitly.
444
444
445 # TODO: johbo: This seems to be out of sync, find out the reason
445 # TODO: johbo: This seems to be out of sync, find out the reason
446 # for the comment below and update it.
446 # for the comment below and update it.
447
447
448 # In case we want to extend this list we should be always in sync with
448 # In case we want to extend this list we should be always in sync with
449 # User.DEFAULT_USER_PERMISSIONS definitions
449 # User.DEFAULT_USER_PERMISSIONS definitions
450 _configurable = frozenset([
450 _configurable = frozenset([
451 'hg.fork.none', 'hg.fork.repository',
451 'hg.fork.none', 'hg.fork.repository',
452 'hg.create.none', 'hg.create.repository',
452 'hg.create.none', 'hg.create.repository',
453 'hg.usergroup.create.false', 'hg.usergroup.create.true',
453 'hg.usergroup.create.false', 'hg.usergroup.create.true',
454 'hg.repogroup.create.false', 'hg.repogroup.create.true',
454 'hg.repogroup.create.false', 'hg.repogroup.create.true',
455 'hg.create.write_on_repogroup.false',
455 'hg.create.write_on_repogroup.false',
456 'hg.create.write_on_repogroup.true',
456 'hg.create.write_on_repogroup.true',
457 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
457 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
458 ])
458 ])
459
459
460 # USER GROUPS comes first user group global permissions
460 # USER GROUPS comes first user group global permissions
461 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
461 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
462 .options(joinedload(UserGroupToPerm.permission))\
462 .options(joinedload(UserGroupToPerm.permission))\
463 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
463 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
464 UserGroupMember.users_group_id))\
464 UserGroupMember.users_group_id))\
465 .filter(UserGroupMember.user_id == self.user_id)\
465 .filter(UserGroupMember.user_id == self.user_id)\
466 .order_by(UserGroupToPerm.users_group_id)\
466 .order_by(UserGroupToPerm.users_group_id)\
467 .all()
467 .all()
468
468
469 # need to group here by groups since user can be in more than
469 # need to group here by groups since user can be in more than
470 # one group, so we get all groups
470 # one group, so we get all groups
471 _explicit_grouped_perms = [
471 _explicit_grouped_perms = [
472 [x, list(y)] for x, y in
472 [x, list(y)] for x, y in
473 itertools.groupby(user_perms_from_users_groups,
473 itertools.groupby(user_perms_from_users_groups,
474 lambda _x: _x.users_group)]
474 lambda _x: _x.users_group)]
475
475
476 for gr, perms in _explicit_grouped_perms:
476 for gr, perms in _explicit_grouped_perms:
477 # since user can be in multiple groups iterate over them and
477 # since user can be in multiple groups iterate over them and
478 # select the lowest permissions first (more explicit)
478 # select the lowest permissions first (more explicit)
479 # TODO: marcink: do this^^
479 # TODO: marcink: do this^^
480
480
481 # group doesn't inherit default permissions so we actually set them
481 # group doesn't inherit default permissions so we actually set them
482 if not gr.inherit_default_permissions:
482 if not gr.inherit_default_permissions:
483 # NEED TO IGNORE all previously set configurable permissions
483 # NEED TO IGNORE all previously set configurable permissions
484 # and replace them with explicitly set from this user
484 # and replace them with explicitly set from this user
485 # group permissions
485 # group permissions
486 self.permissions_global = self.permissions_global.difference(
486 self.permissions_global = self.permissions_global.difference(
487 _configurable)
487 _configurable)
488 for perm in perms:
488 for perm in perms:
489 self.permissions_global.add(perm.permission.permission_name)
489 self.permissions_global.add(perm.permission.permission_name)
490
490
491 # user explicit global permissions
491 # user explicit global permissions
492 user_perms = Session().query(UserToPerm)\
492 user_perms = Session().query(UserToPerm)\
493 .options(joinedload(UserToPerm.permission))\
493 .options(joinedload(UserToPerm.permission))\
494 .filter(UserToPerm.user_id == self.user_id).all()
494 .filter(UserToPerm.user_id == self.user_id).all()
495
495
496 if not self.inherit_default_permissions:
496 if not self.inherit_default_permissions:
497 # NEED TO IGNORE all configurable permissions and
497 # NEED TO IGNORE all configurable permissions and
498 # replace them with explicitly set from this user permissions
498 # replace them with explicitly set from this user permissions
499 self.permissions_global = self.permissions_global.difference(
499 self.permissions_global = self.permissions_global.difference(
500 _configurable)
500 _configurable)
501 for perm in user_perms:
501 for perm in user_perms:
502 self.permissions_global.add(perm.permission.permission_name)
502 self.permissions_global.add(perm.permission.permission_name)
503
503
504 def _calculate_default_permissions(self):
504 def _calculate_default_permissions(self):
505 """
505 """
506 Set default user permissions for repositories, repository groups
506 Set default user permissions for repositories, repository groups
507 taken from the default user.
507 taken from the default user.
508
508
509 Calculate inheritance of object permissions based on what we have now
509 Calculate inheritance of object permissions based on what we have now
510 in GLOBAL permissions. We check if .false is in GLOBAL since this is
510 in GLOBAL permissions. We check if .false is in GLOBAL since this is
511 explicitly set. Inherit is the opposite of .false being there.
511 explicitly set. Inherit is the opposite of .false being there.
512
512
513 .. note::
513 .. note::
514
514
515 the syntax is little bit odd but what we need to check here is
515 the syntax is little bit odd but what we need to check here is
516 the opposite of .false permission being in the list so even for
516 the opposite of .false permission being in the list so even for
517 inconsistent state when both .true/.false is there
517 inconsistent state when both .true/.false is there
518 .false is more important
518 .false is more important
519
519
520 """
520 """
521 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
521 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
522 in self.permissions_global)
522 in self.permissions_global)
523
523
524 # defaults for repositories, taken from `default` user permissions
524 # defaults for repositories, taken from `default` user permissions
525 # on given repo
525 # on given repo
526 for perm in self.default_repo_perms:
526 for perm in self.default_repo_perms:
527 r_k = perm.UserRepoToPerm.repository.repo_name
527 r_k = perm.UserRepoToPerm.repository.repo_name
528 o = PermOrigin.REPO_DEFAULT
528 o = PermOrigin.REPO_DEFAULT
529 if perm.Repository.private and not (
529 if perm.Repository.private and not (
530 perm.Repository.user_id == self.user_id):
530 perm.Repository.user_id == self.user_id):
531 # disable defaults for private repos,
531 # disable defaults for private repos,
532 p = 'repository.none'
532 p = 'repository.none'
533 o = PermOrigin.REPO_PRIVATE
533 o = PermOrigin.REPO_PRIVATE
534 elif perm.Repository.user_id == self.user_id:
534 elif perm.Repository.user_id == self.user_id:
535 # set admin if owner
535 # set admin if owner
536 p = 'repository.admin'
536 p = 'repository.admin'
537 o = PermOrigin.REPO_OWNER
537 o = PermOrigin.REPO_OWNER
538 else:
538 else:
539 p = perm.Permission.permission_name
539 p = perm.Permission.permission_name
540 # if we decide this user isn't inheriting permissions from
540 # if we decide this user isn't inheriting permissions from
541 # default user we set him to .none so only explicit
541 # default user we set him to .none so only explicit
542 # permissions work
542 # permissions work
543 if not user_inherit_object_permissions:
543 if not user_inherit_object_permissions:
544 p = 'repository.none'
544 p = 'repository.none'
545 self.permissions_repositories[r_k] = p, o
545 self.permissions_repositories[r_k] = p, o
546
546
547 # defaults for repository groups taken from `default` user permission
547 # defaults for repository groups taken from `default` user permission
548 # on given group
548 # on given group
549 for perm in self.default_repo_groups_perms:
549 for perm in self.default_repo_groups_perms:
550 rg_k = perm.UserRepoGroupToPerm.group.group_name
550 rg_k = perm.UserRepoGroupToPerm.group.group_name
551 o = PermOrigin.REPOGROUP_DEFAULT
551 o = PermOrigin.REPOGROUP_DEFAULT
552 if perm.RepoGroup.user_id == self.user_id:
552 if perm.RepoGroup.user_id == self.user_id:
553 # set admin if owner
553 # set admin if owner
554 p = 'group.admin'
554 p = 'group.admin'
555 o = PermOrigin.REPOGROUP_OWNER
555 o = PermOrigin.REPOGROUP_OWNER
556 else:
556 else:
557 p = perm.Permission.permission_name
557 p = perm.Permission.permission_name
558
558
559 # if we decide this user isn't inheriting permissions from default
559 # if we decide this user isn't inheriting permissions from default
560 # user we set him to .none so only explicit permissions work
560 # user we set him to .none so only explicit permissions work
561 if not user_inherit_object_permissions:
561 if not user_inherit_object_permissions:
562 p = 'group.none'
562 p = 'group.none'
563 self.permissions_repository_groups[rg_k] = p, o
563 self.permissions_repository_groups[rg_k] = p, o
564
564
565 # defaults for user groups taken from `default` user permission
565 # defaults for user groups taken from `default` user permission
566 # on given user group
566 # on given user group
567 for perm in self.default_user_group_perms:
567 for perm in self.default_user_group_perms:
568 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
568 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
569 p = perm.Permission.permission_name
569 p = perm.Permission.permission_name
570 o = PermOrigin.USERGROUP_DEFAULT
570 o = PermOrigin.USERGROUP_DEFAULT
571 # if we decide this user isn't inheriting permissions from default
571 # if we decide this user isn't inheriting permissions from default
572 # user we set him to .none so only explicit permissions work
572 # user we set him to .none so only explicit permissions work
573 if not user_inherit_object_permissions:
573 if not user_inherit_object_permissions:
574 p = 'usergroup.none'
574 p = 'usergroup.none'
575 self.permissions_user_groups[u_k] = p, o
575 self.permissions_user_groups[u_k] = p, o
576
576
577 def _calculate_repository_permissions(self):
577 def _calculate_repository_permissions(self):
578 """
578 """
579 Repository permissions for the current user.
579 Repository permissions for the current user.
580
580
581 Check if the user is part of user groups for this repository and
581 Check if the user is part of user groups for this repository and
582 fill in the permission from it. `_choose_permission` decides of which
582 fill in the permission from it. `_choose_permission` decides of which
583 permission should be selected based on selected method.
583 permission should be selected based on selected method.
584 """
584 """
585
585
586 # user group for repositories permissions
586 # user group for repositories permissions
587 user_repo_perms_from_user_group = Permission\
587 user_repo_perms_from_user_group = Permission\
588 .get_default_repo_perms_from_user_group(
588 .get_default_repo_perms_from_user_group(
589 self.user_id, self.scope_repo_id)
589 self.user_id, self.scope_repo_id)
590
590
591 multiple_counter = collections.defaultdict(int)
591 multiple_counter = collections.defaultdict(int)
592 for perm in user_repo_perms_from_user_group:
592 for perm in user_repo_perms_from_user_group:
593 r_k = perm.UserGroupRepoToPerm.repository.repo_name
593 r_k = perm.UserGroupRepoToPerm.repository.repo_name
594 ug_k = perm.UserGroupRepoToPerm.users_group.users_group_name
594 ug_k = perm.UserGroupRepoToPerm.users_group.users_group_name
595 multiple_counter[r_k] += 1
595 multiple_counter[r_k] += 1
596 p = perm.Permission.permission_name
596 p = perm.Permission.permission_name
597 o = PermOrigin.REPO_USERGROUP % ug_k
597 o = PermOrigin.REPO_USERGROUP % ug_k
598
598
599 if perm.Repository.user_id == self.user_id:
599 if perm.Repository.user_id == self.user_id:
600 # set admin if owner
600 # set admin if owner
601 p = 'repository.admin'
601 p = 'repository.admin'
602 o = PermOrigin.REPO_OWNER
602 o = PermOrigin.REPO_OWNER
603 else:
603 else:
604 if multiple_counter[r_k] > 1:
604 if multiple_counter[r_k] > 1:
605 cur_perm = self.permissions_repositories[r_k]
605 cur_perm = self.permissions_repositories[r_k]
606 p = self._choose_permission(p, cur_perm)
606 p = self._choose_permission(p, cur_perm)
607 self.permissions_repositories[r_k] = p, o
607 self.permissions_repositories[r_k] = p, o
608
608
609 # user explicit permissions for repositories, overrides any specified
609 # user explicit permissions for repositories, overrides any specified
610 # by the group permission
610 # by the group permission
611 user_repo_perms = Permission.get_default_repo_perms(
611 user_repo_perms = Permission.get_default_repo_perms(
612 self.user_id, self.scope_repo_id)
612 self.user_id, self.scope_repo_id)
613 for perm in user_repo_perms:
613 for perm in user_repo_perms:
614 r_k = perm.UserRepoToPerm.repository.repo_name
614 r_k = perm.UserRepoToPerm.repository.repo_name
615 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
615 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
616 # set admin if owner
616 # set admin if owner
617 if perm.Repository.user_id == self.user_id:
617 if perm.Repository.user_id == self.user_id:
618 p = 'repository.admin'
618 p = 'repository.admin'
619 o = PermOrigin.REPO_OWNER
619 o = PermOrigin.REPO_OWNER
620 else:
620 else:
621 p = perm.Permission.permission_name
621 p = perm.Permission.permission_name
622 if not self.explicit:
622 if not self.explicit:
623 cur_perm = self.permissions_repositories.get(
623 cur_perm = self.permissions_repositories.get(
624 r_k, 'repository.none')
624 r_k, 'repository.none')
625 p = self._choose_permission(p, cur_perm)
625 p = self._choose_permission(p, cur_perm)
626 self.permissions_repositories[r_k] = p, o
626 self.permissions_repositories[r_k] = p, o
627
627
628 def _calculate_repository_group_permissions(self):
628 def _calculate_repository_group_permissions(self):
629 """
629 """
630 Repository group permissions for the current user.
630 Repository group permissions for the current user.
631
631
632 Check if the user is part of user groups for repository groups and
632 Check if the user is part of user groups for repository groups and
633 fill in the permissions from it. `_choose_permmission` decides of which
633 fill in the permissions from it. `_choose_permmission` decides of which
634 permission should be selected based on selected method.
634 permission should be selected based on selected method.
635 """
635 """
636 # user group for repo groups permissions
636 # user group for repo groups permissions
637 user_repo_group_perms_from_user_group = Permission\
637 user_repo_group_perms_from_user_group = Permission\
638 .get_default_group_perms_from_user_group(
638 .get_default_group_perms_from_user_group(
639 self.user_id, self.scope_repo_group_id)
639 self.user_id, self.scope_repo_group_id)
640
640
641 multiple_counter = collections.defaultdict(int)
641 multiple_counter = collections.defaultdict(int)
642 for perm in user_repo_group_perms_from_user_group:
642 for perm in user_repo_group_perms_from_user_group:
643 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
643 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
644 ug_k = perm.UserGroupRepoGroupToPerm.users_group.users_group_name
644 ug_k = perm.UserGroupRepoGroupToPerm.users_group.users_group_name
645 o = PermOrigin.REPOGROUP_USERGROUP % ug_k
645 o = PermOrigin.REPOGROUP_USERGROUP % ug_k
646 multiple_counter[g_k] += 1
646 multiple_counter[g_k] += 1
647 p = perm.Permission.permission_name
647 p = perm.Permission.permission_name
648 if perm.RepoGroup.user_id == self.user_id:
648 if perm.RepoGroup.user_id == self.user_id:
649 # set admin if owner
649 # set admin if owner
650 p = 'group.admin'
650 p = 'group.admin'
651 o = PermOrigin.REPOGROUP_OWNER
651 o = PermOrigin.REPOGROUP_OWNER
652 else:
652 else:
653 if multiple_counter[g_k] > 1:
653 if multiple_counter[g_k] > 1:
654 cur_perm = self.permissions_repository_groups[g_k]
654 cur_perm = self.permissions_repository_groups[g_k]
655 p = self._choose_permission(p, cur_perm)
655 p = self._choose_permission(p, cur_perm)
656 self.permissions_repository_groups[g_k] = p, o
656 self.permissions_repository_groups[g_k] = p, o
657
657
658 # user explicit permissions for repository groups
658 # user explicit permissions for repository groups
659 user_repo_groups_perms = Permission.get_default_group_perms(
659 user_repo_groups_perms = Permission.get_default_group_perms(
660 self.user_id, self.scope_repo_group_id)
660 self.user_id, self.scope_repo_group_id)
661 for perm in user_repo_groups_perms:
661 for perm in user_repo_groups_perms:
662 rg_k = perm.UserRepoGroupToPerm.group.group_name
662 rg_k = perm.UserRepoGroupToPerm.group.group_name
663 u_k = perm.UserRepoGroupToPerm.user.username
663 u_k = perm.UserRepoGroupToPerm.user.username
664 o = PermOrigin.REPOGROUP_USER % u_k
664 o = PermOrigin.REPOGROUP_USER % u_k
665
665
666 if perm.RepoGroup.user_id == self.user_id:
666 if perm.RepoGroup.user_id == self.user_id:
667 # set admin if owner
667 # set admin if owner
668 p = 'group.admin'
668 p = 'group.admin'
669 o = PermOrigin.REPOGROUP_OWNER
669 o = PermOrigin.REPOGROUP_OWNER
670 else:
670 else:
671 p = perm.Permission.permission_name
671 p = perm.Permission.permission_name
672 if not self.explicit:
672 if not self.explicit:
673 cur_perm = self.permissions_repository_groups.get(
673 cur_perm = self.permissions_repository_groups.get(
674 rg_k, 'group.none')
674 rg_k, 'group.none')
675 p = self._choose_permission(p, cur_perm)
675 p = self._choose_permission(p, cur_perm)
676 self.permissions_repository_groups[rg_k] = p, o
676 self.permissions_repository_groups[rg_k] = p, o
677
677
678 def _calculate_user_group_permissions(self):
678 def _calculate_user_group_permissions(self):
679 """
679 """
680 User group permissions for the current user.
680 User group permissions for the current user.
681 """
681 """
682 # user group for user group permissions
682 # user group for user group permissions
683 user_group_from_user_group = Permission\
683 user_group_from_user_group = Permission\
684 .get_default_user_group_perms_from_user_group(
684 .get_default_user_group_perms_from_user_group(
685 self.user_id, self.scope_repo_group_id)
685 self.user_id, self.scope_repo_group_id)
686
686
687 multiple_counter = collections.defaultdict(int)
687 multiple_counter = collections.defaultdict(int)
688 for perm in user_group_from_user_group:
688 for perm in user_group_from_user_group:
689 g_k = perm.UserGroupUserGroupToPerm\
689 g_k = perm.UserGroupUserGroupToPerm\
690 .target_user_group.users_group_name
690 .target_user_group.users_group_name
691 u_k = perm.UserGroupUserGroupToPerm\
691 u_k = perm.UserGroupUserGroupToPerm\
692 .user_group.users_group_name
692 .user_group.users_group_name
693 o = PermOrigin.USERGROUP_USERGROUP % u_k
693 o = PermOrigin.USERGROUP_USERGROUP % u_k
694 multiple_counter[g_k] += 1
694 multiple_counter[g_k] += 1
695 p = perm.Permission.permission_name
695 p = perm.Permission.permission_name
696 if multiple_counter[g_k] > 1:
696 if multiple_counter[g_k] > 1:
697 cur_perm = self.permissions_user_groups[g_k]
697 cur_perm = self.permissions_user_groups[g_k]
698 p = self._choose_permission(p, cur_perm)
698 p = self._choose_permission(p, cur_perm)
699 self.permissions_user_groups[g_k] = p, o
699 self.permissions_user_groups[g_k] = p, o
700
700
701 # user explicit permission for user groups
701 # user explicit permission for user groups
702 user_user_groups_perms = Permission.get_default_user_group_perms(
702 user_user_groups_perms = Permission.get_default_user_group_perms(
703 self.user_id, self.scope_user_group_id)
703 self.user_id, self.scope_user_group_id)
704 for perm in user_user_groups_perms:
704 for perm in user_user_groups_perms:
705 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
705 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
706 u_k = perm.UserUserGroupToPerm.user.username
706 u_k = perm.UserUserGroupToPerm.user.username
707 p = perm.Permission.permission_name
707 p = perm.Permission.permission_name
708 o = PermOrigin.USERGROUP_USER % u_k
708 o = PermOrigin.USERGROUP_USER % u_k
709 if not self.explicit:
709 if not self.explicit:
710 cur_perm = self.permissions_user_groups.get(
710 cur_perm = self.permissions_user_groups.get(
711 ug_k, 'usergroup.none')
711 ug_k, 'usergroup.none')
712 p = self._choose_permission(p, cur_perm)
712 p = self._choose_permission(p, cur_perm)
713 self.permissions_user_groups[ug_k] = p, o
713 self.permissions_user_groups[ug_k] = p, o
714
714
715 def _choose_permission(self, new_perm, cur_perm):
715 def _choose_permission(self, new_perm, cur_perm):
716 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
716 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
717 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
717 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
718 if self.algo == 'higherwin':
718 if self.algo == 'higherwin':
719 if new_perm_val > cur_perm_val:
719 if new_perm_val > cur_perm_val:
720 return new_perm
720 return new_perm
721 return cur_perm
721 return cur_perm
722 elif self.algo == 'lowerwin':
722 elif self.algo == 'lowerwin':
723 if new_perm_val < cur_perm_val:
723 if new_perm_val < cur_perm_val:
724 return new_perm
724 return new_perm
725 return cur_perm
725 return cur_perm
726
726
727 def _permission_structure(self):
727 def _permission_structure(self):
728 return {
728 return {
729 'global': self.permissions_global,
729 'global': self.permissions_global,
730 'repositories': self.permissions_repositories,
730 'repositories': self.permissions_repositories,
731 'repositories_groups': self.permissions_repository_groups,
731 'repositories_groups': self.permissions_repository_groups,
732 'user_groups': self.permissions_user_groups,
732 'user_groups': self.permissions_user_groups,
733 }
733 }
734
734
735
735
736 def allowed_auth_token_access(controller_name, whitelist=None, auth_token=None):
736 def allowed_auth_token_access(controller_name, whitelist=None, auth_token=None):
737 """
737 """
738 Check if given controller_name is in whitelist of auth token access
738 Check if given controller_name is in whitelist of auth token access
739 """
739 """
740 if not whitelist:
740 if not whitelist:
741 from rhodecode import CONFIG
741 from rhodecode import CONFIG
742 whitelist = aslist(
742 whitelist = aslist(
743 CONFIG.get('api_access_controllers_whitelist'), sep=',')
743 CONFIG.get('api_access_controllers_whitelist'), sep=',')
744 log.debug(
744 log.debug(
745 'Allowed controllers for AUTH TOKEN access: %s' % (whitelist,))
745 'Allowed controllers for AUTH TOKEN access: %s' % (whitelist,))
746
746
747 auth_token_access_valid = False
747 auth_token_access_valid = False
748 for entry in whitelist:
748 for entry in whitelist:
749 if fnmatch.fnmatch(controller_name, entry):
749 if fnmatch.fnmatch(controller_name, entry):
750 auth_token_access_valid = True
750 auth_token_access_valid = True
751 break
751 break
752
752
753 if auth_token_access_valid:
753 if auth_token_access_valid:
754 log.debug('controller:%s matches entry in whitelist'
754 log.debug('controller:%s matches entry in whitelist'
755 % (controller_name,))
755 % (controller_name,))
756 else:
756 else:
757 msg = ('controller: %s does *NOT* match any entry in whitelist'
757 msg = ('controller: %s does *NOT* match any entry in whitelist'
758 % (controller_name,))
758 % (controller_name,))
759 if auth_token:
759 if auth_token:
760 # if we use auth token key and don't have access it's a warning
760 # if we use auth token key and don't have access it's a warning
761 log.warning(msg)
761 log.warning(msg)
762 else:
762 else:
763 log.debug(msg)
763 log.debug(msg)
764
764
765 return auth_token_access_valid
765 return auth_token_access_valid
766
766
767
767
768 class AuthUser(object):
768 class AuthUser(object):
769 """
769 """
770 A simple object that handles all attributes of user in RhodeCode
770 A simple object that handles all attributes of user in RhodeCode
771
771
772 It does lookup based on API key,given user, or user present in session
772 It does lookup based on API key,given user, or user present in session
773 Then it fills all required information for such user. It also checks if
773 Then it fills all required information for such user. It also checks if
774 anonymous access is enabled and if so, it returns default user as logged in
774 anonymous access is enabled and if so, it returns default user as logged in
775 """
775 """
776 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
776 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
777
777
778 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
778 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
779
779
780 self.user_id = user_id
780 self.user_id = user_id
781 self._api_key = api_key
781 self._api_key = api_key
782
782
783 self.api_key = None
783 self.api_key = None
784 self.feed_token = ''
784 self.feed_token = ''
785 self.username = username
785 self.username = username
786 self.ip_addr = ip_addr
786 self.ip_addr = ip_addr
787 self.name = ''
787 self.name = ''
788 self.lastname = ''
788 self.lastname = ''
789 self.email = ''
789 self.email = ''
790 self.is_authenticated = False
790 self.is_authenticated = False
791 self.admin = False
791 self.admin = False
792 self.inherit_default_permissions = False
792 self.inherit_default_permissions = False
793 self.password = ''
793 self.password = ''
794
794
795 self.anonymous_user = None # propagated on propagate_data
795 self.anonymous_user = None # propagated on propagate_data
796 self.propagate_data()
796 self.propagate_data()
797 self._instance = None
797 self._instance = None
798 self._permissions_scoped_cache = {} # used to bind scoped calculation
798 self._permissions_scoped_cache = {} # used to bind scoped calculation
799
799
800 @LazyProperty
800 @LazyProperty
801 def permissions(self):
801 def permissions(self):
802 return self.get_perms(user=self, cache=False)
802 return self.get_perms(user=self, cache=False)
803
803
804 def permissions_with_scope(self, scope):
804 def permissions_with_scope(self, scope):
805 """
805 """
806 Call the get_perms function with scoped data. The scope in that function
806 Call the get_perms function with scoped data. The scope in that function
807 narrows the SQL calls to the given ID of objects resulting in fetching
807 narrows the SQL calls to the given ID of objects resulting in fetching
808 Just particular permission we want to obtain. If scope is an empty dict
808 Just particular permission we want to obtain. If scope is an empty dict
809 then it basically narrows the scope to GLOBAL permissions only.
809 then it basically narrows the scope to GLOBAL permissions only.
810
810
811 :param scope: dict
811 :param scope: dict
812 """
812 """
813 if 'repo_name' in scope:
813 if 'repo_name' in scope:
814 obj = Repository.get_by_repo_name(scope['repo_name'])
814 obj = Repository.get_by_repo_name(scope['repo_name'])
815 if obj:
815 if obj:
816 scope['repo_id'] = obj.repo_id
816 scope['repo_id'] = obj.repo_id
817 _scope = {
817 _scope = {
818 'repo_id': -1,
818 'repo_id': -1,
819 'user_group_id': -1,
819 'user_group_id': -1,
820 'repo_group_id': -1,
820 'repo_group_id': -1,
821 }
821 }
822 _scope.update(scope)
822 _scope.update(scope)
823 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
823 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
824 _scope.items())))
824 _scope.items())))
825 if cache_key not in self._permissions_scoped_cache:
825 if cache_key not in self._permissions_scoped_cache:
826 # store in cache to mimic how the @LazyProperty works,
826 # store in cache to mimic how the @LazyProperty works,
827 # the difference here is that we use the unique key calculated
827 # the difference here is that we use the unique key calculated
828 # from params and values
828 # from params and values
829 res = self.get_perms(user=self, cache=False, scope=_scope)
829 res = self.get_perms(user=self, cache=False, scope=_scope)
830 self._permissions_scoped_cache[cache_key] = res
830 self._permissions_scoped_cache[cache_key] = res
831 return self._permissions_scoped_cache[cache_key]
831 return self._permissions_scoped_cache[cache_key]
832
832
833 @property
833 @property
834 def auth_tokens(self):
834 def auth_tokens(self):
835 return self.get_auth_tokens()
835 return self.get_auth_tokens()
836
836
837 def get_instance(self):
837 def get_instance(self):
838 return User.get(self.user_id)
838 return User.get(self.user_id)
839
839
840 def update_lastactivity(self):
840 def update_lastactivity(self):
841 if self.user_id:
841 if self.user_id:
842 User.get(self.user_id).update_lastactivity()
842 User.get(self.user_id).update_lastactivity()
843
843
844 def propagate_data(self):
844 def propagate_data(self):
845 """
845 """
846 Fills in user data and propagates values to this instance. Maps fetched
846 Fills in user data and propagates values to this instance. Maps fetched
847 user attributes to this class instance attributes
847 user attributes to this class instance attributes
848 """
848 """
849
849
850 user_model = UserModel()
850 user_model = UserModel()
851 anon_user = self.anonymous_user = User.get_default_user(cache=True)
851 anon_user = self.anonymous_user = User.get_default_user(cache=True)
852 is_user_loaded = False
852 is_user_loaded = False
853
853
854 # lookup by userid
854 # lookup by userid
855 if self.user_id is not None and self.user_id != anon_user.user_id:
855 if self.user_id is not None and self.user_id != anon_user.user_id:
856 log.debug('Trying Auth User lookup by USER ID %s' % self.user_id)
856 log.debug('Trying Auth User lookup by USER ID %s' % self.user_id)
857 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
857 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
858
858
859 # try go get user by api key
859 # try go get user by api key
860 elif self._api_key and self._api_key != anon_user.api_key:
860 elif self._api_key and self._api_key != anon_user.api_key:
861 log.debug('Trying Auth User lookup by API KEY %s' % self._api_key)
861 log.debug('Trying Auth User lookup by API KEY %s' % self._api_key)
862 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
862 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
863
863
864 # lookup by username
864 # lookup by username
865 elif self.username:
865 elif self.username:
866 log.debug('Trying Auth User lookup by USER NAME %s' % self.username)
866 log.debug('Trying Auth User lookup by USER NAME %s' % self.username)
867 is_user_loaded = user_model.fill_data(self, username=self.username)
867 is_user_loaded = user_model.fill_data(self, username=self.username)
868 else:
868 else:
869 log.debug('No data in %s that could been used to log in' % self)
869 log.debug('No data in %s that could been used to log in' % self)
870
870
871 if not is_user_loaded:
871 if not is_user_loaded:
872 log.debug('Failed to load user. Fallback to default user')
872 log.debug('Failed to load user. Fallback to default user')
873 # if we cannot authenticate user try anonymous
873 # if we cannot authenticate user try anonymous
874 if anon_user.active:
874 if anon_user.active:
875 user_model.fill_data(self, user_id=anon_user.user_id)
875 user_model.fill_data(self, user_id=anon_user.user_id)
876 # then we set this user is logged in
876 # then we set this user is logged in
877 self.is_authenticated = True
877 self.is_authenticated = True
878 else:
878 else:
879 # in case of disabled anonymous user we reset some of the
879 # in case of disabled anonymous user we reset some of the
880 # parameters so such user is "corrupted", skipping the fill_data
880 # parameters so such user is "corrupted", skipping the fill_data
881 for attr in ['user_id', 'username', 'admin', 'active']:
881 for attr in ['user_id', 'username', 'admin', 'active']:
882 setattr(self, attr, None)
882 setattr(self, attr, None)
883 self.is_authenticated = False
883 self.is_authenticated = False
884
884
885 if not self.username:
885 if not self.username:
886 self.username = 'None'
886 self.username = 'None'
887
887
888 log.debug('Auth User is now %s' % self)
888 log.debug('Auth User is now %s' % self)
889
889
890 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
890 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
891 cache=False):
891 cache=False):
892 """
892 """
893 Fills user permission attribute with permissions taken from database
893 Fills user permission attribute with permissions taken from database
894 works for permissions given for repositories, and for permissions that
894 works for permissions given for repositories, and for permissions that
895 are granted to groups
895 are granted to groups
896
896
897 :param user: instance of User object from database
897 :param user: instance of User object from database
898 :param explicit: In case there are permissions both for user and a group
898 :param explicit: In case there are permissions both for user and a group
899 that user is part of, explicit flag will defiine if user will
899 that user is part of, explicit flag will defiine if user will
900 explicitly override permissions from group, if it's False it will
900 explicitly override permissions from group, if it's False it will
901 make decision based on the algo
901 make decision based on the algo
902 :param algo: algorithm to decide what permission should be choose if
902 :param algo: algorithm to decide what permission should be choose if
903 it's multiple defined, eg user in two different groups. It also
903 it's multiple defined, eg user in two different groups. It also
904 decides if explicit flag is turned off how to specify the permission
904 decides if explicit flag is turned off how to specify the permission
905 for case when user is in a group + have defined separate permission
905 for case when user is in a group + have defined separate permission
906 """
906 """
907 user_id = user.user_id
907 user_id = user.user_id
908 user_is_admin = user.is_admin
908 user_is_admin = user.is_admin
909
909
910 # inheritance of global permissions like create repo/fork repo etc
910 # inheritance of global permissions like create repo/fork repo etc
911 user_inherit_default_permissions = user.inherit_default_permissions
911 user_inherit_default_permissions = user.inherit_default_permissions
912
912
913 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
913 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
914 compute = caches.conditional_cache(
914 compute = caches.conditional_cache(
915 'short_term', 'cache_desc',
915 'short_term', 'cache_desc',
916 condition=cache, func=_cached_perms_data)
916 condition=cache, func=_cached_perms_data)
917 result = compute(user_id, scope, user_is_admin,
917 result = compute(user_id, scope, user_is_admin,
918 user_inherit_default_permissions, explicit, algo)
918 user_inherit_default_permissions, explicit, algo)
919
919
920 result_repr = []
920 result_repr = []
921 for k in result:
921 for k in result:
922 result_repr.append((k, len(result[k])))
922 result_repr.append((k, len(result[k])))
923
923
924 log.debug('PERMISSION tree computed %s' % (result_repr,))
924 log.debug('PERMISSION tree computed %s' % (result_repr,))
925 return result
925 return result
926
926
927 def get_auth_tokens(self):
927 def get_auth_tokens(self):
928 auth_tokens = [self.api_key]
928 auth_tokens = [self.api_key]
929 for api_key in UserApiKeys.query()\
929 for api_key in UserApiKeys.query()\
930 .filter(UserApiKeys.user_id == self.user_id)\
930 .filter(UserApiKeys.user_id == self.user_id)\
931 .filter(or_(UserApiKeys.expires == -1,
931 .filter(or_(UserApiKeys.expires == -1,
932 UserApiKeys.expires >= time.time())).all():
932 UserApiKeys.expires >= time.time())).all():
933 auth_tokens.append(api_key.api_key)
933 auth_tokens.append(api_key.api_key)
934
934
935 return auth_tokens
935 return auth_tokens
936
936
937 @property
937 @property
938 def is_default(self):
938 def is_default(self):
939 return self.username == User.DEFAULT_USER
939 return self.username == User.DEFAULT_USER
940
940
941 @property
941 @property
942 def is_admin(self):
942 def is_admin(self):
943 return self.admin
943 return self.admin
944
944
945 @property
945 @property
946 def is_user_object(self):
946 def is_user_object(self):
947 return self.user_id is not None
947 return self.user_id is not None
948
948
949 @property
949 @property
950 def repositories_admin(self):
950 def repositories_admin(self):
951 """
951 """
952 Returns list of repositories you're an admin of
952 Returns list of repositories you're an admin of
953 """
953 """
954 return [x[0] for x in self.permissions['repositories'].iteritems()
954 return [x[0] for x in self.permissions['repositories'].iteritems()
955 if x[1] == 'repository.admin']
955 if x[1] == 'repository.admin']
956
956
957 @property
957 @property
958 def repository_groups_admin(self):
958 def repository_groups_admin(self):
959 """
959 """
960 Returns list of repository groups you're an admin of
960 Returns list of repository groups you're an admin of
961 """
961 """
962 return [x[0]
962 return [x[0]
963 for x in self.permissions['repositories_groups'].iteritems()
963 for x in self.permissions['repositories_groups'].iteritems()
964 if x[1] == 'group.admin']
964 if x[1] == 'group.admin']
965
965
966 @property
966 @property
967 def user_groups_admin(self):
967 def user_groups_admin(self):
968 """
968 """
969 Returns list of user groups you're an admin of
969 Returns list of user groups you're an admin of
970 """
970 """
971 return [x[0] for x in self.permissions['user_groups'].iteritems()
971 return [x[0] for x in self.permissions['user_groups'].iteritems()
972 if x[1] == 'usergroup.admin']
972 if x[1] == 'usergroup.admin']
973
973
974 @property
974 @property
975 def ip_allowed(self):
975 def ip_allowed(self):
976 """
976 """
977 Checks if ip_addr used in constructor is allowed from defined list of
977 Checks if ip_addr used in constructor is allowed from defined list of
978 allowed ip_addresses for user
978 allowed ip_addresses for user
979
979
980 :returns: boolean, True if ip is in allowed ip range
980 :returns: boolean, True if ip is in allowed ip range
981 """
981 """
982 # check IP
982 # check IP
983 inherit = self.inherit_default_permissions
983 inherit = self.inherit_default_permissions
984 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
984 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
985 inherit_from_default=inherit)
985 inherit_from_default=inherit)
986
986
987 @classmethod
987 @classmethod
988 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
988 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
989 allowed_ips = AuthUser.get_allowed_ips(
989 allowed_ips = AuthUser.get_allowed_ips(
990 user_id, cache=True, inherit_from_default=inherit_from_default)
990 user_id, cache=True, inherit_from_default=inherit_from_default)
991 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
991 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
992 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
992 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
993 return True
993 return True
994 else:
994 else:
995 log.info('Access for IP:%s forbidden, '
995 log.info('Access for IP:%s forbidden, '
996 'not in %s' % (ip_addr, allowed_ips))
996 'not in %s' % (ip_addr, allowed_ips))
997 return False
997 return False
998
998
999 def __repr__(self):
999 def __repr__(self):
1000 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1000 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1001 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1001 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1002
1002
1003 def set_authenticated(self, authenticated=True):
1003 def set_authenticated(self, authenticated=True):
1004 if self.user_id != self.anonymous_user.user_id:
1004 if self.user_id != self.anonymous_user.user_id:
1005 self.is_authenticated = authenticated
1005 self.is_authenticated = authenticated
1006
1006
1007 def get_cookie_store(self):
1007 def get_cookie_store(self):
1008 return {
1008 return {
1009 'username': self.username,
1009 'username': self.username,
1010 'password': md5(self.password),
1010 'password': md5(self.password),
1011 'user_id': self.user_id,
1011 'user_id': self.user_id,
1012 'is_authenticated': self.is_authenticated
1012 'is_authenticated': self.is_authenticated
1013 }
1013 }
1014
1014
1015 @classmethod
1015 @classmethod
1016 def from_cookie_store(cls, cookie_store):
1016 def from_cookie_store(cls, cookie_store):
1017 """
1017 """
1018 Creates AuthUser from a cookie store
1018 Creates AuthUser from a cookie store
1019
1019
1020 :param cls:
1020 :param cls:
1021 :param cookie_store:
1021 :param cookie_store:
1022 """
1022 """
1023 user_id = cookie_store.get('user_id')
1023 user_id = cookie_store.get('user_id')
1024 username = cookie_store.get('username')
1024 username = cookie_store.get('username')
1025 api_key = cookie_store.get('api_key')
1025 api_key = cookie_store.get('api_key')
1026 return AuthUser(user_id, api_key, username)
1026 return AuthUser(user_id, api_key, username)
1027
1027
1028 @classmethod
1028 @classmethod
1029 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1029 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1030 _set = set()
1030 _set = set()
1031
1031
1032 if inherit_from_default:
1032 if inherit_from_default:
1033 default_ips = UserIpMap.query().filter(
1033 default_ips = UserIpMap.query().filter(
1034 UserIpMap.user == User.get_default_user(cache=True))
1034 UserIpMap.user == User.get_default_user(cache=True))
1035 if cache:
1035 if cache:
1036 default_ips = default_ips.options(FromCache("sql_cache_short",
1036 default_ips = default_ips.options(FromCache("sql_cache_short",
1037 "get_user_ips_default"))
1037 "get_user_ips_default"))
1038
1038
1039 # populate from default user
1039 # populate from default user
1040 for ip in default_ips:
1040 for ip in default_ips:
1041 try:
1041 try:
1042 _set.add(ip.ip_addr)
1042 _set.add(ip.ip_addr)
1043 except ObjectDeletedError:
1043 except ObjectDeletedError:
1044 # since we use heavy caching sometimes it happens that
1044 # since we use heavy caching sometimes it happens that
1045 # we get deleted objects here, we just skip them
1045 # we get deleted objects here, we just skip them
1046 pass
1046 pass
1047
1047
1048 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1048 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1049 if cache:
1049 if cache:
1050 user_ips = user_ips.options(FromCache("sql_cache_short",
1050 user_ips = user_ips.options(FromCache("sql_cache_short",
1051 "get_user_ips_%s" % user_id))
1051 "get_user_ips_%s" % user_id))
1052
1052
1053 for ip in user_ips:
1053 for ip in user_ips:
1054 try:
1054 try:
1055 _set.add(ip.ip_addr)
1055 _set.add(ip.ip_addr)
1056 except ObjectDeletedError:
1056 except ObjectDeletedError:
1057 # since we use heavy caching sometimes it happens that we get
1057 # since we use heavy caching sometimes it happens that we get
1058 # deleted objects here, we just skip them
1058 # deleted objects here, we just skip them
1059 pass
1059 pass
1060 return _set or set(['0.0.0.0/0', '::/0'])
1060 return _set or set(['0.0.0.0/0', '::/0'])
1061
1061
1062
1062
1063 def set_available_permissions(config):
1063 def set_available_permissions(config):
1064 """
1064 """
1065 This function will propagate pylons globals with all available defined
1065 This function will propagate pylons globals with all available defined
1066 permission given in db. We don't want to check each time from db for new
1066 permission given in db. We don't want to check each time from db for new
1067 permissions since adding a new permission also requires application restart
1067 permissions since adding a new permission also requires application restart
1068 ie. to decorate new views with the newly created permission
1068 ie. to decorate new views with the newly created permission
1069
1069
1070 :param config: current pylons config instance
1070 :param config: current pylons config instance
1071
1071
1072 """
1072 """
1073 log.info('getting information about all available permissions')
1073 log.info('getting information about all available permissions')
1074 try:
1074 try:
1075 sa = meta.Session
1075 sa = meta.Session
1076 all_perms = sa.query(Permission).all()
1076 all_perms = sa.query(Permission).all()
1077 config['available_permissions'] = [x.permission_name for x in all_perms]
1077 config['available_permissions'] = [x.permission_name for x in all_perms]
1078 except Exception:
1078 except Exception:
1079 log.error(traceback.format_exc())
1079 log.error(traceback.format_exc())
1080 finally:
1080 finally:
1081 meta.Session.remove()
1081 meta.Session.remove()
1082
1082
1083
1083
1084 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1084 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1085 """
1085 """
1086 Return the current authentication token, creating one if one doesn't
1086 Return the current authentication token, creating one if one doesn't
1087 already exist and the save_if_missing flag is present.
1087 already exist and the save_if_missing flag is present.
1088
1088
1089 :param session: pass in the pylons session, else we use the global ones
1089 :param session: pass in the pylons session, else we use the global ones
1090 :param force_new: force to re-generate the token and store it in session
1090 :param force_new: force to re-generate the token and store it in session
1091 :param save_if_missing: save the newly generated token if it's missing in
1091 :param save_if_missing: save the newly generated token if it's missing in
1092 session
1092 session
1093 """
1093 """
1094 if not session:
1094 if not session:
1095 from pylons import session
1095 from pylons import session
1096
1096
1097 if (csrf_token_key not in session and save_if_missing) or force_new:
1097 if (csrf_token_key not in session and save_if_missing) or force_new:
1098 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1098 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1099 session[csrf_token_key] = token
1099 session[csrf_token_key] = token
1100 if hasattr(session, 'save'):
1100 if hasattr(session, 'save'):
1101 session.save()
1101 session.save()
1102 return session.get(csrf_token_key)
1102 return session.get(csrf_token_key)
1103
1103
1104
1104
1105 # CHECK DECORATORS
1105 # CHECK DECORATORS
1106 class CSRFRequired(object):
1106 class CSRFRequired(object):
1107 """
1107 """
1108 Decorator for authenticating a form
1108 Decorator for authenticating a form
1109
1109
1110 This decorator uses an authorization token stored in the client's
1110 This decorator uses an authorization token stored in the client's
1111 session for prevention of certain Cross-site request forgery (CSRF)
1111 session for prevention of certain Cross-site request forgery (CSRF)
1112 attacks (See
1112 attacks (See
1113 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1113 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1114 information).
1114 information).
1115
1115
1116 For use with the ``webhelpers.secure_form`` helper functions.
1116 For use with the ``webhelpers.secure_form`` helper functions.
1117
1117
1118 """
1118 """
1119 def __init__(self, token=csrf_token_key, header='X-CSRF-Token'):
1119 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1120 except_methods=None):
1120 self.token = token
1121 self.token = token
1121 self.header = header
1122 self.header = header
1123 self.except_methods = except_methods or []
1122
1124
1123 def __call__(self, func):
1125 def __call__(self, func):
1124 return get_cython_compat_decorator(self.__wrapper, func)
1126 return get_cython_compat_decorator(self.__wrapper, func)
1125
1127
1126 def _get_csrf(self, _request):
1128 def _get_csrf(self, _request):
1127 return _request.POST.get(self.token, _request.headers.get(self.header))
1129 return _request.POST.get(self.token, _request.headers.get(self.header))
1128
1130
1129 def check_csrf(self, _request, cur_token):
1131 def check_csrf(self, _request, cur_token):
1130 supplied_token = self._get_csrf(_request)
1132 supplied_token = self._get_csrf(_request)
1131 return supplied_token and supplied_token == cur_token
1133 return supplied_token and supplied_token == cur_token
1132
1134
1133 def __wrapper(self, func, *fargs, **fkwargs):
1135 def __wrapper(self, func, *fargs, **fkwargs):
1136 if request.method in self.except_methods:
1137 return func(*fargs, **fkwargs)
1138
1134 cur_token = get_csrf_token(save_if_missing=False)
1139 cur_token = get_csrf_token(save_if_missing=False)
1135 if self.check_csrf(request, cur_token):
1140 if self.check_csrf(request, cur_token):
1136 if request.POST.get(self.token):
1141 if request.POST.get(self.token):
1137 del request.POST[self.token]
1142 del request.POST[self.token]
1138 return func(*fargs, **fkwargs)
1143 return func(*fargs, **fkwargs)
1139 else:
1144 else:
1140 reason = 'token-missing'
1145 reason = 'token-missing'
1141 supplied_token = self._get_csrf(request)
1146 supplied_token = self._get_csrf(request)
1142 if supplied_token and cur_token != supplied_token:
1147 if supplied_token and cur_token != supplied_token:
1143 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1148 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1144 supplied_token or ''[:6])
1149 supplied_token or ''[:6])
1145
1150
1146 csrf_message = \
1151 csrf_message = \
1147 ("Cross-site request forgery detected, request denied. See "
1152 ("Cross-site request forgery detected, request denied. See "
1148 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1153 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1149 "more information.")
1154 "more information.")
1150 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1155 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1151 'REMOTE_ADDR:%s, HEADERS:%s' % (
1156 'REMOTE_ADDR:%s, HEADERS:%s' % (
1152 request, reason, request.remote_addr, request.headers))
1157 request, reason, request.remote_addr, request.headers))
1153
1158
1154 abort(403, detail=csrf_message)
1159 abort(403, detail=csrf_message)
1155
1160
1156
1161
1157 class LoginRequired(object):
1162 class LoginRequired(object):
1158 """
1163 """
1159 Must be logged in to execute this function else
1164 Must be logged in to execute this function else
1160 redirect to login page
1165 redirect to login page
1161
1166
1162 :param api_access: if enabled this checks only for valid auth token
1167 :param api_access: if enabled this checks only for valid auth token
1163 and grants access based on valid token
1168 and grants access based on valid token
1164 """
1169 """
1165 def __init__(self, auth_token_access=False):
1170 def __init__(self, auth_token_access=False):
1166 self.auth_token_access = auth_token_access
1171 self.auth_token_access = auth_token_access
1167
1172
1168 def __call__(self, func):
1173 def __call__(self, func):
1169 return get_cython_compat_decorator(self.__wrapper, func)
1174 return get_cython_compat_decorator(self.__wrapper, func)
1170
1175
1171 def __wrapper(self, func, *fargs, **fkwargs):
1176 def __wrapper(self, func, *fargs, **fkwargs):
1172 from rhodecode.lib import helpers as h
1177 from rhodecode.lib import helpers as h
1173 cls = fargs[0]
1178 cls = fargs[0]
1174 user = cls._rhodecode_user
1179 user = cls._rhodecode_user
1175 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1180 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1176 log.debug('Starting login restriction checks for user: %s' % (user,))
1181 log.debug('Starting login restriction checks for user: %s' % (user,))
1177 # check if our IP is allowed
1182 # check if our IP is allowed
1178 ip_access_valid = True
1183 ip_access_valid = True
1179 if not user.ip_allowed:
1184 if not user.ip_allowed:
1180 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1185 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1181 category='warning')
1186 category='warning')
1182 ip_access_valid = False
1187 ip_access_valid = False
1183
1188
1184 # check if we used an APIKEY and it's a valid one
1189 # check if we used an APIKEY and it's a valid one
1185 # defined whitelist of controllers which API access will be enabled
1190 # defined whitelist of controllers which API access will be enabled
1186 _auth_token = request.GET.get(
1191 _auth_token = request.GET.get(
1187 'auth_token', '') or request.GET.get('api_key', '')
1192 'auth_token', '') or request.GET.get('api_key', '')
1188 auth_token_access_valid = allowed_auth_token_access(
1193 auth_token_access_valid = allowed_auth_token_access(
1189 loc, auth_token=_auth_token)
1194 loc, auth_token=_auth_token)
1190
1195
1191 # explicit controller is enabled or API is in our whitelist
1196 # explicit controller is enabled or API is in our whitelist
1192 if self.auth_token_access or auth_token_access_valid:
1197 if self.auth_token_access or auth_token_access_valid:
1193 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1198 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1194
1199
1195 if _auth_token and _auth_token in user.auth_tokens:
1200 if _auth_token and _auth_token in user.auth_tokens:
1196 auth_token_access_valid = True
1201 auth_token_access_valid = True
1197 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1202 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1198 else:
1203 else:
1199 auth_token_access_valid = False
1204 auth_token_access_valid = False
1200 if not _auth_token:
1205 if not _auth_token:
1201 log.debug("AUTH TOKEN *NOT* present in request")
1206 log.debug("AUTH TOKEN *NOT* present in request")
1202 else:
1207 else:
1203 log.warning(
1208 log.warning(
1204 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1209 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1205
1210
1206 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1211 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1207 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1212 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1208 else 'AUTH_TOKEN_AUTH'
1213 else 'AUTH_TOKEN_AUTH'
1209
1214
1210 if ip_access_valid and (
1215 if ip_access_valid and (
1211 user.is_authenticated or auth_token_access_valid):
1216 user.is_authenticated or auth_token_access_valid):
1212 log.info(
1217 log.info(
1213 'user %s authenticating with:%s IS authenticated on func %s'
1218 'user %s authenticating with:%s IS authenticated on func %s'
1214 % (user, reason, loc))
1219 % (user, reason, loc))
1215
1220
1216 # update user data to check last activity
1221 # update user data to check last activity
1217 user.update_lastactivity()
1222 user.update_lastactivity()
1218 Session().commit()
1223 Session().commit()
1219 return func(*fargs, **fkwargs)
1224 return func(*fargs, **fkwargs)
1220 else:
1225 else:
1221 log.warning(
1226 log.warning(
1222 'user %s authenticating with:%s NOT authenticated on '
1227 'user %s authenticating with:%s NOT authenticated on '
1223 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1228 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1224 % (user, reason, loc, ip_access_valid,
1229 % (user, reason, loc, ip_access_valid,
1225 auth_token_access_valid))
1230 auth_token_access_valid))
1226 # we preserve the get PARAM
1231 # we preserve the get PARAM
1227 came_from = request.path_qs
1232 came_from = request.path_qs
1228
1233
1229 log.debug('redirecting to login page with %s' % (came_from,))
1234 log.debug('redirecting to login page with %s' % (came_from,))
1230 return redirect(
1235 return redirect(
1231 h.route_path('login', _query={'came_from': came_from}))
1236 h.route_path('login', _query={'came_from': came_from}))
1232
1237
1233
1238
1234 class NotAnonymous(object):
1239 class NotAnonymous(object):
1235 """
1240 """
1236 Must be logged in to execute this function else
1241 Must be logged in to execute this function else
1237 redirect to login page"""
1242 redirect to login page"""
1238
1243
1239 def __call__(self, func):
1244 def __call__(self, func):
1240 return get_cython_compat_decorator(self.__wrapper, func)
1245 return get_cython_compat_decorator(self.__wrapper, func)
1241
1246
1242 def __wrapper(self, func, *fargs, **fkwargs):
1247 def __wrapper(self, func, *fargs, **fkwargs):
1243 cls = fargs[0]
1248 cls = fargs[0]
1244 self.user = cls._rhodecode_user
1249 self.user = cls._rhodecode_user
1245
1250
1246 log.debug('Checking if user is not anonymous @%s' % cls)
1251 log.debug('Checking if user is not anonymous @%s' % cls)
1247
1252
1248 anonymous = self.user.username == User.DEFAULT_USER
1253 anonymous = self.user.username == User.DEFAULT_USER
1249
1254
1250 if anonymous:
1255 if anonymous:
1251 came_from = request.path_qs
1256 came_from = request.path_qs
1252
1257
1253 import rhodecode.lib.helpers as h
1258 import rhodecode.lib.helpers as h
1254 h.flash(_('You need to be a registered user to '
1259 h.flash(_('You need to be a registered user to '
1255 'perform this action'),
1260 'perform this action'),
1256 category='warning')
1261 category='warning')
1257 return redirect(
1262 return redirect(
1258 h.route_path('login', _query={'came_from': came_from}))
1263 h.route_path('login', _query={'came_from': came_from}))
1259 else:
1264 else:
1260 return func(*fargs, **fkwargs)
1265 return func(*fargs, **fkwargs)
1261
1266
1262
1267
1263 class XHRRequired(object):
1268 class XHRRequired(object):
1264 def __call__(self, func):
1269 def __call__(self, func):
1265 return get_cython_compat_decorator(self.__wrapper, func)
1270 return get_cython_compat_decorator(self.__wrapper, func)
1266
1271
1267 def __wrapper(self, func, *fargs, **fkwargs):
1272 def __wrapper(self, func, *fargs, **fkwargs):
1268 log.debug('Checking if request is XMLHttpRequest (XHR)')
1273 log.debug('Checking if request is XMLHttpRequest (XHR)')
1269 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1274 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1270 if not request.is_xhr:
1275 if not request.is_xhr:
1271 abort(400, detail=xhr_message)
1276 abort(400, detail=xhr_message)
1272
1277
1273 return func(*fargs, **fkwargs)
1278 return func(*fargs, **fkwargs)
1274
1279
1275
1280
1276 class HasAcceptedRepoType(object):
1281 class HasAcceptedRepoType(object):
1277 """
1282 """
1278 Check if requested repo is within given repo type aliases
1283 Check if requested repo is within given repo type aliases
1279
1284
1280 TODO: anderson: not sure where to put this decorator
1285 TODO: anderson: not sure where to put this decorator
1281 """
1286 """
1282
1287
1283 def __init__(self, *repo_type_list):
1288 def __init__(self, *repo_type_list):
1284 self.repo_type_list = set(repo_type_list)
1289 self.repo_type_list = set(repo_type_list)
1285
1290
1286 def __call__(self, func):
1291 def __call__(self, func):
1287 return get_cython_compat_decorator(self.__wrapper, func)
1292 return get_cython_compat_decorator(self.__wrapper, func)
1288
1293
1289 def __wrapper(self, func, *fargs, **fkwargs):
1294 def __wrapper(self, func, *fargs, **fkwargs):
1290 cls = fargs[0]
1295 cls = fargs[0]
1291 rhodecode_repo = cls.rhodecode_repo
1296 rhodecode_repo = cls.rhodecode_repo
1292
1297
1293 log.debug('%s checking repo type for %s in %s',
1298 log.debug('%s checking repo type for %s in %s',
1294 self.__class__.__name__,
1299 self.__class__.__name__,
1295 rhodecode_repo.alias, self.repo_type_list)
1300 rhodecode_repo.alias, self.repo_type_list)
1296
1301
1297 if rhodecode_repo.alias in self.repo_type_list:
1302 if rhodecode_repo.alias in self.repo_type_list:
1298 return func(*fargs, **fkwargs)
1303 return func(*fargs, **fkwargs)
1299 else:
1304 else:
1300 import rhodecode.lib.helpers as h
1305 import rhodecode.lib.helpers as h
1301 h.flash(h.literal(
1306 h.flash(h.literal(
1302 _('Action not supported for %s.' % rhodecode_repo.alias)),
1307 _('Action not supported for %s.' % rhodecode_repo.alias)),
1303 category='warning')
1308 category='warning')
1304 return redirect(
1309 return redirect(
1305 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1310 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1306
1311
1307
1312
1308 class PermsDecorator(object):
1313 class PermsDecorator(object):
1309 """
1314 """
1310 Base class for controller decorators, we extract the current user from
1315 Base class for controller decorators, we extract the current user from
1311 the class itself, which has it stored in base controllers
1316 the class itself, which has it stored in base controllers
1312 """
1317 """
1313
1318
1314 def __init__(self, *required_perms):
1319 def __init__(self, *required_perms):
1315 self.required_perms = set(required_perms)
1320 self.required_perms = set(required_perms)
1316
1321
1317 def __call__(self, func):
1322 def __call__(self, func):
1318 return get_cython_compat_decorator(self.__wrapper, func)
1323 return get_cython_compat_decorator(self.__wrapper, func)
1319
1324
1320 def __wrapper(self, func, *fargs, **fkwargs):
1325 def __wrapper(self, func, *fargs, **fkwargs):
1321 cls = fargs[0]
1326 cls = fargs[0]
1322 _user = cls._rhodecode_user
1327 _user = cls._rhodecode_user
1323
1328
1324 log.debug('checking %s permissions %s for %s %s',
1329 log.debug('checking %s permissions %s for %s %s',
1325 self.__class__.__name__, self.required_perms, cls, _user)
1330 self.__class__.__name__, self.required_perms, cls, _user)
1326
1331
1327 if self.check_permissions(_user):
1332 if self.check_permissions(_user):
1328 log.debug('Permission granted for %s %s', cls, _user)
1333 log.debug('Permission granted for %s %s', cls, _user)
1329 return func(*fargs, **fkwargs)
1334 return func(*fargs, **fkwargs)
1330
1335
1331 else:
1336 else:
1332 log.debug('Permission denied for %s %s', cls, _user)
1337 log.debug('Permission denied for %s %s', cls, _user)
1333 anonymous = _user.username == User.DEFAULT_USER
1338 anonymous = _user.username == User.DEFAULT_USER
1334
1339
1335 if anonymous:
1340 if anonymous:
1336 came_from = request.path_qs
1341 came_from = request.path_qs
1337
1342
1338 import rhodecode.lib.helpers as h
1343 import rhodecode.lib.helpers as h
1339 h.flash(_('You need to be signed in to view this page'),
1344 h.flash(_('You need to be signed in to view this page'),
1340 category='warning')
1345 category='warning')
1341 return redirect(
1346 return redirect(
1342 h.route_path('login', _query={'came_from': came_from}))
1347 h.route_path('login', _query={'came_from': came_from}))
1343
1348
1344 else:
1349 else:
1345 # redirect with forbidden ret code
1350 # redirect with forbidden ret code
1346 return abort(403)
1351 return abort(403)
1347
1352
1348 def check_permissions(self, user):
1353 def check_permissions(self, user):
1349 """Dummy function for overriding"""
1354 """Dummy function for overriding"""
1350 raise NotImplementedError(
1355 raise NotImplementedError(
1351 'You have to write this function in child class')
1356 'You have to write this function in child class')
1352
1357
1353
1358
1354 class HasPermissionAllDecorator(PermsDecorator):
1359 class HasPermissionAllDecorator(PermsDecorator):
1355 """
1360 """
1356 Checks for access permission for all given predicates. All of them
1361 Checks for access permission for all given predicates. All of them
1357 have to be meet in order to fulfill the request
1362 have to be meet in order to fulfill the request
1358 """
1363 """
1359
1364
1360 def check_permissions(self, user):
1365 def check_permissions(self, user):
1361 perms = user.permissions_with_scope({})
1366 perms = user.permissions_with_scope({})
1362 if self.required_perms.issubset(perms['global']):
1367 if self.required_perms.issubset(perms['global']):
1363 return True
1368 return True
1364 return False
1369 return False
1365
1370
1366
1371
1367 class HasPermissionAnyDecorator(PermsDecorator):
1372 class HasPermissionAnyDecorator(PermsDecorator):
1368 """
1373 """
1369 Checks for access permission for any of given predicates. In order to
1374 Checks for access permission for any of given predicates. In order to
1370 fulfill the request any of predicates must be meet
1375 fulfill the request any of predicates must be meet
1371 """
1376 """
1372
1377
1373 def check_permissions(self, user):
1378 def check_permissions(self, user):
1374 perms = user.permissions_with_scope({})
1379 perms = user.permissions_with_scope({})
1375 if self.required_perms.intersection(perms['global']):
1380 if self.required_perms.intersection(perms['global']):
1376 return True
1381 return True
1377 return False
1382 return False
1378
1383
1379
1384
1380 class HasRepoPermissionAllDecorator(PermsDecorator):
1385 class HasRepoPermissionAllDecorator(PermsDecorator):
1381 """
1386 """
1382 Checks for access permission for all given predicates for specific
1387 Checks for access permission for all given predicates for specific
1383 repository. All of them have to be meet in order to fulfill the request
1388 repository. All of them have to be meet in order to fulfill the request
1384 """
1389 """
1385
1390
1386 def check_permissions(self, user):
1391 def check_permissions(self, user):
1387 perms = user.permissions
1392 perms = user.permissions
1388 repo_name = get_repo_slug(request)
1393 repo_name = get_repo_slug(request)
1389 try:
1394 try:
1390 user_perms = set([perms['repositories'][repo_name]])
1395 user_perms = set([perms['repositories'][repo_name]])
1391 except KeyError:
1396 except KeyError:
1392 return False
1397 return False
1393 if self.required_perms.issubset(user_perms):
1398 if self.required_perms.issubset(user_perms):
1394 return True
1399 return True
1395 return False
1400 return False
1396
1401
1397
1402
1398 class HasRepoPermissionAnyDecorator(PermsDecorator):
1403 class HasRepoPermissionAnyDecorator(PermsDecorator):
1399 """
1404 """
1400 Checks for access permission for any of given predicates for specific
1405 Checks for access permission for any of given predicates for specific
1401 repository. In order to fulfill the request any of predicates must be meet
1406 repository. In order to fulfill the request any of predicates must be meet
1402 """
1407 """
1403
1408
1404 def check_permissions(self, user):
1409 def check_permissions(self, user):
1405 perms = user.permissions
1410 perms = user.permissions
1406 repo_name = get_repo_slug(request)
1411 repo_name = get_repo_slug(request)
1407 try:
1412 try:
1408 user_perms = set([perms['repositories'][repo_name]])
1413 user_perms = set([perms['repositories'][repo_name]])
1409 except KeyError:
1414 except KeyError:
1410 return False
1415 return False
1411
1416
1412 if self.required_perms.intersection(user_perms):
1417 if self.required_perms.intersection(user_perms):
1413 return True
1418 return True
1414 return False
1419 return False
1415
1420
1416
1421
1417 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1422 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1418 """
1423 """
1419 Checks for access permission for all given predicates for specific
1424 Checks for access permission for all given predicates for specific
1420 repository group. All of them have to be meet in order to
1425 repository group. All of them have to be meet in order to
1421 fulfill the request
1426 fulfill the request
1422 """
1427 """
1423
1428
1424 def check_permissions(self, user):
1429 def check_permissions(self, user):
1425 perms = user.permissions
1430 perms = user.permissions
1426 group_name = get_repo_group_slug(request)
1431 group_name = get_repo_group_slug(request)
1427 try:
1432 try:
1428 user_perms = set([perms['repositories_groups'][group_name]])
1433 user_perms = set([perms['repositories_groups'][group_name]])
1429 except KeyError:
1434 except KeyError:
1430 return False
1435 return False
1431
1436
1432 if self.required_perms.issubset(user_perms):
1437 if self.required_perms.issubset(user_perms):
1433 return True
1438 return True
1434 return False
1439 return False
1435
1440
1436
1441
1437 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1442 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1438 """
1443 """
1439 Checks for access permission for any of given predicates for specific
1444 Checks for access permission for any of given predicates for specific
1440 repository group. In order to fulfill the request any
1445 repository group. In order to fulfill the request any
1441 of predicates must be met
1446 of predicates must be met
1442 """
1447 """
1443
1448
1444 def check_permissions(self, user):
1449 def check_permissions(self, user):
1445 perms = user.permissions
1450 perms = user.permissions
1446 group_name = get_repo_group_slug(request)
1451 group_name = get_repo_group_slug(request)
1447 try:
1452 try:
1448 user_perms = set([perms['repositories_groups'][group_name]])
1453 user_perms = set([perms['repositories_groups'][group_name]])
1449 except KeyError:
1454 except KeyError:
1450 return False
1455 return False
1451
1456
1452 if self.required_perms.intersection(user_perms):
1457 if self.required_perms.intersection(user_perms):
1453 return True
1458 return True
1454 return False
1459 return False
1455
1460
1456
1461
1457 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1462 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1458 """
1463 """
1459 Checks for access permission for all given predicates for specific
1464 Checks for access permission for all given predicates for specific
1460 user group. All of them have to be meet in order to fulfill the request
1465 user group. All of them have to be meet in order to fulfill the request
1461 """
1466 """
1462
1467
1463 def check_permissions(self, user):
1468 def check_permissions(self, user):
1464 perms = user.permissions
1469 perms = user.permissions
1465 group_name = get_user_group_slug(request)
1470 group_name = get_user_group_slug(request)
1466 try:
1471 try:
1467 user_perms = set([perms['user_groups'][group_name]])
1472 user_perms = set([perms['user_groups'][group_name]])
1468 except KeyError:
1473 except KeyError:
1469 return False
1474 return False
1470
1475
1471 if self.required_perms.issubset(user_perms):
1476 if self.required_perms.issubset(user_perms):
1472 return True
1477 return True
1473 return False
1478 return False
1474
1479
1475
1480
1476 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1481 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1477 """
1482 """
1478 Checks for access permission for any of given predicates for specific
1483 Checks for access permission for any of given predicates for specific
1479 user group. In order to fulfill the request any of predicates must be meet
1484 user group. In order to fulfill the request any of predicates must be meet
1480 """
1485 """
1481
1486
1482 def check_permissions(self, user):
1487 def check_permissions(self, user):
1483 perms = user.permissions
1488 perms = user.permissions
1484 group_name = get_user_group_slug(request)
1489 group_name = get_user_group_slug(request)
1485 try:
1490 try:
1486 user_perms = set([perms['user_groups'][group_name]])
1491 user_perms = set([perms['user_groups'][group_name]])
1487 except KeyError:
1492 except KeyError:
1488 return False
1493 return False
1489
1494
1490 if self.required_perms.intersection(user_perms):
1495 if self.required_perms.intersection(user_perms):
1491 return True
1496 return True
1492 return False
1497 return False
1493
1498
1494
1499
1495 # CHECK FUNCTIONS
1500 # CHECK FUNCTIONS
1496 class PermsFunction(object):
1501 class PermsFunction(object):
1497 """Base function for other check functions"""
1502 """Base function for other check functions"""
1498
1503
1499 def __init__(self, *perms):
1504 def __init__(self, *perms):
1500 self.required_perms = set(perms)
1505 self.required_perms = set(perms)
1501 self.repo_name = None
1506 self.repo_name = None
1502 self.repo_group_name = None
1507 self.repo_group_name = None
1503 self.user_group_name = None
1508 self.user_group_name = None
1504
1509
1505 def __bool__(self):
1510 def __bool__(self):
1506 frame = inspect.currentframe()
1511 frame = inspect.currentframe()
1507 stack_trace = traceback.format_stack(frame)
1512 stack_trace = traceback.format_stack(frame)
1508 log.error('Checking bool value on a class instance of perm '
1513 log.error('Checking bool value on a class instance of perm '
1509 'function is not allowed: %s' % ''.join(stack_trace))
1514 'function is not allowed: %s' % ''.join(stack_trace))
1510 # rather than throwing errors, here we always return False so if by
1515 # rather than throwing errors, here we always return False so if by
1511 # accident someone checks truth for just an instance it will always end
1516 # accident someone checks truth for just an instance it will always end
1512 # up in returning False
1517 # up in returning False
1513 return False
1518 return False
1514 __nonzero__ = __bool__
1519 __nonzero__ = __bool__
1515
1520
1516 def __call__(self, check_location='', user=None):
1521 def __call__(self, check_location='', user=None):
1517 if not user:
1522 if not user:
1518 log.debug('Using user attribute from global request')
1523 log.debug('Using user attribute from global request')
1519 # TODO: remove this someday,put as user as attribute here
1524 # TODO: remove this someday,put as user as attribute here
1520 user = request.user
1525 user = request.user
1521
1526
1522 # init auth user if not already given
1527 # init auth user if not already given
1523 if not isinstance(user, AuthUser):
1528 if not isinstance(user, AuthUser):
1524 log.debug('Wrapping user %s into AuthUser', user)
1529 log.debug('Wrapping user %s into AuthUser', user)
1525 user = AuthUser(user.user_id)
1530 user = AuthUser(user.user_id)
1526
1531
1527 cls_name = self.__class__.__name__
1532 cls_name = self.__class__.__name__
1528 check_scope = self._get_check_scope(cls_name)
1533 check_scope = self._get_check_scope(cls_name)
1529 check_location = check_location or 'unspecified location'
1534 check_location = check_location or 'unspecified location'
1530
1535
1531 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1536 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1532 self.required_perms, user, check_scope, check_location)
1537 self.required_perms, user, check_scope, check_location)
1533 if not user:
1538 if not user:
1534 log.warning('Empty user given for permission check')
1539 log.warning('Empty user given for permission check')
1535 return False
1540 return False
1536
1541
1537 if self.check_permissions(user):
1542 if self.check_permissions(user):
1538 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1543 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1539 check_scope, user, check_location)
1544 check_scope, user, check_location)
1540 return True
1545 return True
1541
1546
1542 else:
1547 else:
1543 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1548 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1544 check_scope, user, check_location)
1549 check_scope, user, check_location)
1545 return False
1550 return False
1546
1551
1547 def _get_check_scope(self, cls_name):
1552 def _get_check_scope(self, cls_name):
1548 return {
1553 return {
1549 'HasPermissionAll': 'GLOBAL',
1554 'HasPermissionAll': 'GLOBAL',
1550 'HasPermissionAny': 'GLOBAL',
1555 'HasPermissionAny': 'GLOBAL',
1551 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1556 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1552 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1557 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1553 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1558 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1554 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1559 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1555 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1560 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1556 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1561 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1557 }.get(cls_name, '?:%s' % cls_name)
1562 }.get(cls_name, '?:%s' % cls_name)
1558
1563
1559 def check_permissions(self, user):
1564 def check_permissions(self, user):
1560 """Dummy function for overriding"""
1565 """Dummy function for overriding"""
1561 raise Exception('You have to write this function in child class')
1566 raise Exception('You have to write this function in child class')
1562
1567
1563
1568
1564 class HasPermissionAll(PermsFunction):
1569 class HasPermissionAll(PermsFunction):
1565 def check_permissions(self, user):
1570 def check_permissions(self, user):
1566 perms = user.permissions_with_scope({})
1571 perms = user.permissions_with_scope({})
1567 if self.required_perms.issubset(perms.get('global')):
1572 if self.required_perms.issubset(perms.get('global')):
1568 return True
1573 return True
1569 return False
1574 return False
1570
1575
1571
1576
1572 class HasPermissionAny(PermsFunction):
1577 class HasPermissionAny(PermsFunction):
1573 def check_permissions(self, user):
1578 def check_permissions(self, user):
1574 perms = user.permissions_with_scope({})
1579 perms = user.permissions_with_scope({})
1575 if self.required_perms.intersection(perms.get('global')):
1580 if self.required_perms.intersection(perms.get('global')):
1576 return True
1581 return True
1577 return False
1582 return False
1578
1583
1579
1584
1580 class HasRepoPermissionAll(PermsFunction):
1585 class HasRepoPermissionAll(PermsFunction):
1581 def __call__(self, repo_name=None, check_location='', user=None):
1586 def __call__(self, repo_name=None, check_location='', user=None):
1582 self.repo_name = repo_name
1587 self.repo_name = repo_name
1583 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1588 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1584
1589
1585 def check_permissions(self, user):
1590 def check_permissions(self, user):
1586 if not self.repo_name:
1591 if not self.repo_name:
1587 self.repo_name = get_repo_slug(request)
1592 self.repo_name = get_repo_slug(request)
1588
1593
1589 perms = user.permissions
1594 perms = user.permissions
1590 try:
1595 try:
1591 user_perms = set([perms['repositories'][self.repo_name]])
1596 user_perms = set([perms['repositories'][self.repo_name]])
1592 except KeyError:
1597 except KeyError:
1593 return False
1598 return False
1594 if self.required_perms.issubset(user_perms):
1599 if self.required_perms.issubset(user_perms):
1595 return True
1600 return True
1596 return False
1601 return False
1597
1602
1598
1603
1599 class HasRepoPermissionAny(PermsFunction):
1604 class HasRepoPermissionAny(PermsFunction):
1600 def __call__(self, repo_name=None, check_location='', user=None):
1605 def __call__(self, repo_name=None, check_location='', user=None):
1601 self.repo_name = repo_name
1606 self.repo_name = repo_name
1602 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1607 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1603
1608
1604 def check_permissions(self, user):
1609 def check_permissions(self, user):
1605 if not self.repo_name:
1610 if not self.repo_name:
1606 self.repo_name = get_repo_slug(request)
1611 self.repo_name = get_repo_slug(request)
1607
1612
1608 perms = user.permissions
1613 perms = user.permissions
1609 try:
1614 try:
1610 user_perms = set([perms['repositories'][self.repo_name]])
1615 user_perms = set([perms['repositories'][self.repo_name]])
1611 except KeyError:
1616 except KeyError:
1612 return False
1617 return False
1613 if self.required_perms.intersection(user_perms):
1618 if self.required_perms.intersection(user_perms):
1614 return True
1619 return True
1615 return False
1620 return False
1616
1621
1617
1622
1618 class HasRepoGroupPermissionAny(PermsFunction):
1623 class HasRepoGroupPermissionAny(PermsFunction):
1619 def __call__(self, group_name=None, check_location='', user=None):
1624 def __call__(self, group_name=None, check_location='', user=None):
1620 self.repo_group_name = group_name
1625 self.repo_group_name = group_name
1621 return super(HasRepoGroupPermissionAny, self).__call__(
1626 return super(HasRepoGroupPermissionAny, self).__call__(
1622 check_location, user)
1627 check_location, user)
1623
1628
1624 def check_permissions(self, user):
1629 def check_permissions(self, user):
1625 perms = user.permissions
1630 perms = user.permissions
1626 try:
1631 try:
1627 user_perms = set(
1632 user_perms = set(
1628 [perms['repositories_groups'][self.repo_group_name]])
1633 [perms['repositories_groups'][self.repo_group_name]])
1629 except KeyError:
1634 except KeyError:
1630 return False
1635 return False
1631 if self.required_perms.intersection(user_perms):
1636 if self.required_perms.intersection(user_perms):
1632 return True
1637 return True
1633 return False
1638 return False
1634
1639
1635
1640
1636 class HasRepoGroupPermissionAll(PermsFunction):
1641 class HasRepoGroupPermissionAll(PermsFunction):
1637 def __call__(self, group_name=None, check_location='', user=None):
1642 def __call__(self, group_name=None, check_location='', user=None):
1638 self.repo_group_name = group_name
1643 self.repo_group_name = group_name
1639 return super(HasRepoGroupPermissionAll, self).__call__(
1644 return super(HasRepoGroupPermissionAll, self).__call__(
1640 check_location, user)
1645 check_location, user)
1641
1646
1642 def check_permissions(self, user):
1647 def check_permissions(self, user):
1643 perms = user.permissions
1648 perms = user.permissions
1644 try:
1649 try:
1645 user_perms = set(
1650 user_perms = set(
1646 [perms['repositories_groups'][self.repo_group_name]])
1651 [perms['repositories_groups'][self.repo_group_name]])
1647 except KeyError:
1652 except KeyError:
1648 return False
1653 return False
1649 if self.required_perms.issubset(user_perms):
1654 if self.required_perms.issubset(user_perms):
1650 return True
1655 return True
1651 return False
1656 return False
1652
1657
1653
1658
1654 class HasUserGroupPermissionAny(PermsFunction):
1659 class HasUserGroupPermissionAny(PermsFunction):
1655 def __call__(self, user_group_name=None, check_location='', user=None):
1660 def __call__(self, user_group_name=None, check_location='', user=None):
1656 self.user_group_name = user_group_name
1661 self.user_group_name = user_group_name
1657 return super(HasUserGroupPermissionAny, self).__call__(
1662 return super(HasUserGroupPermissionAny, self).__call__(
1658 check_location, user)
1663 check_location, user)
1659
1664
1660 def check_permissions(self, user):
1665 def check_permissions(self, user):
1661 perms = user.permissions
1666 perms = user.permissions
1662 try:
1667 try:
1663 user_perms = set([perms['user_groups'][self.user_group_name]])
1668 user_perms = set([perms['user_groups'][self.user_group_name]])
1664 except KeyError:
1669 except KeyError:
1665 return False
1670 return False
1666 if self.required_perms.intersection(user_perms):
1671 if self.required_perms.intersection(user_perms):
1667 return True
1672 return True
1668 return False
1673 return False
1669
1674
1670
1675
1671 class HasUserGroupPermissionAll(PermsFunction):
1676 class HasUserGroupPermissionAll(PermsFunction):
1672 def __call__(self, user_group_name=None, check_location='', user=None):
1677 def __call__(self, user_group_name=None, check_location='', user=None):
1673 self.user_group_name = user_group_name
1678 self.user_group_name = user_group_name
1674 return super(HasUserGroupPermissionAll, self).__call__(
1679 return super(HasUserGroupPermissionAll, self).__call__(
1675 check_location, user)
1680 check_location, user)
1676
1681
1677 def check_permissions(self, user):
1682 def check_permissions(self, user):
1678 perms = user.permissions
1683 perms = user.permissions
1679 try:
1684 try:
1680 user_perms = set([perms['user_groups'][self.user_group_name]])
1685 user_perms = set([perms['user_groups'][self.user_group_name]])
1681 except KeyError:
1686 except KeyError:
1682 return False
1687 return False
1683 if self.required_perms.issubset(user_perms):
1688 if self.required_perms.issubset(user_perms):
1684 return True
1689 return True
1685 return False
1690 return False
1686
1691
1687
1692
1688 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1693 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1689 class HasPermissionAnyMiddleware(object):
1694 class HasPermissionAnyMiddleware(object):
1690 def __init__(self, *perms):
1695 def __init__(self, *perms):
1691 self.required_perms = set(perms)
1696 self.required_perms = set(perms)
1692
1697
1693 def __call__(self, user, repo_name):
1698 def __call__(self, user, repo_name):
1694 # repo_name MUST be unicode, since we handle keys in permission
1699 # repo_name MUST be unicode, since we handle keys in permission
1695 # dict by unicode
1700 # dict by unicode
1696 repo_name = safe_unicode(repo_name)
1701 repo_name = safe_unicode(repo_name)
1697 user = AuthUser(user.user_id)
1702 user = AuthUser(user.user_id)
1698 log.debug(
1703 log.debug(
1699 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1704 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1700 self.required_perms, user, repo_name)
1705 self.required_perms, user, repo_name)
1701
1706
1702 if self.check_permissions(user, repo_name):
1707 if self.check_permissions(user, repo_name):
1703 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1708 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1704 repo_name, user, 'PermissionMiddleware')
1709 repo_name, user, 'PermissionMiddleware')
1705 return True
1710 return True
1706
1711
1707 else:
1712 else:
1708 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1713 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1709 repo_name, user, 'PermissionMiddleware')
1714 repo_name, user, 'PermissionMiddleware')
1710 return False
1715 return False
1711
1716
1712 def check_permissions(self, user, repo_name):
1717 def check_permissions(self, user, repo_name):
1713 perms = user.permissions_with_scope({'repo_name': repo_name})
1718 perms = user.permissions_with_scope({'repo_name': repo_name})
1714
1719
1715 try:
1720 try:
1716 user_perms = set([perms['repositories'][repo_name]])
1721 user_perms = set([perms['repositories'][repo_name]])
1717 except Exception:
1722 except Exception:
1718 log.exception('Error while accessing user permissions')
1723 log.exception('Error while accessing user permissions')
1719 return False
1724 return False
1720
1725
1721 if self.required_perms.intersection(user_perms):
1726 if self.required_perms.intersection(user_perms):
1722 return True
1727 return True
1723 return False
1728 return False
1724
1729
1725
1730
1726 # SPECIAL VERSION TO HANDLE API AUTH
1731 # SPECIAL VERSION TO HANDLE API AUTH
1727 class _BaseApiPerm(object):
1732 class _BaseApiPerm(object):
1728 def __init__(self, *perms):
1733 def __init__(self, *perms):
1729 self.required_perms = set(perms)
1734 self.required_perms = set(perms)
1730
1735
1731 def __call__(self, check_location=None, user=None, repo_name=None,
1736 def __call__(self, check_location=None, user=None, repo_name=None,
1732 group_name=None, user_group_name=None):
1737 group_name=None, user_group_name=None):
1733 cls_name = self.__class__.__name__
1738 cls_name = self.__class__.__name__
1734 check_scope = 'global:%s' % (self.required_perms,)
1739 check_scope = 'global:%s' % (self.required_perms,)
1735 if repo_name:
1740 if repo_name:
1736 check_scope += ', repo_name:%s' % (repo_name,)
1741 check_scope += ', repo_name:%s' % (repo_name,)
1737
1742
1738 if group_name:
1743 if group_name:
1739 check_scope += ', repo_group_name:%s' % (group_name,)
1744 check_scope += ', repo_group_name:%s' % (group_name,)
1740
1745
1741 if user_group_name:
1746 if user_group_name:
1742 check_scope += ', user_group_name:%s' % (user_group_name,)
1747 check_scope += ', user_group_name:%s' % (user_group_name,)
1743
1748
1744 log.debug(
1749 log.debug(
1745 'checking cls:%s %s %s @ %s'
1750 'checking cls:%s %s %s @ %s'
1746 % (cls_name, self.required_perms, check_scope, check_location))
1751 % (cls_name, self.required_perms, check_scope, check_location))
1747 if not user:
1752 if not user:
1748 log.debug('Empty User passed into arguments')
1753 log.debug('Empty User passed into arguments')
1749 return False
1754 return False
1750
1755
1751 # process user
1756 # process user
1752 if not isinstance(user, AuthUser):
1757 if not isinstance(user, AuthUser):
1753 user = AuthUser(user.user_id)
1758 user = AuthUser(user.user_id)
1754 if not check_location:
1759 if not check_location:
1755 check_location = 'unspecified'
1760 check_location = 'unspecified'
1756 if self.check_permissions(user.permissions, repo_name, group_name,
1761 if self.check_permissions(user.permissions, repo_name, group_name,
1757 user_group_name):
1762 user_group_name):
1758 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1763 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1759 check_scope, user, check_location)
1764 check_scope, user, check_location)
1760 return True
1765 return True
1761
1766
1762 else:
1767 else:
1763 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1768 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1764 check_scope, user, check_location)
1769 check_scope, user, check_location)
1765 return False
1770 return False
1766
1771
1767 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1772 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1768 user_group_name=None):
1773 user_group_name=None):
1769 """
1774 """
1770 implement in child class should return True if permissions are ok,
1775 implement in child class should return True if permissions are ok,
1771 False otherwise
1776 False otherwise
1772
1777
1773 :param perm_defs: dict with permission definitions
1778 :param perm_defs: dict with permission definitions
1774 :param repo_name: repo name
1779 :param repo_name: repo name
1775 """
1780 """
1776 raise NotImplementedError()
1781 raise NotImplementedError()
1777
1782
1778
1783
1779 class HasPermissionAllApi(_BaseApiPerm):
1784 class HasPermissionAllApi(_BaseApiPerm):
1780 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1785 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1781 user_group_name=None):
1786 user_group_name=None):
1782 if self.required_perms.issubset(perm_defs.get('global')):
1787 if self.required_perms.issubset(perm_defs.get('global')):
1783 return True
1788 return True
1784 return False
1789 return False
1785
1790
1786
1791
1787 class HasPermissionAnyApi(_BaseApiPerm):
1792 class HasPermissionAnyApi(_BaseApiPerm):
1788 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1793 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1789 user_group_name=None):
1794 user_group_name=None):
1790 if self.required_perms.intersection(perm_defs.get('global')):
1795 if self.required_perms.intersection(perm_defs.get('global')):
1791 return True
1796 return True
1792 return False
1797 return False
1793
1798
1794
1799
1795 class HasRepoPermissionAllApi(_BaseApiPerm):
1800 class HasRepoPermissionAllApi(_BaseApiPerm):
1796 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1801 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1797 user_group_name=None):
1802 user_group_name=None):
1798 try:
1803 try:
1799 _user_perms = set([perm_defs['repositories'][repo_name]])
1804 _user_perms = set([perm_defs['repositories'][repo_name]])
1800 except KeyError:
1805 except KeyError:
1801 log.warning(traceback.format_exc())
1806 log.warning(traceback.format_exc())
1802 return False
1807 return False
1803 if self.required_perms.issubset(_user_perms):
1808 if self.required_perms.issubset(_user_perms):
1804 return True
1809 return True
1805 return False
1810 return False
1806
1811
1807
1812
1808 class HasRepoPermissionAnyApi(_BaseApiPerm):
1813 class HasRepoPermissionAnyApi(_BaseApiPerm):
1809 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1814 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1810 user_group_name=None):
1815 user_group_name=None):
1811 try:
1816 try:
1812 _user_perms = set([perm_defs['repositories'][repo_name]])
1817 _user_perms = set([perm_defs['repositories'][repo_name]])
1813 except KeyError:
1818 except KeyError:
1814 log.warning(traceback.format_exc())
1819 log.warning(traceback.format_exc())
1815 return False
1820 return False
1816 if self.required_perms.intersection(_user_perms):
1821 if self.required_perms.intersection(_user_perms):
1817 return True
1822 return True
1818 return False
1823 return False
1819
1824
1820
1825
1821 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1826 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1822 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1827 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1823 user_group_name=None):
1828 user_group_name=None):
1824 try:
1829 try:
1825 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1830 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1826 except KeyError:
1831 except KeyError:
1827 log.warning(traceback.format_exc())
1832 log.warning(traceback.format_exc())
1828 return False
1833 return False
1829 if self.required_perms.intersection(_user_perms):
1834 if self.required_perms.intersection(_user_perms):
1830 return True
1835 return True
1831 return False
1836 return False
1832
1837
1833
1838
1834 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1839 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1835 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1840 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1836 user_group_name=None):
1841 user_group_name=None):
1837 try:
1842 try:
1838 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1843 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1839 except KeyError:
1844 except KeyError:
1840 log.warning(traceback.format_exc())
1845 log.warning(traceback.format_exc())
1841 return False
1846 return False
1842 if self.required_perms.issubset(_user_perms):
1847 if self.required_perms.issubset(_user_perms):
1843 return True
1848 return True
1844 return False
1849 return False
1845
1850
1846
1851
1847 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1852 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1848 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1853 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1849 user_group_name=None):
1854 user_group_name=None):
1850 try:
1855 try:
1851 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1856 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1852 except KeyError:
1857 except KeyError:
1853 log.warning(traceback.format_exc())
1858 log.warning(traceback.format_exc())
1854 return False
1859 return False
1855 if self.required_perms.intersection(_user_perms):
1860 if self.required_perms.intersection(_user_perms):
1856 return True
1861 return True
1857 return False
1862 return False
1858
1863
1859
1864
1860 def check_ip_access(source_ip, allowed_ips=None):
1865 def check_ip_access(source_ip, allowed_ips=None):
1861 """
1866 """
1862 Checks if source_ip is a subnet of any of allowed_ips.
1867 Checks if source_ip is a subnet of any of allowed_ips.
1863
1868
1864 :param source_ip:
1869 :param source_ip:
1865 :param allowed_ips: list of allowed ips together with mask
1870 :param allowed_ips: list of allowed ips together with mask
1866 """
1871 """
1867 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1872 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1868 source_ip_address = ipaddress.ip_address(source_ip)
1873 source_ip_address = ipaddress.ip_address(source_ip)
1869 if isinstance(allowed_ips, (tuple, list, set)):
1874 if isinstance(allowed_ips, (tuple, list, set)):
1870 for ip in allowed_ips:
1875 for ip in allowed_ips:
1871 try:
1876 try:
1872 network_address = ipaddress.ip_network(ip, strict=False)
1877 network_address = ipaddress.ip_network(ip, strict=False)
1873 if source_ip_address in network_address:
1878 if source_ip_address in network_address:
1874 log.debug('IP %s is network %s' %
1879 log.debug('IP %s is network %s' %
1875 (source_ip_address, network_address))
1880 (source_ip_address, network_address))
1876 return True
1881 return True
1877 # for any case we cannot determine the IP, don't crash just
1882 # for any case we cannot determine the IP, don't crash just
1878 # skip it and log as error, we want to say forbidden still when
1883 # skip it and log as error, we want to say forbidden still when
1879 # sending bad IP
1884 # sending bad IP
1880 except Exception:
1885 except Exception:
1881 log.error(traceback.format_exc())
1886 log.error(traceback.format_exc())
1882 continue
1887 continue
1883 return False
1888 return False
1884
1889
1885
1890
1886 def get_cython_compat_decorator(wrapper, func):
1891 def get_cython_compat_decorator(wrapper, func):
1887 """
1892 """
1888 Creates a cython compatible decorator. The previously used
1893 Creates a cython compatible decorator. The previously used
1889 decorator.decorator() function seems to be incompatible with cython.
1894 decorator.decorator() function seems to be incompatible with cython.
1890
1895
1891 :param wrapper: __wrapper method of the decorator class
1896 :param wrapper: __wrapper method of the decorator class
1892 :param func: decorated function
1897 :param func: decorated function
1893 """
1898 """
1894 @wraps(func)
1899 @wraps(func)
1895 def local_wrapper(*args, **kwds):
1900 def local_wrapper(*args, **kwds):
1896 return wrapper(func, *args, **kwds)
1901 return wrapper(func, *args, **kwds)
1897 local_wrapper.__wrapped__ = func
1902 local_wrapper.__wrapped__ = func
1898 return local_wrapper
1903 return local_wrapper
@@ -1,563 +1,549 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 this is forms validation classes
22 this is forms validation classes
23 http://formencode.org/module-formencode.validators.html
23 http://formencode.org/module-formencode.validators.html
24 for list off all availible validators
24 for list off all availible validators
25
25
26 we can create our own validators
26 we can create our own validators
27
27
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 pre_validators [] These validators will be applied before the schema
29 pre_validators [] These validators will be applied before the schema
30 chained_validators [] These validators will be applied after the schema
30 chained_validators [] These validators will be applied after the schema
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35
35
36
36
37 <name> = formencode.validators.<name of validator>
37 <name> = formencode.validators.<name of validator>
38 <name> must equal form name
38 <name> must equal form name
39 list=[1,2,3,4,5]
39 list=[1,2,3,4,5]
40 for SELECT use formencode.All(OneOf(list), Int())
40 for SELECT use formencode.All(OneOf(list), Int())
41
41
42 """
42 """
43
43
44 import deform
44 import deform
45 import logging
45 import logging
46 import formencode
46 import formencode
47
47
48 from pkg_resources import resource_filename
48 from pkg_resources import resource_filename
49 from formencode import All, Pipe
49 from formencode import All, Pipe
50
50
51 from pylons.i18n.translation import _
51 from pylons.i18n.translation import _
52
52
53 from rhodecode import BACKENDS
53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
54 from rhodecode.lib import helpers
55 from rhodecode.model import validators as v
55 from rhodecode.model import validators as v
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 deform_templates = resource_filename('deform', 'templates')
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
62 search_path = (rhodecode_templates, deform_templates)
63
63
64
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
68 kw['h'] = helpers
69 return self.load(template_name)(**kw)
69 return self.load(template_name)(**kw)
70
70
71
71
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 deform.Form.set_default_renderer(form_renderer)
73 deform.Form.set_default_renderer(form_renderer)
74
74
75
75
76 def LoginForm():
76 def LoginForm():
77 class _LoginForm(formencode.Schema):
77 class _LoginForm(formencode.Schema):
78 allow_extra_fields = True
78 allow_extra_fields = True
79 filter_extra_fields = True
79 filter_extra_fields = True
80 username = v.UnicodeString(
80 username = v.UnicodeString(
81 strip=True,
81 strip=True,
82 min=1,
82 min=1,
83 not_empty=True,
83 not_empty=True,
84 messages={
84 messages={
85 'empty': _(u'Please enter a login'),
85 'empty': _(u'Please enter a login'),
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
87 }
87 }
88 )
88 )
89
89
90 password = v.UnicodeString(
90 password = v.UnicodeString(
91 strip=False,
91 strip=False,
92 min=3,
92 min=3,
93 not_empty=True,
93 not_empty=True,
94 messages={
94 messages={
95 'empty': _(u'Please enter a password'),
95 'empty': _(u'Please enter a password'),
96 'tooShort': _(u'Enter %(min)i characters or more')}
96 'tooShort': _(u'Enter %(min)i characters or more')}
97 )
97 )
98
98
99 remember = v.StringBoolean(if_missing=False)
99 remember = v.StringBoolean(if_missing=False)
100
100
101 chained_validators = [v.ValidAuth()]
101 chained_validators = [v.ValidAuth()]
102 return _LoginForm
102 return _LoginForm
103
103
104
104
105 def PasswordChangeForm(username):
106 class _PasswordChangeForm(formencode.Schema):
107 allow_extra_fields = True
108 filter_extra_fields = True
109
110 current_password = v.ValidOldPassword(username)(not_empty=True)
111 new_password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
112 new_password_confirmation = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
113
114 chained_validators = [v.ValidPasswordsMatch('new_password',
115 'new_password_confirmation')]
116 return _PasswordChangeForm
117
118
119 def UserForm(edit=False, available_languages=[], old_data={}):
105 def UserForm(edit=False, available_languages=[], old_data={}):
120 class _UserForm(formencode.Schema):
106 class _UserForm(formencode.Schema):
121 allow_extra_fields = True
107 allow_extra_fields = True
122 filter_extra_fields = True
108 filter_extra_fields = True
123 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 v.ValidUsername(edit, old_data))
110 v.ValidUsername(edit, old_data))
125 if edit:
111 if edit:
126 new_password = All(
112 new_password = All(
127 v.ValidPassword(),
113 v.ValidPassword(),
128 v.UnicodeString(strip=False, min=6, not_empty=False)
114 v.UnicodeString(strip=False, min=6, not_empty=False)
129 )
115 )
130 password_confirmation = All(
116 password_confirmation = All(
131 v.ValidPassword(),
117 v.ValidPassword(),
132 v.UnicodeString(strip=False, min=6, not_empty=False),
118 v.UnicodeString(strip=False, min=6, not_empty=False),
133 )
119 )
134 admin = v.StringBoolean(if_missing=False)
120 admin = v.StringBoolean(if_missing=False)
135 else:
121 else:
136 password = All(
122 password = All(
137 v.ValidPassword(),
123 v.ValidPassword(),
138 v.UnicodeString(strip=False, min=6, not_empty=True)
124 v.UnicodeString(strip=False, min=6, not_empty=True)
139 )
125 )
140 password_confirmation = All(
126 password_confirmation = All(
141 v.ValidPassword(),
127 v.ValidPassword(),
142 v.UnicodeString(strip=False, min=6, not_empty=False)
128 v.UnicodeString(strip=False, min=6, not_empty=False)
143 )
129 )
144
130
145 password_change = v.StringBoolean(if_missing=False)
131 password_change = v.StringBoolean(if_missing=False)
146 create_repo_group = v.StringBoolean(if_missing=False)
132 create_repo_group = v.StringBoolean(if_missing=False)
147
133
148 active = v.StringBoolean(if_missing=False)
134 active = v.StringBoolean(if_missing=False)
149 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
150 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
151 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
152 extern_name = v.UnicodeString(strip=True)
138 extern_name = v.UnicodeString(strip=True)
153 extern_type = v.UnicodeString(strip=True)
139 extern_type = v.UnicodeString(strip=True)
154 language = v.OneOf(available_languages, hideList=False,
140 language = v.OneOf(available_languages, hideList=False,
155 testValueList=True, if_missing=None)
141 testValueList=True, if_missing=None)
156 chained_validators = [v.ValidPasswordsMatch()]
142 chained_validators = [v.ValidPasswordsMatch()]
157 return _UserForm
143 return _UserForm
158
144
159
145
160 def UserGroupForm(edit=False, old_data=None, available_members=None,
146 def UserGroupForm(edit=False, old_data=None, available_members=None,
161 allow_disabled=False):
147 allow_disabled=False):
162 old_data = old_data or {}
148 old_data = old_data or {}
163 available_members = available_members or []
149 available_members = available_members or []
164
150
165 class _UserGroupForm(formencode.Schema):
151 class _UserGroupForm(formencode.Schema):
166 allow_extra_fields = True
152 allow_extra_fields = True
167 filter_extra_fields = True
153 filter_extra_fields = True
168
154
169 users_group_name = All(
155 users_group_name = All(
170 v.UnicodeString(strip=True, min=1, not_empty=True),
156 v.UnicodeString(strip=True, min=1, not_empty=True),
171 v.ValidUserGroup(edit, old_data)
157 v.ValidUserGroup(edit, old_data)
172 )
158 )
173 user_group_description = v.UnicodeString(strip=True, min=1,
159 user_group_description = v.UnicodeString(strip=True, min=1,
174 not_empty=False)
160 not_empty=False)
175
161
176 users_group_active = v.StringBoolean(if_missing=False)
162 users_group_active = v.StringBoolean(if_missing=False)
177
163
178 if edit:
164 if edit:
179 users_group_members = v.OneOf(
165 users_group_members = v.OneOf(
180 available_members, hideList=False, testValueList=True,
166 available_members, hideList=False, testValueList=True,
181 if_missing=None, not_empty=False
167 if_missing=None, not_empty=False
182 )
168 )
183 # this is user group owner
169 # this is user group owner
184 user = All(
170 user = All(
185 v.UnicodeString(not_empty=True),
171 v.UnicodeString(not_empty=True),
186 v.ValidRepoUser(allow_disabled))
172 v.ValidRepoUser(allow_disabled))
187 return _UserGroupForm
173 return _UserGroupForm
188
174
189
175
190 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
176 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
191 can_create_in_root=False, allow_disabled=False):
177 can_create_in_root=False, allow_disabled=False):
192 old_data = old_data or {}
178 old_data = old_data or {}
193 available_groups = available_groups or []
179 available_groups = available_groups or []
194
180
195 class _RepoGroupForm(formencode.Schema):
181 class _RepoGroupForm(formencode.Schema):
196 allow_extra_fields = True
182 allow_extra_fields = True
197 filter_extra_fields = False
183 filter_extra_fields = False
198
184
199 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
185 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
200 v.SlugifyName(),)
186 v.SlugifyName(),)
201 group_description = v.UnicodeString(strip=True, min=1,
187 group_description = v.UnicodeString(strip=True, min=1,
202 not_empty=False)
188 not_empty=False)
203 group_copy_permissions = v.StringBoolean(if_missing=False)
189 group_copy_permissions = v.StringBoolean(if_missing=False)
204
190
205 group_parent_id = v.OneOf(available_groups, hideList=False,
191 group_parent_id = v.OneOf(available_groups, hideList=False,
206 testValueList=True, not_empty=True)
192 testValueList=True, not_empty=True)
207 enable_locking = v.StringBoolean(if_missing=False)
193 enable_locking = v.StringBoolean(if_missing=False)
208 chained_validators = [
194 chained_validators = [
209 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
195 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
210
196
211 if edit:
197 if edit:
212 # this is repo group owner
198 # this is repo group owner
213 user = All(
199 user = All(
214 v.UnicodeString(not_empty=True),
200 v.UnicodeString(not_empty=True),
215 v.ValidRepoUser(allow_disabled))
201 v.ValidRepoUser(allow_disabled))
216
202
217 return _RepoGroupForm
203 return _RepoGroupForm
218
204
219
205
220 def RegisterForm(edit=False, old_data={}):
206 def RegisterForm(edit=False, old_data={}):
221 class _RegisterForm(formencode.Schema):
207 class _RegisterForm(formencode.Schema):
222 allow_extra_fields = True
208 allow_extra_fields = True
223 filter_extra_fields = True
209 filter_extra_fields = True
224 username = All(
210 username = All(
225 v.ValidUsername(edit, old_data),
211 v.ValidUsername(edit, old_data),
226 v.UnicodeString(strip=True, min=1, not_empty=True)
212 v.UnicodeString(strip=True, min=1, not_empty=True)
227 )
213 )
228 password = All(
214 password = All(
229 v.ValidPassword(),
215 v.ValidPassword(),
230 v.UnicodeString(strip=False, min=6, not_empty=True)
216 v.UnicodeString(strip=False, min=6, not_empty=True)
231 )
217 )
232 password_confirmation = All(
218 password_confirmation = All(
233 v.ValidPassword(),
219 v.ValidPassword(),
234 v.UnicodeString(strip=False, min=6, not_empty=True)
220 v.UnicodeString(strip=False, min=6, not_empty=True)
235 )
221 )
236 active = v.StringBoolean(if_missing=False)
222 active = v.StringBoolean(if_missing=False)
237 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
223 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
238 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
224 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
239 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
225 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
240
226
241 chained_validators = [v.ValidPasswordsMatch()]
227 chained_validators = [v.ValidPasswordsMatch()]
242
228
243 return _RegisterForm
229 return _RegisterForm
244
230
245
231
246 def PasswordResetForm():
232 def PasswordResetForm():
247 class _PasswordResetForm(formencode.Schema):
233 class _PasswordResetForm(formencode.Schema):
248 allow_extra_fields = True
234 allow_extra_fields = True
249 filter_extra_fields = True
235 filter_extra_fields = True
250 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
236 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
251 return _PasswordResetForm
237 return _PasswordResetForm
252
238
253
239
254 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
240 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
255 allow_disabled=False):
241 allow_disabled=False):
256 old_data = old_data or {}
242 old_data = old_data or {}
257 repo_groups = repo_groups or []
243 repo_groups = repo_groups or []
258 landing_revs = landing_revs or []
244 landing_revs = landing_revs or []
259 supported_backends = BACKENDS.keys()
245 supported_backends = BACKENDS.keys()
260
246
261 class _RepoForm(formencode.Schema):
247 class _RepoForm(formencode.Schema):
262 allow_extra_fields = True
248 allow_extra_fields = True
263 filter_extra_fields = False
249 filter_extra_fields = False
264 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
265 v.SlugifyName())
251 v.SlugifyName())
266 repo_group = All(v.CanWriteGroup(old_data),
252 repo_group = All(v.CanWriteGroup(old_data),
267 v.OneOf(repo_groups, hideList=True))
253 v.OneOf(repo_groups, hideList=True))
268 repo_type = v.OneOf(supported_backends, required=False,
254 repo_type = v.OneOf(supported_backends, required=False,
269 if_missing=old_data.get('repo_type'))
255 if_missing=old_data.get('repo_type'))
270 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
256 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
271 repo_private = v.StringBoolean(if_missing=False)
257 repo_private = v.StringBoolean(if_missing=False)
272 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
258 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
273 repo_copy_permissions = v.StringBoolean(if_missing=False)
259 repo_copy_permissions = v.StringBoolean(if_missing=False)
274 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
260 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
275
261
276 repo_enable_statistics = v.StringBoolean(if_missing=False)
262 repo_enable_statistics = v.StringBoolean(if_missing=False)
277 repo_enable_downloads = v.StringBoolean(if_missing=False)
263 repo_enable_downloads = v.StringBoolean(if_missing=False)
278 repo_enable_locking = v.StringBoolean(if_missing=False)
264 repo_enable_locking = v.StringBoolean(if_missing=False)
279
265
280 if edit:
266 if edit:
281 # this is repo owner
267 # this is repo owner
282 user = All(
268 user = All(
283 v.UnicodeString(not_empty=True),
269 v.UnicodeString(not_empty=True),
284 v.ValidRepoUser(allow_disabled))
270 v.ValidRepoUser(allow_disabled))
285 clone_uri_change = v.UnicodeString(
271 clone_uri_change = v.UnicodeString(
286 not_empty=False, if_missing=v.Missing)
272 not_empty=False, if_missing=v.Missing)
287
273
288 chained_validators = [v.ValidCloneUri(),
274 chained_validators = [v.ValidCloneUri(),
289 v.ValidRepoName(edit, old_data)]
275 v.ValidRepoName(edit, old_data)]
290 return _RepoForm
276 return _RepoForm
291
277
292
278
293 def RepoPermsForm():
279 def RepoPermsForm():
294 class _RepoPermsForm(formencode.Schema):
280 class _RepoPermsForm(formencode.Schema):
295 allow_extra_fields = True
281 allow_extra_fields = True
296 filter_extra_fields = False
282 filter_extra_fields = False
297 chained_validators = [v.ValidPerms(type_='repo')]
283 chained_validators = [v.ValidPerms(type_='repo')]
298 return _RepoPermsForm
284 return _RepoPermsForm
299
285
300
286
301 def RepoGroupPermsForm(valid_recursive_choices):
287 def RepoGroupPermsForm(valid_recursive_choices):
302 class _RepoGroupPermsForm(formencode.Schema):
288 class _RepoGroupPermsForm(formencode.Schema):
303 allow_extra_fields = True
289 allow_extra_fields = True
304 filter_extra_fields = False
290 filter_extra_fields = False
305 recursive = v.OneOf(valid_recursive_choices)
291 recursive = v.OneOf(valid_recursive_choices)
306 chained_validators = [v.ValidPerms(type_='repo_group')]
292 chained_validators = [v.ValidPerms(type_='repo_group')]
307 return _RepoGroupPermsForm
293 return _RepoGroupPermsForm
308
294
309
295
310 def UserGroupPermsForm():
296 def UserGroupPermsForm():
311 class _UserPermsForm(formencode.Schema):
297 class _UserPermsForm(formencode.Schema):
312 allow_extra_fields = True
298 allow_extra_fields = True
313 filter_extra_fields = False
299 filter_extra_fields = False
314 chained_validators = [v.ValidPerms(type_='user_group')]
300 chained_validators = [v.ValidPerms(type_='user_group')]
315 return _UserPermsForm
301 return _UserPermsForm
316
302
317
303
318 def RepoFieldForm():
304 def RepoFieldForm():
319 class _RepoFieldForm(formencode.Schema):
305 class _RepoFieldForm(formencode.Schema):
320 filter_extra_fields = True
306 filter_extra_fields = True
321 allow_extra_fields = True
307 allow_extra_fields = True
322
308
323 new_field_key = All(v.FieldKey(),
309 new_field_key = All(v.FieldKey(),
324 v.UnicodeString(strip=True, min=3, not_empty=True))
310 v.UnicodeString(strip=True, min=3, not_empty=True))
325 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
311 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
326 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
312 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
327 if_missing='str')
313 if_missing='str')
328 new_field_label = v.UnicodeString(not_empty=False)
314 new_field_label = v.UnicodeString(not_empty=False)
329 new_field_desc = v.UnicodeString(not_empty=False)
315 new_field_desc = v.UnicodeString(not_empty=False)
330
316
331 return _RepoFieldForm
317 return _RepoFieldForm
332
318
333
319
334 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
320 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
335 repo_groups=[], landing_revs=[]):
321 repo_groups=[], landing_revs=[]):
336 class _RepoForkForm(formencode.Schema):
322 class _RepoForkForm(formencode.Schema):
337 allow_extra_fields = True
323 allow_extra_fields = True
338 filter_extra_fields = False
324 filter_extra_fields = False
339 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
325 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
340 v.SlugifyName())
326 v.SlugifyName())
341 repo_group = All(v.CanWriteGroup(),
327 repo_group = All(v.CanWriteGroup(),
342 v.OneOf(repo_groups, hideList=True))
328 v.OneOf(repo_groups, hideList=True))
343 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
329 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
344 description = v.UnicodeString(strip=True, min=1, not_empty=True)
330 description = v.UnicodeString(strip=True, min=1, not_empty=True)
345 private = v.StringBoolean(if_missing=False)
331 private = v.StringBoolean(if_missing=False)
346 copy_permissions = v.StringBoolean(if_missing=False)
332 copy_permissions = v.StringBoolean(if_missing=False)
347 fork_parent_id = v.UnicodeString()
333 fork_parent_id = v.UnicodeString()
348 chained_validators = [v.ValidForkName(edit, old_data)]
334 chained_validators = [v.ValidForkName(edit, old_data)]
349 landing_rev = v.OneOf(landing_revs, hideList=True)
335 landing_rev = v.OneOf(landing_revs, hideList=True)
350
336
351 return _RepoForkForm
337 return _RepoForkForm
352
338
353
339
354 def ApplicationSettingsForm():
340 def ApplicationSettingsForm():
355 class _ApplicationSettingsForm(formencode.Schema):
341 class _ApplicationSettingsForm(formencode.Schema):
356 allow_extra_fields = True
342 allow_extra_fields = True
357 filter_extra_fields = False
343 filter_extra_fields = False
358 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
344 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
359 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
345 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
360 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
346 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
361 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
347 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
362 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
348 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
363 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
349 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
364
350
365 return _ApplicationSettingsForm
351 return _ApplicationSettingsForm
366
352
367
353
368 def ApplicationVisualisationForm():
354 def ApplicationVisualisationForm():
369 class _ApplicationVisualisationForm(formencode.Schema):
355 class _ApplicationVisualisationForm(formencode.Schema):
370 allow_extra_fields = True
356 allow_extra_fields = True
371 filter_extra_fields = False
357 filter_extra_fields = False
372 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
358 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
373 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
359 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
374 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
360 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
375
361
376 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
362 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
377 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
363 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
378 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
364 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
379 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
365 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
380 rhodecode_show_version = v.StringBoolean(if_missing=False)
366 rhodecode_show_version = v.StringBoolean(if_missing=False)
381 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
367 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
382 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
368 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
383 rhodecode_gravatar_url = v.UnicodeString(min=3)
369 rhodecode_gravatar_url = v.UnicodeString(min=3)
384 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
370 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
385 rhodecode_support_url = v.UnicodeString()
371 rhodecode_support_url = v.UnicodeString()
386 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
372 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
387 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
373 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
388
374
389 return _ApplicationVisualisationForm
375 return _ApplicationVisualisationForm
390
376
391
377
392 class _BaseVcsSettingsForm(formencode.Schema):
378 class _BaseVcsSettingsForm(formencode.Schema):
393 allow_extra_fields = True
379 allow_extra_fields = True
394 filter_extra_fields = False
380 filter_extra_fields = False
395 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
381 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
396 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
382 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
397 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
383 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
398
384
399 extensions_largefiles = v.StringBoolean(if_missing=False)
385 extensions_largefiles = v.StringBoolean(if_missing=False)
400 phases_publish = v.StringBoolean(if_missing=False)
386 phases_publish = v.StringBoolean(if_missing=False)
401
387
402 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
388 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
403 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
389 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
404 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
390 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
405
391
406
392
407 def ApplicationUiSettingsForm():
393 def ApplicationUiSettingsForm():
408 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
394 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
409 web_push_ssl = v.StringBoolean(if_missing=False)
395 web_push_ssl = v.StringBoolean(if_missing=False)
410 paths_root_path = All(
396 paths_root_path = All(
411 v.ValidPath(),
397 v.ValidPath(),
412 v.UnicodeString(strip=True, min=1, not_empty=True)
398 v.UnicodeString(strip=True, min=1, not_empty=True)
413 )
399 )
414 extensions_hgsubversion = v.StringBoolean(if_missing=False)
400 extensions_hgsubversion = v.StringBoolean(if_missing=False)
415 extensions_hggit = v.StringBoolean(if_missing=False)
401 extensions_hggit = v.StringBoolean(if_missing=False)
416 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
402 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
417 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
403 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
418
404
419 return _ApplicationUiSettingsForm
405 return _ApplicationUiSettingsForm
420
406
421
407
422 def RepoVcsSettingsForm(repo_name):
408 def RepoVcsSettingsForm(repo_name):
423 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
409 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
424 inherit_global_settings = v.StringBoolean(if_missing=False)
410 inherit_global_settings = v.StringBoolean(if_missing=False)
425 new_svn_branch = v.ValidSvnPattern(
411 new_svn_branch = v.ValidSvnPattern(
426 section='vcs_svn_branch', repo_name=repo_name)
412 section='vcs_svn_branch', repo_name=repo_name)
427 new_svn_tag = v.ValidSvnPattern(
413 new_svn_tag = v.ValidSvnPattern(
428 section='vcs_svn_tag', repo_name=repo_name)
414 section='vcs_svn_tag', repo_name=repo_name)
429
415
430 return _RepoVcsSettingsForm
416 return _RepoVcsSettingsForm
431
417
432
418
433 def LabsSettingsForm():
419 def LabsSettingsForm():
434 class _LabSettingsForm(formencode.Schema):
420 class _LabSettingsForm(formencode.Schema):
435 allow_extra_fields = True
421 allow_extra_fields = True
436 filter_extra_fields = False
422 filter_extra_fields = False
437
423
438 rhodecode_proxy_subversion_http_requests = v.StringBoolean(
424 rhodecode_proxy_subversion_http_requests = v.StringBoolean(
439 if_missing=False)
425 if_missing=False)
440 rhodecode_subversion_http_server_url = v.UnicodeString(
426 rhodecode_subversion_http_server_url = v.UnicodeString(
441 strip=True, if_missing=None)
427 strip=True, if_missing=None)
442
428
443 return _LabSettingsForm
429 return _LabSettingsForm
444
430
445
431
446 def ApplicationPermissionsForm(register_choices, extern_activate_choices):
432 def ApplicationPermissionsForm(register_choices, extern_activate_choices):
447 class _DefaultPermissionsForm(formencode.Schema):
433 class _DefaultPermissionsForm(formencode.Schema):
448 allow_extra_fields = True
434 allow_extra_fields = True
449 filter_extra_fields = True
435 filter_extra_fields = True
450
436
451 anonymous = v.StringBoolean(if_missing=False)
437 anonymous = v.StringBoolean(if_missing=False)
452 default_register = v.OneOf(register_choices)
438 default_register = v.OneOf(register_choices)
453 default_register_message = v.UnicodeString()
439 default_register_message = v.UnicodeString()
454 default_extern_activate = v.OneOf(extern_activate_choices)
440 default_extern_activate = v.OneOf(extern_activate_choices)
455
441
456 return _DefaultPermissionsForm
442 return _DefaultPermissionsForm
457
443
458
444
459 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
445 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
460 user_group_perms_choices):
446 user_group_perms_choices):
461 class _ObjectPermissionsForm(formencode.Schema):
447 class _ObjectPermissionsForm(formencode.Schema):
462 allow_extra_fields = True
448 allow_extra_fields = True
463 filter_extra_fields = True
449 filter_extra_fields = True
464 overwrite_default_repo = v.StringBoolean(if_missing=False)
450 overwrite_default_repo = v.StringBoolean(if_missing=False)
465 overwrite_default_group = v.StringBoolean(if_missing=False)
451 overwrite_default_group = v.StringBoolean(if_missing=False)
466 overwrite_default_user_group = v.StringBoolean(if_missing=False)
452 overwrite_default_user_group = v.StringBoolean(if_missing=False)
467 default_repo_perm = v.OneOf(repo_perms_choices)
453 default_repo_perm = v.OneOf(repo_perms_choices)
468 default_group_perm = v.OneOf(group_perms_choices)
454 default_group_perm = v.OneOf(group_perms_choices)
469 default_user_group_perm = v.OneOf(user_group_perms_choices)
455 default_user_group_perm = v.OneOf(user_group_perms_choices)
470
456
471 return _ObjectPermissionsForm
457 return _ObjectPermissionsForm
472
458
473
459
474 def UserPermissionsForm(create_choices, create_on_write_choices,
460 def UserPermissionsForm(create_choices, create_on_write_choices,
475 repo_group_create_choices, user_group_create_choices,
461 repo_group_create_choices, user_group_create_choices,
476 fork_choices, inherit_default_permissions_choices):
462 fork_choices, inherit_default_permissions_choices):
477 class _DefaultPermissionsForm(formencode.Schema):
463 class _DefaultPermissionsForm(formencode.Schema):
478 allow_extra_fields = True
464 allow_extra_fields = True
479 filter_extra_fields = True
465 filter_extra_fields = True
480
466
481 anonymous = v.StringBoolean(if_missing=False)
467 anonymous = v.StringBoolean(if_missing=False)
482
468
483 default_repo_create = v.OneOf(create_choices)
469 default_repo_create = v.OneOf(create_choices)
484 default_repo_create_on_write = v.OneOf(create_on_write_choices)
470 default_repo_create_on_write = v.OneOf(create_on_write_choices)
485 default_user_group_create = v.OneOf(user_group_create_choices)
471 default_user_group_create = v.OneOf(user_group_create_choices)
486 default_repo_group_create = v.OneOf(repo_group_create_choices)
472 default_repo_group_create = v.OneOf(repo_group_create_choices)
487 default_fork_create = v.OneOf(fork_choices)
473 default_fork_create = v.OneOf(fork_choices)
488 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
474 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
489
475
490 return _DefaultPermissionsForm
476 return _DefaultPermissionsForm
491
477
492
478
493 def UserIndividualPermissionsForm():
479 def UserIndividualPermissionsForm():
494 class _DefaultPermissionsForm(formencode.Schema):
480 class _DefaultPermissionsForm(formencode.Schema):
495 allow_extra_fields = True
481 allow_extra_fields = True
496 filter_extra_fields = True
482 filter_extra_fields = True
497
483
498 inherit_default_permissions = v.StringBoolean(if_missing=False)
484 inherit_default_permissions = v.StringBoolean(if_missing=False)
499
485
500 return _DefaultPermissionsForm
486 return _DefaultPermissionsForm
501
487
502
488
503 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
489 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
504 class _DefaultsForm(formencode.Schema):
490 class _DefaultsForm(formencode.Schema):
505 allow_extra_fields = True
491 allow_extra_fields = True
506 filter_extra_fields = True
492 filter_extra_fields = True
507 default_repo_type = v.OneOf(supported_backends)
493 default_repo_type = v.OneOf(supported_backends)
508 default_repo_private = v.StringBoolean(if_missing=False)
494 default_repo_private = v.StringBoolean(if_missing=False)
509 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
495 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
510 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
496 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
511 default_repo_enable_locking = v.StringBoolean(if_missing=False)
497 default_repo_enable_locking = v.StringBoolean(if_missing=False)
512
498
513 return _DefaultsForm
499 return _DefaultsForm
514
500
515
501
516 def AuthSettingsForm():
502 def AuthSettingsForm():
517 class _AuthSettingsForm(formencode.Schema):
503 class _AuthSettingsForm(formencode.Schema):
518 allow_extra_fields = True
504 allow_extra_fields = True
519 filter_extra_fields = True
505 filter_extra_fields = True
520 auth_plugins = All(v.ValidAuthPlugins(),
506 auth_plugins = All(v.ValidAuthPlugins(),
521 v.UniqueListFromString()(not_empty=True))
507 v.UniqueListFromString()(not_empty=True))
522
508
523 return _AuthSettingsForm
509 return _AuthSettingsForm
524
510
525
511
526 def UserExtraEmailForm():
512 def UserExtraEmailForm():
527 class _UserExtraEmailForm(formencode.Schema):
513 class _UserExtraEmailForm(formencode.Schema):
528 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
514 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
529 return _UserExtraEmailForm
515 return _UserExtraEmailForm
530
516
531
517
532 def UserExtraIpForm():
518 def UserExtraIpForm():
533 class _UserExtraIpForm(formencode.Schema):
519 class _UserExtraIpForm(formencode.Schema):
534 ip = v.ValidIp()(not_empty=True)
520 ip = v.ValidIp()(not_empty=True)
535 return _UserExtraIpForm
521 return _UserExtraIpForm
536
522
537
523
538 def PullRequestForm(repo_id):
524 def PullRequestForm(repo_id):
539 class _PullRequestForm(formencode.Schema):
525 class _PullRequestForm(formencode.Schema):
540 allow_extra_fields = True
526 allow_extra_fields = True
541 filter_extra_fields = True
527 filter_extra_fields = True
542
528
543 user = v.UnicodeString(strip=True, required=True)
529 user = v.UnicodeString(strip=True, required=True)
544 source_repo = v.UnicodeString(strip=True, required=True)
530 source_repo = v.UnicodeString(strip=True, required=True)
545 source_ref = v.UnicodeString(strip=True, required=True)
531 source_ref = v.UnicodeString(strip=True, required=True)
546 target_repo = v.UnicodeString(strip=True, required=True)
532 target_repo = v.UnicodeString(strip=True, required=True)
547 target_ref = v.UnicodeString(strip=True, required=True)
533 target_ref = v.UnicodeString(strip=True, required=True)
548 revisions = All(#v.NotReviewedRevisions(repo_id)(),
534 revisions = All(#v.NotReviewedRevisions(repo_id)(),
549 v.UniqueList()(not_empty=True))
535 v.UniqueList()(not_empty=True))
550 review_members = v.UniqueList(convert=int)(not_empty=True)
536 review_members = v.UniqueList(convert=int)(not_empty=True)
551
537
552 pullrequest_title = v.UnicodeString(strip=True, required=True)
538 pullrequest_title = v.UnicodeString(strip=True, required=True)
553 pullrequest_desc = v.UnicodeString(strip=True, required=False)
539 pullrequest_desc = v.UnicodeString(strip=True, required=False)
554
540
555 return _PullRequestForm
541 return _PullRequestForm
556
542
557
543
558 def IssueTrackerPatternsForm():
544 def IssueTrackerPatternsForm():
559 class _IssueTrackerPatternsForm(formencode.Schema):
545 class _IssueTrackerPatternsForm(formencode.Schema):
560 allow_extra_fields = True
546 allow_extra_fields = True
561 filter_extra_fields = False
547 filter_extra_fields = False
562 chained_validators = [v.ValidPattern()]
548 chained_validators = [v.ValidPattern()]
563 return _IssueTrackerPatternsForm
549 return _IssueTrackerPatternsForm
@@ -1,839 +1,838 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 users model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27
27
28 import datetime
28 import datetime
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 import ipaddress
31 import ipaddress
32 from sqlalchemy.exc import DatabaseError
32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.sql.expression import true, false
33 from sqlalchemy.sql.expression import true, false
34
34
35 from rhodecode import events
35 from rhodecode import events
36 from rhodecode.lib.utils2 import (
36 from rhodecode.lib.utils2 import (
37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
38 AttributeDict)
38 AttributeDict)
39 from rhodecode.lib.caching_query import FromCache
39 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.model import BaseModel
40 from rhodecode.model import BaseModel
41 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 User, UserToPerm, UserEmailMap, UserIpMap)
43 User, UserToPerm, UserEmailMap, UserIpMap)
44 from rhodecode.lib.exceptions import (
44 from rhodecode.lib.exceptions import (
45 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
45 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
46 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
46 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(FromCache("sql_cache_short",
60 user = user.options(FromCache("sql_cache_short",
61 "get_user_%s" % user_id))
61 "get_user_%s" % user_id))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def get_by_username(self, username, cache=False, case_insensitive=False):
67 def get_by_username(self, username, cache=False, case_insensitive=False):
68
68
69 if case_insensitive:
69 if case_insensitive:
70 user = self.sa.query(User).filter(User.username.ilike(username))
70 user = self.sa.query(User).filter(User.username.ilike(username))
71 else:
71 else:
72 user = self.sa.query(User)\
72 user = self.sa.query(User)\
73 .filter(User.username == username)
73 .filter(User.username == username)
74 if cache:
74 if cache:
75 user = user.options(FromCache("sql_cache_short",
75 user = user.options(FromCache("sql_cache_short",
76 "get_user_%s" % username))
76 "get_user_%s" % username))
77 return user.scalar()
77 return user.scalar()
78
78
79 def get_by_email(self, email, cache=False, case_insensitive=False):
79 def get_by_email(self, email, cache=False, case_insensitive=False):
80 return User.get_by_email(email, case_insensitive, cache)
80 return User.get_by_email(email, case_insensitive, cache)
81
81
82 def get_by_auth_token(self, auth_token, cache=False):
82 def get_by_auth_token(self, auth_token, cache=False):
83 return User.get_by_auth_token(auth_token, cache)
83 return User.get_by_auth_token(auth_token, cache)
84
84
85 def get_active_user_count(self, cache=False):
85 def get_active_user_count(self, cache=False):
86 return User.query().filter(
86 return User.query().filter(
87 User.active == True).filter(
87 User.active == True).filter(
88 User.username != User.DEFAULT_USER).count()
88 User.username != User.DEFAULT_USER).count()
89
89
90 def create(self, form_data, cur_user=None):
90 def create(self, form_data, cur_user=None):
91 if not cur_user:
91 if not cur_user:
92 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
92 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
93
93
94 user_data = {
94 user_data = {
95 'username': form_data['username'],
95 'username': form_data['username'],
96 'password': form_data['password'],
96 'password': form_data['password'],
97 'email': form_data['email'],
97 'email': form_data['email'],
98 'firstname': form_data['firstname'],
98 'firstname': form_data['firstname'],
99 'lastname': form_data['lastname'],
99 'lastname': form_data['lastname'],
100 'active': form_data['active'],
100 'active': form_data['active'],
101 'extern_type': form_data['extern_type'],
101 'extern_type': form_data['extern_type'],
102 'extern_name': form_data['extern_name'],
102 'extern_name': form_data['extern_name'],
103 'admin': False,
103 'admin': False,
104 'cur_user': cur_user
104 'cur_user': cur_user
105 }
105 }
106
106
107 try:
107 try:
108 if form_data.get('create_repo_group'):
108 if form_data.get('create_repo_group'):
109 user_data['create_repo_group'] = True
109 user_data['create_repo_group'] = True
110 if form_data.get('password_change'):
110 if form_data.get('password_change'):
111 user_data['force_password_change'] = True
111 user_data['force_password_change'] = True
112
112
113 return UserModel().create_or_update(**user_data)
113 return UserModel().create_or_update(**user_data)
114 except Exception:
114 except Exception:
115 log.error(traceback.format_exc())
115 log.error(traceback.format_exc())
116 raise
116 raise
117
117
118 def update_user(self, user, skip_attrs=None, **kwargs):
118 def update_user(self, user, skip_attrs=None, **kwargs):
119 from rhodecode.lib.auth import get_crypt_password
119 from rhodecode.lib.auth import get_crypt_password
120
120
121 user = self._get_user(user)
121 user = self._get_user(user)
122 if user.username == User.DEFAULT_USER:
122 if user.username == User.DEFAULT_USER:
123 raise DefaultUserException(
123 raise DefaultUserException(
124 _("You can't Edit this user since it's"
124 _("You can't Edit this user since it's"
125 " crucial for entire application"))
125 " crucial for entire application"))
126
126
127 # first store only defaults
127 # first store only defaults
128 user_attrs = {
128 user_attrs = {
129 'updating_user_id': user.user_id,
129 'updating_user_id': user.user_id,
130 'username': user.username,
130 'username': user.username,
131 'password': user.password,
131 'password': user.password,
132 'email': user.email,
132 'email': user.email,
133 'firstname': user.name,
133 'firstname': user.name,
134 'lastname': user.lastname,
134 'lastname': user.lastname,
135 'active': user.active,
135 'active': user.active,
136 'admin': user.admin,
136 'admin': user.admin,
137 'extern_name': user.extern_name,
137 'extern_name': user.extern_name,
138 'extern_type': user.extern_type,
138 'extern_type': user.extern_type,
139 'language': user.user_data.get('language')
139 'language': user.user_data.get('language')
140 }
140 }
141
141
142 # in case there's new_password, that comes from form, use it to
142 # in case there's new_password, that comes from form, use it to
143 # store password
143 # store password
144 if kwargs.get('new_password'):
144 if kwargs.get('new_password'):
145 kwargs['password'] = kwargs['new_password']
145 kwargs['password'] = kwargs['new_password']
146
146
147 # cleanups, my_account password change form
147 # cleanups, my_account password change form
148 kwargs.pop('current_password', None)
148 kwargs.pop('current_password', None)
149 kwargs.pop('new_password', None)
149 kwargs.pop('new_password', None)
150 kwargs.pop('new_password_confirmation', None)
151
150
152 # cleanups, user edit password change form
151 # cleanups, user edit password change form
153 kwargs.pop('password_confirmation', None)
152 kwargs.pop('password_confirmation', None)
154 kwargs.pop('password_change', None)
153 kwargs.pop('password_change', None)
155
154
156 # create repo group on user creation
155 # create repo group on user creation
157 kwargs.pop('create_repo_group', None)
156 kwargs.pop('create_repo_group', None)
158
157
159 # legacy forms send name, which is the firstname
158 # legacy forms send name, which is the firstname
160 firstname = kwargs.pop('name', None)
159 firstname = kwargs.pop('name', None)
161 if firstname:
160 if firstname:
162 kwargs['firstname'] = firstname
161 kwargs['firstname'] = firstname
163
162
164 for k, v in kwargs.items():
163 for k, v in kwargs.items():
165 # skip if we don't want to update this
164 # skip if we don't want to update this
166 if skip_attrs and k in skip_attrs:
165 if skip_attrs and k in skip_attrs:
167 continue
166 continue
168
167
169 user_attrs[k] = v
168 user_attrs[k] = v
170
169
171 try:
170 try:
172 return self.create_or_update(**user_attrs)
171 return self.create_or_update(**user_attrs)
173 except Exception:
172 except Exception:
174 log.error(traceback.format_exc())
173 log.error(traceback.format_exc())
175 raise
174 raise
176
175
177 def create_or_update(
176 def create_or_update(
178 self, username, password, email, firstname='', lastname='',
177 self, username, password, email, firstname='', lastname='',
179 active=True, admin=False, extern_type=None, extern_name=None,
178 active=True, admin=False, extern_type=None, extern_name=None,
180 cur_user=None, plugin=None, force_password_change=False,
179 cur_user=None, plugin=None, force_password_change=False,
181 allow_to_create_user=True, create_repo_group=False,
180 allow_to_create_user=True, create_repo_group=False,
182 updating_user_id=None, language=None, strict_creation_check=True):
181 updating_user_id=None, language=None, strict_creation_check=True):
183 """
182 """
184 Creates a new instance if not found, or updates current one
183 Creates a new instance if not found, or updates current one
185
184
186 :param username:
185 :param username:
187 :param password:
186 :param password:
188 :param email:
187 :param email:
189 :param firstname:
188 :param firstname:
190 :param lastname:
189 :param lastname:
191 :param active:
190 :param active:
192 :param admin:
191 :param admin:
193 :param extern_type:
192 :param extern_type:
194 :param extern_name:
193 :param extern_name:
195 :param cur_user:
194 :param cur_user:
196 :param plugin: optional plugin this method was called from
195 :param plugin: optional plugin this method was called from
197 :param force_password_change: toggles new or existing user flag
196 :param force_password_change: toggles new or existing user flag
198 for password change
197 for password change
199 :param allow_to_create_user: Defines if the method can actually create
198 :param allow_to_create_user: Defines if the method can actually create
200 new users
199 new users
201 :param create_repo_group: Defines if the method should also
200 :param create_repo_group: Defines if the method should also
202 create an repo group with user name, and owner
201 create an repo group with user name, and owner
203 :param updating_user_id: if we set it up this is the user we want to
202 :param updating_user_id: if we set it up this is the user we want to
204 update this allows to editing username.
203 update this allows to editing username.
205 :param language: language of user from interface.
204 :param language: language of user from interface.
206
205
207 :returns: new User object with injected `is_new_user` attribute.
206 :returns: new User object with injected `is_new_user` attribute.
208 """
207 """
209 if not cur_user:
208 if not cur_user:
210 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
209 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
211
210
212 from rhodecode.lib.auth import (
211 from rhodecode.lib.auth import (
213 get_crypt_password, check_password, generate_auth_token)
212 get_crypt_password, check_password, generate_auth_token)
214 from rhodecode.lib.hooks_base import (
213 from rhodecode.lib.hooks_base import (
215 log_create_user, check_allowed_create_user)
214 log_create_user, check_allowed_create_user)
216
215
217 def _password_change(new_user, password):
216 def _password_change(new_user, password):
218 # empty password
217 # empty password
219 if not new_user.password:
218 if not new_user.password:
220 return False
219 return False
221
220
222 # password check is only needed for RhodeCode internal auth calls
221 # password check is only needed for RhodeCode internal auth calls
223 # in case it's a plugin we don't care
222 # in case it's a plugin we don't care
224 if not plugin:
223 if not plugin:
225
224
226 # first check if we gave crypted password back, and if it matches
225 # first check if we gave crypted password back, and if it matches
227 # it's not password change
226 # it's not password change
228 if new_user.password == password:
227 if new_user.password == password:
229 return False
228 return False
230
229
231 password_match = check_password(password, new_user.password)
230 password_match = check_password(password, new_user.password)
232 if not password_match:
231 if not password_match:
233 return True
232 return True
234
233
235 return False
234 return False
236
235
237 user_data = {
236 user_data = {
238 'username': username,
237 'username': username,
239 'password': password,
238 'password': password,
240 'email': email,
239 'email': email,
241 'firstname': firstname,
240 'firstname': firstname,
242 'lastname': lastname,
241 'lastname': lastname,
243 'active': active,
242 'active': active,
244 'admin': admin
243 'admin': admin
245 }
244 }
246
245
247 if updating_user_id:
246 if updating_user_id:
248 log.debug('Checking for existing account in RhodeCode '
247 log.debug('Checking for existing account in RhodeCode '
249 'database with user_id `%s` ' % (updating_user_id,))
248 'database with user_id `%s` ' % (updating_user_id,))
250 user = User.get(updating_user_id)
249 user = User.get(updating_user_id)
251 else:
250 else:
252 log.debug('Checking for existing account in RhodeCode '
251 log.debug('Checking for existing account in RhodeCode '
253 'database with username `%s` ' % (username,))
252 'database with username `%s` ' % (username,))
254 user = User.get_by_username(username, case_insensitive=True)
253 user = User.get_by_username(username, case_insensitive=True)
255
254
256 if user is None:
255 if user is None:
257 # we check internal flag if this method is actually allowed to
256 # we check internal flag if this method is actually allowed to
258 # create new user
257 # create new user
259 if not allow_to_create_user:
258 if not allow_to_create_user:
260 msg = ('Method wants to create new user, but it is not '
259 msg = ('Method wants to create new user, but it is not '
261 'allowed to do so')
260 'allowed to do so')
262 log.warning(msg)
261 log.warning(msg)
263 raise NotAllowedToCreateUserError(msg)
262 raise NotAllowedToCreateUserError(msg)
264
263
265 log.debug('Creating new user %s', username)
264 log.debug('Creating new user %s', username)
266
265
267 # only if we create user that is active
266 # only if we create user that is active
268 new_active_user = active
267 new_active_user = active
269 if new_active_user and strict_creation_check:
268 if new_active_user and strict_creation_check:
270 # raises UserCreationError if it's not allowed for any reason to
269 # raises UserCreationError if it's not allowed for any reason to
271 # create new active user, this also executes pre-create hooks
270 # create new active user, this also executes pre-create hooks
272 check_allowed_create_user(user_data, cur_user, strict_check=True)
271 check_allowed_create_user(user_data, cur_user, strict_check=True)
273 events.trigger(events.UserPreCreate(user_data))
272 events.trigger(events.UserPreCreate(user_data))
274 new_user = User()
273 new_user = User()
275 edit = False
274 edit = False
276 else:
275 else:
277 log.debug('updating user %s', username)
276 log.debug('updating user %s', username)
278 events.trigger(events.UserPreUpdate(user, user_data))
277 events.trigger(events.UserPreUpdate(user, user_data))
279 new_user = user
278 new_user = user
280 edit = True
279 edit = True
281
280
282 # we're not allowed to edit default user
281 # we're not allowed to edit default user
283 if user.username == User.DEFAULT_USER:
282 if user.username == User.DEFAULT_USER:
284 raise DefaultUserException(
283 raise DefaultUserException(
285 _("You can't edit this user (`%(username)s`) since it's "
284 _("You can't edit this user (`%(username)s`) since it's "
286 "crucial for entire application") % {'username': user.username})
285 "crucial for entire application") % {'username': user.username})
287
286
288 # inject special attribute that will tell us if User is new or old
287 # inject special attribute that will tell us if User is new or old
289 new_user.is_new_user = not edit
288 new_user.is_new_user = not edit
290 # for users that didn's specify auth type, we use RhodeCode built in
289 # for users that didn's specify auth type, we use RhodeCode built in
291 from rhodecode.authentication.plugins import auth_rhodecode
290 from rhodecode.authentication.plugins import auth_rhodecode
292 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
291 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
293 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
292 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
294
293
295 try:
294 try:
296 new_user.username = username
295 new_user.username = username
297 new_user.admin = admin
296 new_user.admin = admin
298 new_user.email = email
297 new_user.email = email
299 new_user.active = active
298 new_user.active = active
300 new_user.extern_name = safe_unicode(extern_name)
299 new_user.extern_name = safe_unicode(extern_name)
301 new_user.extern_type = safe_unicode(extern_type)
300 new_user.extern_type = safe_unicode(extern_type)
302 new_user.name = firstname
301 new_user.name = firstname
303 new_user.lastname = lastname
302 new_user.lastname = lastname
304
303
305 if not edit:
304 if not edit:
306 new_user.api_key = generate_auth_token(username)
305 new_user.api_key = generate_auth_token(username)
307
306
308 # set password only if creating an user or password is changed
307 # set password only if creating an user or password is changed
309 if not edit or _password_change(new_user, password):
308 if not edit or _password_change(new_user, password):
310 reason = 'new password' if edit else 'new user'
309 reason = 'new password' if edit else 'new user'
311 log.debug('Updating password reason=>%s', reason)
310 log.debug('Updating password reason=>%s', reason)
312 new_user.password = get_crypt_password(password) if password else None
311 new_user.password = get_crypt_password(password) if password else None
313
312
314 if force_password_change:
313 if force_password_change:
315 new_user.update_userdata(force_password_change=True)
314 new_user.update_userdata(force_password_change=True)
316 if language:
315 if language:
317 new_user.update_userdata(language=language)
316 new_user.update_userdata(language=language)
318
317
319 self.sa.add(new_user)
318 self.sa.add(new_user)
320
319
321 if not edit and create_repo_group:
320 if not edit and create_repo_group:
322 # create new group same as username, and make this user an owner
321 # create new group same as username, and make this user an owner
323 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
322 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
324 RepoGroupModel().create(group_name=username,
323 RepoGroupModel().create(group_name=username,
325 group_description=desc,
324 group_description=desc,
326 owner=username, commit_early=False)
325 owner=username, commit_early=False)
327 if not edit:
326 if not edit:
328 # add the RSS token
327 # add the RSS token
329 AuthTokenModel().create(username,
328 AuthTokenModel().create(username,
330 description='Generated feed token',
329 description='Generated feed token',
331 role=AuthTokenModel.cls.ROLE_FEED)
330 role=AuthTokenModel.cls.ROLE_FEED)
332 log_create_user(created_by=cur_user, **new_user.get_dict())
331 log_create_user(created_by=cur_user, **new_user.get_dict())
333 return new_user
332 return new_user
334 except (DatabaseError,):
333 except (DatabaseError,):
335 log.error(traceback.format_exc())
334 log.error(traceback.format_exc())
336 raise
335 raise
337
336
338 def create_registration(self, form_data):
337 def create_registration(self, form_data):
339 from rhodecode.model.notification import NotificationModel
338 from rhodecode.model.notification import NotificationModel
340 from rhodecode.model.notification import EmailNotificationModel
339 from rhodecode.model.notification import EmailNotificationModel
341
340
342 try:
341 try:
343 form_data['admin'] = False
342 form_data['admin'] = False
344 form_data['extern_name'] = 'rhodecode'
343 form_data['extern_name'] = 'rhodecode'
345 form_data['extern_type'] = 'rhodecode'
344 form_data['extern_type'] = 'rhodecode'
346 new_user = self.create(form_data)
345 new_user = self.create(form_data)
347
346
348 self.sa.add(new_user)
347 self.sa.add(new_user)
349 self.sa.flush()
348 self.sa.flush()
350
349
351 user_data = new_user.get_dict()
350 user_data = new_user.get_dict()
352 kwargs = {
351 kwargs = {
353 # use SQLALCHEMY safe dump of user data
352 # use SQLALCHEMY safe dump of user data
354 'user': AttributeDict(user_data),
353 'user': AttributeDict(user_data),
355 'date': datetime.datetime.now()
354 'date': datetime.datetime.now()
356 }
355 }
357 notification_type = EmailNotificationModel.TYPE_REGISTRATION
356 notification_type = EmailNotificationModel.TYPE_REGISTRATION
358 # pre-generate the subject for notification itself
357 # pre-generate the subject for notification itself
359 (subject,
358 (subject,
360 _h, _e, # we don't care about those
359 _h, _e, # we don't care about those
361 body_plaintext) = EmailNotificationModel().render_email(
360 body_plaintext) = EmailNotificationModel().render_email(
362 notification_type, **kwargs)
361 notification_type, **kwargs)
363
362
364 # create notification objects, and emails
363 # create notification objects, and emails
365 NotificationModel().create(
364 NotificationModel().create(
366 created_by=new_user,
365 created_by=new_user,
367 notification_subject=subject,
366 notification_subject=subject,
368 notification_body=body_plaintext,
367 notification_body=body_plaintext,
369 notification_type=notification_type,
368 notification_type=notification_type,
370 recipients=None, # all admins
369 recipients=None, # all admins
371 email_kwargs=kwargs,
370 email_kwargs=kwargs,
372 )
371 )
373
372
374 return new_user
373 return new_user
375 except Exception:
374 except Exception:
376 log.error(traceback.format_exc())
375 log.error(traceback.format_exc())
377 raise
376 raise
378
377
379 def _handle_user_repos(self, username, repositories, handle_mode=None):
378 def _handle_user_repos(self, username, repositories, handle_mode=None):
380 _superadmin = self.cls.get_first_super_admin()
379 _superadmin = self.cls.get_first_super_admin()
381 left_overs = True
380 left_overs = True
382
381
383 from rhodecode.model.repo import RepoModel
382 from rhodecode.model.repo import RepoModel
384
383
385 if handle_mode == 'detach':
384 if handle_mode == 'detach':
386 for obj in repositories:
385 for obj in repositories:
387 obj.user = _superadmin
386 obj.user = _superadmin
388 # set description we know why we super admin now owns
387 # set description we know why we super admin now owns
389 # additional repositories that were orphaned !
388 # additional repositories that were orphaned !
390 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
389 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
391 self.sa.add(obj)
390 self.sa.add(obj)
392 left_overs = False
391 left_overs = False
393 elif handle_mode == 'delete':
392 elif handle_mode == 'delete':
394 for obj in repositories:
393 for obj in repositories:
395 RepoModel().delete(obj, forks='detach')
394 RepoModel().delete(obj, forks='detach')
396 left_overs = False
395 left_overs = False
397
396
398 # if nothing is done we have left overs left
397 # if nothing is done we have left overs left
399 return left_overs
398 return left_overs
400
399
401 def _handle_user_repo_groups(self, username, repository_groups,
400 def _handle_user_repo_groups(self, username, repository_groups,
402 handle_mode=None):
401 handle_mode=None):
403 _superadmin = self.cls.get_first_super_admin()
402 _superadmin = self.cls.get_first_super_admin()
404 left_overs = True
403 left_overs = True
405
404
406 from rhodecode.model.repo_group import RepoGroupModel
405 from rhodecode.model.repo_group import RepoGroupModel
407
406
408 if handle_mode == 'detach':
407 if handle_mode == 'detach':
409 for r in repository_groups:
408 for r in repository_groups:
410 r.user = _superadmin
409 r.user = _superadmin
411 # set description we know why we super admin now owns
410 # set description we know why we super admin now owns
412 # additional repositories that were orphaned !
411 # additional repositories that were orphaned !
413 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
412 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
414 self.sa.add(r)
413 self.sa.add(r)
415 left_overs = False
414 left_overs = False
416 elif handle_mode == 'delete':
415 elif handle_mode == 'delete':
417 for r in repository_groups:
416 for r in repository_groups:
418 RepoGroupModel().delete(r)
417 RepoGroupModel().delete(r)
419 left_overs = False
418 left_overs = False
420
419
421 # if nothing is done we have left overs left
420 # if nothing is done we have left overs left
422 return left_overs
421 return left_overs
423
422
424 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
423 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
425 _superadmin = self.cls.get_first_super_admin()
424 _superadmin = self.cls.get_first_super_admin()
426 left_overs = True
425 left_overs = True
427
426
428 from rhodecode.model.user_group import UserGroupModel
427 from rhodecode.model.user_group import UserGroupModel
429
428
430 if handle_mode == 'detach':
429 if handle_mode == 'detach':
431 for r in user_groups:
430 for r in user_groups:
432 for user_user_group_to_perm in r.user_user_group_to_perm:
431 for user_user_group_to_perm in r.user_user_group_to_perm:
433 if user_user_group_to_perm.user.username == username:
432 if user_user_group_to_perm.user.username == username:
434 user_user_group_to_perm.user = _superadmin
433 user_user_group_to_perm.user = _superadmin
435 r.user = _superadmin
434 r.user = _superadmin
436 # set description we know why we super admin now owns
435 # set description we know why we super admin now owns
437 # additional repositories that were orphaned !
436 # additional repositories that were orphaned !
438 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
437 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
439 self.sa.add(r)
438 self.sa.add(r)
440 left_overs = False
439 left_overs = False
441 elif handle_mode == 'delete':
440 elif handle_mode == 'delete':
442 for r in user_groups:
441 for r in user_groups:
443 UserGroupModel().delete(r)
442 UserGroupModel().delete(r)
444 left_overs = False
443 left_overs = False
445
444
446 # if nothing is done we have left overs left
445 # if nothing is done we have left overs left
447 return left_overs
446 return left_overs
448
447
449 def delete(self, user, cur_user=None, handle_repos=None,
448 def delete(self, user, cur_user=None, handle_repos=None,
450 handle_repo_groups=None, handle_user_groups=None):
449 handle_repo_groups=None, handle_user_groups=None):
451 if not cur_user:
450 if not cur_user:
452 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
451 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
453 user = self._get_user(user)
452 user = self._get_user(user)
454
453
455 try:
454 try:
456 if user.username == User.DEFAULT_USER:
455 if user.username == User.DEFAULT_USER:
457 raise DefaultUserException(
456 raise DefaultUserException(
458 _(u"You can't remove this user since it's"
457 _(u"You can't remove this user since it's"
459 u" crucial for entire application"))
458 u" crucial for entire application"))
460
459
461 left_overs = self._handle_user_repos(
460 left_overs = self._handle_user_repos(
462 user.username, user.repositories, handle_repos)
461 user.username, user.repositories, handle_repos)
463 if left_overs and user.repositories:
462 if left_overs and user.repositories:
464 repos = [x.repo_name for x in user.repositories]
463 repos = [x.repo_name for x in user.repositories]
465 raise UserOwnsReposException(
464 raise UserOwnsReposException(
466 _(u'user "%s" still owns %s repositories and cannot be '
465 _(u'user "%s" still owns %s repositories and cannot be '
467 u'removed. Switch owners or remove those repositories:%s')
466 u'removed. Switch owners or remove those repositories:%s')
468 % (user.username, len(repos), ', '.join(repos)))
467 % (user.username, len(repos), ', '.join(repos)))
469
468
470 left_overs = self._handle_user_repo_groups(
469 left_overs = self._handle_user_repo_groups(
471 user.username, user.repository_groups, handle_repo_groups)
470 user.username, user.repository_groups, handle_repo_groups)
472 if left_overs and user.repository_groups:
471 if left_overs and user.repository_groups:
473 repo_groups = [x.group_name for x in user.repository_groups]
472 repo_groups = [x.group_name for x in user.repository_groups]
474 raise UserOwnsRepoGroupsException(
473 raise UserOwnsRepoGroupsException(
475 _(u'user "%s" still owns %s repository groups and cannot be '
474 _(u'user "%s" still owns %s repository groups and cannot be '
476 u'removed. Switch owners or remove those repository groups:%s')
475 u'removed. Switch owners or remove those repository groups:%s')
477 % (user.username, len(repo_groups), ', '.join(repo_groups)))
476 % (user.username, len(repo_groups), ', '.join(repo_groups)))
478
477
479 left_overs = self._handle_user_user_groups(
478 left_overs = self._handle_user_user_groups(
480 user.username, user.user_groups, handle_user_groups)
479 user.username, user.user_groups, handle_user_groups)
481 if left_overs and user.user_groups:
480 if left_overs and user.user_groups:
482 user_groups = [x.users_group_name for x in user.user_groups]
481 user_groups = [x.users_group_name for x in user.user_groups]
483 raise UserOwnsUserGroupsException(
482 raise UserOwnsUserGroupsException(
484 _(u'user "%s" still owns %s user groups and cannot be '
483 _(u'user "%s" still owns %s user groups and cannot be '
485 u'removed. Switch owners or remove those user groups:%s')
484 u'removed. Switch owners or remove those user groups:%s')
486 % (user.username, len(user_groups), ', '.join(user_groups)))
485 % (user.username, len(user_groups), ', '.join(user_groups)))
487
486
488 # we might change the user data with detach/delete, make sure
487 # we might change the user data with detach/delete, make sure
489 # the object is marked as expired before actually deleting !
488 # the object is marked as expired before actually deleting !
490 self.sa.expire(user)
489 self.sa.expire(user)
491 self.sa.delete(user)
490 self.sa.delete(user)
492 from rhodecode.lib.hooks_base import log_delete_user
491 from rhodecode.lib.hooks_base import log_delete_user
493 log_delete_user(deleted_by=cur_user, **user.get_dict())
492 log_delete_user(deleted_by=cur_user, **user.get_dict())
494 except Exception:
493 except Exception:
495 log.error(traceback.format_exc())
494 log.error(traceback.format_exc())
496 raise
495 raise
497
496
498 def reset_password_link(self, data, pwd_reset_url):
497 def reset_password_link(self, data, pwd_reset_url):
499 from rhodecode.lib.celerylib import tasks, run_task
498 from rhodecode.lib.celerylib import tasks, run_task
500 from rhodecode.model.notification import EmailNotificationModel
499 from rhodecode.model.notification import EmailNotificationModel
501 user_email = data['email']
500 user_email = data['email']
502 try:
501 try:
503 user = User.get_by_email(user_email)
502 user = User.get_by_email(user_email)
504 if user:
503 if user:
505 log.debug('password reset user found %s', user)
504 log.debug('password reset user found %s', user)
506
505
507 email_kwargs = {
506 email_kwargs = {
508 'password_reset_url': pwd_reset_url,
507 'password_reset_url': pwd_reset_url,
509 'user': user,
508 'user': user,
510 'email': user_email,
509 'email': user_email,
511 'date': datetime.datetime.now()
510 'date': datetime.datetime.now()
512 }
511 }
513
512
514 (subject, headers, email_body,
513 (subject, headers, email_body,
515 email_body_plaintext) = EmailNotificationModel().render_email(
514 email_body_plaintext) = EmailNotificationModel().render_email(
516 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
515 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
517
516
518 recipients = [user_email]
517 recipients = [user_email]
519
518
520 action_logger_generic(
519 action_logger_generic(
521 'sending password reset email to user: {}'.format(
520 'sending password reset email to user: {}'.format(
522 user), namespace='security.password_reset')
521 user), namespace='security.password_reset')
523
522
524 run_task(tasks.send_email, recipients, subject,
523 run_task(tasks.send_email, recipients, subject,
525 email_body_plaintext, email_body)
524 email_body_plaintext, email_body)
526
525
527 else:
526 else:
528 log.debug("password reset email %s not found", user_email)
527 log.debug("password reset email %s not found", user_email)
529 except Exception:
528 except Exception:
530 log.error(traceback.format_exc())
529 log.error(traceback.format_exc())
531 return False
530 return False
532
531
533 return True
532 return True
534
533
535 def reset_password(self, data, pwd_reset_url):
534 def reset_password(self, data, pwd_reset_url):
536 from rhodecode.lib.celerylib import tasks, run_task
535 from rhodecode.lib.celerylib import tasks, run_task
537 from rhodecode.model.notification import EmailNotificationModel
536 from rhodecode.model.notification import EmailNotificationModel
538 from rhodecode.lib import auth
537 from rhodecode.lib import auth
539 user_email = data['email']
538 user_email = data['email']
540 pre_db = True
539 pre_db = True
541 try:
540 try:
542 user = User.get_by_email(user_email)
541 user = User.get_by_email(user_email)
543 new_passwd = auth.PasswordGenerator().gen_password(
542 new_passwd = auth.PasswordGenerator().gen_password(
544 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
543 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
545 if user:
544 if user:
546 user.password = auth.get_crypt_password(new_passwd)
545 user.password = auth.get_crypt_password(new_passwd)
547 # also force this user to reset his password !
546 # also force this user to reset his password !
548 user.update_userdata(force_password_change=True)
547 user.update_userdata(force_password_change=True)
549
548
550 Session().add(user)
549 Session().add(user)
551 Session().commit()
550 Session().commit()
552 log.info('change password for %s', user_email)
551 log.info('change password for %s', user_email)
553 if new_passwd is None:
552 if new_passwd is None:
554 raise Exception('unable to generate new password')
553 raise Exception('unable to generate new password')
555
554
556 pre_db = False
555 pre_db = False
557
556
558 email_kwargs = {
557 email_kwargs = {
559 'new_password': new_passwd,
558 'new_password': new_passwd,
560 'password_reset_url': pwd_reset_url,
559 'password_reset_url': pwd_reset_url,
561 'user': user,
560 'user': user,
562 'email': user_email,
561 'email': user_email,
563 'date': datetime.datetime.now()
562 'date': datetime.datetime.now()
564 }
563 }
565
564
566 (subject, headers, email_body,
565 (subject, headers, email_body,
567 email_body_plaintext) = EmailNotificationModel().render_email(
566 email_body_plaintext) = EmailNotificationModel().render_email(
568 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
567 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
569
568
570 recipients = [user_email]
569 recipients = [user_email]
571
570
572 action_logger_generic(
571 action_logger_generic(
573 'sent new password to user: {} with email: {}'.format(
572 'sent new password to user: {} with email: {}'.format(
574 user, user_email), namespace='security.password_reset')
573 user, user_email), namespace='security.password_reset')
575
574
576 run_task(tasks.send_email, recipients, subject,
575 run_task(tasks.send_email, recipients, subject,
577 email_body_plaintext, email_body)
576 email_body_plaintext, email_body)
578
577
579 except Exception:
578 except Exception:
580 log.error('Failed to update user password')
579 log.error('Failed to update user password')
581 log.error(traceback.format_exc())
580 log.error(traceback.format_exc())
582 if pre_db:
581 if pre_db:
583 # we rollback only if local db stuff fails. If it goes into
582 # we rollback only if local db stuff fails. If it goes into
584 # run_task, we're pass rollback state this wouldn't work then
583 # run_task, we're pass rollback state this wouldn't work then
585 Session().rollback()
584 Session().rollback()
586
585
587 return True
586 return True
588
587
589 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
588 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
590 """
589 """
591 Fetches auth_user by user_id,or api_key if present.
590 Fetches auth_user by user_id,or api_key if present.
592 Fills auth_user attributes with those taken from database.
591 Fills auth_user attributes with those taken from database.
593 Additionally set's is_authenitated if lookup fails
592 Additionally set's is_authenitated if lookup fails
594 present in database
593 present in database
595
594
596 :param auth_user: instance of user to set attributes
595 :param auth_user: instance of user to set attributes
597 :param user_id: user id to fetch by
596 :param user_id: user id to fetch by
598 :param api_key: api key to fetch by
597 :param api_key: api key to fetch by
599 :param username: username to fetch by
598 :param username: username to fetch by
600 """
599 """
601 if user_id is None and api_key is None and username is None:
600 if user_id is None and api_key is None and username is None:
602 raise Exception('You need to pass user_id, api_key or username')
601 raise Exception('You need to pass user_id, api_key or username')
603
602
604 log.debug(
603 log.debug(
605 'doing fill data based on: user_id:%s api_key:%s username:%s',
604 'doing fill data based on: user_id:%s api_key:%s username:%s',
606 user_id, api_key, username)
605 user_id, api_key, username)
607 try:
606 try:
608 dbuser = None
607 dbuser = None
609 if user_id:
608 if user_id:
610 dbuser = self.get(user_id)
609 dbuser = self.get(user_id)
611 elif api_key:
610 elif api_key:
612 dbuser = self.get_by_auth_token(api_key)
611 dbuser = self.get_by_auth_token(api_key)
613 elif username:
612 elif username:
614 dbuser = self.get_by_username(username)
613 dbuser = self.get_by_username(username)
615
614
616 if not dbuser:
615 if not dbuser:
617 log.warning(
616 log.warning(
618 'Unable to lookup user by id:%s api_key:%s username:%s',
617 'Unable to lookup user by id:%s api_key:%s username:%s',
619 user_id, api_key, username)
618 user_id, api_key, username)
620 return False
619 return False
621 if not dbuser.active:
620 if not dbuser.active:
622 log.debug('User `%s` is inactive, skipping fill data', username)
621 log.debug('User `%s` is inactive, skipping fill data', username)
623 return False
622 return False
624
623
625 log.debug('filling user:%s data', dbuser)
624 log.debug('filling user:%s data', dbuser)
626
625
627 # TODO: johbo: Think about this and find a clean solution
626 # TODO: johbo: Think about this and find a clean solution
628 user_data = dbuser.get_dict()
627 user_data = dbuser.get_dict()
629 user_data.update(dbuser.get_api_data(include_secrets=True))
628 user_data.update(dbuser.get_api_data(include_secrets=True))
630
629
631 for k, v in user_data.iteritems():
630 for k, v in user_data.iteritems():
632 # properties of auth user we dont update
631 # properties of auth user we dont update
633 if k not in ['auth_tokens', 'permissions']:
632 if k not in ['auth_tokens', 'permissions']:
634 setattr(auth_user, k, v)
633 setattr(auth_user, k, v)
635
634
636 # few extras
635 # few extras
637 setattr(auth_user, 'feed_token', dbuser.feed_token)
636 setattr(auth_user, 'feed_token', dbuser.feed_token)
638 except Exception:
637 except Exception:
639 log.error(traceback.format_exc())
638 log.error(traceback.format_exc())
640 auth_user.is_authenticated = False
639 auth_user.is_authenticated = False
641 return False
640 return False
642
641
643 return True
642 return True
644
643
645 def has_perm(self, user, perm):
644 def has_perm(self, user, perm):
646 perm = self._get_perm(perm)
645 perm = self._get_perm(perm)
647 user = self._get_user(user)
646 user = self._get_user(user)
648
647
649 return UserToPerm.query().filter(UserToPerm.user == user)\
648 return UserToPerm.query().filter(UserToPerm.user == user)\
650 .filter(UserToPerm.permission == perm).scalar() is not None
649 .filter(UserToPerm.permission == perm).scalar() is not None
651
650
652 def grant_perm(self, user, perm):
651 def grant_perm(self, user, perm):
653 """
652 """
654 Grant user global permissions
653 Grant user global permissions
655
654
656 :param user:
655 :param user:
657 :param perm:
656 :param perm:
658 """
657 """
659 user = self._get_user(user)
658 user = self._get_user(user)
660 perm = self._get_perm(perm)
659 perm = self._get_perm(perm)
661 # if this permission is already granted skip it
660 # if this permission is already granted skip it
662 _perm = UserToPerm.query()\
661 _perm = UserToPerm.query()\
663 .filter(UserToPerm.user == user)\
662 .filter(UserToPerm.user == user)\
664 .filter(UserToPerm.permission == perm)\
663 .filter(UserToPerm.permission == perm)\
665 .scalar()
664 .scalar()
666 if _perm:
665 if _perm:
667 return
666 return
668 new = UserToPerm()
667 new = UserToPerm()
669 new.user = user
668 new.user = user
670 new.permission = perm
669 new.permission = perm
671 self.sa.add(new)
670 self.sa.add(new)
672 return new
671 return new
673
672
674 def revoke_perm(self, user, perm):
673 def revoke_perm(self, user, perm):
675 """
674 """
676 Revoke users global permissions
675 Revoke users global permissions
677
676
678 :param user:
677 :param user:
679 :param perm:
678 :param perm:
680 """
679 """
681 user = self._get_user(user)
680 user = self._get_user(user)
682 perm = self._get_perm(perm)
681 perm = self._get_perm(perm)
683
682
684 obj = UserToPerm.query()\
683 obj = UserToPerm.query()\
685 .filter(UserToPerm.user == user)\
684 .filter(UserToPerm.user == user)\
686 .filter(UserToPerm.permission == perm)\
685 .filter(UserToPerm.permission == perm)\
687 .scalar()
686 .scalar()
688 if obj:
687 if obj:
689 self.sa.delete(obj)
688 self.sa.delete(obj)
690
689
691 def add_extra_email(self, user, email):
690 def add_extra_email(self, user, email):
692 """
691 """
693 Adds email address to UserEmailMap
692 Adds email address to UserEmailMap
694
693
695 :param user:
694 :param user:
696 :param email:
695 :param email:
697 """
696 """
698 from rhodecode.model import forms
697 from rhodecode.model import forms
699 form = forms.UserExtraEmailForm()()
698 form = forms.UserExtraEmailForm()()
700 data = form.to_python({'email': email})
699 data = form.to_python({'email': email})
701 user = self._get_user(user)
700 user = self._get_user(user)
702
701
703 obj = UserEmailMap()
702 obj = UserEmailMap()
704 obj.user = user
703 obj.user = user
705 obj.email = data['email']
704 obj.email = data['email']
706 self.sa.add(obj)
705 self.sa.add(obj)
707 return obj
706 return obj
708
707
709 def delete_extra_email(self, user, email_id):
708 def delete_extra_email(self, user, email_id):
710 """
709 """
711 Removes email address from UserEmailMap
710 Removes email address from UserEmailMap
712
711
713 :param user:
712 :param user:
714 :param email_id:
713 :param email_id:
715 """
714 """
716 user = self._get_user(user)
715 user = self._get_user(user)
717 obj = UserEmailMap.query().get(email_id)
716 obj = UserEmailMap.query().get(email_id)
718 if obj:
717 if obj:
719 self.sa.delete(obj)
718 self.sa.delete(obj)
720
719
721 def parse_ip_range(self, ip_range):
720 def parse_ip_range(self, ip_range):
722 ip_list = []
721 ip_list = []
723 def make_unique(value):
722 def make_unique(value):
724 seen = []
723 seen = []
725 return [c for c in value if not (c in seen or seen.append(c))]
724 return [c for c in value if not (c in seen or seen.append(c))]
726
725
727 # firsts split by commas
726 # firsts split by commas
728 for ip_range in ip_range.split(','):
727 for ip_range in ip_range.split(','):
729 if not ip_range:
728 if not ip_range:
730 continue
729 continue
731 ip_range = ip_range.strip()
730 ip_range = ip_range.strip()
732 if '-' in ip_range:
731 if '-' in ip_range:
733 start_ip, end_ip = ip_range.split('-', 1)
732 start_ip, end_ip = ip_range.split('-', 1)
734 start_ip = ipaddress.ip_address(start_ip.strip())
733 start_ip = ipaddress.ip_address(start_ip.strip())
735 end_ip = ipaddress.ip_address(end_ip.strip())
734 end_ip = ipaddress.ip_address(end_ip.strip())
736 parsed_ip_range = []
735 parsed_ip_range = []
737
736
738 for index in xrange(int(start_ip), int(end_ip) + 1):
737 for index in xrange(int(start_ip), int(end_ip) + 1):
739 new_ip = ipaddress.ip_address(index)
738 new_ip = ipaddress.ip_address(index)
740 parsed_ip_range.append(str(new_ip))
739 parsed_ip_range.append(str(new_ip))
741 ip_list.extend(parsed_ip_range)
740 ip_list.extend(parsed_ip_range)
742 else:
741 else:
743 ip_list.append(ip_range)
742 ip_list.append(ip_range)
744
743
745 return make_unique(ip_list)
744 return make_unique(ip_list)
746
745
747 def add_extra_ip(self, user, ip, description=None):
746 def add_extra_ip(self, user, ip, description=None):
748 """
747 """
749 Adds ip address to UserIpMap
748 Adds ip address to UserIpMap
750
749
751 :param user:
750 :param user:
752 :param ip:
751 :param ip:
753 """
752 """
754 from rhodecode.model import forms
753 from rhodecode.model import forms
755 form = forms.UserExtraIpForm()()
754 form = forms.UserExtraIpForm()()
756 data = form.to_python({'ip': ip})
755 data = form.to_python({'ip': ip})
757 user = self._get_user(user)
756 user = self._get_user(user)
758
757
759 obj = UserIpMap()
758 obj = UserIpMap()
760 obj.user = user
759 obj.user = user
761 obj.ip_addr = data['ip']
760 obj.ip_addr = data['ip']
762 obj.description = description
761 obj.description = description
763 self.sa.add(obj)
762 self.sa.add(obj)
764 return obj
763 return obj
765
764
766 def delete_extra_ip(self, user, ip_id):
765 def delete_extra_ip(self, user, ip_id):
767 """
766 """
768 Removes ip address from UserIpMap
767 Removes ip address from UserIpMap
769
768
770 :param user:
769 :param user:
771 :param ip_id:
770 :param ip_id:
772 """
771 """
773 user = self._get_user(user)
772 user = self._get_user(user)
774 obj = UserIpMap.query().get(ip_id)
773 obj = UserIpMap.query().get(ip_id)
775 if obj:
774 if obj:
776 self.sa.delete(obj)
775 self.sa.delete(obj)
777
776
778 def get_accounts_in_creation_order(self, current_user=None):
777 def get_accounts_in_creation_order(self, current_user=None):
779 """
778 """
780 Get accounts in order of creation for deactivation for license limits
779 Get accounts in order of creation for deactivation for license limits
781
780
782 pick currently logged in user, and append to the list in position 0
781 pick currently logged in user, and append to the list in position 0
783 pick all super-admins in order of creation date and add it to the list
782 pick all super-admins in order of creation date and add it to the list
784 pick all other accounts in order of creation and add it to the list.
783 pick all other accounts in order of creation and add it to the list.
785
784
786 Based on that list, the last accounts can be disabled as they are
785 Based on that list, the last accounts can be disabled as they are
787 created at the end and don't include any of the super admins as well
786 created at the end and don't include any of the super admins as well
788 as the current user.
787 as the current user.
789
788
790 :param current_user: optionally current user running this operation
789 :param current_user: optionally current user running this operation
791 """
790 """
792
791
793 if not current_user:
792 if not current_user:
794 current_user = get_current_rhodecode_user()
793 current_user = get_current_rhodecode_user()
795 active_super_admins = [
794 active_super_admins = [
796 x.user_id for x in User.query()
795 x.user_id for x in User.query()
797 .filter(User.user_id != current_user.user_id)
796 .filter(User.user_id != current_user.user_id)
798 .filter(User.active == true())
797 .filter(User.active == true())
799 .filter(User.admin == true())
798 .filter(User.admin == true())
800 .order_by(User.created_on.asc())]
799 .order_by(User.created_on.asc())]
801
800
802 active_regular_users = [
801 active_regular_users = [
803 x.user_id for x in User.query()
802 x.user_id for x in User.query()
804 .filter(User.user_id != current_user.user_id)
803 .filter(User.user_id != current_user.user_id)
805 .filter(User.active == true())
804 .filter(User.active == true())
806 .filter(User.admin == false())
805 .filter(User.admin == false())
807 .order_by(User.created_on.asc())]
806 .order_by(User.created_on.asc())]
808
807
809 list_of_accounts = [current_user.user_id]
808 list_of_accounts = [current_user.user_id]
810 list_of_accounts += active_super_admins
809 list_of_accounts += active_super_admins
811 list_of_accounts += active_regular_users
810 list_of_accounts += active_regular_users
812
811
813 return list_of_accounts
812 return list_of_accounts
814
813
815 def deactivate_last_users(self, expected_users):
814 def deactivate_last_users(self, expected_users):
816 """
815 """
817 Deactivate accounts that are over the license limits.
816 Deactivate accounts that are over the license limits.
818 Algorithm of which accounts to disabled is based on the formula:
817 Algorithm of which accounts to disabled is based on the formula:
819
818
820 Get current user, then super admins in creation order, then regular
819 Get current user, then super admins in creation order, then regular
821 active users in creation order.
820 active users in creation order.
822
821
823 Using that list we mark all accounts from the end of it as inactive.
822 Using that list we mark all accounts from the end of it as inactive.
824 This way we block only latest created accounts.
823 This way we block only latest created accounts.
825
824
826 :param expected_users: list of users in special order, we deactivate
825 :param expected_users: list of users in special order, we deactivate
827 the end N ammoun of users from that list
826 the end N ammoun of users from that list
828 """
827 """
829
828
830 list_of_accounts = self.get_accounts_in_creation_order()
829 list_of_accounts = self.get_accounts_in_creation_order()
831
830
832 for acc_id in list_of_accounts[expected_users + 1:]:
831 for acc_id in list_of_accounts[expected_users + 1:]:
833 user = User.get(acc_id)
832 user = User.get(acc_id)
834 log.info('Deactivating account %s for license unlock', user)
833 log.info('Deactivating account %s for license unlock', user)
835 user.active = False
834 user.active = False
836 Session().add(user)
835 Session().add(user)
837 Session().commit()
836 Session().commit()
838
837
839 return
838 return
@@ -1,19 +1,15 b''
1 import os
1 import os
2
2
3 import ipaddress
3 import ipaddress
4 import colander
4 import colander
5
5
6 from rhodecode.translation import _
6 from rhodecode.translation import _
7
7
8
8
9 def ip_addr_validator(node, value):
9 def ip_addr_validator(node, value):
10 try:
10 try:
11 # this raises an ValueError if address is not IpV4 or IpV6
11 # this raises an ValueError if address is not IpV4 or IpV6
12 ipaddress.ip_network(value, strict=False)
12 ipaddress.ip_network(value, strict=False)
13 except ValueError:
13 except ValueError:
14 msg = _(u'Please enter a valid IPv4 or IpV6 address')
14 msg = _(u'Please enter a valid IPv4 or IpV6 address')
15 raise colander.Invalid(node, msg)
15 raise colander.Invalid(node, msg)
16
17
18
19
@@ -1,91 +1,106 b''
1 .deform {
1 .deform {
2
2
3 * {
3 * {
4 box-sizing: border-box;
4 box-sizing: border-box;
5 }
5 }
6
6
7 .required:after {
7 .required:after {
8 color: #e32;
8 color: #e32;
9 content: '*';
9 content: '*';
10 display:inline;
10 display:inline;
11 }
11 }
12
12
13 .control-label {
13 .control-label {
14 width: 200px;
14 width: 200px;
15 padding: 10px;
15 float: left;
16 float: left;
16 }
17 }
17 .control-inputs {
18 .control-inputs {
18 width: 400px;
19 width: 400px;
19 float: left;
20 float: left;
20 }
21 }
21 .form-group .radio, .form-group .checkbox {
22 .form-group .radio, .form-group .checkbox {
22 position: relative;
23 position: relative;
23 display: block;
24 display: block;
24 /* margin-bottom: 10px; */
25 /* margin-bottom: 10px; */
25 }
26 }
26
27
27 .form-group {
28 .form-group {
28 clear: left;
29 clear: left;
30 margin-bottom: 20px;
31
32 &:after { /* clear fix */
33 content: " ";
34 display: block;
35 clear: left;
36 }
29 }
37 }
30
38
31 .form-control {
39 .form-control {
32 width: 100%;
40 width: 100%;
33 }
41 }
34
42
35 .error-block {
43 .error-block {
36 color: red;
44 color: red;
45 margin: 0;
46 }
47
48 .help-block {
49 margin: 0;
37 }
50 }
38
51
39 .deform-seq-container .control-inputs {
52 .deform-seq-container .control-inputs {
40 width: 100%;
53 width: 100%;
41 }
54 }
42
55
43 .deform-seq-container .deform-seq-item-handle {
56 .deform-seq-container .deform-seq-item-handle {
44 width: 8.3%;
57 width: 8.3%;
45 float: left;
58 float: left;
46 }
59 }
47
60
48 .deform-seq-container .deform-seq-item-group {
61 .deform-seq-container .deform-seq-item-group {
49 width: 91.6%;
62 width: 91.6%;
50 float: left;
63 float: left;
51 }
64 }
52
65
53 .form-control {
66 .form-control {
54 input {
67 input {
55 height: 40px;
68 height: 40px;
56 }
69 }
57 input[type=checkbox], input[type=radio] {
70 input[type=checkbox], input[type=radio] {
58 height: auto;
71 height: auto;
59 }
72 }
60 select {
73 select {
61 height: 40px;
74 height: 40px;
62 }
75 }
63 }
76 }
64
77
65 .form-control.select2-container { height: 40px; }
78 .form-control.select2-container {
79 height: 40px;
80 }
66
81
67 .deform-two-field-sequence .deform-seq-container .deform-seq-item label {
82 .deform-two-field-sequence .deform-seq-container .deform-seq-item label {
68 display: none;
83 display: none;
69 }
84 }
70 .deform-two-field-sequence .deform-seq-container .deform-seq-item:first-child label {
85 .deform-two-field-sequence .deform-seq-container .deform-seq-item:first-child label {
71 display: block;
86 display: block;
72 }
87 }
73 .deform-two-field-sequence .deform-seq-container .deform-seq-item .panel-heading {
88 .deform-two-field-sequence .deform-seq-container .deform-seq-item .panel-heading {
74 display: none;
89 display: none;
75 }
90 }
76 .deform-two-field-sequence .deform-seq-container .deform-seq-item.form-group {
91 .deform-two-field-sequence .deform-seq-container .deform-seq-item.form-group {
77 background: red;
92 margin: 0;
78 }
93 }
79 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group .form-group {
94 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group .form-group {
80 width: 45%; padding: 0 2px; float: left; clear: none;
95 width: 45%; padding: 0 2px; float: left; clear: none;
81 }
96 }
82 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel {
97 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel {
83 padding: 0;
98 padding: 0;
84 margin: 5px 0;
99 margin: 5px 0;
85 border: none;
100 border: none;
86 }
101 }
87 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel > .panel-body {
102 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel > .panel-body {
88 padding: 0;
103 padding: 0;
89 }
104 }
90
105
91 }
106 }
@@ -1,42 +1,5 b''
1 <div class="panel panel-default">
1 <%namespace name="widgets" file="/widgets.html"/>
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Change Your Account Password')}</h3>
4 </div>
5 ${h.secure_form(url('my_account_password'), method='post')}
6 <div class="panel-body">
7 <div class="fields">
8 <div class="field">
9 <div class="label">
10 <label for="current_password">${_('Current Password')}:</label>
11 </div>
12 <div class="input">
13 ${h.password('current_password',class_='medium',autocomplete="off")}
14 </div>
15 </div>
16
2
17 <div class="field">
3 <%widgets:panel title="${_('Change Your Account Password')}">
18 <div class="label">
4 ${c.form.render() | n}
19 <label for="new_password">${_('New Password')}:</label>
5 </%widgets:panel>
20 </div>
21 <div class="input">
22 ${h.password('new_password',class_='medium', autocomplete="off")}
23 </div>
24 </div>
25
26 <div class="field">
27 <div class="label">
28 <label for="password_confirmation">${_('Confirm New Password')}:</label>
29 </div>
30 <div class="input">
31 ${h.password('new_password_confirmation',class_='medium', autocomplete="off")}
32 </div>
33 </div>
34
35 <div class="buttons">
36 ${h.submit('save',_('Save'),class_="btn")}
37 ${h.reset('reset',_('Reset'),class_="btn")}
38 </div>
39 </div>
40 </div>
41 ${h.end_form()}
42 </div> No newline at end of file
@@ -1,139 +1,138 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html>
2 <!DOCTYPE html>
3
3
4 <%
4 <%
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
6
6
7 if hasattr(c, 'rhodecode_db_repo'):
7 if hasattr(c, 'rhodecode_db_repo'):
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
10
10
11 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
11 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
12 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
12 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
13 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
13 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
14 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
14 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
15
15
16 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
16 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
17 %>
17 %>
18
19 <html xmlns="http://www.w3.org/1999/xhtml">
18 <html xmlns="http://www.w3.org/1999/xhtml">
20 <head>
19 <head>
21 <title>${self.title()}</title>
20 <title>${self.title()}</title>
22 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
21 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
23 <%def name="robots()">
22 <%def name="robots()">
24 <meta name="robots" content="index, nofollow"/>
23 <meta name="robots" content="index, nofollow"/>
25 </%def>
24 </%def>
26 ${self.robots()}
25 ${self.robots()}
27 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
26 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
28
27
29 ## CSS definitions
28 ## CSS definitions
30 <%def name="css()">
29 <%def name="css()">
31 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
30 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
32 <!--[if lt IE 9]>
31 <!--[if lt IE 9]>
33 <link rel="stylesheet" type="text/css" href="${h.asset('css/ie.css', ver=c.rhodecode_version_hash)}" media="screen"/>
32 <link rel="stylesheet" type="text/css" href="${h.asset('css/ie.css', ver=c.rhodecode_version_hash)}" media="screen"/>
34 <![endif]-->
33 <![endif]-->
35 ## EXTRA FOR CSS
34 ## EXTRA FOR CSS
36 ${self.css_extra()}
35 ${self.css_extra()}
37 </%def>
36 </%def>
38 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
37 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
39 <%def name="css_extra()">
38 <%def name="css_extra()">
40 </%def>
39 </%def>
41
40
42 ${self.css()}
41 ${self.css()}
43
42
44 ## JAVASCRIPT
43 ## JAVASCRIPT
45 <%def name="js()">
44 <%def name="js()">
46 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
45 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
47 <script type="text/javascript">
46 <script type="text/javascript">
48 // register templateContext to pass template variables to JS
47 // register templateContext to pass template variables to JS
49 var templateContext = ${h.json.dumps(c.template_context)|n};
48 var templateContext = ${h.json.dumps(c.template_context)|n};
50
49
51 var REPO_NAME = "${getattr(c, 'repo_name', '')}";
50 var REPO_NAME = "${getattr(c, 'repo_name', '')}";
52 %if hasattr(c, 'rhodecode_db_repo'):
51 %if hasattr(c, 'rhodecode_db_repo'):
53 var REPO_LANDING_REV = '${c.rhodecode_db_repo.landing_rev[1]}';
52 var REPO_LANDING_REV = '${c.rhodecode_db_repo.landing_rev[1]}';
54 var REPO_TYPE = '${c.rhodecode_db_repo.repo_type}';
53 var REPO_TYPE = '${c.rhodecode_db_repo.repo_type}';
55 %else:
54 %else:
56 var REPO_LANDING_REV = '';
55 var REPO_LANDING_REV = '';
57 var REPO_TYPE = '';
56 var REPO_TYPE = '';
58 %endif
57 %endif
59 var APPLICATION_URL = "${h.url('home').rstrip('/')}";
58 var APPLICATION_URL = "${h.url('home').rstrip('/')}";
60 var ASSET_URL = "${h.asset('')}";
59 var ASSET_URL = "${h.asset('')}";
61 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
60 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
62 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
61 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
63 % if getattr(c, 'rhodecode_user', None):
62 % if getattr(c, 'rhodecode_user', None):
64 var USER = {name:'${c.rhodecode_user.username}'};
63 var USER = {name:'${c.rhodecode_user.username}'};
65 % else:
64 % else:
66 var USER = {name:null};
65 var USER = {name:null};
67 % endif
66 % endif
68
67
69 var APPENLIGHT = {
68 var APPENLIGHT = {
70 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
69 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
71 key: '${getattr(c, "appenlight_api_public_key", "")}',
70 key: '${getattr(c, "appenlight_api_public_key", "")}',
72 % if getattr(c, 'appenlight_server_url', None):
71 % if getattr(c, 'appenlight_server_url', None):
73 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
72 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
74 % endif
73 % endif
75 requestInfo: {
74 requestInfo: {
76 % if getattr(c, 'rhodecode_user', None):
75 % if getattr(c, 'rhodecode_user', None):
77 ip: '${c.rhodecode_user.ip_addr}',
76 ip: '${c.rhodecode_user.ip_addr}',
78 username: '${c.rhodecode_user.username}'
77 username: '${c.rhodecode_user.username}'
79 % endif
78 % endif
80 }
79 }
81 };
80 };
82 </script>
81 </script>
83 <!--[if lt IE 9]>
82 <!--[if lt IE 9]>
84 <script language="javascript" type="text/javascript" src="${h.asset('js/excanvas.min.js')}"></script>
83 <script language="javascript" type="text/javascript" src="${h.asset('js/excanvas.min.js')}"></script>
85 <![endif]-->
84 <![endif]-->
86 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
85 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
87 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
86 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
88 ## avoide escaping the %N
87 ## avoide escaping the %N
89 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
88 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
90
89
91
90
92 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
91 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
93 ${self.js_extra()}
92 ${self.js_extra()}
94
93
95 <script type="text/javascript">
94 <script type="text/javascript">
96 $(document).ready(function(){
95 $(document).ready(function(){
97 show_more_event();
96 show_more_event();
98 timeagoActivate();
97 timeagoActivate();
99 })
98 })
100 </script>
99 </script>
101
100
102 </%def>
101 </%def>
103
102
104 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
103 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
105 <%def name="js_extra()"></%def>
104 <%def name="js_extra()"></%def>
106 ${self.js()}
105 ${self.js()}
107
106
108 <%def name="head_extra()"></%def>
107 <%def name="head_extra()"></%def>
109 ${self.head_extra()}
108 ${self.head_extra()}
110 <%include file="/base/plugins_base.html"/>
109 <%include file="/base/plugins_base.html"/>
111
110
112 ## extra stuff
111 ## extra stuff
113 %if c.pre_code:
112 %if c.pre_code:
114 ${c.pre_code|n}
113 ${c.pre_code|n}
115 %endif
114 %endif
116 </head>
115 </head>
117 <body id="body">
116 <body id="body">
118 <noscript>
117 <noscript>
119 <div class="noscript-error">
118 <div class="noscript-error">
120 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
119 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
121 </div>
120 </div>
122 </noscript>
121 </noscript>
123 ## IE hacks
122 ## IE hacks
124 <!--[if IE 7]>
123 <!--[if IE 7]>
125 <script>$(document.body).addClass('ie7')</script>
124 <script>$(document.body).addClass('ie7')</script>
126 <![endif]-->
125 <![endif]-->
127 <!--[if IE 8]>
126 <!--[if IE 8]>
128 <script>$(document.body).addClass('ie8')</script>
127 <script>$(document.body).addClass('ie8')</script>
129 <![endif]-->
128 <![endif]-->
130 <!--[if IE 9]>
129 <!--[if IE 9]>
131 <script>$(document.body).addClass('ie9')</script>
130 <script>$(document.body).addClass('ie9')</script>
132 <![endif]-->
131 <![endif]-->
133
132
134 ${next.body()}
133 ${next.body()}
135 %if c.post_code:
134 %if c.post_code:
136 ${c.post_code|n}
135 ${c.post_code|n}
137 %endif
136 %endif
138 </body>
137 </body>
139 </html>
138 </html>
@@ -1,344 +1,397 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib import helpers as h
23 from rhodecode.lib import helpers as h
24 from rhodecode.lib.auth import check_password
24 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
25 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
25 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
26 from rhodecode.tests import (
27 from rhodecode.tests import (
27 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
28 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
28 assert_session_flash)
29 assert_session_flash)
29 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
31
32
32 fixture = Fixture()
33 fixture = Fixture()
33
34
34
35
35 class TestMyAccountController(TestController):
36 class TestMyAccountController(TestController):
36 test_user_1 = 'testme'
37 test_user_1 = 'testme'
38 test_user_1_password = '0jd83nHNS/d23n'
37 destroy_users = set()
39 destroy_users = set()
38
40
39 @classmethod
41 @classmethod
40 def teardown_class(cls):
42 def teardown_class(cls):
41 fixture.destroy_users(cls.destroy_users)
43 fixture.destroy_users(cls.destroy_users)
42
44
43 def test_my_account(self):
45 def test_my_account(self):
44 self.log_user()
46 self.log_user()
45 response = self.app.get(url('my_account'))
47 response = self.app.get(url('my_account'))
46
48
47 response.mustcontain('test_admin')
49 response.mustcontain('test_admin')
48 response.mustcontain('href="/_admin/my_account/edit"')
50 response.mustcontain('href="/_admin/my_account/edit"')
49
51
50 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
52 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
51 response = self.app.get(url('my_account'))
53 response = self.app.get(url('my_account'))
52 assert_response = AssertResponse(response)
54 assert_response = AssertResponse(response)
53 element = assert_response.get_element('.logout #csrf_token')
55 element = assert_response.get_element('.logout #csrf_token')
54 assert element.value == csrf_token
56 assert element.value == csrf_token
55
57
56 def test_my_account_edit(self):
58 def test_my_account_edit(self):
57 self.log_user()
59 self.log_user()
58 response = self.app.get(url('my_account_edit'))
60 response = self.app.get(url('my_account_edit'))
59
61
60 response.mustcontain('value="test_admin')
62 response.mustcontain('value="test_admin')
61
63
62 def test_my_account_my_repos(self):
64 def test_my_account_my_repos(self):
63 self.log_user()
65 self.log_user()
64 response = self.app.get(url('my_account_repos'))
66 response = self.app.get(url('my_account_repos'))
65 repos = Repository.query().filter(
67 repos = Repository.query().filter(
66 Repository.user == User.get_by_username(
68 Repository.user == User.get_by_username(
67 TEST_USER_ADMIN_LOGIN)).all()
69 TEST_USER_ADMIN_LOGIN)).all()
68 for repo in repos:
70 for repo in repos:
69 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
71 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
70
72
71 def test_my_account_my_watched(self):
73 def test_my_account_my_watched(self):
72 self.log_user()
74 self.log_user()
73 response = self.app.get(url('my_account_watched'))
75 response = self.app.get(url('my_account_watched'))
74
76
75 repos = UserFollowing.query().filter(
77 repos = UserFollowing.query().filter(
76 UserFollowing.user == User.get_by_username(
78 UserFollowing.user == User.get_by_username(
77 TEST_USER_ADMIN_LOGIN)).all()
79 TEST_USER_ADMIN_LOGIN)).all()
78 for repo in repos:
80 for repo in repos:
79 response.mustcontain(
81 response.mustcontain(
80 '"name_raw": "%s"' % repo.follows_repository.repo_name)
82 '"name_raw": "%s"' % repo.follows_repository.repo_name)
81
83
82 @pytest.mark.backends("git", "hg")
84 @pytest.mark.backends("git", "hg")
83 def test_my_account_my_pullrequests(self, pr_util):
85 def test_my_account_my_pullrequests(self, pr_util):
84 self.log_user()
86 self.log_user()
85 response = self.app.get(url('my_account_pullrequests'))
87 response = self.app.get(url('my_account_pullrequests'))
86 response.mustcontain('You currently have no open pull requests.')
88 response.mustcontain('You currently have no open pull requests.')
87
89
88 pr = pr_util.create_pull_request(title='TestMyAccountPR')
90 pr = pr_util.create_pull_request(title='TestMyAccountPR')
89 response = self.app.get(url('my_account_pullrequests'))
91 response = self.app.get(url('my_account_pullrequests'))
90 response.mustcontain('There are currently no open pull requests '
92 response.mustcontain('There are currently no open pull requests '
91 'requiring your participation')
93 'requiring your participation')
92
94
93 response.mustcontain('#%s: TestMyAccountPR' % pr.pull_request_id)
95 response.mustcontain('#%s: TestMyAccountPR' % pr.pull_request_id)
94
96
95 def test_my_account_my_emails(self):
97 def test_my_account_my_emails(self):
96 self.log_user()
98 self.log_user()
97 response = self.app.get(url('my_account_emails'))
99 response = self.app.get(url('my_account_emails'))
98 response.mustcontain('No additional emails specified')
100 response.mustcontain('No additional emails specified')
99
101
100 def test_my_account_my_emails_add_existing_email(self):
102 def test_my_account_my_emails_add_existing_email(self):
101 self.log_user()
103 self.log_user()
102 response = self.app.get(url('my_account_emails'))
104 response = self.app.get(url('my_account_emails'))
103 response.mustcontain('No additional emails specified')
105 response.mustcontain('No additional emails specified')
104 response = self.app.post(url('my_account_emails'),
106 response = self.app.post(url('my_account_emails'),
105 {'new_email': TEST_USER_REGULAR_EMAIL,
107 {'new_email': TEST_USER_REGULAR_EMAIL,
106 'csrf_token': self.csrf_token})
108 'csrf_token': self.csrf_token})
107 assert_session_flash(response, 'This e-mail address is already taken')
109 assert_session_flash(response, 'This e-mail address is already taken')
108
110
109 def test_my_account_my_emails_add_mising_email_in_form(self):
111 def test_my_account_my_emails_add_mising_email_in_form(self):
110 self.log_user()
112 self.log_user()
111 response = self.app.get(url('my_account_emails'))
113 response = self.app.get(url('my_account_emails'))
112 response.mustcontain('No additional emails specified')
114 response.mustcontain('No additional emails specified')
113 response = self.app.post(url('my_account_emails'),
115 response = self.app.post(url('my_account_emails'),
114 {'csrf_token': self.csrf_token})
116 {'csrf_token': self.csrf_token})
115 assert_session_flash(response, 'Please enter an email address')
117 assert_session_flash(response, 'Please enter an email address')
116
118
117 def test_my_account_my_emails_add_remove(self):
119 def test_my_account_my_emails_add_remove(self):
118 self.log_user()
120 self.log_user()
119 response = self.app.get(url('my_account_emails'))
121 response = self.app.get(url('my_account_emails'))
120 response.mustcontain('No additional emails specified')
122 response.mustcontain('No additional emails specified')
121
123
122 response = self.app.post(url('my_account_emails'),
124 response = self.app.post(url('my_account_emails'),
123 {'new_email': 'foo@barz.com',
125 {'new_email': 'foo@barz.com',
124 'csrf_token': self.csrf_token})
126 'csrf_token': self.csrf_token})
125
127
126 response = self.app.get(url('my_account_emails'))
128 response = self.app.get(url('my_account_emails'))
127
129
128 from rhodecode.model.db import UserEmailMap
130 from rhodecode.model.db import UserEmailMap
129 email_id = UserEmailMap.query().filter(
131 email_id = UserEmailMap.query().filter(
130 UserEmailMap.user == User.get_by_username(
132 UserEmailMap.user == User.get_by_username(
131 TEST_USER_ADMIN_LOGIN)).filter(
133 TEST_USER_ADMIN_LOGIN)).filter(
132 UserEmailMap.email == 'foo@barz.com').one().email_id
134 UserEmailMap.email == 'foo@barz.com').one().email_id
133
135
134 response.mustcontain('foo@barz.com')
136 response.mustcontain('foo@barz.com')
135 response.mustcontain('<input id="del_email_id" name="del_email_id" '
137 response.mustcontain('<input id="del_email_id" name="del_email_id" '
136 'type="hidden" value="%s" />' % email_id)
138 'type="hidden" value="%s" />' % email_id)
137
139
138 response = self.app.post(
140 response = self.app.post(
139 url('my_account_emails'), {
141 url('my_account_emails'), {
140 'del_email_id': email_id, '_method': 'delete',
142 'del_email_id': email_id, '_method': 'delete',
141 'csrf_token': self.csrf_token})
143 'csrf_token': self.csrf_token})
142 assert_session_flash(response, 'Removed email address from user account')
144 assert_session_flash(response, 'Removed email address from user account')
143 response = self.app.get(url('my_account_emails'))
145 response = self.app.get(url('my_account_emails'))
144 response.mustcontain('No additional emails specified')
146 response.mustcontain('No additional emails specified')
145
147
146 @pytest.mark.parametrize(
148 @pytest.mark.parametrize(
147 "name, attrs", [
149 "name, attrs", [
148 ('firstname', {'firstname': 'new_username'}),
150 ('firstname', {'firstname': 'new_username'}),
149 ('lastname', {'lastname': 'new_username'}),
151 ('lastname', {'lastname': 'new_username'}),
150 ('admin', {'admin': True}),
152 ('admin', {'admin': True}),
151 ('admin', {'admin': False}),
153 ('admin', {'admin': False}),
152 ('extern_type', {'extern_type': 'ldap'}),
154 ('extern_type', {'extern_type': 'ldap'}),
153 ('extern_type', {'extern_type': None}),
155 ('extern_type', {'extern_type': None}),
154 # ('extern_name', {'extern_name': 'test'}),
156 # ('extern_name', {'extern_name': 'test'}),
155 # ('extern_name', {'extern_name': None}),
157 # ('extern_name', {'extern_name': None}),
156 ('active', {'active': False}),
158 ('active', {'active': False}),
157 ('active', {'active': True}),
159 ('active', {'active': True}),
158 ('email', {'email': 'some@email.com'}),
160 ('email', {'email': 'some@email.com'}),
159 ])
161 ])
160 def test_my_account_update(self, name, attrs):
162 def test_my_account_update(self, name, attrs):
161 usr = fixture.create_user(self.test_user_1, password='qweqwe',
163 usr = fixture.create_user(self.test_user_1,
164 password=self.test_user_1_password,
162 email='testme@rhodecode.org',
165 email='testme@rhodecode.org',
163 extern_type='rhodecode',
166 extern_type='rhodecode',
164 extern_name=self.test_user_1,
167 extern_name=self.test_user_1,
165 skip_if_exists=True)
168 skip_if_exists=True)
166 self.destroy_users.add(self.test_user_1)
169 self.destroy_users.add(self.test_user_1)
167
170
168 params = usr.get_api_data() # current user data
171 params = usr.get_api_data() # current user data
169 user_id = usr.user_id
172 user_id = usr.user_id
170 self.log_user(username=self.test_user_1, password='qweqwe')
173 self.log_user(
174 username=self.test_user_1, password=self.test_user_1_password)
171
175
172 params.update({'password_confirmation': ''})
176 params.update({'password_confirmation': ''})
173 params.update({'new_password': ''})
177 params.update({'new_password': ''})
174 params.update({'extern_type': 'rhodecode'})
178 params.update({'extern_type': 'rhodecode'})
175 params.update({'extern_name': self.test_user_1})
179 params.update({'extern_name': self.test_user_1})
176 params.update({'csrf_token': self.csrf_token})
180 params.update({'csrf_token': self.csrf_token})
177
181
178 params.update(attrs)
182 params.update(attrs)
179 # my account page cannot set language param yet, only for admins
183 # my account page cannot set language param yet, only for admins
180 del params['language']
184 del params['language']
181 response = self.app.post(url('my_account'), params)
185 response = self.app.post(url('my_account'), params)
182
186
183 assert_session_flash(
187 assert_session_flash(
184 response, 'Your account was updated successfully')
188 response, 'Your account was updated successfully')
185
189
186 del params['csrf_token']
190 del params['csrf_token']
187
191
188 updated_user = User.get_by_username(self.test_user_1)
192 updated_user = User.get_by_username(self.test_user_1)
189 updated_params = updated_user.get_api_data()
193 updated_params = updated_user.get_api_data()
190 updated_params.update({'password_confirmation': ''})
194 updated_params.update({'password_confirmation': ''})
191 updated_params.update({'new_password': ''})
195 updated_params.update({'new_password': ''})
192
196
193 params['last_login'] = updated_params['last_login']
197 params['last_login'] = updated_params['last_login']
194 # my account page cannot set language param yet, only for admins
198 # my account page cannot set language param yet, only for admins
195 # but we get this info from API anyway
199 # but we get this info from API anyway
196 params['language'] = updated_params['language']
200 params['language'] = updated_params['language']
197
201
198 if name == 'email':
202 if name == 'email':
199 params['emails'] = [attrs['email']]
203 params['emails'] = [attrs['email']]
200 if name == 'extern_type':
204 if name == 'extern_type':
201 # cannot update this via form, expected value is original one
205 # cannot update this via form, expected value is original one
202 params['extern_type'] = "rhodecode"
206 params['extern_type'] = "rhodecode"
203 if name == 'extern_name':
207 if name == 'extern_name':
204 # cannot update this via form, expected value is original one
208 # cannot update this via form, expected value is original one
205 params['extern_name'] = str(user_id)
209 params['extern_name'] = str(user_id)
206 if name == 'active':
210 if name == 'active':
207 # my account cannot deactivate account
211 # my account cannot deactivate account
208 params['active'] = True
212 params['active'] = True
209 if name == 'admin':
213 if name == 'admin':
210 # my account cannot make you an admin !
214 # my account cannot make you an admin !
211 params['admin'] = False
215 params['admin'] = False
212
216
213 assert params == updated_params
217 assert params == updated_params
214
218
215 def test_my_account_update_err_email_exists(self):
219 def test_my_account_update_err_email_exists(self):
216 self.log_user()
220 self.log_user()
217
221
218 new_email = 'test_regular@mail.com' # already exisitn email
222 new_email = 'test_regular@mail.com' # already exisitn email
219 response = self.app.post(url('my_account'),
223 response = self.app.post(url('my_account'),
220 params={
224 params={
221 'username': 'test_admin',
225 'username': 'test_admin',
222 'new_password': 'test12',
226 'new_password': 'test12',
223 'password_confirmation': 'test122',
227 'password_confirmation': 'test122',
224 'firstname': 'NewName',
228 'firstname': 'NewName',
225 'lastname': 'NewLastname',
229 'lastname': 'NewLastname',
226 'email': new_email,
230 'email': new_email,
227 'csrf_token': self.csrf_token,
231 'csrf_token': self.csrf_token,
228 })
232 })
229
233
230 response.mustcontain('This e-mail address is already taken')
234 response.mustcontain('This e-mail address is already taken')
231
235
232 def test_my_account_update_err(self):
236 def test_my_account_update_err(self):
233 self.log_user('test_regular2', 'test12')
237 self.log_user('test_regular2', 'test12')
234
238
235 new_email = 'newmail.pl'
239 new_email = 'newmail.pl'
236 response = self.app.post(url('my_account'),
240 response = self.app.post(url('my_account'),
237 params={
241 params={
238 'username': 'test_admin',
242 'username': 'test_admin',
239 'new_password': 'test12',
243 'new_password': 'test12',
240 'password_confirmation': 'test122',
244 'password_confirmation': 'test122',
241 'firstname': 'NewName',
245 'firstname': 'NewName',
242 'lastname': 'NewLastname',
246 'lastname': 'NewLastname',
243 'email': new_email,
247 'email': new_email,
244 'csrf_token': self.csrf_token,
248 'csrf_token': self.csrf_token,
245 })
249 })
246
250
247 response.mustcontain('An email address must contain a single @')
251 response.mustcontain('An email address must contain a single @')
248 from rhodecode.model import validators
252 from rhodecode.model import validators
249 msg = validators.ValidUsername(
253 msg = validators.ValidUsername(
250 edit=False, old_data={})._messages['username_exists']
254 edit=False, old_data={})._messages['username_exists']
251 msg = h.html_escape(msg % {'username': 'test_admin'})
255 msg = h.html_escape(msg % {'username': 'test_admin'})
252 response.mustcontain(u"%s" % msg)
256 response.mustcontain(u"%s" % msg)
253
257
254 def test_my_account_auth_tokens(self):
258 def test_my_account_auth_tokens(self):
255 usr = self.log_user('test_regular2', 'test12')
259 usr = self.log_user('test_regular2', 'test12')
256 user = User.get(usr['user_id'])
260 user = User.get(usr['user_id'])
257 response = self.app.get(url('my_account_auth_tokens'))
261 response = self.app.get(url('my_account_auth_tokens'))
258 response.mustcontain(user.api_key)
262 response.mustcontain(user.api_key)
259 response.mustcontain('expires: never')
263 response.mustcontain('expires: never')
260
264
261 @pytest.mark.parametrize("desc, lifetime", [
265 @pytest.mark.parametrize("desc, lifetime", [
262 ('forever', -1),
266 ('forever', -1),
263 ('5mins', 60*5),
267 ('5mins', 60*5),
264 ('30days', 60*60*24*30),
268 ('30days', 60*60*24*30),
265 ])
269 ])
266 def test_my_account_add_auth_tokens(self, desc, lifetime):
270 def test_my_account_add_auth_tokens(self, desc, lifetime):
267 usr = self.log_user('test_regular2', 'test12')
271 usr = self.log_user('test_regular2', 'test12')
268 user = User.get(usr['user_id'])
272 user = User.get(usr['user_id'])
269 response = self.app.post(url('my_account_auth_tokens'),
273 response = self.app.post(url('my_account_auth_tokens'),
270 {'description': desc, 'lifetime': lifetime,
274 {'description': desc, 'lifetime': lifetime,
271 'csrf_token': self.csrf_token})
275 'csrf_token': self.csrf_token})
272 assert_session_flash(response, 'Auth token successfully created')
276 assert_session_flash(response, 'Auth token successfully created')
273 try:
277 try:
274 response = response.follow()
278 response = response.follow()
275 user = User.get(usr['user_id'])
279 user = User.get(usr['user_id'])
276 for auth_token in user.auth_tokens:
280 for auth_token in user.auth_tokens:
277 response.mustcontain(auth_token)
281 response.mustcontain(auth_token)
278 finally:
282 finally:
279 for auth_token in UserApiKeys.query().all():
283 for auth_token in UserApiKeys.query().all():
280 Session().delete(auth_token)
284 Session().delete(auth_token)
281 Session().commit()
285 Session().commit()
282
286
283 def test_my_account_remove_auth_token(self):
287 def test_my_account_remove_auth_token(self):
284 # TODO: without this cleanup it fails when run with the whole
288 # TODO: without this cleanup it fails when run with the whole
285 # test suite, so there must be some interference with other tests.
289 # test suite, so there must be some interference with other tests.
286 UserApiKeys.query().delete()
290 UserApiKeys.query().delete()
287
291
288 usr = self.log_user('test_regular2', 'test12')
292 usr = self.log_user('test_regular2', 'test12')
289 User.get(usr['user_id'])
293 User.get(usr['user_id'])
290 response = self.app.post(url('my_account_auth_tokens'),
294 response = self.app.post(url('my_account_auth_tokens'),
291 {'description': 'desc', 'lifetime': -1,
295 {'description': 'desc', 'lifetime': -1,
292 'csrf_token': self.csrf_token})
296 'csrf_token': self.csrf_token})
293 assert_session_flash(response, 'Auth token successfully created')
297 assert_session_flash(response, 'Auth token successfully created')
294 response = response.follow()
298 response = response.follow()
295
299
296 # now delete our key
300 # now delete our key
297 keys = UserApiKeys.query().all()
301 keys = UserApiKeys.query().all()
298 assert 1 == len(keys)
302 assert 1 == len(keys)
299
303
300 response = self.app.post(
304 response = self.app.post(
301 url('my_account_auth_tokens'),
305 url('my_account_auth_tokens'),
302 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
306 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
303 'csrf_token': self.csrf_token})
307 'csrf_token': self.csrf_token})
304 assert_session_flash(response, 'Auth token successfully deleted')
308 assert_session_flash(response, 'Auth token successfully deleted')
305 keys = UserApiKeys.query().all()
309 keys = UserApiKeys.query().all()
306 assert 0 == len(keys)
310 assert 0 == len(keys)
307
311
308 def test_my_account_reset_main_auth_token(self):
312 def test_my_account_reset_main_auth_token(self):
309 usr = self.log_user('test_regular2', 'test12')
313 usr = self.log_user('test_regular2', 'test12')
310 user = User.get(usr['user_id'])
314 user = User.get(usr['user_id'])
311 api_key = user.api_key
315 api_key = user.api_key
312 response = self.app.get(url('my_account_auth_tokens'))
316 response = self.app.get(url('my_account_auth_tokens'))
313 response.mustcontain(api_key)
317 response.mustcontain(api_key)
314 response.mustcontain('expires: never')
318 response.mustcontain('expires: never')
315
319
316 response = self.app.post(
320 response = self.app.post(
317 url('my_account_auth_tokens'),
321 url('my_account_auth_tokens'),
318 {'_method': 'delete', 'del_auth_token_builtin': api_key,
322 {'_method': 'delete', 'del_auth_token_builtin': api_key,
319 'csrf_token': self.csrf_token})
323 'csrf_token': self.csrf_token})
320 assert_session_flash(response, 'Auth token successfully reset')
324 assert_session_flash(response, 'Auth token successfully reset')
321 response = response.follow()
325 response = response.follow()
322 response.mustcontain(no=[api_key])
326 response.mustcontain(no=[api_key])
323
327
324 def test_password_is_updated_in_session_on_password_change(
328 def test_valid_change_password(self, user_util):
325 self, user_util):
329 new_password = 'my_new_valid_password'
330 user = user_util.create_user(password=self.test_user_1_password)
331 session = self.log_user(user.username, self.test_user_1_password)
332 form_data = [
333 ('current_password', self.test_user_1_password),
334 ('__start__', 'new_password:mapping'),
335 ('new_password', new_password),
336 ('new_password-confirm', new_password),
337 ('__end__', 'new_password:mapping'),
338 ('csrf_token', self.csrf_token),
339 ]
340 response = self.app.post(url('my_account_password'), form_data).follow()
341 assert 'Successfully updated password' in response
342
343 # check_password depends on user being in session
344 Session().add(user)
345 try:
346 assert check_password(new_password, user.password)
347 finally:
348 Session().expunge(user)
349
350 @pytest.mark.parametrize('current_pw,new_pw,confirm_pw', [
351 ('', 'abcdef123', 'abcdef123'),
352 ('wrong_pw', 'abcdef123', 'abcdef123'),
353 (test_user_1_password, test_user_1_password, test_user_1_password),
354 (test_user_1_password, '', ''),
355 (test_user_1_password, 'abcdef123', ''),
356 (test_user_1_password, '', 'abcdef123'),
357 (test_user_1_password, 'not_the', 'same_pw'),
358 (test_user_1_password, 'short', 'short'),
359 ])
360 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
361 user_util):
362 user = user_util.create_user(password=self.test_user_1_password)
363 session = self.log_user(user.username, self.test_user_1_password)
364 old_password_hash = session['password']
365 form_data = [
366 ('current_password', current_pw),
367 ('__start__', 'new_password:mapping'),
368 ('new_password', new_pw),
369 ('new_password-confirm', confirm_pw),
370 ('__end__', 'new_password:mapping'),
371 ('csrf_token', self.csrf_token),
372 ]
373 response = self.app.post(url('my_account_password'), form_data)
374 assert 'Error occurred' in response
375
376 def test_password_is_updated_in_session_on_password_change(self, user_util):
326 old_password = 'abcdef123'
377 old_password = 'abcdef123'
327 new_password = 'abcdef124'
378 new_password = 'abcdef124'
328
379
329 user = user_util.create_user(password=old_password)
380 user = user_util.create_user(password=old_password)
330 session = self.log_user(user.username, old_password)
381 session = self.log_user(user.username, old_password)
331 old_password_hash = session['password']
382 old_password_hash = session['password']
332
383
333 form_data = {
384 form_data = [
334 'current_password': old_password,
385 ('current_password', old_password),
335 'new_password': new_password,
386 ('__start__', 'new_password:mapping'),
336 'new_password_confirmation': new_password,
387 ('new_password', new_password),
337 'csrf_token': self.csrf_token
388 ('new_password-confirm', new_password),
338 }
389 ('__end__', 'new_password:mapping'),
390 ('csrf_token', self.csrf_token),
391 ]
339 self.app.post(url('my_account_password'), form_data)
392 self.app.post(url('my_account_password'), form_data)
340
393
341 response = self.app.get(url('home'))
394 response = self.app.get(url('home'))
342 new_password_hash = response.session['rhodecode_user']['password']
395 new_password_hash = response.session['rhodecode_user']['password']
343
396
344 assert old_password_hash != new_password_hash
397 assert old_password_hash != new_password_hash
General Comments 0
You need to be logged in to leave comments. Login now