##// END OF EJS Templates
AuthUser: Drop ip_addr field...
Søren Løvborg -
r5211:4a2a66bf default
parent child Browse files
Show More
@@ -1,272 +1,272 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.admin.my_account
15 kallithea.controllers.admin.my_account
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 my account controller for Kallithea admin
18 my account controller for Kallithea admin
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: August 20, 2013
22 :created_on: August 20, 2013
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import formencode
30 import formencode
31
31
32 from sqlalchemy import func
32 from sqlalchemy import func
33 from formencode import htmlfill
33 from formencode import htmlfill
34 from pylons import request, tmpl_context as c, url
34 from pylons import request, tmpl_context as c, url
35 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from kallithea import EXTERN_TYPE_INTERNAL
38 from kallithea import EXTERN_TYPE_INTERNAL
39 from kallithea.lib import helpers as h
39 from kallithea.lib import helpers as h
40 from kallithea.lib.auth import LoginRequired, NotAnonymous, AuthUser
40 from kallithea.lib.auth import LoginRequired, NotAnonymous, AuthUser
41 from kallithea.lib.base import BaseController, render
41 from kallithea.lib.base import BaseController, render
42 from kallithea.lib.utils2 import generate_api_key, safe_int
42 from kallithea.lib.utils2 import generate_api_key, safe_int
43 from kallithea.lib.compat import json
43 from kallithea.lib.compat import json
44 from kallithea.model.db import Repository, \
44 from kallithea.model.db import Repository, \
45 UserEmailMap, UserApiKeys, User, UserFollowing
45 UserEmailMap, UserApiKeys, User, UserFollowing
46 from kallithea.model.forms import UserForm, PasswordChangeForm
46 from kallithea.model.forms import UserForm, PasswordChangeForm
47 from kallithea.model.user import UserModel
47 from kallithea.model.user import UserModel
48 from kallithea.model.repo import RepoModel
48 from kallithea.model.repo import RepoModel
49 from kallithea.model.api_key import ApiKeyModel
49 from kallithea.model.api_key import ApiKeyModel
50 from kallithea.model.meta import Session
50 from kallithea.model.meta import Session
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class MyAccountController(BaseController):
55 class MyAccountController(BaseController):
56 """REST Controller styled on the Atom Publishing Protocol"""
56 """REST Controller styled on the Atom Publishing Protocol"""
57 # To properly map this controller, ensure your config/routing.py
57 # To properly map this controller, ensure your config/routing.py
58 # file has a resource setup:
58 # file has a resource setup:
59 # map.resource('setting', 'settings', controller='admin/settings',
59 # map.resource('setting', 'settings', controller='admin/settings',
60 # path_prefix='/admin', name_prefix='admin_')
60 # path_prefix='/admin', name_prefix='admin_')
61
61
62 @LoginRequired()
62 @LoginRequired()
63 @NotAnonymous()
63 @NotAnonymous()
64 def __before__(self):
64 def __before__(self):
65 super(MyAccountController, self).__before__()
65 super(MyAccountController, self).__before__()
66
66
67 def __load_data(self):
67 def __load_data(self):
68 c.user = User.get(self.authuser.user_id)
68 c.user = User.get(self.authuser.user_id)
69 if c.user.username == User.DEFAULT_USER:
69 if c.user.username == User.DEFAULT_USER:
70 h.flash(_("You can't edit this user since it's"
70 h.flash(_("You can't edit this user since it's"
71 " crucial for entire application"), category='warning')
71 " crucial for entire application"), category='warning')
72 return redirect(url('users'))
72 return redirect(url('users'))
73 c.EXTERN_TYPE_INTERNAL = EXTERN_TYPE_INTERNAL
73 c.EXTERN_TYPE_INTERNAL = EXTERN_TYPE_INTERNAL
74
74
75 def _load_my_repos_data(self, watched=False):
75 def _load_my_repos_data(self, watched=False):
76 if watched:
76 if watched:
77 admin = False
77 admin = False
78 repos_list = [x.follows_repository for x in
78 repos_list = [x.follows_repository for x in
79 Session().query(UserFollowing).filter(
79 Session().query(UserFollowing).filter(
80 UserFollowing.user_id ==
80 UserFollowing.user_id ==
81 self.authuser.user_id).all()]
81 self.authuser.user_id).all()]
82 else:
82 else:
83 admin = True
83 admin = True
84 repos_list = Session().query(Repository)\
84 repos_list = Session().query(Repository)\
85 .filter(Repository.user_id ==
85 .filter(Repository.user_id ==
86 self.authuser.user_id)\
86 self.authuser.user_id)\
87 .order_by(func.lower(Repository.repo_name)).all()
87 .order_by(func.lower(Repository.repo_name)).all()
88
88
89 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
89 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
90 admin=admin)
90 admin=admin)
91 #json used to render the grid
91 #json used to render the grid
92 return json.dumps(repos_data)
92 return json.dumps(repos_data)
93
93
94 def my_account(self):
94 def my_account(self):
95 """
95 """
96 GET /_admin/my_account Displays info about my account
96 GET /_admin/my_account Displays info about my account
97 """
97 """
98 # url('my_account')
98 # url('my_account')
99 c.active = 'profile'
99 c.active = 'profile'
100 self.__load_data()
100 self.__load_data()
101 c.perm_user = AuthUser(user_id=self.authuser.user_id,
101 c.perm_user = AuthUser(user_id=self.authuser.user_id)
102 ip_addr=self.ip_addr)
102 c.ip_addr = self.ip_addr
103 c.extern_type = c.user.extern_type
103 c.extern_type = c.user.extern_type
104 c.extern_name = c.user.extern_name
104 c.extern_name = c.user.extern_name
105
105
106 defaults = c.user.get_dict()
106 defaults = c.user.get_dict()
107 update = False
107 update = False
108 if request.POST:
108 if request.POST:
109 _form = UserForm(edit=True,
109 _form = UserForm(edit=True,
110 old_data={'user_id': self.authuser.user_id,
110 old_data={'user_id': self.authuser.user_id,
111 'email': self.authuser.email})()
111 'email': self.authuser.email})()
112 form_result = {}
112 form_result = {}
113 try:
113 try:
114 post_data = dict(request.POST)
114 post_data = dict(request.POST)
115 post_data['new_password'] = ''
115 post_data['new_password'] = ''
116 post_data['password_confirmation'] = ''
116 post_data['password_confirmation'] = ''
117 form_result = _form.to_python(post_data)
117 form_result = _form.to_python(post_data)
118 # skip updating those attrs for my account
118 # skip updating those attrs for my account
119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
120 'new_password', 'password_confirmation']
120 'new_password', 'password_confirmation']
121 #TODO: plugin should define if username can be updated
121 #TODO: plugin should define if username can be updated
122 if c.extern_type != EXTERN_TYPE_INTERNAL:
122 if c.extern_type != EXTERN_TYPE_INTERNAL:
123 # forbid updating username for external accounts
123 # forbid updating username for external accounts
124 skip_attrs.append('username')
124 skip_attrs.append('username')
125
125
126 UserModel().update(self.authuser.user_id, form_result,
126 UserModel().update(self.authuser.user_id, form_result,
127 skip_attrs=skip_attrs)
127 skip_attrs=skip_attrs)
128 h.flash(_('Your account was updated successfully'),
128 h.flash(_('Your account was updated successfully'),
129 category='success')
129 category='success')
130 Session().commit()
130 Session().commit()
131 update = True
131 update = True
132
132
133 except formencode.Invalid, errors:
133 except formencode.Invalid, errors:
134 return htmlfill.render(
134 return htmlfill.render(
135 render('admin/my_account/my_account.html'),
135 render('admin/my_account/my_account.html'),
136 defaults=errors.value,
136 defaults=errors.value,
137 errors=errors.error_dict or {},
137 errors=errors.error_dict or {},
138 prefix_error=False,
138 prefix_error=False,
139 encoding="UTF-8",
139 encoding="UTF-8",
140 force_defaults=False)
140 force_defaults=False)
141 except Exception:
141 except Exception:
142 log.error(traceback.format_exc())
142 log.error(traceback.format_exc())
143 h.flash(_('Error occurred during update of user %s') \
143 h.flash(_('Error occurred during update of user %s') \
144 % form_result.get('username'), category='error')
144 % form_result.get('username'), category='error')
145 if update:
145 if update:
146 return redirect('my_account')
146 return redirect('my_account')
147 return htmlfill.render(
147 return htmlfill.render(
148 render('admin/my_account/my_account.html'),
148 render('admin/my_account/my_account.html'),
149 defaults=defaults,
149 defaults=defaults,
150 encoding="UTF-8",
150 encoding="UTF-8",
151 force_defaults=False)
151 force_defaults=False)
152
152
153 def my_account_password(self):
153 def my_account_password(self):
154 c.active = 'password'
154 c.active = 'password'
155 self.__load_data()
155 self.__load_data()
156 if request.POST:
156 if request.POST:
157 _form = PasswordChangeForm(self.authuser.username)()
157 _form = PasswordChangeForm(self.authuser.username)()
158 try:
158 try:
159 form_result = _form.to_python(request.POST)
159 form_result = _form.to_python(request.POST)
160 UserModel().update(self.authuser.user_id, form_result)
160 UserModel().update(self.authuser.user_id, form_result)
161 Session().commit()
161 Session().commit()
162 h.flash(_("Successfully updated password"), category='success')
162 h.flash(_("Successfully updated password"), category='success')
163 except formencode.Invalid as errors:
163 except formencode.Invalid as errors:
164 return htmlfill.render(
164 return htmlfill.render(
165 render('admin/my_account/my_account.html'),
165 render('admin/my_account/my_account.html'),
166 defaults=errors.value,
166 defaults=errors.value,
167 errors=errors.error_dict or {},
167 errors=errors.error_dict or {},
168 prefix_error=False,
168 prefix_error=False,
169 encoding="UTF-8",
169 encoding="UTF-8",
170 force_defaults=False)
170 force_defaults=False)
171 except Exception:
171 except Exception:
172 log.error(traceback.format_exc())
172 log.error(traceback.format_exc())
173 h.flash(_('Error occurred during update of user password'),
173 h.flash(_('Error occurred during update of user password'),
174 category='error')
174 category='error')
175 return render('admin/my_account/my_account.html')
175 return render('admin/my_account/my_account.html')
176
176
177 def my_account_repos(self):
177 def my_account_repos(self):
178 c.active = 'repos'
178 c.active = 'repos'
179 self.__load_data()
179 self.__load_data()
180
180
181 #json used to render the grid
181 #json used to render the grid
182 c.data = self._load_my_repos_data()
182 c.data = self._load_my_repos_data()
183 return render('admin/my_account/my_account.html')
183 return render('admin/my_account/my_account.html')
184
184
185 def my_account_watched(self):
185 def my_account_watched(self):
186 c.active = 'watched'
186 c.active = 'watched'
187 self.__load_data()
187 self.__load_data()
188
188
189 #json used to render the grid
189 #json used to render the grid
190 c.data = self._load_my_repos_data(watched=True)
190 c.data = self._load_my_repos_data(watched=True)
191 return render('admin/my_account/my_account.html')
191 return render('admin/my_account/my_account.html')
192
192
193 def my_account_perms(self):
193 def my_account_perms(self):
194 c.active = 'perms'
194 c.active = 'perms'
195 self.__load_data()
195 self.__load_data()
196 c.perm_user = AuthUser(user_id=self.authuser.user_id,
196 c.perm_user = AuthUser(user_id=self.authuser.user_id)
197 ip_addr=self.ip_addr)
197 c.ip_addr = self.ip_addr
198
198
199 return render('admin/my_account/my_account.html')
199 return render('admin/my_account/my_account.html')
200
200
201 def my_account_emails(self):
201 def my_account_emails(self):
202 c.active = 'emails'
202 c.active = 'emails'
203 self.__load_data()
203 self.__load_data()
204
204
205 c.user_email_map = UserEmailMap.query()\
205 c.user_email_map = UserEmailMap.query()\
206 .filter(UserEmailMap.user == c.user).all()
206 .filter(UserEmailMap.user == c.user).all()
207 return render('admin/my_account/my_account.html')
207 return render('admin/my_account/my_account.html')
208
208
209 def my_account_emails_add(self):
209 def my_account_emails_add(self):
210 email = request.POST.get('new_email')
210 email = request.POST.get('new_email')
211
211
212 try:
212 try:
213 UserModel().add_extra_email(self.authuser.user_id, email)
213 UserModel().add_extra_email(self.authuser.user_id, email)
214 Session().commit()
214 Session().commit()
215 h.flash(_("Added email %s to user") % email, category='success')
215 h.flash(_("Added email %s to user") % email, category='success')
216 except formencode.Invalid, error:
216 except formencode.Invalid, error:
217 msg = error.error_dict['email']
217 msg = error.error_dict['email']
218 h.flash(msg, category='error')
218 h.flash(msg, category='error')
219 except Exception:
219 except Exception:
220 log.error(traceback.format_exc())
220 log.error(traceback.format_exc())
221 h.flash(_('An error occurred during email saving'),
221 h.flash(_('An error occurred during email saving'),
222 category='error')
222 category='error')
223 return redirect(url('my_account_emails'))
223 return redirect(url('my_account_emails'))
224
224
225 def my_account_emails_delete(self):
225 def my_account_emails_delete(self):
226 email_id = request.POST.get('del_email_id')
226 email_id = request.POST.get('del_email_id')
227 user_model = UserModel()
227 user_model = UserModel()
228 user_model.delete_extra_email(self.authuser.user_id, email_id)
228 user_model.delete_extra_email(self.authuser.user_id, email_id)
229 Session().commit()
229 Session().commit()
230 h.flash(_("Removed email from user"), category='success')
230 h.flash(_("Removed email from user"), category='success')
231 return redirect(url('my_account_emails'))
231 return redirect(url('my_account_emails'))
232
232
233 def my_account_api_keys(self):
233 def my_account_api_keys(self):
234 c.active = 'api_keys'
234 c.active = 'api_keys'
235 self.__load_data()
235 self.__load_data()
236 show_expired = True
236 show_expired = True
237 c.lifetime_values = [
237 c.lifetime_values = [
238 (str(-1), _('Forever')),
238 (str(-1), _('Forever')),
239 (str(5), _('5 minutes')),
239 (str(5), _('5 minutes')),
240 (str(60), _('1 hour')),
240 (str(60), _('1 hour')),
241 (str(60 * 24), _('1 day')),
241 (str(60 * 24), _('1 day')),
242 (str(60 * 24 * 30), _('1 month')),
242 (str(60 * 24 * 30), _('1 month')),
243 ]
243 ]
244 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
244 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
245 c.user_api_keys = ApiKeyModel().get_api_keys(self.authuser.user_id,
245 c.user_api_keys = ApiKeyModel().get_api_keys(self.authuser.user_id,
246 show_expired=show_expired)
246 show_expired=show_expired)
247 return render('admin/my_account/my_account.html')
247 return render('admin/my_account/my_account.html')
248
248
249 def my_account_api_keys_add(self):
249 def my_account_api_keys_add(self):
250 lifetime = safe_int(request.POST.get('lifetime'), -1)
250 lifetime = safe_int(request.POST.get('lifetime'), -1)
251 description = request.POST.get('description')
251 description = request.POST.get('description')
252 ApiKeyModel().create(self.authuser.user_id, description, lifetime)
252 ApiKeyModel().create(self.authuser.user_id, description, lifetime)
253 Session().commit()
253 Session().commit()
254 h.flash(_("API key successfully created"), category='success')
254 h.flash(_("API key successfully created"), category='success')
255 return redirect(url('my_account_api_keys'))
255 return redirect(url('my_account_api_keys'))
256
256
257 def my_account_api_keys_delete(self):
257 def my_account_api_keys_delete(self):
258 api_key = request.POST.get('del_api_key')
258 api_key = request.POST.get('del_api_key')
259 user_id = self.authuser.user_id
259 user_id = self.authuser.user_id
260 if request.POST.get('del_api_key_builtin'):
260 if request.POST.get('del_api_key_builtin'):
261 user = User.get(user_id)
261 user = User.get(user_id)
262 if user:
262 if user:
263 user.api_key = generate_api_key(user.username)
263 user.api_key = generate_api_key(user.username)
264 Session().add(user)
264 Session().add(user)
265 Session().commit()
265 Session().commit()
266 h.flash(_("API key successfully reset"), category='success')
266 h.flash(_("API key successfully reset"), category='success')
267 elif api_key:
267 elif api_key:
268 ApiKeyModel().delete(api_key, self.authuser.user_id)
268 ApiKeyModel().delete(api_key, self.authuser.user_id)
269 Session().commit()
269 Session().commit()
270 h.flash(_("API key successfully deleted"), category='success')
270 h.flash(_("API key successfully deleted"), category='success')
271
271
272 return redirect(url('my_account_api_keys'))
272 return redirect(url('my_account_api_keys'))
@@ -1,485 +1,489 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.admin.users
15 kallithea.controllers.admin.users
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Users crud controller for pylons
18 Users crud controller for pylons
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 4, 2010
22 :created_on: Apr 4, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import formencode
30 import formencode
31
31
32 from formencode import htmlfill
32 from formencode import htmlfill
33 from pylons import request, tmpl_context as c, url, config
33 from pylons import request, tmpl_context as c, url, config
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36 from sqlalchemy.sql.expression import func
36 from sqlalchemy.sql.expression import func
37 from webob.exc import HTTPNotFound
37 from webob.exc import HTTPNotFound
38
38
39 import kallithea
39 import kallithea
40 from kallithea.lib.exceptions import DefaultUserException, \
40 from kallithea.lib.exceptions import DefaultUserException, \
41 UserOwnsReposException, UserCreationError
41 UserOwnsReposException, UserCreationError
42 from kallithea.lib import helpers as h
42 from kallithea.lib import helpers as h
43 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
43 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
44 AuthUser
44 AuthUser
45 import kallithea.lib.auth_modules.auth_internal
45 import kallithea.lib.auth_modules.auth_internal
46 from kallithea.lib import auth_modules
46 from kallithea.lib import auth_modules
47 from kallithea.lib.base import BaseController, render
47 from kallithea.lib.base import BaseController, render
48 from kallithea.model.api_key import ApiKeyModel
48 from kallithea.model.api_key import ApiKeyModel
49
49
50 from kallithea.model.db import User, UserEmailMap, UserIpMap, UserToPerm
50 from kallithea.model.db import User, UserEmailMap, UserIpMap, UserToPerm
51 from kallithea.model.forms import UserForm, CustomDefaultPermissionsForm
51 from kallithea.model.forms import UserForm, CustomDefaultPermissionsForm
52 from kallithea.model.user import UserModel
52 from kallithea.model.user import UserModel
53 from kallithea.model.meta import Session
53 from kallithea.model.meta import Session
54 from kallithea.lib.utils import action_logger
54 from kallithea.lib.utils import action_logger
55 from kallithea.lib.compat import json
55 from kallithea.lib.compat import json
56 from kallithea.lib.utils2 import datetime_to_time, safe_int, generate_api_key
56 from kallithea.lib.utils2 import datetime_to_time, safe_int, generate_api_key
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60
60
61 class UsersController(BaseController):
61 class UsersController(BaseController):
62 """REST Controller styled on the Atom Publishing Protocol"""
62 """REST Controller styled on the Atom Publishing Protocol"""
63
63
64 @LoginRequired()
64 @LoginRequired()
65 @HasPermissionAllDecorator('hg.admin')
65 @HasPermissionAllDecorator('hg.admin')
66 def __before__(self):
66 def __before__(self):
67 super(UsersController, self).__before__()
67 super(UsersController, self).__before__()
68 c.available_permissions = config['available_permissions']
68 c.available_permissions = config['available_permissions']
69 c.EXTERN_TYPE_INTERNAL = kallithea.EXTERN_TYPE_INTERNAL
69 c.EXTERN_TYPE_INTERNAL = kallithea.EXTERN_TYPE_INTERNAL
70
70
71 def index(self, format='html'):
71 def index(self, format='html'):
72 """GET /users: All items in the collection"""
72 """GET /users: All items in the collection"""
73 # url('users')
73 # url('users')
74
74
75 c.users_list = User.query().order_by(User.username)\
75 c.users_list = User.query().order_by(User.username)\
76 .filter(User.username != User.DEFAULT_USER)\
76 .filter(User.username != User.DEFAULT_USER)\
77 .order_by(func.lower(User.username))\
77 .order_by(func.lower(User.username))\
78 .all()
78 .all()
79
79
80 users_data = []
80 users_data = []
81 total_records = len(c.users_list)
81 total_records = len(c.users_list)
82 _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
82 _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
83 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
83 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
84
84
85 grav_tmpl = '<div class="gravatar">%s</div>'
85 grav_tmpl = '<div class="gravatar">%s</div>'
86
86
87 username = lambda user_id, username: (
87 username = lambda user_id, username: (
88 template.get_def("user_name")
88 template.get_def("user_name")
89 .render(user_id, username, _=_, h=h, c=c))
89 .render(user_id, username, _=_, h=h, c=c))
90
90
91 user_actions = lambda user_id, username: (
91 user_actions = lambda user_id, username: (
92 template.get_def("user_actions")
92 template.get_def("user_actions")
93 .render(user_id, username, _=_, h=h, c=c))
93 .render(user_id, username, _=_, h=h, c=c))
94
94
95 for user in c.users_list:
95 for user in c.users_list:
96 users_data.append({
96 users_data.append({
97 "gravatar": grav_tmpl % h.gravatar(user.email, size=20),
97 "gravatar": grav_tmpl % h.gravatar(user.email, size=20),
98 "raw_name": user.username,
98 "raw_name": user.username,
99 "username": username(user.user_id, user.username),
99 "username": username(user.user_id, user.username),
100 "firstname": h.escape(user.name),
100 "firstname": h.escape(user.name),
101 "lastname": h.escape(user.lastname),
101 "lastname": h.escape(user.lastname),
102 "last_login": h.fmt_date(user.last_login),
102 "last_login": h.fmt_date(user.last_login),
103 "last_login_raw": datetime_to_time(user.last_login),
103 "last_login_raw": datetime_to_time(user.last_login),
104 "active": h.boolicon(user.active),
104 "active": h.boolicon(user.active),
105 "admin": h.boolicon(user.admin),
105 "admin": h.boolicon(user.admin),
106 "extern_type": user.extern_type,
106 "extern_type": user.extern_type,
107 "extern_name": user.extern_name,
107 "extern_name": user.extern_name,
108 "action": user_actions(user.user_id, user.username),
108 "action": user_actions(user.user_id, user.username),
109 })
109 })
110
110
111 c.data = json.dumps({
111 c.data = json.dumps({
112 "totalRecords": total_records,
112 "totalRecords": total_records,
113 "startIndex": 0,
113 "startIndex": 0,
114 "sort": None,
114 "sort": None,
115 "dir": "asc",
115 "dir": "asc",
116 "records": users_data
116 "records": users_data
117 })
117 })
118
118
119 return render('admin/users/users.html')
119 return render('admin/users/users.html')
120
120
121 def create(self):
121 def create(self):
122 """POST /users: Create a new item"""
122 """POST /users: Create a new item"""
123 # url('users')
123 # url('users')
124 c.default_extern_type = auth_modules.auth_internal.KallitheaAuthPlugin.name
124 c.default_extern_type = auth_modules.auth_internal.KallitheaAuthPlugin.name
125 user_model = UserModel()
125 user_model = UserModel()
126 user_form = UserForm()()
126 user_form = UserForm()()
127 try:
127 try:
128 form_result = user_form.to_python(dict(request.POST))
128 form_result = user_form.to_python(dict(request.POST))
129 user = user_model.create(form_result)
129 user = user_model.create(form_result)
130 usr = form_result['username']
130 usr = form_result['username']
131 action_logger(self.authuser, 'admin_created_user:%s' % usr,
131 action_logger(self.authuser, 'admin_created_user:%s' % usr,
132 None, self.ip_addr, self.sa)
132 None, self.ip_addr, self.sa)
133 h.flash(h.literal(_('Created user %s') % h.link_to(h.escape(usr), url('edit_user', id=user.user_id))),
133 h.flash(h.literal(_('Created user %s') % h.link_to(h.escape(usr), url('edit_user', id=user.user_id))),
134 category='success')
134 category='success')
135 Session().commit()
135 Session().commit()
136 except formencode.Invalid, errors:
136 except formencode.Invalid, errors:
137 return htmlfill.render(
137 return htmlfill.render(
138 render('admin/users/user_add.html'),
138 render('admin/users/user_add.html'),
139 defaults=errors.value,
139 defaults=errors.value,
140 errors=errors.error_dict or {},
140 errors=errors.error_dict or {},
141 prefix_error=False,
141 prefix_error=False,
142 encoding="UTF-8",
142 encoding="UTF-8",
143 force_defaults=False)
143 force_defaults=False)
144 except UserCreationError, e:
144 except UserCreationError, e:
145 h.flash(e, 'error')
145 h.flash(e, 'error')
146 except Exception:
146 except Exception:
147 log.error(traceback.format_exc())
147 log.error(traceback.format_exc())
148 h.flash(_('Error occurred during creation of user %s') \
148 h.flash(_('Error occurred during creation of user %s') \
149 % request.POST.get('username'), category='error')
149 % request.POST.get('username'), category='error')
150 return redirect(url('users'))
150 return redirect(url('users'))
151
151
152 def new(self, format='html'):
152 def new(self, format='html'):
153 """GET /users/new: Form to create a new item"""
153 """GET /users/new: Form to create a new item"""
154 # url('new_user')
154 # url('new_user')
155 c.default_extern_type = auth_modules.auth_internal.KallitheaAuthPlugin.name
155 c.default_extern_type = auth_modules.auth_internal.KallitheaAuthPlugin.name
156 return render('admin/users/user_add.html')
156 return render('admin/users/user_add.html')
157
157
158 def update(self, id):
158 def update(self, id):
159 """PUT /users/id: Update an existing item"""
159 """PUT /users/id: Update an existing item"""
160 # Forms posted to this method should contain a hidden field:
160 # Forms posted to this method should contain a hidden field:
161 # <input type="hidden" name="_method" value="PUT" />
161 # <input type="hidden" name="_method" value="PUT" />
162 # Or using helpers:
162 # Or using helpers:
163 # h.form(url('update_user', id=ID),
163 # h.form(url('update_user', id=ID),
164 # method='put')
164 # method='put')
165 # url('user', id=ID)
165 # url('user', id=ID)
166 c.active = 'profile'
166 c.active = 'profile'
167 user_model = UserModel()
167 user_model = UserModel()
168 c.user = user_model.get(id)
168 c.user = user_model.get(id)
169 c.extern_type = c.user.extern_type
169 c.extern_type = c.user.extern_type
170 c.extern_name = c.user.extern_name
170 c.extern_name = c.user.extern_name
171 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
171 c.perm_user = AuthUser(user_id=id)
172 c.ip_addr = self.ip_addr
172 _form = UserForm(edit=True, old_data={'user_id': id,
173 _form = UserForm(edit=True, old_data={'user_id': id,
173 'email': c.user.email})()
174 'email': c.user.email})()
174 form_result = {}
175 form_result = {}
175 try:
176 try:
176 form_result = _form.to_python(dict(request.POST))
177 form_result = _form.to_python(dict(request.POST))
177 skip_attrs = ['extern_type', 'extern_name']
178 skip_attrs = ['extern_type', 'extern_name']
178 #TODO: plugin should define if username can be updated
179 #TODO: plugin should define if username can be updated
179 if c.extern_type != kallithea.EXTERN_TYPE_INTERNAL:
180 if c.extern_type != kallithea.EXTERN_TYPE_INTERNAL:
180 # forbid updating username for external accounts
181 # forbid updating username for external accounts
181 skip_attrs.append('username')
182 skip_attrs.append('username')
182
183
183 user_model.update(id, form_result, skip_attrs=skip_attrs)
184 user_model.update(id, form_result, skip_attrs=skip_attrs)
184 usr = form_result['username']
185 usr = form_result['username']
185 action_logger(self.authuser, 'admin_updated_user:%s' % usr,
186 action_logger(self.authuser, 'admin_updated_user:%s' % usr,
186 None, self.ip_addr, self.sa)
187 None, self.ip_addr, self.sa)
187 h.flash(_('User updated successfully'), category='success')
188 h.flash(_('User updated successfully'), category='success')
188 Session().commit()
189 Session().commit()
189 except formencode.Invalid, errors:
190 except formencode.Invalid, errors:
190 defaults = errors.value
191 defaults = errors.value
191 e = errors.error_dict or {}
192 e = errors.error_dict or {}
192 defaults.update({
193 defaults.update({
193 'create_repo_perm': user_model.has_perm(id,
194 'create_repo_perm': user_model.has_perm(id,
194 'hg.create.repository'),
195 'hg.create.repository'),
195 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
196 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
196 '_method': 'put'
197 '_method': 'put'
197 })
198 })
198 return htmlfill.render(
199 return htmlfill.render(
199 render('admin/users/user_edit.html'),
200 render('admin/users/user_edit.html'),
200 defaults=defaults,
201 defaults=defaults,
201 errors=e,
202 errors=e,
202 prefix_error=False,
203 prefix_error=False,
203 encoding="UTF-8",
204 encoding="UTF-8",
204 force_defaults=False)
205 force_defaults=False)
205 except Exception:
206 except Exception:
206 log.error(traceback.format_exc())
207 log.error(traceback.format_exc())
207 h.flash(_('Error occurred during update of user %s') \
208 h.flash(_('Error occurred during update of user %s') \
208 % form_result.get('username'), category='error')
209 % form_result.get('username'), category='error')
209 return redirect(url('edit_user', id=id))
210 return redirect(url('edit_user', id=id))
210
211
211 def delete(self, id):
212 def delete(self, id):
212 """DELETE /users/id: Delete an existing item"""
213 """DELETE /users/id: Delete an existing item"""
213 # Forms posted to this method should contain a hidden field:
214 # Forms posted to this method should contain a hidden field:
214 # <input type="hidden" name="_method" value="DELETE" />
215 # <input type="hidden" name="_method" value="DELETE" />
215 # Or using helpers:
216 # Or using helpers:
216 # h.form(url('delete_user', id=ID),
217 # h.form(url('delete_user', id=ID),
217 # method='delete')
218 # method='delete')
218 # url('user', id=ID)
219 # url('user', id=ID)
219 usr = User.get_or_404(id)
220 usr = User.get_or_404(id)
220 try:
221 try:
221 UserModel().delete(usr)
222 UserModel().delete(usr)
222 Session().commit()
223 Session().commit()
223 h.flash(_('Successfully deleted user'), category='success')
224 h.flash(_('Successfully deleted user'), category='success')
224 except (UserOwnsReposException, DefaultUserException), e:
225 except (UserOwnsReposException, DefaultUserException), e:
225 h.flash(e, category='warning')
226 h.flash(e, category='warning')
226 except Exception:
227 except Exception:
227 log.error(traceback.format_exc())
228 log.error(traceback.format_exc())
228 h.flash(_('An error occurred during deletion of user'),
229 h.flash(_('An error occurred during deletion of user'),
229 category='error')
230 category='error')
230 return redirect(url('users'))
231 return redirect(url('users'))
231
232
232 def show(self, id, format='html'):
233 def show(self, id, format='html'):
233 """GET /users/id: Show a specific item"""
234 """GET /users/id: Show a specific item"""
234 # url('user', id=ID)
235 # url('user', id=ID)
235 User.get_or_404(-1)
236 User.get_or_404(-1)
236
237
237 def _get_user_or_raise_if_default(self, id):
238 def _get_user_or_raise_if_default(self, id):
238 try:
239 try:
239 return User.get_or_404(id, allow_default=False)
240 return User.get_or_404(id, allow_default=False)
240 except DefaultUserException:
241 except DefaultUserException:
241 h.flash(_("The default user cannot be edited"), category='warning')
242 h.flash(_("The default user cannot be edited"), category='warning')
242 raise HTTPNotFound
243 raise HTTPNotFound
243
244
244 def edit(self, id, format='html'):
245 def edit(self, id, format='html'):
245 """GET /users/id/edit: Form to edit an existing item"""
246 """GET /users/id/edit: Form to edit an existing item"""
246 # url('edit_user', id=ID)
247 # url('edit_user', id=ID)
247 c.user = self._get_user_or_raise_if_default(id)
248 c.user = self._get_user_or_raise_if_default(id)
248 c.active = 'profile'
249 c.active = 'profile'
249 c.extern_type = c.user.extern_type
250 c.extern_type = c.user.extern_type
250 c.extern_name = c.user.extern_name
251 c.extern_name = c.user.extern_name
251 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
252 c.perm_user = AuthUser(user_id=id)
253 c.ip_addr = self.ip_addr
252
254
253 defaults = c.user.get_dict()
255 defaults = c.user.get_dict()
254 return htmlfill.render(
256 return htmlfill.render(
255 render('admin/users/user_edit.html'),
257 render('admin/users/user_edit.html'),
256 defaults=defaults,
258 defaults=defaults,
257 encoding="UTF-8",
259 encoding="UTF-8",
258 force_defaults=False)
260 force_defaults=False)
259
261
260 def edit_advanced(self, id):
262 def edit_advanced(self, id):
261 c.user = self._get_user_or_raise_if_default(id)
263 c.user = self._get_user_or_raise_if_default(id)
262 c.active = 'advanced'
264 c.active = 'advanced'
263 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
265 c.perm_user = AuthUser(user_id=id)
266 c.ip_addr = self.ip_addr
264
267
265 umodel = UserModel()
268 umodel = UserModel()
266 defaults = c.user.get_dict()
269 defaults = c.user.get_dict()
267 defaults.update({
270 defaults.update({
268 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
271 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
269 'create_user_group_perm': umodel.has_perm(c.user,
272 'create_user_group_perm': umodel.has_perm(c.user,
270 'hg.usergroup.create.true'),
273 'hg.usergroup.create.true'),
271 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
274 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
272 })
275 })
273 return htmlfill.render(
276 return htmlfill.render(
274 render('admin/users/user_edit.html'),
277 render('admin/users/user_edit.html'),
275 defaults=defaults,
278 defaults=defaults,
276 encoding="UTF-8",
279 encoding="UTF-8",
277 force_defaults=False)
280 force_defaults=False)
278
281
279 def edit_api_keys(self, id):
282 def edit_api_keys(self, id):
280 c.user = self._get_user_or_raise_if_default(id)
283 c.user = self._get_user_or_raise_if_default(id)
281 c.active = 'api_keys'
284 c.active = 'api_keys'
282 show_expired = True
285 show_expired = True
283 c.lifetime_values = [
286 c.lifetime_values = [
284 (str(-1), _('Forever')),
287 (str(-1), _('Forever')),
285 (str(5), _('5 minutes')),
288 (str(5), _('5 minutes')),
286 (str(60), _('1 hour')),
289 (str(60), _('1 hour')),
287 (str(60 * 24), _('1 day')),
290 (str(60 * 24), _('1 day')),
288 (str(60 * 24 * 30), _('1 month')),
291 (str(60 * 24 * 30), _('1 month')),
289 ]
292 ]
290 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
293 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
291 c.user_api_keys = ApiKeyModel().get_api_keys(c.user.user_id,
294 c.user_api_keys = ApiKeyModel().get_api_keys(c.user.user_id,
292 show_expired=show_expired)
295 show_expired=show_expired)
293 defaults = c.user.get_dict()
296 defaults = c.user.get_dict()
294 return htmlfill.render(
297 return htmlfill.render(
295 render('admin/users/user_edit.html'),
298 render('admin/users/user_edit.html'),
296 defaults=defaults,
299 defaults=defaults,
297 encoding="UTF-8",
300 encoding="UTF-8",
298 force_defaults=False)
301 force_defaults=False)
299
302
300 def add_api_key(self, id):
303 def add_api_key(self, id):
301 c.user = self._get_user_or_raise_if_default(id)
304 c.user = self._get_user_or_raise_if_default(id)
302
305
303 lifetime = safe_int(request.POST.get('lifetime'), -1)
306 lifetime = safe_int(request.POST.get('lifetime'), -1)
304 description = request.POST.get('description')
307 description = request.POST.get('description')
305 ApiKeyModel().create(c.user.user_id, description, lifetime)
308 ApiKeyModel().create(c.user.user_id, description, lifetime)
306 Session().commit()
309 Session().commit()
307 h.flash(_("API key successfully created"), category='success')
310 h.flash(_("API key successfully created"), category='success')
308 return redirect(url('edit_user_api_keys', id=c.user.user_id))
311 return redirect(url('edit_user_api_keys', id=c.user.user_id))
309
312
310 def delete_api_key(self, id):
313 def delete_api_key(self, id):
311 c.user = self._get_user_or_raise_if_default(id)
314 c.user = self._get_user_or_raise_if_default(id)
312
315
313 api_key = request.POST.get('del_api_key')
316 api_key = request.POST.get('del_api_key')
314 if request.POST.get('del_api_key_builtin'):
317 if request.POST.get('del_api_key_builtin'):
315 user = User.get(c.user.user_id)
318 user = User.get(c.user.user_id)
316 if user:
319 if user:
317 user.api_key = generate_api_key(user.username)
320 user.api_key = generate_api_key(user.username)
318 Session().add(user)
321 Session().add(user)
319 Session().commit()
322 Session().commit()
320 h.flash(_("API key successfully reset"), category='success')
323 h.flash(_("API key successfully reset"), category='success')
321 elif api_key:
324 elif api_key:
322 ApiKeyModel().delete(api_key, c.user.user_id)
325 ApiKeyModel().delete(api_key, c.user.user_id)
323 Session().commit()
326 Session().commit()
324 h.flash(_("API key successfully deleted"), category='success')
327 h.flash(_("API key successfully deleted"), category='success')
325
328
326 return redirect(url('edit_user_api_keys', id=c.user.user_id))
329 return redirect(url('edit_user_api_keys', id=c.user.user_id))
327
330
328 def update_account(self, id):
331 def update_account(self, id):
329 pass
332 pass
330
333
331 def edit_perms(self, id):
334 def edit_perms(self, id):
332 c.user = self._get_user_or_raise_if_default(id)
335 c.user = self._get_user_or_raise_if_default(id)
333 c.active = 'perms'
336 c.active = 'perms'
334 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
337 c.perm_user = AuthUser(user_id=id)
338 c.ip_addr = self.ip_addr
335
339
336 umodel = UserModel()
340 umodel = UserModel()
337 defaults = c.user.get_dict()
341 defaults = c.user.get_dict()
338 defaults.update({
342 defaults.update({
339 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
343 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
340 'create_user_group_perm': umodel.has_perm(c.user,
344 'create_user_group_perm': umodel.has_perm(c.user,
341 'hg.usergroup.create.true'),
345 'hg.usergroup.create.true'),
342 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
346 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
343 })
347 })
344 return htmlfill.render(
348 return htmlfill.render(
345 render('admin/users/user_edit.html'),
349 render('admin/users/user_edit.html'),
346 defaults=defaults,
350 defaults=defaults,
347 encoding="UTF-8",
351 encoding="UTF-8",
348 force_defaults=False)
352 force_defaults=False)
349
353
350 def update_perms(self, id):
354 def update_perms(self, id):
351 """PUT /users_perm/id: Update an existing item"""
355 """PUT /users_perm/id: Update an existing item"""
352 # url('user_perm', id=ID, method='put')
356 # url('user_perm', id=ID, method='put')
353 user = self._get_user_or_raise_if_default(id)
357 user = self._get_user_or_raise_if_default(id)
354
358
355 try:
359 try:
356 form = CustomDefaultPermissionsForm()()
360 form = CustomDefaultPermissionsForm()()
357 form_result = form.to_python(request.POST)
361 form_result = form.to_python(request.POST)
358
362
359 inherit_perms = form_result['inherit_default_permissions']
363 inherit_perms = form_result['inherit_default_permissions']
360 user.inherit_default_permissions = inherit_perms
364 user.inherit_default_permissions = inherit_perms
361 Session().add(user)
365 Session().add(user)
362 user_model = UserModel()
366 user_model = UserModel()
363
367
364 defs = UserToPerm.query()\
368 defs = UserToPerm.query()\
365 .filter(UserToPerm.user == user)\
369 .filter(UserToPerm.user == user)\
366 .all()
370 .all()
367 for ug in defs:
371 for ug in defs:
368 Session().delete(ug)
372 Session().delete(ug)
369
373
370 if form_result['create_repo_perm']:
374 if form_result['create_repo_perm']:
371 user_model.grant_perm(id, 'hg.create.repository')
375 user_model.grant_perm(id, 'hg.create.repository')
372 else:
376 else:
373 user_model.grant_perm(id, 'hg.create.none')
377 user_model.grant_perm(id, 'hg.create.none')
374 if form_result['create_user_group_perm']:
378 if form_result['create_user_group_perm']:
375 user_model.grant_perm(id, 'hg.usergroup.create.true')
379 user_model.grant_perm(id, 'hg.usergroup.create.true')
376 else:
380 else:
377 user_model.grant_perm(id, 'hg.usergroup.create.false')
381 user_model.grant_perm(id, 'hg.usergroup.create.false')
378 if form_result['fork_repo_perm']:
382 if form_result['fork_repo_perm']:
379 user_model.grant_perm(id, 'hg.fork.repository')
383 user_model.grant_perm(id, 'hg.fork.repository')
380 else:
384 else:
381 user_model.grant_perm(id, 'hg.fork.none')
385 user_model.grant_perm(id, 'hg.fork.none')
382 h.flash(_("Updated permissions"), category='success')
386 h.flash(_("Updated permissions"), category='success')
383 Session().commit()
387 Session().commit()
384 except Exception:
388 except Exception:
385 log.error(traceback.format_exc())
389 log.error(traceback.format_exc())
386 h.flash(_('An error occurred during permissions saving'),
390 h.flash(_('An error occurred during permissions saving'),
387 category='error')
391 category='error')
388 return redirect(url('edit_user_perms', id=id))
392 return redirect(url('edit_user_perms', id=id))
389
393
390 def edit_emails(self, id):
394 def edit_emails(self, id):
391 c.user = self._get_user_or_raise_if_default(id)
395 c.user = self._get_user_or_raise_if_default(id)
392 c.active = 'emails'
396 c.active = 'emails'
393 c.user_email_map = UserEmailMap.query()\
397 c.user_email_map = UserEmailMap.query()\
394 .filter(UserEmailMap.user == c.user).all()
398 .filter(UserEmailMap.user == c.user).all()
395
399
396 defaults = c.user.get_dict()
400 defaults = c.user.get_dict()
397 return htmlfill.render(
401 return htmlfill.render(
398 render('admin/users/user_edit.html'),
402 render('admin/users/user_edit.html'),
399 defaults=defaults,
403 defaults=defaults,
400 encoding="UTF-8",
404 encoding="UTF-8",
401 force_defaults=False)
405 force_defaults=False)
402
406
403 def add_email(self, id):
407 def add_email(self, id):
404 """POST /user_emails:Add an existing item"""
408 """POST /user_emails:Add an existing item"""
405 # url('user_emails', id=ID, method='put')
409 # url('user_emails', id=ID, method='put')
406 user = self._get_user_or_raise_if_default(id)
410 user = self._get_user_or_raise_if_default(id)
407 email = request.POST.get('new_email')
411 email = request.POST.get('new_email')
408 user_model = UserModel()
412 user_model = UserModel()
409
413
410 try:
414 try:
411 user_model.add_extra_email(id, email)
415 user_model.add_extra_email(id, email)
412 Session().commit()
416 Session().commit()
413 h.flash(_("Added email %s to user") % email, category='success')
417 h.flash(_("Added email %s to user") % email, category='success')
414 except formencode.Invalid, error:
418 except formencode.Invalid, error:
415 msg = error.error_dict['email']
419 msg = error.error_dict['email']
416 h.flash(msg, category='error')
420 h.flash(msg, category='error')
417 except Exception:
421 except Exception:
418 log.error(traceback.format_exc())
422 log.error(traceback.format_exc())
419 h.flash(_('An error occurred during email saving'),
423 h.flash(_('An error occurred during email saving'),
420 category='error')
424 category='error')
421 return redirect(url('edit_user_emails', id=id))
425 return redirect(url('edit_user_emails', id=id))
422
426
423 def delete_email(self, id):
427 def delete_email(self, id):
424 """DELETE /user_emails_delete/id: Delete an existing item"""
428 """DELETE /user_emails_delete/id: Delete an existing item"""
425 # url('user_emails_delete', id=ID, method='delete')
429 # url('user_emails_delete', id=ID, method='delete')
426 user = self._get_user_or_raise_if_default(id)
430 user = self._get_user_or_raise_if_default(id)
427 email_id = request.POST.get('del_email_id')
431 email_id = request.POST.get('del_email_id')
428 user_model = UserModel()
432 user_model = UserModel()
429 user_model.delete_extra_email(id, email_id)
433 user_model.delete_extra_email(id, email_id)
430 Session().commit()
434 Session().commit()
431 h.flash(_("Removed email from user"), category='success')
435 h.flash(_("Removed email from user"), category='success')
432 return redirect(url('edit_user_emails', id=id))
436 return redirect(url('edit_user_emails', id=id))
433
437
434 def edit_ips(self, id):
438 def edit_ips(self, id):
435 c.user = self._get_user_or_raise_if_default(id)
439 c.user = self._get_user_or_raise_if_default(id)
436 c.active = 'ips'
440 c.active = 'ips'
437 c.user_ip_map = UserIpMap.query()\
441 c.user_ip_map = UserIpMap.query()\
438 .filter(UserIpMap.user == c.user).all()
442 .filter(UserIpMap.user == c.user).all()
439
443
440 c.inherit_default_ips = c.user.inherit_default_permissions
444 c.inherit_default_ips = c.user.inherit_default_permissions
441 c.default_user_ip_map = UserIpMap.query()\
445 c.default_user_ip_map = UserIpMap.query()\
442 .filter(UserIpMap.user == User.get_default_user()).all()
446 .filter(UserIpMap.user == User.get_default_user()).all()
443
447
444 defaults = c.user.get_dict()
448 defaults = c.user.get_dict()
445 return htmlfill.render(
449 return htmlfill.render(
446 render('admin/users/user_edit.html'),
450 render('admin/users/user_edit.html'),
447 defaults=defaults,
451 defaults=defaults,
448 encoding="UTF-8",
452 encoding="UTF-8",
449 force_defaults=False)
453 force_defaults=False)
450
454
451 def add_ip(self, id):
455 def add_ip(self, id):
452 """POST /user_ips:Add an existing item"""
456 """POST /user_ips:Add an existing item"""
453 # url('user_ips', id=ID, method='put')
457 # url('user_ips', id=ID, method='put')
454
458
455 ip = request.POST.get('new_ip')
459 ip = request.POST.get('new_ip')
456 user_model = UserModel()
460 user_model = UserModel()
457
461
458 try:
462 try:
459 user_model.add_extra_ip(id, ip)
463 user_model.add_extra_ip(id, ip)
460 Session().commit()
464 Session().commit()
461 h.flash(_("Added IP address %s to user whitelist") % ip, category='success')
465 h.flash(_("Added IP address %s to user whitelist") % ip, category='success')
462 except formencode.Invalid, error:
466 except formencode.Invalid, error:
463 msg = error.error_dict['ip']
467 msg = error.error_dict['ip']
464 h.flash(msg, category='error')
468 h.flash(msg, category='error')
465 except Exception:
469 except Exception:
466 log.error(traceback.format_exc())
470 log.error(traceback.format_exc())
467 h.flash(_('An error occurred during ip saving'),
471 h.flash(_('An error occurred during ip saving'),
468 category='error')
472 category='error')
469
473
470 if 'default_user' in request.POST:
474 if 'default_user' in request.POST:
471 return redirect(url('admin_permissions_ips'))
475 return redirect(url('admin_permissions_ips'))
472 return redirect(url('edit_user_ips', id=id))
476 return redirect(url('edit_user_ips', id=id))
473
477
474 def delete_ip(self, id):
478 def delete_ip(self, id):
475 """DELETE /user_ips_delete/id: Delete an existing item"""
479 """DELETE /user_ips_delete/id: Delete an existing item"""
476 # url('user_ips_delete', id=ID, method='delete')
480 # url('user_ips_delete', id=ID, method='delete')
477 ip_id = request.POST.get('del_ip_id')
481 ip_id = request.POST.get('del_ip_id')
478 user_model = UserModel()
482 user_model = UserModel()
479 user_model.delete_extra_ip(id, ip_id)
483 user_model.delete_extra_ip(id, ip_id)
480 Session().commit()
484 Session().commit()
481 h.flash(_("Removed IP address from user whitelist"), category='success')
485 h.flash(_("Removed IP address from user whitelist"), category='success')
482
486
483 if 'default_user' in request.POST:
487 if 'default_user' in request.POST:
484 return redirect(url('admin_permissions_ips'))
488 return redirect(url('admin_permissions_ips'))
485 return redirect(url('edit_user_ips', id=id))
489 return redirect(url('edit_user_ips', id=id))
@@ -1,303 +1,303 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.api
15 kallithea.controllers.api
16 ~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 JSON RPC controller
18 JSON RPC controller
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Aug 20, 2011
22 :created_on: Aug 20, 2011
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import inspect
28 import inspect
29 import logging
29 import logging
30 import types
30 import types
31 import traceback
31 import traceback
32 import time
32 import time
33
33
34 from paste.response import replace_header
34 from paste.response import replace_header
35 from pylons.controllers import WSGIController
35 from pylons.controllers import WSGIController
36
36
37 from webob.exc import HTTPError
37 from webob.exc import HTTPError
38
38
39 from kallithea.model.db import User
39 from kallithea.model.db import User
40 from kallithea.model import meta
40 from kallithea.model import meta
41 from kallithea.lib.compat import izip_longest, json
41 from kallithea.lib.compat import izip_longest, json
42 from kallithea.lib.auth import AuthUser
42 from kallithea.lib.auth import AuthUser
43 from kallithea.lib.base import _get_ip_addr as _get_ip, _get_access_path
43 from kallithea.lib.base import _get_ip_addr as _get_ip, _get_access_path
44 from kallithea.lib.utils2 import safe_unicode, safe_str
44 from kallithea.lib.utils2 import safe_unicode, safe_str
45
45
46 log = logging.getLogger('JSONRPC')
46 log = logging.getLogger('JSONRPC')
47
47
48
48
49 class JSONRPCError(BaseException):
49 class JSONRPCError(BaseException):
50
50
51 def __init__(self, message):
51 def __init__(self, message):
52 self.message = message
52 self.message = message
53 super(JSONRPCError, self).__init__()
53 super(JSONRPCError, self).__init__()
54
54
55 def __str__(self):
55 def __str__(self):
56 return safe_str(self.message)
56 return safe_str(self.message)
57
57
58
58
59 def jsonrpc_error(message, retid=None, code=None):
59 def jsonrpc_error(message, retid=None, code=None):
60 """
60 """
61 Generate a Response object with a JSON-RPC error body
61 Generate a Response object with a JSON-RPC error body
62
62
63 :param code:
63 :param code:
64 :param retid:
64 :param retid:
65 :param message:
65 :param message:
66 """
66 """
67 from pylons.controllers.util import Response
67 from pylons.controllers.util import Response
68 return Response(
68 return Response(
69 body=json.dumps(dict(id=retid, result=None, error=message)),
69 body=json.dumps(dict(id=retid, result=None, error=message)),
70 status=code,
70 status=code,
71 content_type='application/json'
71 content_type='application/json'
72 )
72 )
73
73
74
74
75 class JSONRPCController(WSGIController):
75 class JSONRPCController(WSGIController):
76 """
76 """
77 A WSGI-speaking JSON-RPC controller class
77 A WSGI-speaking JSON-RPC controller class
78
78
79 See the specification:
79 See the specification:
80 <http://json-rpc.org/wiki/specification>`.
80 <http://json-rpc.org/wiki/specification>`.
81
81
82 Valid controller return values should be json-serializable objects.
82 Valid controller return values should be json-serializable objects.
83
83
84 Sub-classes should catch their exceptions and raise JSONRPCError
84 Sub-classes should catch their exceptions and raise JSONRPCError
85 if they want to pass meaningful errors to the client.
85 if they want to pass meaningful errors to the client.
86
86
87 """
87 """
88
88
89 def _get_ip_addr(self, environ):
89 def _get_ip_addr(self, environ):
90 return _get_ip(environ)
90 return _get_ip(environ)
91
91
92 def _get_method_args(self):
92 def _get_method_args(self):
93 """
93 """
94 Return `self._rpc_args` to dispatched controller method
94 Return `self._rpc_args` to dispatched controller method
95 chosen by __call__
95 chosen by __call__
96 """
96 """
97 return self._rpc_args
97 return self._rpc_args
98
98
99 def __call__(self, environ, start_response):
99 def __call__(self, environ, start_response):
100 """
100 """
101 Parse the request body as JSON, look up the method on the
101 Parse the request body as JSON, look up the method on the
102 controller and if it exists, dispatch to it.
102 controller and if it exists, dispatch to it.
103 """
103 """
104 try:
104 try:
105 return self._handle_request(environ, start_response)
105 return self._handle_request(environ, start_response)
106 finally:
106 finally:
107 meta.Session.remove()
107 meta.Session.remove()
108
108
109 def _handle_request(self, environ, start_response):
109 def _handle_request(self, environ, start_response):
110 start = time.time()
110 start = time.time()
111 ip_addr = self.ip_addr = self._get_ip_addr(environ)
111 ip_addr = self.ip_addr = self._get_ip_addr(environ)
112 self._req_id = None
112 self._req_id = None
113 if 'CONTENT_LENGTH' not in environ:
113 if 'CONTENT_LENGTH' not in environ:
114 log.debug("No Content-Length")
114 log.debug("No Content-Length")
115 return jsonrpc_error(retid=self._req_id,
115 return jsonrpc_error(retid=self._req_id,
116 message="No Content-Length in request")
116 message="No Content-Length in request")
117 else:
117 else:
118 length = environ['CONTENT_LENGTH'] or 0
118 length = environ['CONTENT_LENGTH'] or 0
119 length = int(environ['CONTENT_LENGTH'])
119 length = int(environ['CONTENT_LENGTH'])
120 log.debug('Content-Length: %s' % length)
120 log.debug('Content-Length: %s' % length)
121
121
122 if length == 0:
122 if length == 0:
123 log.debug("Content-Length is 0")
123 log.debug("Content-Length is 0")
124 return jsonrpc_error(retid=self._req_id,
124 return jsonrpc_error(retid=self._req_id,
125 message="Content-Length is 0")
125 message="Content-Length is 0")
126
126
127 raw_body = environ['wsgi.input'].read(length)
127 raw_body = environ['wsgi.input'].read(length)
128
128
129 try:
129 try:
130 json_body = json.loads(raw_body)
130 json_body = json.loads(raw_body)
131 except ValueError, e:
131 except ValueError, e:
132 # catch JSON errors Here
132 # catch JSON errors Here
133 return jsonrpc_error(retid=self._req_id,
133 return jsonrpc_error(retid=self._req_id,
134 message="JSON parse error ERR:%s RAW:%r"
134 message="JSON parse error ERR:%s RAW:%r"
135 % (e, raw_body))
135 % (e, raw_body))
136
136
137 # check AUTH based on API key
137 # check AUTH based on API key
138 try:
138 try:
139 self._req_api_key = json_body['api_key']
139 self._req_api_key = json_body['api_key']
140 self._req_id = json_body['id']
140 self._req_id = json_body['id']
141 self._req_method = json_body['method']
141 self._req_method = json_body['method']
142 self._request_params = json_body['args']
142 self._request_params = json_body['args']
143 if not isinstance(self._request_params, dict):
143 if not isinstance(self._request_params, dict):
144 self._request_params = {}
144 self._request_params = {}
145
145
146 log.debug(
146 log.debug(
147 'method: %s, params: %s' % (self._req_method,
147 'method: %s, params: %s' % (self._req_method,
148 self._request_params)
148 self._request_params)
149 )
149 )
150 except KeyError, e:
150 except KeyError, e:
151 return jsonrpc_error(retid=self._req_id,
151 return jsonrpc_error(retid=self._req_id,
152 message='Incorrect JSON query missing %s' % e)
152 message='Incorrect JSON query missing %s' % e)
153
153
154 # check if we can find this session using api_key
154 # check if we can find this session using api_key
155 try:
155 try:
156 u = User.get_by_api_key(self._req_api_key)
156 u = User.get_by_api_key(self._req_api_key)
157 if u is None:
157 if u is None:
158 return jsonrpc_error(retid=self._req_id,
158 return jsonrpc_error(retid=self._req_id,
159 message='Invalid API key')
159 message='Invalid API key')
160
160
161 #check if we are allowed to use this IP
161 #check if we are allowed to use this IP
162 auth_u = AuthUser(u.user_id, self._req_api_key, ip_addr=ip_addr)
162 auth_u = AuthUser(u.user_id, self._req_api_key)
163 if not auth_u.ip_allowed:
163 if not auth_u.is_ip_allowed(ip_addr):
164 return jsonrpc_error(retid=self._req_id,
164 return jsonrpc_error(retid=self._req_id,
165 message='request from IP:%s not allowed' % (ip_addr,))
165 message='request from IP:%s not allowed' % (ip_addr,))
166 else:
166 else:
167 log.info('Access for IP:%s allowed' % (ip_addr,))
167 log.info('Access for IP:%s allowed' % (ip_addr,))
168
168
169 except Exception, e:
169 except Exception, e:
170 return jsonrpc_error(retid=self._req_id,
170 return jsonrpc_error(retid=self._req_id,
171 message='Invalid API key')
171 message='Invalid API key')
172
172
173 self._error = None
173 self._error = None
174 try:
174 try:
175 self._func = self._find_method()
175 self._func = self._find_method()
176 except AttributeError, e:
176 except AttributeError, e:
177 return jsonrpc_error(retid=self._req_id,
177 return jsonrpc_error(retid=self._req_id,
178 message=str(e))
178 message=str(e))
179
179
180 # now that we have a method, add self._req_params to
180 # now that we have a method, add self._req_params to
181 # self.kargs and dispatch control to WGIController
181 # self.kargs and dispatch control to WGIController
182 argspec = inspect.getargspec(self._func)
182 argspec = inspect.getargspec(self._func)
183 arglist = argspec[0][1:]
183 arglist = argspec[0][1:]
184 defaults = map(type, argspec[3] or [])
184 defaults = map(type, argspec[3] or [])
185 default_empty = types.NotImplementedType
185 default_empty = types.NotImplementedType
186
186
187 # kw arguments required by this method
187 # kw arguments required by this method
188 func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
188 func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
189 fillvalue=default_empty))
189 fillvalue=default_empty))
190
190
191 # this is little trick to inject logged in user for
191 # this is little trick to inject logged in user for
192 # perms decorators to work they expect the controller class to have
192 # perms decorators to work they expect the controller class to have
193 # authuser attribute set
193 # authuser attribute set
194 self.authuser = auth_u
194 self.authuser = auth_u
195
195
196 # This attribute will need to be first param of a method that uses
196 # This attribute will need to be first param of a method that uses
197 # api_key, which is translated to instance of user at that name
197 # api_key, which is translated to instance of user at that name
198 USER_SESSION_ATTR = 'apiuser'
198 USER_SESSION_ATTR = 'apiuser'
199
199
200 if USER_SESSION_ATTR not in arglist:
200 if USER_SESSION_ATTR not in arglist:
201 return jsonrpc_error(
201 return jsonrpc_error(
202 retid=self._req_id,
202 retid=self._req_id,
203 message='This method [%s] does not support '
203 message='This method [%s] does not support '
204 'authentication (missing %s param)' % (
204 'authentication (missing %s param)' % (
205 self._func.__name__, USER_SESSION_ATTR)
205 self._func.__name__, USER_SESSION_ATTR)
206 )
206 )
207
207
208 # get our arglist and check if we provided them as args
208 # get our arglist and check if we provided them as args
209 for arg, default in func_kwargs.iteritems():
209 for arg, default in func_kwargs.iteritems():
210 if arg == USER_SESSION_ATTR:
210 if arg == USER_SESSION_ATTR:
211 # USER_SESSION_ATTR is something translated from API key and
211 # USER_SESSION_ATTR is something translated from API key and
212 # this is checked before so we don't need validate it
212 # this is checked before so we don't need validate it
213 continue
213 continue
214
214
215 # skip the required param check if it's default value is
215 # skip the required param check if it's default value is
216 # NotImplementedType (default_empty)
216 # NotImplementedType (default_empty)
217 if default == default_empty and arg not in self._request_params:
217 if default == default_empty and arg not in self._request_params:
218 return jsonrpc_error(
218 return jsonrpc_error(
219 retid=self._req_id,
219 retid=self._req_id,
220 message=(
220 message=(
221 'Missing non optional `%s` arg in JSON DATA' % arg
221 'Missing non optional `%s` arg in JSON DATA' % arg
222 )
222 )
223 )
223 )
224
224
225 self._rpc_args = {USER_SESSION_ATTR: u}
225 self._rpc_args = {USER_SESSION_ATTR: u}
226
226
227 self._rpc_args.update(self._request_params)
227 self._rpc_args.update(self._request_params)
228
228
229 self._rpc_args['action'] = self._req_method
229 self._rpc_args['action'] = self._req_method
230 self._rpc_args['environ'] = environ
230 self._rpc_args['environ'] = environ
231 self._rpc_args['start_response'] = start_response
231 self._rpc_args['start_response'] = start_response
232
232
233 status = []
233 status = []
234 headers = []
234 headers = []
235 exc_info = []
235 exc_info = []
236
236
237 def change_content(new_status, new_headers, new_exc_info=None):
237 def change_content(new_status, new_headers, new_exc_info=None):
238 status.append(new_status)
238 status.append(new_status)
239 headers.extend(new_headers)
239 headers.extend(new_headers)
240 exc_info.append(new_exc_info)
240 exc_info.append(new_exc_info)
241
241
242 output = WSGIController.__call__(self, environ, change_content)
242 output = WSGIController.__call__(self, environ, change_content)
243 output = list(output)
243 output = list(output)
244 headers.append(('Content-Length', str(len(output[0]))))
244 headers.append(('Content-Length', str(len(output[0]))))
245 replace_header(headers, 'Content-Type', 'application/json')
245 replace_header(headers, 'Content-Type', 'application/json')
246 start_response(status[0], headers, exc_info[0])
246 start_response(status[0], headers, exc_info[0])
247 log.info('IP: %s Request to %s time: %.3fs' % (
247 log.info('IP: %s Request to %s time: %.3fs' % (
248 self._get_ip_addr(environ),
248 self._get_ip_addr(environ),
249 safe_unicode(_get_access_path(environ)), time.time() - start)
249 safe_unicode(_get_access_path(environ)), time.time() - start)
250 )
250 )
251 return output
251 return output
252
252
253 def _dispatch_call(self):
253 def _dispatch_call(self):
254 """
254 """
255 Implement dispatch interface specified by WSGIController
255 Implement dispatch interface specified by WSGIController
256 """
256 """
257 raw_response = ''
257 raw_response = ''
258 try:
258 try:
259 raw_response = self._inspect_call(self._func)
259 raw_response = self._inspect_call(self._func)
260 if isinstance(raw_response, HTTPError):
260 if isinstance(raw_response, HTTPError):
261 self._error = str(raw_response)
261 self._error = str(raw_response)
262 except JSONRPCError, e:
262 except JSONRPCError, e:
263 self._error = safe_str(e)
263 self._error = safe_str(e)
264 except Exception, e:
264 except Exception, e:
265 log.error('Encountered unhandled exception: %s'
265 log.error('Encountered unhandled exception: %s'
266 % (traceback.format_exc(),))
266 % (traceback.format_exc(),))
267 json_exc = JSONRPCError('Internal server error')
267 json_exc = JSONRPCError('Internal server error')
268 self._error = safe_str(json_exc)
268 self._error = safe_str(json_exc)
269
269
270 if self._error is not None:
270 if self._error is not None:
271 raw_response = None
271 raw_response = None
272
272
273 response = dict(id=self._req_id, result=raw_response, error=self._error)
273 response = dict(id=self._req_id, result=raw_response, error=self._error)
274 try:
274 try:
275 return json.dumps(response)
275 return json.dumps(response)
276 except TypeError, e:
276 except TypeError, e:
277 log.error('API FAILED. Error encoding response: %s' % e)
277 log.error('API FAILED. Error encoding response: %s' % e)
278 return json.dumps(
278 return json.dumps(
279 dict(
279 dict(
280 id=self._req_id,
280 id=self._req_id,
281 result=None,
281 result=None,
282 error="Error encoding response"
282 error="Error encoding response"
283 )
283 )
284 )
284 )
285
285
286 def _find_method(self):
286 def _find_method(self):
287 """
287 """
288 Return method named by `self._req_method` in controller if able
288 Return method named by `self._req_method` in controller if able
289 """
289 """
290 log.debug('Trying to find JSON-RPC method: %s' % (self._req_method,))
290 log.debug('Trying to find JSON-RPC method: %s' % (self._req_method,))
291 if self._req_method.startswith('_'):
291 if self._req_method.startswith('_'):
292 raise AttributeError("Method not allowed")
292 raise AttributeError("Method not allowed")
293
293
294 try:
294 try:
295 func = getattr(self, self._req_method, None)
295 func = getattr(self, self._req_method, None)
296 except UnicodeEncodeError:
296 except UnicodeEncodeError:
297 raise AttributeError("Problem decoding unicode in requested "
297 raise AttributeError("Problem decoding unicode in requested "
298 "method name.")
298 "method name.")
299
299
300 if isinstance(func, types.MethodType):
300 if isinstance(func, types.MethodType):
301 return func
301 return func
302 else:
302 else:
303 raise AttributeError("No such method: %s" % (self._req_method,))
303 raise AttributeError("No such method: %s" % (self._req_method,))
@@ -1,283 +1,283 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.login
15 kallithea.controllers.login
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Login controller for Kallithea
18 Login controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 22, 2010
22 :created_on: Apr 22, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import logging
29 import logging
30 import formencode
30 import formencode
31 import datetime
31 import datetime
32 import urlparse
32 import urlparse
33
33
34 from formencode import htmlfill
34 from formencode import htmlfill
35 from webob.exc import HTTPFound
35 from webob.exc import HTTPFound
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37 from pylons.controllers.util import redirect
37 from pylons.controllers.util import redirect
38 from pylons import request, session, tmpl_context as c, url
38 from pylons import request, session, tmpl_context as c, url
39
39
40 import kallithea.lib.helpers as h
40 import kallithea.lib.helpers as h
41 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
41 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
42 from kallithea.lib.auth_modules import importplugin
42 from kallithea.lib.auth_modules import importplugin
43 from kallithea.lib.base import BaseController, render
43 from kallithea.lib.base import BaseController, render
44 from kallithea.lib.exceptions import UserCreationError
44 from kallithea.lib.exceptions import UserCreationError
45 from kallithea.lib.utils2 import safe_str
45 from kallithea.lib.utils2 import safe_str
46 from kallithea.model.db import User, Setting
46 from kallithea.model.db import User, Setting
47 from kallithea.model.forms import LoginForm, RegisterForm, PasswordResetForm
47 from kallithea.model.forms import LoginForm, RegisterForm, PasswordResetForm
48 from kallithea.model.user import UserModel
48 from kallithea.model.user import UserModel
49 from kallithea.model.meta import Session
49 from kallithea.model.meta import Session
50
50
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class LoginController(BaseController):
55 class LoginController(BaseController):
56
56
57 def __before__(self):
57 def __before__(self):
58 super(LoginController, self).__before__()
58 super(LoginController, self).__before__()
59
59
60 def _store_user_in_session(self, username, remember=False):
60 def _store_user_in_session(self, username, remember=False):
61 user = User.get_by_username(username, case_insensitive=True)
61 user = User.get_by_username(username, case_insensitive=True)
62 auth_user = AuthUser(user.user_id)
62 auth_user = AuthUser(user.user_id)
63 auth_user.set_authenticated()
63 auth_user.set_authenticated()
64 cs = auth_user.get_cookie_store()
64 cs = auth_user.get_cookie_store()
65 session['authuser'] = cs
65 session['authuser'] = cs
66 user.update_lastlogin()
66 user.update_lastlogin()
67 Session().commit()
67 Session().commit()
68
68
69 # If they want to be remembered, update the cookie
69 # If they want to be remembered, update the cookie
70 if remember:
70 if remember:
71 _year = (datetime.datetime.now() +
71 _year = (datetime.datetime.now() +
72 datetime.timedelta(seconds=60 * 60 * 24 * 365))
72 datetime.timedelta(seconds=60 * 60 * 24 * 365))
73 session._set_cookie_expires(_year)
73 session._set_cookie_expires(_year)
74
74
75 session.save()
75 session.save()
76
76
77 log.info('user %s is now authenticated and stored in '
77 log.info('user %s is now authenticated and stored in '
78 'session, session attrs %s' % (username, cs))
78 'session, session attrs %s' % (username, cs))
79
79
80 # dumps session attrs back to cookie
80 # dumps session attrs back to cookie
81 session._update_cookie_out()
81 session._update_cookie_out()
82
82
83 def _validate_came_from(self, came_from):
83 def _validate_came_from(self, came_from):
84 """Return True if came_from is valid and can and should be used"""
84 """Return True if came_from is valid and can and should be used"""
85 if not came_from:
85 if not came_from:
86 return False
86 return False
87
87
88 parsed = urlparse.urlparse(came_from)
88 parsed = urlparse.urlparse(came_from)
89 server_parsed = urlparse.urlparse(url.current())
89 server_parsed = urlparse.urlparse(url.current())
90 allowed_schemes = ['http', 'https']
90 allowed_schemes = ['http', 'https']
91 if parsed.scheme and parsed.scheme not in allowed_schemes:
91 if parsed.scheme and parsed.scheme not in allowed_schemes:
92 log.error('Suspicious URL scheme detected %s for url %s' %
92 log.error('Suspicious URL scheme detected %s for url %s' %
93 (parsed.scheme, parsed))
93 (parsed.scheme, parsed))
94 return False
94 return False
95 if server_parsed.netloc != parsed.netloc:
95 if server_parsed.netloc != parsed.netloc:
96 log.error('Suspicious NETLOC detected %s for url %s server url '
96 log.error('Suspicious NETLOC detected %s for url %s server url '
97 'is: %s' % (parsed.netloc, parsed, server_parsed))
97 'is: %s' % (parsed.netloc, parsed, server_parsed))
98 return False
98 return False
99 return True
99 return True
100
100
101 def _redirect_to_origin(self, origin):
101 def _redirect_to_origin(self, origin):
102 '''redirect to the original page, preserving any get arguments given'''
102 '''redirect to the original page, preserving any get arguments given'''
103 request.GET.pop('came_from', None)
103 request.GET.pop('came_from', None)
104 raise HTTPFound(location=url(origin, **request.GET))
104 raise HTTPFound(location=url(origin, **request.GET))
105
105
106 def index(self):
106 def index(self):
107 c.came_from = safe_str(request.GET.get('came_from', ''))
107 c.came_from = safe_str(request.GET.get('came_from', ''))
108 if not self._validate_came_from(c.came_from):
108 if not self._validate_came_from(c.came_from):
109 c.came_from = url('home')
109 c.came_from = url('home')
110
110
111 not_default = self.authuser.username != User.DEFAULT_USER
111 not_default = self.authuser.username != User.DEFAULT_USER
112 ip_allowed = self.authuser.ip_allowed
112 ip_allowed = self.authuser.is_ip_allowed(self.ip_addr)
113
113
114 # redirect if already logged in
114 # redirect if already logged in
115 if self.authuser.is_authenticated and not_default and ip_allowed:
115 if self.authuser.is_authenticated and not_default and ip_allowed:
116 return self._redirect_to_origin(c.came_from)
116 return self._redirect_to_origin(c.came_from)
117
117
118 if request.POST:
118 if request.POST:
119 # import Login Form validator class
119 # import Login Form validator class
120 login_form = LoginForm()
120 login_form = LoginForm()
121 try:
121 try:
122 session.invalidate()
122 session.invalidate()
123 c.form_result = login_form.to_python(dict(request.POST))
123 c.form_result = login_form.to_python(dict(request.POST))
124 # form checks for username/password, now we're authenticated
124 # form checks for username/password, now we're authenticated
125 self._store_user_in_session(
125 self._store_user_in_session(
126 username=c.form_result['username'],
126 username=c.form_result['username'],
127 remember=c.form_result['remember'])
127 remember=c.form_result['remember'])
128 return self._redirect_to_origin(c.came_from)
128 return self._redirect_to_origin(c.came_from)
129
129
130 except formencode.Invalid, errors:
130 except formencode.Invalid, errors:
131 defaults = errors.value
131 defaults = errors.value
132 # remove password from filling in form again
132 # remove password from filling in form again
133 del defaults['password']
133 del defaults['password']
134 return htmlfill.render(
134 return htmlfill.render(
135 render('/login.html'),
135 render('/login.html'),
136 defaults=errors.value,
136 defaults=errors.value,
137 errors=errors.error_dict or {},
137 errors=errors.error_dict or {},
138 prefix_error=False,
138 prefix_error=False,
139 encoding="UTF-8",
139 encoding="UTF-8",
140 force_defaults=False)
140 force_defaults=False)
141 except UserCreationError, e:
141 except UserCreationError, e:
142 # container auth or other auth functions that create users on
142 # container auth or other auth functions that create users on
143 # the fly can throw this exception signaling that there's issue
143 # the fly can throw this exception signaling that there's issue
144 # with user creation, explanation should be provided in
144 # with user creation, explanation should be provided in
145 # Exception itself
145 # Exception itself
146 h.flash(e, 'error')
146 h.flash(e, 'error')
147
147
148 # check if we use container plugin, and try to login using it.
148 # check if we use container plugin, and try to login using it.
149 auth_plugins = Setting.get_auth_plugins()
149 auth_plugins = Setting.get_auth_plugins()
150 if any((importplugin(name).is_container_auth for name in auth_plugins)):
150 if any((importplugin(name).is_container_auth for name in auth_plugins)):
151 from kallithea.lib import auth_modules
151 from kallithea.lib import auth_modules
152 try:
152 try:
153 auth_info = auth_modules.authenticate('', '', request.environ)
153 auth_info = auth_modules.authenticate('', '', request.environ)
154 except UserCreationError, e:
154 except UserCreationError, e:
155 log.error(e)
155 log.error(e)
156 h.flash(e, 'error')
156 h.flash(e, 'error')
157 # render login, with flash message about limit
157 # render login, with flash message about limit
158 return render('/login.html')
158 return render('/login.html')
159
159
160 if auth_info:
160 if auth_info:
161 self._store_user_in_session(auth_info.get('username'))
161 self._store_user_in_session(auth_info.get('username'))
162 return self._redirect_to_origin(c.came_from)
162 return self._redirect_to_origin(c.came_from)
163
163
164 return render('/login.html')
164 return render('/login.html')
165
165
166 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
166 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
167 'hg.register.manual_activate')
167 'hg.register.manual_activate')
168 def register(self):
168 def register(self):
169 c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
169 c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
170 .AuthUser.permissions['global']
170 .AuthUser.permissions['global']
171
171
172 settings = Setting.get_app_settings()
172 settings = Setting.get_app_settings()
173 captcha_private_key = settings.get('captcha_private_key')
173 captcha_private_key = settings.get('captcha_private_key')
174 c.captcha_active = bool(captcha_private_key)
174 c.captcha_active = bool(captcha_private_key)
175 c.captcha_public_key = settings.get('captcha_public_key')
175 c.captcha_public_key = settings.get('captcha_public_key')
176
176
177 if request.POST:
177 if request.POST:
178 register_form = RegisterForm()()
178 register_form = RegisterForm()()
179 try:
179 try:
180 form_result = register_form.to_python(dict(request.POST))
180 form_result = register_form.to_python(dict(request.POST))
181 form_result['active'] = c.auto_active
181 form_result['active'] = c.auto_active
182
182
183 if c.captcha_active:
183 if c.captcha_active:
184 from kallithea.lib.recaptcha import submit
184 from kallithea.lib.recaptcha import submit
185 response = submit(request.POST.get('recaptcha_challenge_field'),
185 response = submit(request.POST.get('recaptcha_challenge_field'),
186 request.POST.get('recaptcha_response_field'),
186 request.POST.get('recaptcha_response_field'),
187 private_key=captcha_private_key,
187 private_key=captcha_private_key,
188 remoteip=self.ip_addr)
188 remoteip=self.ip_addr)
189 if c.captcha_active and not response.is_valid:
189 if c.captcha_active and not response.is_valid:
190 _value = form_result
190 _value = form_result
191 _msg = _('Bad captcha')
191 _msg = _('Bad captcha')
192 error_dict = {'recaptcha_field': _msg}
192 error_dict = {'recaptcha_field': _msg}
193 raise formencode.Invalid(_msg, _value, None,
193 raise formencode.Invalid(_msg, _value, None,
194 error_dict=error_dict)
194 error_dict=error_dict)
195
195
196 UserModel().create_registration(form_result)
196 UserModel().create_registration(form_result)
197 h.flash(_('You have successfully registered into Kallithea'),
197 h.flash(_('You have successfully registered into Kallithea'),
198 category='success')
198 category='success')
199 Session().commit()
199 Session().commit()
200 return redirect(url('login_home'))
200 return redirect(url('login_home'))
201
201
202 except formencode.Invalid, errors:
202 except formencode.Invalid, errors:
203 return htmlfill.render(
203 return htmlfill.render(
204 render('/register.html'),
204 render('/register.html'),
205 defaults=errors.value,
205 defaults=errors.value,
206 errors=errors.error_dict or {},
206 errors=errors.error_dict or {},
207 prefix_error=False,
207 prefix_error=False,
208 encoding="UTF-8",
208 encoding="UTF-8",
209 force_defaults=False)
209 force_defaults=False)
210 except UserCreationError, e:
210 except UserCreationError, e:
211 # container auth or other auth functions that create users on
211 # container auth or other auth functions that create users on
212 # the fly can throw this exception signaling that there's issue
212 # the fly can throw this exception signaling that there's issue
213 # with user creation, explanation should be provided in
213 # with user creation, explanation should be provided in
214 # Exception itself
214 # Exception itself
215 h.flash(e, 'error')
215 h.flash(e, 'error')
216
216
217 return render('/register.html')
217 return render('/register.html')
218
218
219 def password_reset(self):
219 def password_reset(self):
220 settings = Setting.get_app_settings()
220 settings = Setting.get_app_settings()
221 captcha_private_key = settings.get('captcha_private_key')
221 captcha_private_key = settings.get('captcha_private_key')
222 c.captcha_active = bool(captcha_private_key)
222 c.captcha_active = bool(captcha_private_key)
223 c.captcha_public_key = settings.get('captcha_public_key')
223 c.captcha_public_key = settings.get('captcha_public_key')
224
224
225 if request.POST:
225 if request.POST:
226 password_reset_form = PasswordResetForm()()
226 password_reset_form = PasswordResetForm()()
227 try:
227 try:
228 form_result = password_reset_form.to_python(dict(request.POST))
228 form_result = password_reset_form.to_python(dict(request.POST))
229 if c.captcha_active:
229 if c.captcha_active:
230 from kallithea.lib.recaptcha import submit
230 from kallithea.lib.recaptcha import submit
231 response = submit(request.POST.get('recaptcha_challenge_field'),
231 response = submit(request.POST.get('recaptcha_challenge_field'),
232 request.POST.get('recaptcha_response_field'),
232 request.POST.get('recaptcha_response_field'),
233 private_key=captcha_private_key,
233 private_key=captcha_private_key,
234 remoteip=self.ip_addr)
234 remoteip=self.ip_addr)
235 if c.captcha_active and not response.is_valid:
235 if c.captcha_active and not response.is_valid:
236 _value = form_result
236 _value = form_result
237 _msg = _('Bad captcha')
237 _msg = _('Bad captcha')
238 error_dict = {'recaptcha_field': _msg}
238 error_dict = {'recaptcha_field': _msg}
239 raise formencode.Invalid(_msg, _value, None,
239 raise formencode.Invalid(_msg, _value, None,
240 error_dict=error_dict)
240 error_dict=error_dict)
241 UserModel().reset_password_link(form_result)
241 UserModel().reset_password_link(form_result)
242 h.flash(_('Your password reset link was sent'),
242 h.flash(_('Your password reset link was sent'),
243 category='success')
243 category='success')
244 return redirect(url('login_home'))
244 return redirect(url('login_home'))
245
245
246 except formencode.Invalid, errors:
246 except formencode.Invalid, errors:
247 return htmlfill.render(
247 return htmlfill.render(
248 render('/password_reset.html'),
248 render('/password_reset.html'),
249 defaults=errors.value,
249 defaults=errors.value,
250 errors=errors.error_dict or {},
250 errors=errors.error_dict or {},
251 prefix_error=False,
251 prefix_error=False,
252 encoding="UTF-8",
252 encoding="UTF-8",
253 force_defaults=False)
253 force_defaults=False)
254
254
255 return render('/password_reset.html')
255 return render('/password_reset.html')
256
256
257 def password_reset_confirmation(self):
257 def password_reset_confirmation(self):
258 if request.GET and request.GET.get('key'):
258 if request.GET and request.GET.get('key'):
259 try:
259 try:
260 user = User.get_by_api_key(request.GET.get('key'))
260 user = User.get_by_api_key(request.GET.get('key'))
261 data = dict(email=user.email)
261 data = dict(email=user.email)
262 UserModel().reset_password(data)
262 UserModel().reset_password(data)
263 h.flash(_('Your password reset was successful, '
263 h.flash(_('Your password reset was successful, '
264 'new password has been sent to your email'),
264 'new password has been sent to your email'),
265 category='success')
265 category='success')
266 except Exception, e:
266 except Exception, e:
267 log.error(e)
267 log.error(e)
268 return redirect(url('reset_password'))
268 return redirect(url('reset_password'))
269
269
270 return redirect(url('login_home'))
270 return redirect(url('login_home'))
271
271
272 def logout(self):
272 def logout(self):
273 session.delete()
273 session.delete()
274 log.info('Logging out and deleting session for user')
274 log.info('Logging out and deleting session for user')
275 redirect(url('home'))
275 redirect(url('home'))
276
276
277 def authentication_token(self):
277 def authentication_token(self):
278 """Return the CSRF protection token for the session - just like it
278 """Return the CSRF protection token for the session - just like it
279 could have been screen scrabed from a page with a form.
279 could have been screen scrabed from a page with a form.
280 Only intended for testing but might also be useful for other kinds
280 Only intended for testing but might also be useful for other kinds
281 of automation.
281 of automation.
282 """
282 """
283 return h.authentication_token()
283 return h.authentication_token()
@@ -1,1293 +1,1288 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.auth
15 kallithea.lib.auth
16 ~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~
17
17
18 authentication and permission libraries
18 authentication and permission libraries
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 4, 2010
22 :created_on: Apr 4, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27 from __future__ import with_statement
27 from __future__ import with_statement
28 import time
28 import time
29 import random
29 import random
30 import logging
30 import logging
31 import traceback
31 import traceback
32 import hashlib
32 import hashlib
33 import itertools
33 import itertools
34 import collections
34 import collections
35
35
36 from tempfile import _RandomNameSequence
36 from tempfile import _RandomNameSequence
37 from decorator import decorator
37 from decorator import decorator
38
38
39 from pylons import url, request
39 from pylons import url, request
40 from pylons.controllers.util import abort, redirect
40 from pylons.controllers.util import abort, redirect
41 from pylons.i18n.translation import _
41 from pylons.i18n.translation import _
42 from webhelpers.pylonslib import secure_form
42 from webhelpers.pylonslib import secure_form
43 from sqlalchemy import or_
43 from sqlalchemy import or_
44 from sqlalchemy.orm.exc import ObjectDeletedError
44 from sqlalchemy.orm.exc import ObjectDeletedError
45 from sqlalchemy.orm import joinedload
45 from sqlalchemy.orm import joinedload
46
46
47 from kallithea import __platform__, is_windows, is_unix
47 from kallithea import __platform__, is_windows, is_unix
48 from kallithea.lib.vcs.utils.lazy import LazyProperty
48 from kallithea.lib.vcs.utils.lazy import LazyProperty
49 from kallithea.model import meta
49 from kallithea.model import meta
50 from kallithea.model.meta import Session
50 from kallithea.model.meta import Session
51 from kallithea.model.user import UserModel
51 from kallithea.model.user import UserModel
52 from kallithea.model.db import User, Repository, Permission, \
52 from kallithea.model.db import User, Repository, Permission, \
53 UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
53 UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
54 RepoGroup, UserGroupRepoGroupToPerm, UserIpMap, UserGroupUserGroupToPerm, \
54 RepoGroup, UserGroupRepoGroupToPerm, UserIpMap, UserGroupUserGroupToPerm, \
55 UserGroup, UserApiKeys
55 UserGroup, UserApiKeys
56
56
57 from kallithea.lib.utils2 import safe_unicode, aslist
57 from kallithea.lib.utils2 import safe_unicode, aslist
58 from kallithea.lib.utils import get_repo_slug, get_repo_group_slug, \
58 from kallithea.lib.utils import get_repo_slug, get_repo_group_slug, \
59 get_user_group_slug, conditional_cache
59 get_user_group_slug, conditional_cache
60 from kallithea.lib.caching_query import FromCache
60 from kallithea.lib.caching_query import FromCache
61
61
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 class PasswordGenerator(object):
66 class PasswordGenerator(object):
67 """
67 """
68 This is a simple class for generating password from different sets of
68 This is a simple class for generating password from different sets of
69 characters
69 characters
70 usage::
70 usage::
71
71
72 passwd_gen = PasswordGenerator()
72 passwd_gen = PasswordGenerator()
73 #print 8-letter password containing only big and small letters
73 #print 8-letter password containing only big and small letters
74 of alphabet
74 of alphabet
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
76 """
76 """
77 ALPHABETS_NUM = r'''1234567890'''
77 ALPHABETS_NUM = r'''1234567890'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
87
87
88 def __init__(self, passwd=''):
88 def __init__(self, passwd=''):
89 self.passwd = passwd
89 self.passwd = passwd
90
90
91 def gen_password(self, length, type_=None):
91 def gen_password(self, length, type_=None):
92 if type_ is None:
92 if type_ is None:
93 type_ = self.ALPHABETS_FULL
93 type_ = self.ALPHABETS_FULL
94 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
94 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
95 return self.passwd
95 return self.passwd
96
96
97
97
98 class KallitheaCrypto(object):
98 class KallitheaCrypto(object):
99
99
100 @classmethod
100 @classmethod
101 def hash_string(cls, str_):
101 def hash_string(cls, str_):
102 """
102 """
103 Cryptographic function used for password hashing based on pybcrypt
103 Cryptographic function used for password hashing based on pybcrypt
104 or pycrypto in windows
104 or pycrypto in windows
105
105
106 :param password: password to hash
106 :param password: password to hash
107 """
107 """
108 if is_windows:
108 if is_windows:
109 from hashlib import sha256
109 from hashlib import sha256
110 return sha256(str_).hexdigest()
110 return sha256(str_).hexdigest()
111 elif is_unix:
111 elif is_unix:
112 import bcrypt
112 import bcrypt
113 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
113 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
114 else:
114 else:
115 raise Exception('Unknown or unsupported platform %s' \
115 raise Exception('Unknown or unsupported platform %s' \
116 % __platform__)
116 % __platform__)
117
117
118 @classmethod
118 @classmethod
119 def hash_check(cls, password, hashed):
119 def hash_check(cls, password, hashed):
120 """
120 """
121 Checks matching password with it's hashed value, runs different
121 Checks matching password with it's hashed value, runs different
122 implementation based on platform it runs on
122 implementation based on platform it runs on
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
127
128 if is_windows:
128 if is_windows:
129 from hashlib import sha256
129 from hashlib import sha256
130 return sha256(password).hexdigest() == hashed
130 return sha256(password).hexdigest() == hashed
131 elif is_unix:
131 elif is_unix:
132 import bcrypt
132 import bcrypt
133 return bcrypt.hashpw(password, hashed) == hashed
133 return bcrypt.hashpw(password, hashed) == hashed
134 else:
134 else:
135 raise Exception('Unknown or unsupported platform %s' \
135 raise Exception('Unknown or unsupported platform %s' \
136 % __platform__)
136 % __platform__)
137
137
138
138
139 def get_crypt_password(password):
139 def get_crypt_password(password):
140 return KallitheaCrypto.hash_string(password)
140 return KallitheaCrypto.hash_string(password)
141
141
142
142
143 def check_password(password, hashed):
143 def check_password(password, hashed):
144 return KallitheaCrypto.hash_check(password, hashed)
144 return KallitheaCrypto.hash_check(password, hashed)
145
145
146 class CookieStoreWrapper(object):
146 class CookieStoreWrapper(object):
147
147
148 def __init__(self, cookie_store):
148 def __init__(self, cookie_store):
149 self.cookie_store = cookie_store
149 self.cookie_store = cookie_store
150
150
151 def __repr__(self):
151 def __repr__(self):
152 return 'CookieStore<%s>' % (self.cookie_store)
152 return 'CookieStore<%s>' % (self.cookie_store)
153
153
154 def get(self, key, other=None):
154 def get(self, key, other=None):
155 if isinstance(self.cookie_store, dict):
155 if isinstance(self.cookie_store, dict):
156 return self.cookie_store.get(key, other)
156 return self.cookie_store.get(key, other)
157 elif isinstance(self.cookie_store, AuthUser):
157 elif isinstance(self.cookie_store, AuthUser):
158 return self.cookie_store.__dict__.get(key, other)
158 return self.cookie_store.__dict__.get(key, other)
159
159
160
160
161
161
162 def _cached_perms_data(user_id, user_is_admin, user_inherit_default_permissions,
162 def _cached_perms_data(user_id, user_is_admin, user_inherit_default_permissions,
163 explicit, algo):
163 explicit, algo):
164 RK = 'repositories'
164 RK = 'repositories'
165 GK = 'repositories_groups'
165 GK = 'repositories_groups'
166 UK = 'user_groups'
166 UK = 'user_groups'
167 GLOBAL = 'global'
167 GLOBAL = 'global'
168 PERM_WEIGHTS = Permission.PERM_WEIGHTS
168 PERM_WEIGHTS = Permission.PERM_WEIGHTS
169 permissions = {RK: {}, GK: {}, UK: {}, GLOBAL: set()}
169 permissions = {RK: {}, GK: {}, UK: {}, GLOBAL: set()}
170
170
171 def _choose_perm(new_perm, cur_perm):
171 def _choose_perm(new_perm, cur_perm):
172 new_perm_val = PERM_WEIGHTS[new_perm]
172 new_perm_val = PERM_WEIGHTS[new_perm]
173 cur_perm_val = PERM_WEIGHTS[cur_perm]
173 cur_perm_val = PERM_WEIGHTS[cur_perm]
174 if algo == 'higherwin':
174 if algo == 'higherwin':
175 if new_perm_val > cur_perm_val:
175 if new_perm_val > cur_perm_val:
176 return new_perm
176 return new_perm
177 return cur_perm
177 return cur_perm
178 elif algo == 'lowerwin':
178 elif algo == 'lowerwin':
179 if new_perm_val < cur_perm_val:
179 if new_perm_val < cur_perm_val:
180 return new_perm
180 return new_perm
181 return cur_perm
181 return cur_perm
182
182
183 #======================================================================
183 #======================================================================
184 # fetch default permissions
184 # fetch default permissions
185 #======================================================================
185 #======================================================================
186 default_user = User.get_by_username('default', cache=True)
186 default_user = User.get_by_username('default', cache=True)
187 default_user_id = default_user.user_id
187 default_user_id = default_user.user_id
188
188
189 default_repo_perms = Permission.get_default_perms(default_user_id)
189 default_repo_perms = Permission.get_default_perms(default_user_id)
190 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
190 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
191 default_user_group_perms = Permission.get_default_user_group_perms(default_user_id)
191 default_user_group_perms = Permission.get_default_user_group_perms(default_user_id)
192
192
193 if user_is_admin:
193 if user_is_admin:
194 #==================================================================
194 #==================================================================
195 # admin user have all default rights for repositories
195 # admin user have all default rights for repositories
196 # and groups set to admin
196 # and groups set to admin
197 #==================================================================
197 #==================================================================
198 permissions[GLOBAL].add('hg.admin')
198 permissions[GLOBAL].add('hg.admin')
199 permissions[GLOBAL].add('hg.create.write_on_repogroup.true')
199 permissions[GLOBAL].add('hg.create.write_on_repogroup.true')
200
200
201 # repositories
201 # repositories
202 for perm in default_repo_perms:
202 for perm in default_repo_perms:
203 r_k = perm.UserRepoToPerm.repository.repo_name
203 r_k = perm.UserRepoToPerm.repository.repo_name
204 p = 'repository.admin'
204 p = 'repository.admin'
205 permissions[RK][r_k] = p
205 permissions[RK][r_k] = p
206
206
207 # repository groups
207 # repository groups
208 for perm in default_repo_groups_perms:
208 for perm in default_repo_groups_perms:
209 rg_k = perm.UserRepoGroupToPerm.group.group_name
209 rg_k = perm.UserRepoGroupToPerm.group.group_name
210 p = 'group.admin'
210 p = 'group.admin'
211 permissions[GK][rg_k] = p
211 permissions[GK][rg_k] = p
212
212
213 # user groups
213 # user groups
214 for perm in default_user_group_perms:
214 for perm in default_user_group_perms:
215 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
215 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
216 p = 'usergroup.admin'
216 p = 'usergroup.admin'
217 permissions[UK][u_k] = p
217 permissions[UK][u_k] = p
218 return permissions
218 return permissions
219
219
220 #==================================================================
220 #==================================================================
221 # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
221 # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
222 #==================================================================
222 #==================================================================
223 uid = user_id
223 uid = user_id
224
224
225 # default global permissions taken from the default user
225 # default global permissions taken from the default user
226 default_global_perms = UserToPerm.query()\
226 default_global_perms = UserToPerm.query()\
227 .filter(UserToPerm.user_id == default_user_id)\
227 .filter(UserToPerm.user_id == default_user_id)\
228 .options(joinedload(UserToPerm.permission))
228 .options(joinedload(UserToPerm.permission))
229
229
230 for perm in default_global_perms:
230 for perm in default_global_perms:
231 permissions[GLOBAL].add(perm.permission.permission_name)
231 permissions[GLOBAL].add(perm.permission.permission_name)
232
232
233 # defaults for repositories, taken from default user
233 # defaults for repositories, taken from default user
234 for perm in default_repo_perms:
234 for perm in default_repo_perms:
235 r_k = perm.UserRepoToPerm.repository.repo_name
235 r_k = perm.UserRepoToPerm.repository.repo_name
236 if perm.Repository.private and not (perm.Repository.user_id == uid):
236 if perm.Repository.private and not (perm.Repository.user_id == uid):
237 # disable defaults for private repos,
237 # disable defaults for private repos,
238 p = 'repository.none'
238 p = 'repository.none'
239 elif perm.Repository.user_id == uid:
239 elif perm.Repository.user_id == uid:
240 # set admin if owner
240 # set admin if owner
241 p = 'repository.admin'
241 p = 'repository.admin'
242 else:
242 else:
243 p = perm.Permission.permission_name
243 p = perm.Permission.permission_name
244
244
245 permissions[RK][r_k] = p
245 permissions[RK][r_k] = p
246
246
247 # defaults for repository groups taken from default user permission
247 # defaults for repository groups taken from default user permission
248 # on given group
248 # on given group
249 for perm in default_repo_groups_perms:
249 for perm in default_repo_groups_perms:
250 rg_k = perm.UserRepoGroupToPerm.group.group_name
250 rg_k = perm.UserRepoGroupToPerm.group.group_name
251 p = perm.Permission.permission_name
251 p = perm.Permission.permission_name
252 permissions[GK][rg_k] = p
252 permissions[GK][rg_k] = p
253
253
254 # defaults for user groups taken from default user permission
254 # defaults for user groups taken from default user permission
255 # on given user group
255 # on given user group
256 for perm in default_user_group_perms:
256 for perm in default_user_group_perms:
257 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
257 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
258 p = perm.Permission.permission_name
258 p = perm.Permission.permission_name
259 permissions[UK][u_k] = p
259 permissions[UK][u_k] = p
260
260
261 #======================================================================
261 #======================================================================
262 # !! OVERRIDE GLOBALS !! with user permissions if any found
262 # !! OVERRIDE GLOBALS !! with user permissions if any found
263 #======================================================================
263 #======================================================================
264 # those can be configured from groups or users explicitly
264 # those can be configured from groups or users explicitly
265 _configurable = set([
265 _configurable = set([
266 'hg.fork.none', 'hg.fork.repository',
266 'hg.fork.none', 'hg.fork.repository',
267 'hg.create.none', 'hg.create.repository',
267 'hg.create.none', 'hg.create.repository',
268 'hg.usergroup.create.false', 'hg.usergroup.create.true'
268 'hg.usergroup.create.false', 'hg.usergroup.create.true'
269 ])
269 ])
270
270
271 # USER GROUPS comes first
271 # USER GROUPS comes first
272 # user group global permissions
272 # user group global permissions
273 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
273 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
274 .options(joinedload(UserGroupToPerm.permission))\
274 .options(joinedload(UserGroupToPerm.permission))\
275 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
275 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
276 UserGroupMember.users_group_id))\
276 UserGroupMember.users_group_id))\
277 .filter(UserGroupMember.user_id == uid)\
277 .filter(UserGroupMember.user_id == uid)\
278 .order_by(UserGroupToPerm.users_group_id)\
278 .order_by(UserGroupToPerm.users_group_id)\
279 .all()
279 .all()
280 # need to group here by groups since user can be in more than
280 # need to group here by groups since user can be in more than
281 # one group
281 # one group
282 _grouped = [[x, list(y)] for x, y in
282 _grouped = [[x, list(y)] for x, y in
283 itertools.groupby(user_perms_from_users_groups,
283 itertools.groupby(user_perms_from_users_groups,
284 lambda x:x.users_group)]
284 lambda x:x.users_group)]
285 for gr, perms in _grouped:
285 for gr, perms in _grouped:
286 # since user can be in multiple groups iterate over them and
286 # since user can be in multiple groups iterate over them and
287 # select the lowest permissions first (more explicit)
287 # select the lowest permissions first (more explicit)
288 ##TODO: do this^^
288 ##TODO: do this^^
289 if not gr.inherit_default_permissions:
289 if not gr.inherit_default_permissions:
290 # NEED TO IGNORE all configurable permissions and
290 # NEED TO IGNORE all configurable permissions and
291 # replace them with explicitly set
291 # replace them with explicitly set
292 permissions[GLOBAL] = permissions[GLOBAL]\
292 permissions[GLOBAL] = permissions[GLOBAL]\
293 .difference(_configurable)
293 .difference(_configurable)
294 for perm in perms:
294 for perm in perms:
295 permissions[GLOBAL].add(perm.permission.permission_name)
295 permissions[GLOBAL].add(perm.permission.permission_name)
296
296
297 # user specific global permissions
297 # user specific global permissions
298 user_perms = Session().query(UserToPerm)\
298 user_perms = Session().query(UserToPerm)\
299 .options(joinedload(UserToPerm.permission))\
299 .options(joinedload(UserToPerm.permission))\
300 .filter(UserToPerm.user_id == uid).all()
300 .filter(UserToPerm.user_id == uid).all()
301
301
302 if not user_inherit_default_permissions:
302 if not user_inherit_default_permissions:
303 # NEED TO IGNORE all configurable permissions and
303 # NEED TO IGNORE all configurable permissions and
304 # replace them with explicitly set
304 # replace them with explicitly set
305 permissions[GLOBAL] = permissions[GLOBAL]\
305 permissions[GLOBAL] = permissions[GLOBAL]\
306 .difference(_configurable)
306 .difference(_configurable)
307
307
308 for perm in user_perms:
308 for perm in user_perms:
309 permissions[GLOBAL].add(perm.permission.permission_name)
309 permissions[GLOBAL].add(perm.permission.permission_name)
310 ## END GLOBAL PERMISSIONS
310 ## END GLOBAL PERMISSIONS
311
311
312 #======================================================================
312 #======================================================================
313 # !! PERMISSIONS FOR REPOSITORIES !!
313 # !! PERMISSIONS FOR REPOSITORIES !!
314 #======================================================================
314 #======================================================================
315 #======================================================================
315 #======================================================================
316 # check if user is part of user groups for this repository and
316 # check if user is part of user groups for this repository and
317 # fill in his permission from it. _choose_perm decides of which
317 # fill in his permission from it. _choose_perm decides of which
318 # permission should be selected based on selected method
318 # permission should be selected based on selected method
319 #======================================================================
319 #======================================================================
320
320
321 # user group for repositories permissions
321 # user group for repositories permissions
322 user_repo_perms_from_users_groups = \
322 user_repo_perms_from_users_groups = \
323 Session().query(UserGroupRepoToPerm, Permission, Repository,)\
323 Session().query(UserGroupRepoToPerm, Permission, Repository,)\
324 .join((Repository, UserGroupRepoToPerm.repository_id ==
324 .join((Repository, UserGroupRepoToPerm.repository_id ==
325 Repository.repo_id))\
325 Repository.repo_id))\
326 .join((Permission, UserGroupRepoToPerm.permission_id ==
326 .join((Permission, UserGroupRepoToPerm.permission_id ==
327 Permission.permission_id))\
327 Permission.permission_id))\
328 .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
328 .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
329 UserGroupMember.users_group_id))\
329 UserGroupMember.users_group_id))\
330 .filter(UserGroupMember.user_id == uid)\
330 .filter(UserGroupMember.user_id == uid)\
331 .all()
331 .all()
332
332
333 multiple_counter = collections.defaultdict(int)
333 multiple_counter = collections.defaultdict(int)
334 for perm in user_repo_perms_from_users_groups:
334 for perm in user_repo_perms_from_users_groups:
335 r_k = perm.UserGroupRepoToPerm.repository.repo_name
335 r_k = perm.UserGroupRepoToPerm.repository.repo_name
336 multiple_counter[r_k] += 1
336 multiple_counter[r_k] += 1
337 p = perm.Permission.permission_name
337 p = perm.Permission.permission_name
338 cur_perm = permissions[RK][r_k]
338 cur_perm = permissions[RK][r_k]
339
339
340 if perm.Repository.user_id == uid:
340 if perm.Repository.user_id == uid:
341 # set admin if owner
341 # set admin if owner
342 p = 'repository.admin'
342 p = 'repository.admin'
343 else:
343 else:
344 if multiple_counter[r_k] > 1:
344 if multiple_counter[r_k] > 1:
345 p = _choose_perm(p, cur_perm)
345 p = _choose_perm(p, cur_perm)
346 permissions[RK][r_k] = p
346 permissions[RK][r_k] = p
347
347
348 # user explicit permissions for repositories, overrides any specified
348 # user explicit permissions for repositories, overrides any specified
349 # by the group permission
349 # by the group permission
350 user_repo_perms = Permission.get_default_perms(uid)
350 user_repo_perms = Permission.get_default_perms(uid)
351 for perm in user_repo_perms:
351 for perm in user_repo_perms:
352 r_k = perm.UserRepoToPerm.repository.repo_name
352 r_k = perm.UserRepoToPerm.repository.repo_name
353 cur_perm = permissions[RK][r_k]
353 cur_perm = permissions[RK][r_k]
354 # set admin if owner
354 # set admin if owner
355 if perm.Repository.user_id == uid:
355 if perm.Repository.user_id == uid:
356 p = 'repository.admin'
356 p = 'repository.admin'
357 else:
357 else:
358 p = perm.Permission.permission_name
358 p = perm.Permission.permission_name
359 if not explicit:
359 if not explicit:
360 p = _choose_perm(p, cur_perm)
360 p = _choose_perm(p, cur_perm)
361 permissions[RK][r_k] = p
361 permissions[RK][r_k] = p
362
362
363 #======================================================================
363 #======================================================================
364 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
364 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
365 #======================================================================
365 #======================================================================
366 #======================================================================
366 #======================================================================
367 # check if user is part of user groups for this repository groups and
367 # check if user is part of user groups for this repository groups and
368 # fill in his permission from it. _choose_perm decides of which
368 # fill in his permission from it. _choose_perm decides of which
369 # permission should be selected based on selected method
369 # permission should be selected based on selected method
370 #======================================================================
370 #======================================================================
371 # user group for repo groups permissions
371 # user group for repo groups permissions
372 user_repo_group_perms_from_users_groups = \
372 user_repo_group_perms_from_users_groups = \
373 Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
373 Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
374 .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
374 .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
375 .join((Permission, UserGroupRepoGroupToPerm.permission_id
375 .join((Permission, UserGroupRepoGroupToPerm.permission_id
376 == Permission.permission_id))\
376 == Permission.permission_id))\
377 .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
377 .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
378 == UserGroupMember.users_group_id))\
378 == UserGroupMember.users_group_id))\
379 .filter(UserGroupMember.user_id == uid)\
379 .filter(UserGroupMember.user_id == uid)\
380 .all()
380 .all()
381
381
382 multiple_counter = collections.defaultdict(int)
382 multiple_counter = collections.defaultdict(int)
383 for perm in user_repo_group_perms_from_users_groups:
383 for perm in user_repo_group_perms_from_users_groups:
384 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
384 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
385 multiple_counter[g_k] += 1
385 multiple_counter[g_k] += 1
386 p = perm.Permission.permission_name
386 p = perm.Permission.permission_name
387 cur_perm = permissions[GK][g_k]
387 cur_perm = permissions[GK][g_k]
388 if multiple_counter[g_k] > 1:
388 if multiple_counter[g_k] > 1:
389 p = _choose_perm(p, cur_perm)
389 p = _choose_perm(p, cur_perm)
390 permissions[GK][g_k] = p
390 permissions[GK][g_k] = p
391
391
392 # user explicit permissions for repository groups
392 # user explicit permissions for repository groups
393 user_repo_groups_perms = Permission.get_default_group_perms(uid)
393 user_repo_groups_perms = Permission.get_default_group_perms(uid)
394 for perm in user_repo_groups_perms:
394 for perm in user_repo_groups_perms:
395 rg_k = perm.UserRepoGroupToPerm.group.group_name
395 rg_k = perm.UserRepoGroupToPerm.group.group_name
396 p = perm.Permission.permission_name
396 p = perm.Permission.permission_name
397 cur_perm = permissions[GK][rg_k]
397 cur_perm = permissions[GK][rg_k]
398 if not explicit:
398 if not explicit:
399 p = _choose_perm(p, cur_perm)
399 p = _choose_perm(p, cur_perm)
400 permissions[GK][rg_k] = p
400 permissions[GK][rg_k] = p
401
401
402 #======================================================================
402 #======================================================================
403 # !! PERMISSIONS FOR USER GROUPS !!
403 # !! PERMISSIONS FOR USER GROUPS !!
404 #======================================================================
404 #======================================================================
405 # user group for user group permissions
405 # user group for user group permissions
406 user_group_user_groups_perms = \
406 user_group_user_groups_perms = \
407 Session().query(UserGroupUserGroupToPerm, Permission, UserGroup)\
407 Session().query(UserGroupUserGroupToPerm, Permission, UserGroup)\
408 .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
408 .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
409 == UserGroup.users_group_id))\
409 == UserGroup.users_group_id))\
410 .join((Permission, UserGroupUserGroupToPerm.permission_id
410 .join((Permission, UserGroupUserGroupToPerm.permission_id
411 == Permission.permission_id))\
411 == Permission.permission_id))\
412 .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
412 .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
413 == UserGroupMember.users_group_id))\
413 == UserGroupMember.users_group_id))\
414 .filter(UserGroupMember.user_id == uid)\
414 .filter(UserGroupMember.user_id == uid)\
415 .all()
415 .all()
416
416
417 multiple_counter = collections.defaultdict(int)
417 multiple_counter = collections.defaultdict(int)
418 for perm in user_group_user_groups_perms:
418 for perm in user_group_user_groups_perms:
419 g_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
419 g_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
420 multiple_counter[g_k] += 1
420 multiple_counter[g_k] += 1
421 p = perm.Permission.permission_name
421 p = perm.Permission.permission_name
422 cur_perm = permissions[UK][g_k]
422 cur_perm = permissions[UK][g_k]
423 if multiple_counter[g_k] > 1:
423 if multiple_counter[g_k] > 1:
424 p = _choose_perm(p, cur_perm)
424 p = _choose_perm(p, cur_perm)
425 permissions[UK][g_k] = p
425 permissions[UK][g_k] = p
426
426
427 #user explicit permission for user groups
427 #user explicit permission for user groups
428 user_user_groups_perms = Permission.get_default_user_group_perms(uid)
428 user_user_groups_perms = Permission.get_default_user_group_perms(uid)
429 for perm in user_user_groups_perms:
429 for perm in user_user_groups_perms:
430 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
430 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
431 p = perm.Permission.permission_name
431 p = perm.Permission.permission_name
432 cur_perm = permissions[UK][u_k]
432 cur_perm = permissions[UK][u_k]
433 if not explicit:
433 if not explicit:
434 p = _choose_perm(p, cur_perm)
434 p = _choose_perm(p, cur_perm)
435 permissions[UK][u_k] = p
435 permissions[UK][u_k] = p
436
436
437 return permissions
437 return permissions
438
438
439
439
440 def allowed_api_access(controller_name, whitelist=None, api_key=None):
440 def allowed_api_access(controller_name, whitelist=None, api_key=None):
441 """
441 """
442 Check if given controller_name is in whitelist API access
442 Check if given controller_name is in whitelist API access
443 """
443 """
444 if not whitelist:
444 if not whitelist:
445 from kallithea import CONFIG
445 from kallithea import CONFIG
446 whitelist = aslist(CONFIG.get('api_access_controllers_whitelist'),
446 whitelist = aslist(CONFIG.get('api_access_controllers_whitelist'),
447 sep=',')
447 sep=',')
448 log.debug('whitelist of API access is: %s' % (whitelist))
448 log.debug('whitelist of API access is: %s' % (whitelist))
449 api_access_valid = controller_name in whitelist
449 api_access_valid = controller_name in whitelist
450 if api_access_valid:
450 if api_access_valid:
451 log.debug('controller:%s is in API whitelist' % (controller_name))
451 log.debug('controller:%s is in API whitelist' % (controller_name))
452 else:
452 else:
453 msg = 'controller: %s is *NOT* in API whitelist' % (controller_name)
453 msg = 'controller: %s is *NOT* in API whitelist' % (controller_name)
454 if api_key:
454 if api_key:
455 #if we use API key and don't have access it's a warning
455 #if we use API key and don't have access it's a warning
456 log.warning(msg)
456 log.warning(msg)
457 else:
457 else:
458 log.debug(msg)
458 log.debug(msg)
459 return api_access_valid
459 return api_access_valid
460
460
461
461
462 class AuthUser(object):
462 class AuthUser(object):
463 """
463 """
464 A simple object that handles all attributes of user in Kallithea
464 A simple object that handles all attributes of user in Kallithea
465
465
466 It does lookup based on API key,given user, or user present in session
466 It does lookup based on API key,given user, or user present in session
467 Then it fills all required information for such user. It also checks if
467 Then it fills all required information for such user. It also checks if
468 anonymous access is enabled and if so, it returns default user as logged in
468 anonymous access is enabled and if so, it returns default user as logged in
469 """
469 """
470
470
471 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
471 def __init__(self, user_id=None, api_key=None, username=None):
472
472
473 self.user_id = user_id
473 self.user_id = user_id
474 self._api_key = api_key
474 self._api_key = api_key
475
475
476 self.api_key = None
476 self.api_key = None
477 self.username = username
477 self.username = username
478 self.ip_addr = ip_addr
479 self.name = ''
478 self.name = ''
480 self.lastname = ''
479 self.lastname = ''
481 self.email = ''
480 self.email = ''
482 self.is_authenticated = False
481 self.is_authenticated = False
483 self.admin = False
482 self.admin = False
484 self.inherit_default_permissions = False
483 self.inherit_default_permissions = False
485
484
486 self.propagate_data()
485 self.propagate_data()
487 self._instance = None
486 self._instance = None
488
487
489 @LazyProperty
488 @LazyProperty
490 def permissions(self):
489 def permissions(self):
491 return self.get_perms(user=self, cache=False)
490 return self.get_perms(user=self, cache=False)
492
491
493 @property
492 @property
494 def api_keys(self):
493 def api_keys(self):
495 return self.get_api_keys()
494 return self.get_api_keys()
496
495
497 def propagate_data(self):
496 def propagate_data(self):
498 user_model = UserModel()
497 user_model = UserModel()
499 self.anonymous_user = User.get_default_user(cache=True)
498 self.anonymous_user = User.get_default_user(cache=True)
500 is_user_loaded = False
499 is_user_loaded = False
501
500
502 # lookup by userid
501 # lookup by userid
503 if self.user_id is not None and self.user_id != self.anonymous_user.user_id:
502 if self.user_id is not None and self.user_id != self.anonymous_user.user_id:
504 log.debug('Auth User lookup by USER ID %s' % self.user_id)
503 log.debug('Auth User lookup by USER ID %s' % self.user_id)
505 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
504 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
506
505
507 # try go get user by API key
506 # try go get user by API key
508 elif self._api_key and self._api_key != self.anonymous_user.api_key:
507 elif self._api_key and self._api_key != self.anonymous_user.api_key:
509 log.debug('Auth User lookup by API key %s' % self._api_key)
508 log.debug('Auth User lookup by API key %s' % self._api_key)
510 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
509 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
511
510
512 # lookup by username
511 # lookup by username
513 elif self.username:
512 elif self.username:
514 log.debug('Auth User lookup by USER NAME %s' % self.username)
513 log.debug('Auth User lookup by USER NAME %s' % self.username)
515 is_user_loaded = user_model.fill_data(self, username=self.username)
514 is_user_loaded = user_model.fill_data(self, username=self.username)
516 else:
515 else:
517 log.debug('No data in %s that could been used to log in' % self)
516 log.debug('No data in %s that could been used to log in' % self)
518
517
519 if not is_user_loaded:
518 if not is_user_loaded:
520 # if we cannot authenticate user try anonymous
519 # if we cannot authenticate user try anonymous
521 if self.anonymous_user.active:
520 if self.anonymous_user.active:
522 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
521 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
523 # then we set this user is logged in
522 # then we set this user is logged in
524 self.is_authenticated = True
523 self.is_authenticated = True
525 else:
524 else:
526 self.user_id = None
525 self.user_id = None
527 self.username = None
526 self.username = None
528 self.is_authenticated = False
527 self.is_authenticated = False
529
528
530 if not self.username:
529 if not self.username:
531 self.username = 'None'
530 self.username = 'None'
532
531
533 log.debug('Auth User is now %s' % self)
532 log.debug('Auth User is now %s' % self)
534
533
535 def get_perms(self, user, explicit=True, algo='higherwin', cache=False):
534 def get_perms(self, user, explicit=True, algo='higherwin', cache=False):
536 """
535 """
537 Fills user permission attribute with permissions taken from database
536 Fills user permission attribute with permissions taken from database
538 works for permissions given for repositories, and for permissions that
537 works for permissions given for repositories, and for permissions that
539 are granted to groups
538 are granted to groups
540
539
541 :param user: instance of User object from database
540 :param user: instance of User object from database
542 :param explicit: In case there are permissions both for user and a group
541 :param explicit: In case there are permissions both for user and a group
543 that user is part of, explicit flag will define if user will
542 that user is part of, explicit flag will define if user will
544 explicitly override permissions from group, if it's False it will
543 explicitly override permissions from group, if it's False it will
545 make decision based on the algo
544 make decision based on the algo
546 :param algo: algorithm to decide what permission should be choose if
545 :param algo: algorithm to decide what permission should be choose if
547 it's multiple defined, eg user in two different groups. It also
546 it's multiple defined, eg user in two different groups. It also
548 decides if explicit flag is turned off how to specify the permission
547 decides if explicit flag is turned off how to specify the permission
549 for case when user is in a group + have defined separate permission
548 for case when user is in a group + have defined separate permission
550 """
549 """
551 user_id = user.user_id
550 user_id = user.user_id
552 user_is_admin = user.is_admin
551 user_is_admin = user.is_admin
553 user_inherit_default_permissions = user.inherit_default_permissions
552 user_inherit_default_permissions = user.inherit_default_permissions
554
553
555 log.debug('Getting PERMISSION tree')
554 log.debug('Getting PERMISSION tree')
556 compute = conditional_cache('short_term', 'cache_desc',
555 compute = conditional_cache('short_term', 'cache_desc',
557 condition=cache, func=_cached_perms_data)
556 condition=cache, func=_cached_perms_data)
558 return compute(user_id, user_is_admin,
557 return compute(user_id, user_is_admin,
559 user_inherit_default_permissions, explicit, algo)
558 user_inherit_default_permissions, explicit, algo)
560
559
561 def get_api_keys(self):
560 def get_api_keys(self):
562 api_keys = [self.api_key]
561 api_keys = [self.api_key]
563 for api_key in UserApiKeys.query()\
562 for api_key in UserApiKeys.query()\
564 .filter(UserApiKeys.user_id == self.user_id)\
563 .filter(UserApiKeys.user_id == self.user_id)\
565 .filter(or_(UserApiKeys.expires == -1,
564 .filter(or_(UserApiKeys.expires == -1,
566 UserApiKeys.expires >= time.time())).all():
565 UserApiKeys.expires >= time.time())).all():
567 api_keys.append(api_key.api_key)
566 api_keys.append(api_key.api_key)
568
567
569 return api_keys
568 return api_keys
570
569
571 @property
570 @property
572 def is_admin(self):
571 def is_admin(self):
573 return self.admin
572 return self.admin
574
573
575 @property
574 @property
576 def repositories_admin(self):
575 def repositories_admin(self):
577 """
576 """
578 Returns list of repositories you're an admin of
577 Returns list of repositories you're an admin of
579 """
578 """
580 return [x[0] for x in self.permissions['repositories'].iteritems()
579 return [x[0] for x in self.permissions['repositories'].iteritems()
581 if x[1] == 'repository.admin']
580 if x[1] == 'repository.admin']
582
581
583 @property
582 @property
584 def repository_groups_admin(self):
583 def repository_groups_admin(self):
585 """
584 """
586 Returns list of repository groups you're an admin of
585 Returns list of repository groups you're an admin of
587 """
586 """
588 return [x[0] for x in self.permissions['repositories_groups'].iteritems()
587 return [x[0] for x in self.permissions['repositories_groups'].iteritems()
589 if x[1] == 'group.admin']
588 if x[1] == 'group.admin']
590
589
591 @property
590 @property
592 def user_groups_admin(self):
591 def user_groups_admin(self):
593 """
592 """
594 Returns list of user groups you're an admin of
593 Returns list of user groups you're an admin of
595 """
594 """
596 return [x[0] for x in self.permissions['user_groups'].iteritems()
595 return [x[0] for x in self.permissions['user_groups'].iteritems()
597 if x[1] == 'usergroup.admin']
596 if x[1] == 'usergroup.admin']
598
597
599 @property
598 def is_ip_allowed(self, ip_addr):
600 def ip_allowed(self):
601 """
599 """
602 Checks if ip_addr used in constructor is allowed from defined list of
600 Determine if `ip_addr` is on the list of allowed IP addresses
603 allowed ip_addresses for user
601 for this user.
604
605 :returns: boolean, True if ip is in allowed ip range
606 """
602 """
607 # check IP
608 inherit = self.inherit_default_permissions
603 inherit = self.inherit_default_permissions
609 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
604 return AuthUser.check_ip_allowed(self.user_id, ip_addr,
610 inherit_from_default=inherit)
605 inherit_from_default=inherit)
611
606
612 @classmethod
607 @classmethod
613 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
608 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
614 allowed_ips = AuthUser.get_allowed_ips(user_id, cache=True,
609 allowed_ips = AuthUser.get_allowed_ips(user_id, cache=True,
615 inherit_from_default=inherit_from_default)
610 inherit_from_default=inherit_from_default)
616 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
611 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
617 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
612 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
618 return True
613 return True
619 else:
614 else:
620 log.info('Access for IP:%s forbidden, '
615 log.info('Access for IP:%s forbidden, '
621 'not in %s' % (ip_addr, allowed_ips))
616 'not in %s' % (ip_addr, allowed_ips))
622 return False
617 return False
623
618
624 def __repr__(self):
619 def __repr__(self):
625 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
620 return "<AuthUser('id:%s[%s] auth:%s')>"\
626 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
621 % (self.user_id, self.username, self.is_authenticated)
627
622
628 def set_authenticated(self, authenticated=True):
623 def set_authenticated(self, authenticated=True):
629 if self.user_id != self.anonymous_user.user_id:
624 if self.user_id != self.anonymous_user.user_id:
630 self.is_authenticated = authenticated
625 self.is_authenticated = authenticated
631
626
632 def get_cookie_store(self):
627 def get_cookie_store(self):
633 return {'username': self.username,
628 return {'username': self.username,
634 'user_id': self.user_id,
629 'user_id': self.user_id,
635 'is_authenticated': self.is_authenticated}
630 'is_authenticated': self.is_authenticated}
636
631
637 @classmethod
632 @classmethod
638 def from_cookie_store(cls, cookie_store):
633 def from_cookie_store(cls, cookie_store):
639 """
634 """
640 Creates AuthUser from a cookie store
635 Creates AuthUser from a cookie store
641
636
642 :param cls:
637 :param cls:
643 :param cookie_store:
638 :param cookie_store:
644 """
639 """
645 user_id = cookie_store.get('user_id')
640 user_id = cookie_store.get('user_id')
646 username = cookie_store.get('username')
641 username = cookie_store.get('username')
647 api_key = cookie_store.get('api_key')
642 api_key = cookie_store.get('api_key')
648 return AuthUser(user_id, api_key, username)
643 return AuthUser(user_id, api_key, username)
649
644
650 @classmethod
645 @classmethod
651 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
646 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
652 _set = set()
647 _set = set()
653
648
654 if inherit_from_default:
649 if inherit_from_default:
655 default_ips = UserIpMap.query().filter(UserIpMap.user ==
650 default_ips = UserIpMap.query().filter(UserIpMap.user ==
656 User.get_default_user(cache=True))
651 User.get_default_user(cache=True))
657 if cache:
652 if cache:
658 default_ips = default_ips.options(FromCache("sql_cache_short",
653 default_ips = default_ips.options(FromCache("sql_cache_short",
659 "get_user_ips_default"))
654 "get_user_ips_default"))
660
655
661 # populate from default user
656 # populate from default user
662 for ip in default_ips:
657 for ip in default_ips:
663 try:
658 try:
664 _set.add(ip.ip_addr)
659 _set.add(ip.ip_addr)
665 except ObjectDeletedError:
660 except ObjectDeletedError:
666 # since we use heavy caching sometimes it happens that we get
661 # since we use heavy caching sometimes it happens that we get
667 # deleted objects here, we just skip them
662 # deleted objects here, we just skip them
668 pass
663 pass
669
664
670 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
665 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
671 if cache:
666 if cache:
672 user_ips = user_ips.options(FromCache("sql_cache_short",
667 user_ips = user_ips.options(FromCache("sql_cache_short",
673 "get_user_ips_%s" % user_id))
668 "get_user_ips_%s" % user_id))
674
669
675 for ip in user_ips:
670 for ip in user_ips:
676 try:
671 try:
677 _set.add(ip.ip_addr)
672 _set.add(ip.ip_addr)
678 except ObjectDeletedError:
673 except ObjectDeletedError:
679 # since we use heavy caching sometimes it happens that we get
674 # since we use heavy caching sometimes it happens that we get
680 # deleted objects here, we just skip them
675 # deleted objects here, we just skip them
681 pass
676 pass
682 return _set or set(['0.0.0.0/0', '::/0'])
677 return _set or set(['0.0.0.0/0', '::/0'])
683
678
684
679
685 def set_available_permissions(config):
680 def set_available_permissions(config):
686 """
681 """
687 This function will propagate pylons globals with all available defined
682 This function will propagate pylons globals with all available defined
688 permission given in db. We don't want to check each time from db for new
683 permission given in db. We don't want to check each time from db for new
689 permissions since adding a new permission also requires application restart
684 permissions since adding a new permission also requires application restart
690 ie. to decorate new views with the newly created permission
685 ie. to decorate new views with the newly created permission
691
686
692 :param config: current pylons config instance
687 :param config: current pylons config instance
693
688
694 """
689 """
695 log.info('getting information about all available permissions')
690 log.info('getting information about all available permissions')
696 try:
691 try:
697 sa = meta.Session
692 sa = meta.Session
698 all_perms = sa.query(Permission).all()
693 all_perms = sa.query(Permission).all()
699 config['available_permissions'] = [x.permission_name for x in all_perms]
694 config['available_permissions'] = [x.permission_name for x in all_perms]
700 finally:
695 finally:
701 meta.Session.remove()
696 meta.Session.remove()
702
697
703
698
704 #==============================================================================
699 #==============================================================================
705 # CHECK DECORATORS
700 # CHECK DECORATORS
706 #==============================================================================
701 #==============================================================================
707
702
708 def redirect_to_login(message=None):
703 def redirect_to_login(message=None):
709 from kallithea.lib import helpers as h
704 from kallithea.lib import helpers as h
710 p = url.current()
705 p = url.current()
711 if message:
706 if message:
712 h.flash(h.literal(message), category='warning')
707 h.flash(h.literal(message), category='warning')
713 log.debug('Redirecting to login page, origin: %s' % p)
708 log.debug('Redirecting to login page, origin: %s' % p)
714 return redirect(url('login_home', came_from=p, **request.GET))
709 return redirect(url('login_home', came_from=p, **request.GET))
715
710
716 class LoginRequired(object):
711 class LoginRequired(object):
717 """
712 """
718 Must be logged in to execute this function else
713 Must be logged in to execute this function else
719 redirect to login page
714 redirect to login page
720
715
721 :param api_access: if enabled this checks only for valid auth token
716 :param api_access: if enabled this checks only for valid auth token
722 and grants access based on valid token
717 and grants access based on valid token
723 """
718 """
724
719
725 def __init__(self, api_access=False):
720 def __init__(self, api_access=False):
726 self.api_access = api_access
721 self.api_access = api_access
727
722
728 def __call__(self, func):
723 def __call__(self, func):
729 return decorator(self.__wrapper, func)
724 return decorator(self.__wrapper, func)
730
725
731 def __wrapper(self, func, *fargs, **fkwargs):
726 def __wrapper(self, func, *fargs, **fkwargs):
732 cls = fargs[0]
727 controller = fargs[0]
733 user = cls.authuser
728 user = controller.authuser
734 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
729 loc = "%s:%s" % (controller.__class__.__name__, func.__name__)
735 log.debug('Checking access for user %s @ %s' % (user, loc))
730 log.debug('Checking access for user %s @ %s' % (user, loc))
736
731
737 # check if our IP is allowed
732 # check if our IP is allowed
738 if not user.ip_allowed:
733 if not user.is_ip_allowed(controller.ip_addr):
739 return redirect_to_login(_('IP %s not allowed' % (user.ip_addr)))
734 return redirect_to_login(_('IP %s not allowed') % controller.ip_addr)
740
735
741 # check if we used an API key and it's a valid one
736 # check if we used an API key and it's a valid one
742 api_key = request.GET.get('api_key')
737 api_key = request.GET.get('api_key')
743 if api_key is not None:
738 if api_key is not None:
744 # explicit controller is enabled or API is in our whitelist
739 # explicit controller is enabled or API is in our whitelist
745 if self.api_access or allowed_api_access(loc, api_key=api_key):
740 if self.api_access or allowed_api_access(loc, api_key=api_key):
746 if api_key in user.api_keys:
741 if api_key in user.api_keys:
747 log.info('user %s authenticated with API key ****%s @ %s'
742 log.info('user %s authenticated with API key ****%s @ %s'
748 % (user, api_key[-4:], loc))
743 % (user, api_key[-4:], loc))
749 return func(*fargs, **fkwargs)
744 return func(*fargs, **fkwargs)
750 else:
745 else:
751 log.warning('API key ****%s is NOT valid' % api_key[-4:])
746 log.warning('API key ****%s is NOT valid' % api_key[-4:])
752 return redirect_to_login(_('Invalid API key'))
747 return redirect_to_login(_('Invalid API key'))
753 else:
748 else:
754 # controller does not allow API access
749 # controller does not allow API access
755 log.warning('API access to %s is not allowed' % loc)
750 log.warning('API access to %s is not allowed' % loc)
756 return abort(403)
751 return abort(403)
757
752
758 # CSRF protection - POSTs with session auth must contain correct token
753 # CSRF protection - POSTs with session auth must contain correct token
759 if request.POST and user.is_authenticated:
754 if request.POST and user.is_authenticated:
760 token = request.POST.get(secure_form.token_key)
755 token = request.POST.get(secure_form.token_key)
761 if not token or token != secure_form.authentication_token():
756 if not token or token != secure_form.authentication_token():
762 log.error('CSRF check failed')
757 log.error('CSRF check failed')
763 return abort(403)
758 return abort(403)
764
759
765 # regular user authentication
760 # regular user authentication
766 if user.is_authenticated:
761 if user.is_authenticated:
767 log.info('user %s authenticated with regular auth @ %s' % (user, loc))
762 log.info('user %s authenticated with regular auth @ %s' % (user, loc))
768 return func(*fargs, **fkwargs)
763 return func(*fargs, **fkwargs)
769 else:
764 else:
770 log.warning('user %s NOT authenticated with regular auth @ %s' % (user, loc))
765 log.warning('user %s NOT authenticated with regular auth @ %s' % (user, loc))
771 return redirect_to_login()
766 return redirect_to_login()
772
767
773 class NotAnonymous(object):
768 class NotAnonymous(object):
774 """
769 """
775 Must be logged in to execute this function else
770 Must be logged in to execute this function else
776 redirect to login page"""
771 redirect to login page"""
777
772
778 def __call__(self, func):
773 def __call__(self, func):
779 return decorator(self.__wrapper, func)
774 return decorator(self.__wrapper, func)
780
775
781 def __wrapper(self, func, *fargs, **fkwargs):
776 def __wrapper(self, func, *fargs, **fkwargs):
782 cls = fargs[0]
777 cls = fargs[0]
783 self.user = cls.authuser
778 self.user = cls.authuser
784
779
785 log.debug('Checking if user is not anonymous @%s' % cls)
780 log.debug('Checking if user is not anonymous @%s' % cls)
786
781
787 anonymous = self.user.username == User.DEFAULT_USER
782 anonymous = self.user.username == User.DEFAULT_USER
788
783
789 if anonymous:
784 if anonymous:
790 return redirect_to_login(_('You need to be a registered user to '
785 return redirect_to_login(_('You need to be a registered user to '
791 'perform this action'))
786 'perform this action'))
792 else:
787 else:
793 return func(*fargs, **fkwargs)
788 return func(*fargs, **fkwargs)
794
789
795
790
796 class PermsDecorator(object):
791 class PermsDecorator(object):
797 """Base class for controller decorators"""
792 """Base class for controller decorators"""
798
793
799 def __init__(self, *required_perms):
794 def __init__(self, *required_perms):
800 self.required_perms = set(required_perms)
795 self.required_perms = set(required_perms)
801 self.user_perms = None
796 self.user_perms = None
802
797
803 def __call__(self, func):
798 def __call__(self, func):
804 return decorator(self.__wrapper, func)
799 return decorator(self.__wrapper, func)
805
800
806 def __wrapper(self, func, *fargs, **fkwargs):
801 def __wrapper(self, func, *fargs, **fkwargs):
807 cls = fargs[0]
802 cls = fargs[0]
808 self.user = cls.authuser
803 self.user = cls.authuser
809 self.user_perms = self.user.permissions
804 self.user_perms = self.user.permissions
810 log.debug('checking %s permissions %s for %s %s',
805 log.debug('checking %s permissions %s for %s %s',
811 self.__class__.__name__, self.required_perms, cls, self.user)
806 self.__class__.__name__, self.required_perms, cls, self.user)
812
807
813 if self.check_permissions():
808 if self.check_permissions():
814 log.debug('Permission granted for %s %s' % (cls, self.user))
809 log.debug('Permission granted for %s %s' % (cls, self.user))
815 return func(*fargs, **fkwargs)
810 return func(*fargs, **fkwargs)
816
811
817 else:
812 else:
818 log.debug('Permission denied for %s %s' % (cls, self.user))
813 log.debug('Permission denied for %s %s' % (cls, self.user))
819 anonymous = self.user.username == User.DEFAULT_USER
814 anonymous = self.user.username == User.DEFAULT_USER
820
815
821 if anonymous:
816 if anonymous:
822 return redirect_to_login(_('You need to be signed in to view this page'))
817 return redirect_to_login(_('You need to be signed in to view this page'))
823 else:
818 else:
824 # redirect with forbidden ret code
819 # redirect with forbidden ret code
825 return abort(403)
820 return abort(403)
826
821
827 def check_permissions(self):
822 def check_permissions(self):
828 """Dummy function for overriding"""
823 """Dummy function for overriding"""
829 raise Exception('You have to write this function in child class')
824 raise Exception('You have to write this function in child class')
830
825
831
826
832 class HasPermissionAllDecorator(PermsDecorator):
827 class HasPermissionAllDecorator(PermsDecorator):
833 """
828 """
834 Checks for access permission for all given predicates. All of them
829 Checks for access permission for all given predicates. All of them
835 have to be meet in order to fulfill the request
830 have to be meet in order to fulfill the request
836 """
831 """
837
832
838 def check_permissions(self):
833 def check_permissions(self):
839 if self.required_perms.issubset(self.user_perms.get('global')):
834 if self.required_perms.issubset(self.user_perms.get('global')):
840 return True
835 return True
841 return False
836 return False
842
837
843
838
844 class HasPermissionAnyDecorator(PermsDecorator):
839 class HasPermissionAnyDecorator(PermsDecorator):
845 """
840 """
846 Checks for access permission for any of given predicates. In order to
841 Checks for access permission for any of given predicates. In order to
847 fulfill the request any of predicates must be meet
842 fulfill the request any of predicates must be meet
848 """
843 """
849
844
850 def check_permissions(self):
845 def check_permissions(self):
851 if self.required_perms.intersection(self.user_perms.get('global')):
846 if self.required_perms.intersection(self.user_perms.get('global')):
852 return True
847 return True
853 return False
848 return False
854
849
855
850
856 class HasRepoPermissionAllDecorator(PermsDecorator):
851 class HasRepoPermissionAllDecorator(PermsDecorator):
857 """
852 """
858 Checks for access permission for all given predicates for specific
853 Checks for access permission for all given predicates for specific
859 repository. All of them have to be meet in order to fulfill the request
854 repository. All of them have to be meet in order to fulfill the request
860 """
855 """
861
856
862 def check_permissions(self):
857 def check_permissions(self):
863 repo_name = get_repo_slug(request)
858 repo_name = get_repo_slug(request)
864 try:
859 try:
865 user_perms = set([self.user_perms['repositories'][repo_name]])
860 user_perms = set([self.user_perms['repositories'][repo_name]])
866 except KeyError:
861 except KeyError:
867 return False
862 return False
868 if self.required_perms.issubset(user_perms):
863 if self.required_perms.issubset(user_perms):
869 return True
864 return True
870 return False
865 return False
871
866
872
867
873 class HasRepoPermissionAnyDecorator(PermsDecorator):
868 class HasRepoPermissionAnyDecorator(PermsDecorator):
874 """
869 """
875 Checks for access permission for any of given predicates for specific
870 Checks for access permission for any of given predicates for specific
876 repository. In order to fulfill the request any of predicates must be meet
871 repository. In order to fulfill the request any of predicates must be meet
877 """
872 """
878
873
879 def check_permissions(self):
874 def check_permissions(self):
880 repo_name = get_repo_slug(request)
875 repo_name = get_repo_slug(request)
881 try:
876 try:
882 user_perms = set([self.user_perms['repositories'][repo_name]])
877 user_perms = set([self.user_perms['repositories'][repo_name]])
883 except KeyError:
878 except KeyError:
884 return False
879 return False
885
880
886 if self.required_perms.intersection(user_perms):
881 if self.required_perms.intersection(user_perms):
887 return True
882 return True
888 return False
883 return False
889
884
890
885
891 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
886 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
892 """
887 """
893 Checks for access permission for all given predicates for specific
888 Checks for access permission for all given predicates for specific
894 repository group. All of them have to be meet in order to fulfill the request
889 repository group. All of them have to be meet in order to fulfill the request
895 """
890 """
896
891
897 def check_permissions(self):
892 def check_permissions(self):
898 group_name = get_repo_group_slug(request)
893 group_name = get_repo_group_slug(request)
899 try:
894 try:
900 user_perms = set([self.user_perms['repositories_groups'][group_name]])
895 user_perms = set([self.user_perms['repositories_groups'][group_name]])
901 except KeyError:
896 except KeyError:
902 return False
897 return False
903
898
904 if self.required_perms.issubset(user_perms):
899 if self.required_perms.issubset(user_perms):
905 return True
900 return True
906 return False
901 return False
907
902
908
903
909 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
904 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
910 """
905 """
911 Checks for access permission for any of given predicates for specific
906 Checks for access permission for any of given predicates for specific
912 repository group. In order to fulfill the request any of predicates must be meet
907 repository group. In order to fulfill the request any of predicates must be meet
913 """
908 """
914
909
915 def check_permissions(self):
910 def check_permissions(self):
916 group_name = get_repo_group_slug(request)
911 group_name = get_repo_group_slug(request)
917 try:
912 try:
918 user_perms = set([self.user_perms['repositories_groups'][group_name]])
913 user_perms = set([self.user_perms['repositories_groups'][group_name]])
919 except KeyError:
914 except KeyError:
920 return False
915 return False
921
916
922 if self.required_perms.intersection(user_perms):
917 if self.required_perms.intersection(user_perms):
923 return True
918 return True
924 return False
919 return False
925
920
926
921
927 class HasUserGroupPermissionAllDecorator(PermsDecorator):
922 class HasUserGroupPermissionAllDecorator(PermsDecorator):
928 """
923 """
929 Checks for access permission for all given predicates for specific
924 Checks for access permission for all given predicates for specific
930 user group. All of them have to be meet in order to fulfill the request
925 user group. All of them have to be meet in order to fulfill the request
931 """
926 """
932
927
933 def check_permissions(self):
928 def check_permissions(self):
934 group_name = get_user_group_slug(request)
929 group_name = get_user_group_slug(request)
935 try:
930 try:
936 user_perms = set([self.user_perms['user_groups'][group_name]])
931 user_perms = set([self.user_perms['user_groups'][group_name]])
937 except KeyError:
932 except KeyError:
938 return False
933 return False
939
934
940 if self.required_perms.issubset(user_perms):
935 if self.required_perms.issubset(user_perms):
941 return True
936 return True
942 return False
937 return False
943
938
944
939
945 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
940 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
946 """
941 """
947 Checks for access permission for any of given predicates for specific
942 Checks for access permission for any of given predicates for specific
948 user group. In order to fulfill the request any of predicates must be meet
943 user group. In order to fulfill the request any of predicates must be meet
949 """
944 """
950
945
951 def check_permissions(self):
946 def check_permissions(self):
952 group_name = get_user_group_slug(request)
947 group_name = get_user_group_slug(request)
953 try:
948 try:
954 user_perms = set([self.user_perms['user_groups'][group_name]])
949 user_perms = set([self.user_perms['user_groups'][group_name]])
955 except KeyError:
950 except KeyError:
956 return False
951 return False
957
952
958 if self.required_perms.intersection(user_perms):
953 if self.required_perms.intersection(user_perms):
959 return True
954 return True
960 return False
955 return False
961
956
962
957
963 #==============================================================================
958 #==============================================================================
964 # CHECK FUNCTIONS
959 # CHECK FUNCTIONS
965 #==============================================================================
960 #==============================================================================
966 class PermsFunction(object):
961 class PermsFunction(object):
967 """Base function for other check functions"""
962 """Base function for other check functions"""
968
963
969 def __init__(self, *perms):
964 def __init__(self, *perms):
970 self.required_perms = set(perms)
965 self.required_perms = set(perms)
971 self.user_perms = None
966 self.user_perms = None
972 self.repo_name = None
967 self.repo_name = None
973 self.group_name = None
968 self.group_name = None
974
969
975 def __call__(self, check_location='', user=None):
970 def __call__(self, check_location='', user=None):
976 if not user:
971 if not user:
977 #TODO: remove this someday,put as user as attribute here
972 #TODO: remove this someday,put as user as attribute here
978 user = request.user
973 user = request.user
979
974
980 # init auth user if not already given
975 # init auth user if not already given
981 if not isinstance(user, AuthUser):
976 if not isinstance(user, AuthUser):
982 user = AuthUser(user.user_id)
977 user = AuthUser(user.user_id)
983
978
984 cls_name = self.__class__.__name__
979 cls_name = self.__class__.__name__
985 check_scope = {
980 check_scope = {
986 'HasPermissionAll': '',
981 'HasPermissionAll': '',
987 'HasPermissionAny': '',
982 'HasPermissionAny': '',
988 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
983 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
989 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
984 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
990 'HasRepoGroupPermissionAll': 'group:%s' % self.group_name,
985 'HasRepoGroupPermissionAll': 'group:%s' % self.group_name,
991 'HasRepoGroupPermissionAny': 'group:%s' % self.group_name,
986 'HasRepoGroupPermissionAny': 'group:%s' % self.group_name,
992 }.get(cls_name, '?')
987 }.get(cls_name, '?')
993 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
988 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
994 self.required_perms, user, check_scope,
989 self.required_perms, user, check_scope,
995 check_location or 'unspecified location')
990 check_location or 'unspecified location')
996 if not user:
991 if not user:
997 log.debug('Empty request user')
992 log.debug('Empty request user')
998 return False
993 return False
999 self.user_perms = user.permissions
994 self.user_perms = user.permissions
1000 if self.check_permissions():
995 if self.check_permissions():
1001 log.debug('Permission to %s granted for user: %s @ %s'
996 log.debug('Permission to %s granted for user: %s @ %s'
1002 % (check_scope, user,
997 % (check_scope, user,
1003 check_location or 'unspecified location'))
998 check_location or 'unspecified location'))
1004 return True
999 return True
1005
1000
1006 else:
1001 else:
1007 log.debug('Permission to %s denied for user: %s @ %s'
1002 log.debug('Permission to %s denied for user: %s @ %s'
1008 % (check_scope, user,
1003 % (check_scope, user,
1009 check_location or 'unspecified location'))
1004 check_location or 'unspecified location'))
1010 return False
1005 return False
1011
1006
1012 def check_permissions(self):
1007 def check_permissions(self):
1013 """Dummy function for overriding"""
1008 """Dummy function for overriding"""
1014 raise Exception('You have to write this function in child class')
1009 raise Exception('You have to write this function in child class')
1015
1010
1016
1011
1017 class HasPermissionAll(PermsFunction):
1012 class HasPermissionAll(PermsFunction):
1018 def check_permissions(self):
1013 def check_permissions(self):
1019 if self.required_perms.issubset(self.user_perms.get('global')):
1014 if self.required_perms.issubset(self.user_perms.get('global')):
1020 return True
1015 return True
1021 return False
1016 return False
1022
1017
1023
1018
1024 class HasPermissionAny(PermsFunction):
1019 class HasPermissionAny(PermsFunction):
1025 def check_permissions(self):
1020 def check_permissions(self):
1026 if self.required_perms.intersection(self.user_perms.get('global')):
1021 if self.required_perms.intersection(self.user_perms.get('global')):
1027 return True
1022 return True
1028 return False
1023 return False
1029
1024
1030
1025
1031 class HasRepoPermissionAll(PermsFunction):
1026 class HasRepoPermissionAll(PermsFunction):
1032 def __call__(self, repo_name=None, check_location='', user=None):
1027 def __call__(self, repo_name=None, check_location='', user=None):
1033 self.repo_name = repo_name
1028 self.repo_name = repo_name
1034 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1029 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1035
1030
1036 def check_permissions(self):
1031 def check_permissions(self):
1037 if not self.repo_name:
1032 if not self.repo_name:
1038 self.repo_name = get_repo_slug(request)
1033 self.repo_name = get_repo_slug(request)
1039
1034
1040 try:
1035 try:
1041 self._user_perms = set(
1036 self._user_perms = set(
1042 [self.user_perms['repositories'][self.repo_name]]
1037 [self.user_perms['repositories'][self.repo_name]]
1043 )
1038 )
1044 except KeyError:
1039 except KeyError:
1045 return False
1040 return False
1046 if self.required_perms.issubset(self._user_perms):
1041 if self.required_perms.issubset(self._user_perms):
1047 return True
1042 return True
1048 return False
1043 return False
1049
1044
1050
1045
1051 class HasRepoPermissionAny(PermsFunction):
1046 class HasRepoPermissionAny(PermsFunction):
1052 def __call__(self, repo_name=None, check_location='', user=None):
1047 def __call__(self, repo_name=None, check_location='', user=None):
1053 self.repo_name = repo_name
1048 self.repo_name = repo_name
1054 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1049 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1055
1050
1056 def check_permissions(self):
1051 def check_permissions(self):
1057 if not self.repo_name:
1052 if not self.repo_name:
1058 self.repo_name = get_repo_slug(request)
1053 self.repo_name = get_repo_slug(request)
1059
1054
1060 try:
1055 try:
1061 self._user_perms = set(
1056 self._user_perms = set(
1062 [self.user_perms['repositories'][self.repo_name]]
1057 [self.user_perms['repositories'][self.repo_name]]
1063 )
1058 )
1064 except KeyError:
1059 except KeyError:
1065 return False
1060 return False
1066 if self.required_perms.intersection(self._user_perms):
1061 if self.required_perms.intersection(self._user_perms):
1067 return True
1062 return True
1068 return False
1063 return False
1069
1064
1070
1065
1071 class HasRepoGroupPermissionAny(PermsFunction):
1066 class HasRepoGroupPermissionAny(PermsFunction):
1072 def __call__(self, group_name=None, check_location='', user=None):
1067 def __call__(self, group_name=None, check_location='', user=None):
1073 self.group_name = group_name
1068 self.group_name = group_name
1074 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
1069 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
1075
1070
1076 def check_permissions(self):
1071 def check_permissions(self):
1077 try:
1072 try:
1078 self._user_perms = set(
1073 self._user_perms = set(
1079 [self.user_perms['repositories_groups'][self.group_name]]
1074 [self.user_perms['repositories_groups'][self.group_name]]
1080 )
1075 )
1081 except KeyError:
1076 except KeyError:
1082 return False
1077 return False
1083 if self.required_perms.intersection(self._user_perms):
1078 if self.required_perms.intersection(self._user_perms):
1084 return True
1079 return True
1085 return False
1080 return False
1086
1081
1087
1082
1088 class HasRepoGroupPermissionAll(PermsFunction):
1083 class HasRepoGroupPermissionAll(PermsFunction):
1089 def __call__(self, group_name=None, check_location='', user=None):
1084 def __call__(self, group_name=None, check_location='', user=None):
1090 self.group_name = group_name
1085 self.group_name = group_name
1091 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
1086 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
1092
1087
1093 def check_permissions(self):
1088 def check_permissions(self):
1094 try:
1089 try:
1095 self._user_perms = set(
1090 self._user_perms = set(
1096 [self.user_perms['repositories_groups'][self.group_name]]
1091 [self.user_perms['repositories_groups'][self.group_name]]
1097 )
1092 )
1098 except KeyError:
1093 except KeyError:
1099 return False
1094 return False
1100 if self.required_perms.issubset(self._user_perms):
1095 if self.required_perms.issubset(self._user_perms):
1101 return True
1096 return True
1102 return False
1097 return False
1103
1098
1104
1099
1105 class HasUserGroupPermissionAny(PermsFunction):
1100 class HasUserGroupPermissionAny(PermsFunction):
1106 def __call__(self, user_group_name=None, check_location='', user=None):
1101 def __call__(self, user_group_name=None, check_location='', user=None):
1107 self.user_group_name = user_group_name
1102 self.user_group_name = user_group_name
1108 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
1103 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
1109
1104
1110 def check_permissions(self):
1105 def check_permissions(self):
1111 try:
1106 try:
1112 self._user_perms = set(
1107 self._user_perms = set(
1113 [self.user_perms['user_groups'][self.user_group_name]]
1108 [self.user_perms['user_groups'][self.user_group_name]]
1114 )
1109 )
1115 except KeyError:
1110 except KeyError:
1116 return False
1111 return False
1117 if self.required_perms.intersection(self._user_perms):
1112 if self.required_perms.intersection(self._user_perms):
1118 return True
1113 return True
1119 return False
1114 return False
1120
1115
1121
1116
1122 class HasUserGroupPermissionAll(PermsFunction):
1117 class HasUserGroupPermissionAll(PermsFunction):
1123 def __call__(self, user_group_name=None, check_location='', user=None):
1118 def __call__(self, user_group_name=None, check_location='', user=None):
1124 self.user_group_name = user_group_name
1119 self.user_group_name = user_group_name
1125 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
1120 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
1126
1121
1127 def check_permissions(self):
1122 def check_permissions(self):
1128 try:
1123 try:
1129 self._user_perms = set(
1124 self._user_perms = set(
1130 [self.user_perms['user_groups'][self.user_group_name]]
1125 [self.user_perms['user_groups'][self.user_group_name]]
1131 )
1126 )
1132 except KeyError:
1127 except KeyError:
1133 return False
1128 return False
1134 if self.required_perms.issubset(self._user_perms):
1129 if self.required_perms.issubset(self._user_perms):
1135 return True
1130 return True
1136 return False
1131 return False
1137
1132
1138
1133
1139 #==============================================================================
1134 #==============================================================================
1140 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1135 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1141 #==============================================================================
1136 #==============================================================================
1142 class HasPermissionAnyMiddleware(object):
1137 class HasPermissionAnyMiddleware(object):
1143 def __init__(self, *perms):
1138 def __init__(self, *perms):
1144 self.required_perms = set(perms)
1139 self.required_perms = set(perms)
1145
1140
1146 def __call__(self, user, repo_name):
1141 def __call__(self, user, repo_name):
1147 # repo_name MUST be unicode, since we handle keys in permission
1142 # repo_name MUST be unicode, since we handle keys in permission
1148 # dict by unicode
1143 # dict by unicode
1149 repo_name = safe_unicode(repo_name)
1144 repo_name = safe_unicode(repo_name)
1150 usr = AuthUser(user.user_id)
1145 usr = AuthUser(user.user_id)
1151 self.user_perms = set([usr.permissions['repositories'][repo_name]])
1146 self.user_perms = set([usr.permissions['repositories'][repo_name]])
1152 self.username = user.username
1147 self.username = user.username
1153 self.repo_name = repo_name
1148 self.repo_name = repo_name
1154 return self.check_permissions()
1149 return self.check_permissions()
1155
1150
1156 def check_permissions(self):
1151 def check_permissions(self):
1157 log.debug('checking VCS protocol '
1152 log.debug('checking VCS protocol '
1158 'permissions %s for user:%s repository:%s', self.user_perms,
1153 'permissions %s for user:%s repository:%s', self.user_perms,
1159 self.username, self.repo_name)
1154 self.username, self.repo_name)
1160 if self.required_perms.intersection(self.user_perms):
1155 if self.required_perms.intersection(self.user_perms):
1161 log.debug('Permission to repo: %s granted for user: %s @ %s'
1156 log.debug('Permission to repo: %s granted for user: %s @ %s'
1162 % (self.repo_name, self.username, 'PermissionMiddleware'))
1157 % (self.repo_name, self.username, 'PermissionMiddleware'))
1163 return True
1158 return True
1164 log.debug('Permission to repo: %s denied for user: %s @ %s'
1159 log.debug('Permission to repo: %s denied for user: %s @ %s'
1165 % (self.repo_name, self.username, 'PermissionMiddleware'))
1160 % (self.repo_name, self.username, 'PermissionMiddleware'))
1166 return False
1161 return False
1167
1162
1168
1163
1169 #==============================================================================
1164 #==============================================================================
1170 # SPECIAL VERSION TO HANDLE API AUTH
1165 # SPECIAL VERSION TO HANDLE API AUTH
1171 #==============================================================================
1166 #==============================================================================
1172 class _BaseApiPerm(object):
1167 class _BaseApiPerm(object):
1173 def __init__(self, *perms):
1168 def __init__(self, *perms):
1174 self.required_perms = set(perms)
1169 self.required_perms = set(perms)
1175
1170
1176 def __call__(self, check_location=None, user=None, repo_name=None,
1171 def __call__(self, check_location=None, user=None, repo_name=None,
1177 group_name=None):
1172 group_name=None):
1178 cls_name = self.__class__.__name__
1173 cls_name = self.__class__.__name__
1179 check_scope = 'user:%s' % (user)
1174 check_scope = 'user:%s' % (user)
1180 if repo_name:
1175 if repo_name:
1181 check_scope += ', repo:%s' % (repo_name)
1176 check_scope += ', repo:%s' % (repo_name)
1182
1177
1183 if group_name:
1178 if group_name:
1184 check_scope += ', repo group:%s' % (group_name)
1179 check_scope += ', repo group:%s' % (group_name)
1185
1180
1186 log.debug('checking cls:%s %s %s @ %s'
1181 log.debug('checking cls:%s %s %s @ %s'
1187 % (cls_name, self.required_perms, check_scope, check_location))
1182 % (cls_name, self.required_perms, check_scope, check_location))
1188 if not user:
1183 if not user:
1189 log.debug('Empty User passed into arguments')
1184 log.debug('Empty User passed into arguments')
1190 return False
1185 return False
1191
1186
1192 ## process user
1187 ## process user
1193 if not isinstance(user, AuthUser):
1188 if not isinstance(user, AuthUser):
1194 user = AuthUser(user.user_id)
1189 user = AuthUser(user.user_id)
1195 if not check_location:
1190 if not check_location:
1196 check_location = 'unspecified'
1191 check_location = 'unspecified'
1197 if self.check_permissions(user.permissions, repo_name, group_name):
1192 if self.check_permissions(user.permissions, repo_name, group_name):
1198 log.debug('Permission to %s granted for user: %s @ %s'
1193 log.debug('Permission to %s granted for user: %s @ %s'
1199 % (check_scope, user, check_location))
1194 % (check_scope, user, check_location))
1200 return True
1195 return True
1201
1196
1202 else:
1197 else:
1203 log.debug('Permission to %s denied for user: %s @ %s'
1198 log.debug('Permission to %s denied for user: %s @ %s'
1204 % (check_scope, user, check_location))
1199 % (check_scope, user, check_location))
1205 return False
1200 return False
1206
1201
1207 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1202 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1208 """
1203 """
1209 implement in child class should return True if permissions are ok,
1204 implement in child class should return True if permissions are ok,
1210 False otherwise
1205 False otherwise
1211
1206
1212 :param perm_defs: dict with permission definitions
1207 :param perm_defs: dict with permission definitions
1213 :param repo_name: repo name
1208 :param repo_name: repo name
1214 """
1209 """
1215 raise NotImplementedError()
1210 raise NotImplementedError()
1216
1211
1217
1212
1218 class HasPermissionAllApi(_BaseApiPerm):
1213 class HasPermissionAllApi(_BaseApiPerm):
1219 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1214 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1220 if self.required_perms.issubset(perm_defs.get('global')):
1215 if self.required_perms.issubset(perm_defs.get('global')):
1221 return True
1216 return True
1222 return False
1217 return False
1223
1218
1224
1219
1225 class HasPermissionAnyApi(_BaseApiPerm):
1220 class HasPermissionAnyApi(_BaseApiPerm):
1226 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1221 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1227 if self.required_perms.intersection(perm_defs.get('global')):
1222 if self.required_perms.intersection(perm_defs.get('global')):
1228 return True
1223 return True
1229 return False
1224 return False
1230
1225
1231
1226
1232 class HasRepoPermissionAllApi(_BaseApiPerm):
1227 class HasRepoPermissionAllApi(_BaseApiPerm):
1233 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1228 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1234 try:
1229 try:
1235 _user_perms = set([perm_defs['repositories'][repo_name]])
1230 _user_perms = set([perm_defs['repositories'][repo_name]])
1236 except KeyError:
1231 except KeyError:
1237 log.warning(traceback.format_exc())
1232 log.warning(traceback.format_exc())
1238 return False
1233 return False
1239 if self.required_perms.issubset(_user_perms):
1234 if self.required_perms.issubset(_user_perms):
1240 return True
1235 return True
1241 return False
1236 return False
1242
1237
1243
1238
1244 class HasRepoPermissionAnyApi(_BaseApiPerm):
1239 class HasRepoPermissionAnyApi(_BaseApiPerm):
1245 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1240 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1246 try:
1241 try:
1247 _user_perms = set([perm_defs['repositories'][repo_name]])
1242 _user_perms = set([perm_defs['repositories'][repo_name]])
1248 except KeyError:
1243 except KeyError:
1249 log.warning(traceback.format_exc())
1244 log.warning(traceback.format_exc())
1250 return False
1245 return False
1251 if self.required_perms.intersection(_user_perms):
1246 if self.required_perms.intersection(_user_perms):
1252 return True
1247 return True
1253 return False
1248 return False
1254
1249
1255
1250
1256 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1251 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1257 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1252 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1258 try:
1253 try:
1259 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1254 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1260 except KeyError:
1255 except KeyError:
1261 log.warning(traceback.format_exc())
1256 log.warning(traceback.format_exc())
1262 return False
1257 return False
1263 if self.required_perms.intersection(_user_perms):
1258 if self.required_perms.intersection(_user_perms):
1264 return True
1259 return True
1265 return False
1260 return False
1266
1261
1267 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1262 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1268 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1263 def check_permissions(self, perm_defs, repo_name=None, group_name=None):
1269 try:
1264 try:
1270 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1265 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1271 except KeyError:
1266 except KeyError:
1272 log.warning(traceback.format_exc())
1267 log.warning(traceback.format_exc())
1273 return False
1268 return False
1274 if self.required_perms.issubset(_user_perms):
1269 if self.required_perms.issubset(_user_perms):
1275 return True
1270 return True
1276 return False
1271 return False
1277
1272
1278 def check_ip_access(source_ip, allowed_ips=None):
1273 def check_ip_access(source_ip, allowed_ips=None):
1279 """
1274 """
1280 Checks if source_ip is a subnet of any of allowed_ips.
1275 Checks if source_ip is a subnet of any of allowed_ips.
1281
1276
1282 :param source_ip:
1277 :param source_ip:
1283 :param allowed_ips: list of allowed ips together with mask
1278 :param allowed_ips: list of allowed ips together with mask
1284 """
1279 """
1285 from kallithea.lib import ipaddr
1280 from kallithea.lib import ipaddr
1286 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1281 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1287 if isinstance(allowed_ips, (tuple, list, set)):
1282 if isinstance(allowed_ips, (tuple, list, set)):
1288 for ip in allowed_ips:
1283 for ip in allowed_ips:
1289 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1284 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1290 log.debug('IP %s is network %s' %
1285 log.debug('IP %s is network %s' %
1291 (ipaddr.IPAddress(source_ip), ipaddr.IPNetwork(ip)))
1286 (ipaddr.IPAddress(source_ip), ipaddr.IPNetwork(ip)))
1292 return True
1287 return True
1293 return False
1288 return False
@@ -1,474 +1,473 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
14
15 """
15 """
16 kallithea.lib.base
16 kallithea.lib.base
17 ~~~~~~~~~~~~~~~~~~
17 ~~~~~~~~~~~~~~~~~~
18
18
19 The base Controller API
19 The base Controller API
20 Provides the BaseController class for subclassing. And usage in different
20 Provides the BaseController class for subclassing. And usage in different
21 controllers
21 controllers
22
22
23 This file was forked by the Kallithea project in July 2014.
23 This file was forked by the Kallithea project in July 2014.
24 Original author and date, and relevant copyright and licensing information is below:
24 Original author and date, and relevant copyright and licensing information is below:
25 :created_on: Oct 06, 2010
25 :created_on: Oct 06, 2010
26 :author: marcink
26 :author: marcink
27 :copyright: (c) 2013 RhodeCode GmbH, and others.
27 :copyright: (c) 2013 RhodeCode GmbH, and others.
28 :license: GPLv3, see LICENSE.md for more details.
28 :license: GPLv3, see LICENSE.md for more details.
29 """
29 """
30
30
31 import logging
31 import logging
32 import time
32 import time
33 import traceback
33 import traceback
34
34
35 import webob.exc
35 import webob.exc
36 import paste.httpexceptions
36 import paste.httpexceptions
37 import paste.auth.basic
37 import paste.auth.basic
38 import paste.httpheaders
38 import paste.httpheaders
39
39
40 from pylons import config, tmpl_context as c, request, session, url
40 from pylons import config, tmpl_context as c, request, session, url
41 from pylons.controllers import WSGIController
41 from pylons.controllers import WSGIController
42 from pylons.controllers.util import redirect
42 from pylons.controllers.util import redirect
43 from pylons.templating import render_mako as render # don't remove this import
43 from pylons.templating import render_mako as render # don't remove this import
44 from pylons.i18n.translation import _
44 from pylons.i18n.translation import _
45
45
46 from kallithea import __version__, BACKENDS
46 from kallithea import __version__, BACKENDS
47
47
48 from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
48 from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
49 safe_str, safe_int
49 safe_str, safe_int
50 from kallithea.lib import auth_modules
50 from kallithea.lib import auth_modules
51 from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware, CookieStoreWrapper
51 from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware, CookieStoreWrapper
52 from kallithea.lib.utils import get_repo_slug
52 from kallithea.lib.utils import get_repo_slug
53 from kallithea.lib.exceptions import UserCreationError
53 from kallithea.lib.exceptions import UserCreationError
54 from kallithea.lib.vcs.exceptions import RepositoryError, EmptyRepositoryError, ChangesetDoesNotExistError
54 from kallithea.lib.vcs.exceptions import RepositoryError, EmptyRepositoryError, ChangesetDoesNotExistError
55 from kallithea.model import meta
55 from kallithea.model import meta
56
56
57 from kallithea.model.db import Repository, Ui, User, Setting
57 from kallithea.model.db import Repository, Ui, User, Setting
58 from kallithea.model.notification import NotificationModel
58 from kallithea.model.notification import NotificationModel
59 from kallithea.model.scm import ScmModel
59 from kallithea.model.scm import ScmModel
60 from kallithea.model.pull_request import PullRequestModel
60 from kallithea.model.pull_request import PullRequestModel
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 def _filter_proxy(ip):
65 def _filter_proxy(ip):
66 """
66 """
67 HEADERS can have multiple ips inside the left-most being the original
67 HEADERS can have multiple ips inside the left-most being the original
68 client, and each successive proxy that passed the request adding the IP
68 client, and each successive proxy that passed the request adding the IP
69 address where it received the request from.
69 address where it received the request from.
70
70
71 :param ip:
71 :param ip:
72 """
72 """
73 if ',' in ip:
73 if ',' in ip:
74 _ips = ip.split(',')
74 _ips = ip.split(',')
75 _first_ip = _ips[0].strip()
75 _first_ip = _ips[0].strip()
76 log.debug('Got multiple IPs %s, using %s' % (','.join(_ips), _first_ip))
76 log.debug('Got multiple IPs %s, using %s' % (','.join(_ips), _first_ip))
77 return _first_ip
77 return _first_ip
78 return ip
78 return ip
79
79
80
80
81 def _get_ip_addr(environ):
81 def _get_ip_addr(environ):
82 proxy_key = 'HTTP_X_REAL_IP'
82 proxy_key = 'HTTP_X_REAL_IP'
83 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
83 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
84 def_key = 'REMOTE_ADDR'
84 def_key = 'REMOTE_ADDR'
85
85
86 ip = environ.get(proxy_key)
86 ip = environ.get(proxy_key)
87 if ip:
87 if ip:
88 return _filter_proxy(ip)
88 return _filter_proxy(ip)
89
89
90 ip = environ.get(proxy_key2)
90 ip = environ.get(proxy_key2)
91 if ip:
91 if ip:
92 return _filter_proxy(ip)
92 return _filter_proxy(ip)
93
93
94 ip = environ.get(def_key, '0.0.0.0')
94 ip = environ.get(def_key, '0.0.0.0')
95 return _filter_proxy(ip)
95 return _filter_proxy(ip)
96
96
97
97
98 def _get_access_path(environ):
98 def _get_access_path(environ):
99 path = environ.get('PATH_INFO')
99 path = environ.get('PATH_INFO')
100 org_req = environ.get('pylons.original_request')
100 org_req = environ.get('pylons.original_request')
101 if org_req:
101 if org_req:
102 path = org_req.environ.get('PATH_INFO')
102 path = org_req.environ.get('PATH_INFO')
103 return path
103 return path
104
104
105
105
106 class BasicAuth(paste.auth.basic.AuthBasicAuthenticator):
106 class BasicAuth(paste.auth.basic.AuthBasicAuthenticator):
107
107
108 def __init__(self, realm, authfunc, auth_http_code=None):
108 def __init__(self, realm, authfunc, auth_http_code=None):
109 self.realm = realm
109 self.realm = realm
110 self.authfunc = authfunc
110 self.authfunc = authfunc
111 self._rc_auth_http_code = auth_http_code
111 self._rc_auth_http_code = auth_http_code
112
112
113 def build_authentication(self):
113 def build_authentication(self):
114 head = paste.httpheaders.WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
114 head = paste.httpheaders.WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
115 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
115 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
116 # return 403 if alternative http return code is specified in
116 # return 403 if alternative http return code is specified in
117 # Kallithea config
117 # Kallithea config
118 return paste.httpexceptions.HTTPForbidden(headers=head)
118 return paste.httpexceptions.HTTPForbidden(headers=head)
119 return paste.httpexceptions.HTTPUnauthorized(headers=head)
119 return paste.httpexceptions.HTTPUnauthorized(headers=head)
120
120
121 def authenticate(self, environ):
121 def authenticate(self, environ):
122 authorization = paste.httpheaders.AUTHORIZATION(environ)
122 authorization = paste.httpheaders.AUTHORIZATION(environ)
123 if not authorization:
123 if not authorization:
124 return self.build_authentication()
124 return self.build_authentication()
125 (authmeth, auth) = authorization.split(' ', 1)
125 (authmeth, auth) = authorization.split(' ', 1)
126 if 'basic' != authmeth.lower():
126 if 'basic' != authmeth.lower():
127 return self.build_authentication()
127 return self.build_authentication()
128 auth = auth.strip().decode('base64')
128 auth = auth.strip().decode('base64')
129 _parts = auth.split(':', 1)
129 _parts = auth.split(':', 1)
130 if len(_parts) == 2:
130 if len(_parts) == 2:
131 username, password = _parts
131 username, password = _parts
132 if self.authfunc(username, password, environ):
132 if self.authfunc(username, password, environ):
133 return username
133 return username
134 return self.build_authentication()
134 return self.build_authentication()
135
135
136 __call__ = authenticate
136 __call__ = authenticate
137
137
138
138
139 class BaseVCSController(object):
139 class BaseVCSController(object):
140
140
141 def __init__(self, application, config):
141 def __init__(self, application, config):
142 self.application = application
142 self.application = application
143 self.config = config
143 self.config = config
144 # base path of repo locations
144 # base path of repo locations
145 self.basepath = self.config['base_path']
145 self.basepath = self.config['base_path']
146 #authenticate this VCS request using authfunc
146 #authenticate this VCS request using authfunc
147 self.authenticate = BasicAuth('', auth_modules.authenticate,
147 self.authenticate = BasicAuth('', auth_modules.authenticate,
148 config.get('auth_ret_code'))
148 config.get('auth_ret_code'))
149 self.ip_addr = '0.0.0.0'
149 self.ip_addr = '0.0.0.0'
150
150
151 def _handle_request(self, environ, start_response):
151 def _handle_request(self, environ, start_response):
152 raise NotImplementedError()
152 raise NotImplementedError()
153
153
154 def _get_by_id(self, repo_name):
154 def _get_by_id(self, repo_name):
155 """
155 """
156 Gets a special pattern _<ID> from clone url and tries to replace it
156 Gets a special pattern _<ID> from clone url and tries to replace it
157 with a repository_name for support of _<ID> permanent URLs
157 with a repository_name for support of _<ID> permanent URLs
158
158
159 :param repo_name:
159 :param repo_name:
160 """
160 """
161
161
162 data = repo_name.split('/')
162 data = repo_name.split('/')
163 if len(data) >= 2:
163 if len(data) >= 2:
164 from kallithea.lib.utils import get_repo_by_id
164 from kallithea.lib.utils import get_repo_by_id
165 by_id_match = get_repo_by_id(repo_name)
165 by_id_match = get_repo_by_id(repo_name)
166 if by_id_match:
166 if by_id_match:
167 data[1] = by_id_match
167 data[1] = by_id_match
168
168
169 return '/'.join(data)
169 return '/'.join(data)
170
170
171 def _invalidate_cache(self, repo_name):
171 def _invalidate_cache(self, repo_name):
172 """
172 """
173 Sets cache for this repository for invalidation on next access
173 Sets cache for this repository for invalidation on next access
174
174
175 :param repo_name: full repo name, also a cache key
175 :param repo_name: full repo name, also a cache key
176 """
176 """
177 ScmModel().mark_for_invalidation(repo_name)
177 ScmModel().mark_for_invalidation(repo_name)
178
178
179 def _check_permission(self, action, user, repo_name, ip_addr=None):
179 def _check_permission(self, action, user, repo_name, ip_addr=None):
180 """
180 """
181 Checks permissions using action (push/pull) user and repository
181 Checks permissions using action (push/pull) user and repository
182 name
182 name
183
183
184 :param action: push or pull action
184 :param action: push or pull action
185 :param user: user instance
185 :param user: user instance
186 :param repo_name: repository name
186 :param repo_name: repository name
187 """
187 """
188 # check IP
188 # check IP
189 inherit = user.inherit_default_permissions
189 inherit = user.inherit_default_permissions
190 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
190 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
191 inherit_from_default=inherit)
191 inherit_from_default=inherit)
192 if ip_allowed:
192 if ip_allowed:
193 log.info('Access for IP:%s allowed' % (ip_addr,))
193 log.info('Access for IP:%s allowed' % (ip_addr,))
194 else:
194 else:
195 return False
195 return False
196
196
197 if action == 'push':
197 if action == 'push':
198 if not HasPermissionAnyMiddleware('repository.write',
198 if not HasPermissionAnyMiddleware('repository.write',
199 'repository.admin')(user,
199 'repository.admin')(user,
200 repo_name):
200 repo_name):
201 return False
201 return False
202
202
203 else:
203 else:
204 #any other action need at least read permission
204 #any other action need at least read permission
205 if not HasPermissionAnyMiddleware('repository.read',
205 if not HasPermissionAnyMiddleware('repository.read',
206 'repository.write',
206 'repository.write',
207 'repository.admin')(user,
207 'repository.admin')(user,
208 repo_name):
208 repo_name):
209 return False
209 return False
210
210
211 return True
211 return True
212
212
213 def _get_ip_addr(self, environ):
213 def _get_ip_addr(self, environ):
214 return _get_ip_addr(environ)
214 return _get_ip_addr(environ)
215
215
216 def _check_ssl(self, environ):
216 def _check_ssl(self, environ):
217 """
217 """
218 Checks the SSL check flag and returns False if SSL is not present
218 Checks the SSL check flag and returns False if SSL is not present
219 and required True otherwise
219 and required True otherwise
220 """
220 """
221 #check if we have SSL required ! if not it's a bad request !
221 #check if we have SSL required ! if not it's a bad request !
222 if str2bool(Ui.get_by_key('push_ssl').ui_value):
222 if str2bool(Ui.get_by_key('push_ssl').ui_value):
223 org_proto = environ.get('wsgi._org_proto', environ['wsgi.url_scheme'])
223 org_proto = environ.get('wsgi._org_proto', environ['wsgi.url_scheme'])
224 if org_proto != 'https':
224 if org_proto != 'https':
225 log.debug('proto is %s and SSL is required BAD REQUEST !'
225 log.debug('proto is %s and SSL is required BAD REQUEST !'
226 % org_proto)
226 % org_proto)
227 return False
227 return False
228 return True
228 return True
229
229
230 def _check_locking_state(self, environ, action, repo, user_id):
230 def _check_locking_state(self, environ, action, repo, user_id):
231 """
231 """
232 Checks locking on this repository, if locking is enabled and lock is
232 Checks locking on this repository, if locking is enabled and lock is
233 present returns a tuple of make_lock, locked, locked_by.
233 present returns a tuple of make_lock, locked, locked_by.
234 make_lock can have 3 states None (do nothing) True, make lock
234 make_lock can have 3 states None (do nothing) True, make lock
235 False release lock, This value is later propagated to hooks, which
235 False release lock, This value is later propagated to hooks, which
236 do the locking. Think about this as signals passed to hooks what to do.
236 do the locking. Think about this as signals passed to hooks what to do.
237
237
238 """
238 """
239 locked = False # defines that locked error should be thrown to user
239 locked = False # defines that locked error should be thrown to user
240 make_lock = None
240 make_lock = None
241 repo = Repository.get_by_repo_name(repo)
241 repo = Repository.get_by_repo_name(repo)
242 user = User.get(user_id)
242 user = User.get(user_id)
243
243
244 # this is kind of hacky, but due to how mercurial handles client-server
244 # this is kind of hacky, but due to how mercurial handles client-server
245 # server see all operation on changeset; bookmarks, phases and
245 # server see all operation on changeset; bookmarks, phases and
246 # obsolescence marker in different transaction, we don't want to check
246 # obsolescence marker in different transaction, we don't want to check
247 # locking on those
247 # locking on those
248 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
248 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
249 locked_by = repo.locked
249 locked_by = repo.locked
250 if repo and repo.enable_locking and not obsolete_call:
250 if repo and repo.enable_locking and not obsolete_call:
251 if action == 'push':
251 if action == 'push':
252 #check if it's already locked !, if it is compare users
252 #check if it's already locked !, if it is compare users
253 user_id, _date = repo.locked
253 user_id, _date = repo.locked
254 if user.user_id == user_id:
254 if user.user_id == user_id:
255 log.debug('Got push from user %s, now unlocking' % (user))
255 log.debug('Got push from user %s, now unlocking' % (user))
256 # unlock if we have push from user who locked
256 # unlock if we have push from user who locked
257 make_lock = False
257 make_lock = False
258 else:
258 else:
259 # we're not the same user who locked, ban with 423 !
259 # we're not the same user who locked, ban with 423 !
260 locked = True
260 locked = True
261 if action == 'pull':
261 if action == 'pull':
262 if repo.locked[0] and repo.locked[1]:
262 if repo.locked[0] and repo.locked[1]:
263 locked = True
263 locked = True
264 else:
264 else:
265 log.debug('Setting lock on repo %s by %s' % (repo, user))
265 log.debug('Setting lock on repo %s by %s' % (repo, user))
266 make_lock = True
266 make_lock = True
267
267
268 else:
268 else:
269 log.debug('Repository %s do not have locking enabled' % (repo))
269 log.debug('Repository %s do not have locking enabled' % (repo))
270 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
270 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
271 % (make_lock, locked, locked_by))
271 % (make_lock, locked, locked_by))
272 return make_lock, locked, locked_by
272 return make_lock, locked, locked_by
273
273
274 def __call__(self, environ, start_response):
274 def __call__(self, environ, start_response):
275 start = time.time()
275 start = time.time()
276 try:
276 try:
277 return self._handle_request(environ, start_response)
277 return self._handle_request(environ, start_response)
278 finally:
278 finally:
279 log = logging.getLogger('kallithea.' + self.__class__.__name__)
279 log = logging.getLogger('kallithea.' + self.__class__.__name__)
280 log.debug('Request time: %.3fs' % (time.time() - start))
280 log.debug('Request time: %.3fs' % (time.time() - start))
281 meta.Session.remove()
281 meta.Session.remove()
282
282
283
283
284 class BaseController(WSGIController):
284 class BaseController(WSGIController):
285
285
286 def __before__(self):
286 def __before__(self):
287 """
287 """
288 __before__ is called before controller methods and after __call__
288 __before__ is called before controller methods and after __call__
289 """
289 """
290 c.kallithea_version = __version__
290 c.kallithea_version = __version__
291 rc_config = Setting.get_app_settings()
291 rc_config = Setting.get_app_settings()
292
292
293 # Visual options
293 # Visual options
294 c.visual = AttributeDict({})
294 c.visual = AttributeDict({})
295
295
296 ## DB stored
296 ## DB stored
297 c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon'))
297 c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon'))
298 c.visual.show_private_icon = str2bool(rc_config.get('show_private_icon'))
298 c.visual.show_private_icon = str2bool(rc_config.get('show_private_icon'))
299 c.visual.stylify_metatags = str2bool(rc_config.get('stylify_metatags'))
299 c.visual.stylify_metatags = str2bool(rc_config.get('stylify_metatags'))
300 c.visual.dashboard_items = safe_int(rc_config.get('dashboard_items', 100))
300 c.visual.dashboard_items = safe_int(rc_config.get('dashboard_items', 100))
301 c.visual.admin_grid_items = safe_int(rc_config.get('admin_grid_items', 100))
301 c.visual.admin_grid_items = safe_int(rc_config.get('admin_grid_items', 100))
302 c.visual.repository_fields = str2bool(rc_config.get('repository_fields'))
302 c.visual.repository_fields = str2bool(rc_config.get('repository_fields'))
303 c.visual.show_version = str2bool(rc_config.get('show_version'))
303 c.visual.show_version = str2bool(rc_config.get('show_version'))
304 c.visual.use_gravatar = str2bool(rc_config.get('use_gravatar'))
304 c.visual.use_gravatar = str2bool(rc_config.get('use_gravatar'))
305 c.visual.gravatar_url = rc_config.get('gravatar_url')
305 c.visual.gravatar_url = rc_config.get('gravatar_url')
306
306
307 c.ga_code = rc_config.get('ga_code')
307 c.ga_code = rc_config.get('ga_code')
308 # TODO: replace undocumented backwards compatibility hack with db upgrade and rename ga_code
308 # TODO: replace undocumented backwards compatibility hack with db upgrade and rename ga_code
309 if c.ga_code and '<' not in c.ga_code:
309 if c.ga_code and '<' not in c.ga_code:
310 c.ga_code = '''<script type="text/javascript">
310 c.ga_code = '''<script type="text/javascript">
311 var _gaq = _gaq || [];
311 var _gaq = _gaq || [];
312 _gaq.push(['_setAccount', '%s']);
312 _gaq.push(['_setAccount', '%s']);
313 _gaq.push(['_trackPageview']);
313 _gaq.push(['_trackPageview']);
314
314
315 (function() {
315 (function() {
316 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
316 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
317 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
317 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
318 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
318 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
319 })();
319 })();
320 </script>''' % c.ga_code
320 </script>''' % c.ga_code
321 c.site_name = rc_config.get('title')
321 c.site_name = rc_config.get('title')
322 c.clone_uri_tmpl = rc_config.get('clone_uri_tmpl')
322 c.clone_uri_tmpl = rc_config.get('clone_uri_tmpl')
323
323
324 ## INI stored
324 ## INI stored
325 c.visual.allow_repo_location_change = str2bool(config.get('allow_repo_location_change', True))
325 c.visual.allow_repo_location_change = str2bool(config.get('allow_repo_location_change', True))
326 c.visual.allow_custom_hooks_settings = str2bool(config.get('allow_custom_hooks_settings', True))
326 c.visual.allow_custom_hooks_settings = str2bool(config.get('allow_custom_hooks_settings', True))
327
327
328 c.instance_id = config.get('instance_id')
328 c.instance_id = config.get('instance_id')
329 c.issues_url = config.get('bugtracker', url('issues_url'))
329 c.issues_url = config.get('bugtracker', url('issues_url'))
330 # END CONFIG VARS
330 # END CONFIG VARS
331
331
332 c.repo_name = get_repo_slug(request) # can be empty
332 c.repo_name = get_repo_slug(request) # can be empty
333 c.backends = BACKENDS.keys()
333 c.backends = BACKENDS.keys()
334 c.unread_notifications = NotificationModel()\
334 c.unread_notifications = NotificationModel()\
335 .get_unread_cnt_for_user(c.authuser.user_id)
335 .get_unread_cnt_for_user(c.authuser.user_id)
336
336
337 self.cut_off_limit = safe_int(config.get('cut_off_limit'))
337 self.cut_off_limit = safe_int(config.get('cut_off_limit'))
338
338
339 c.my_pr_count = PullRequestModel().get_pullrequest_cnt_for_user(c.authuser.user_id)
339 c.my_pr_count = PullRequestModel().get_pullrequest_cnt_for_user(c.authuser.user_id)
340
340
341 self.sa = meta.Session
341 self.sa = meta.Session
342 self.scm_model = ScmModel(self.sa)
342 self.scm_model = ScmModel(self.sa)
343
343
344 @staticmethod
344 @staticmethod
345 def _determine_auth_user(ip_addr, api_key, session_authuser):
345 def _determine_auth_user(api_key, session_authuser):
346 """
346 """
347 Create an `AuthUser` object given the IP address of the request, the
347 Create an `AuthUser` object given the IP address of the request, the
348 API key (if any), and the authuser from the session.
348 API key (if any), and the authuser from the session.
349 """
349 """
350
350
351 if api_key:
351 if api_key:
352 # when using API_KEY we are sure user exists.
352 # when using API_KEY we are sure user exists.
353 auth_user = AuthUser(api_key=api_key, ip_addr=ip_addr)
353 auth_user = AuthUser(api_key=api_key)
354 authenticated = False
354 authenticated = False
355 else:
355 else:
356 cookie_store = CookieStoreWrapper(session_authuser)
356 cookie_store = CookieStoreWrapper(session_authuser)
357 user_id = cookie_store.get('user_id')
357 user_id = cookie_store.get('user_id')
358 try:
358 try:
359 auth_user = AuthUser(user_id=user_id, ip_addr=ip_addr)
359 auth_user = AuthUser(user_id=user_id)
360 except UserCreationError as e:
360 except UserCreationError as e:
361 # container auth or other auth functions that create users on
361 # container auth or other auth functions that create users on
362 # the fly can throw UserCreationError to signal issues with
362 # the fly can throw UserCreationError to signal issues with
363 # user creation. Explanation should be provided in the
363 # user creation. Explanation should be provided in the
364 # exception object.
364 # exception object.
365 from kallithea.lib import helpers as h
365 from kallithea.lib import helpers as h
366 h.flash(e, 'error')
366 h.flash(e, 'error')
367 auth_user = AuthUser(ip_addr=ip_addr)
367 auth_user = AuthUser()
368
368
369 authenticated = cookie_store.get('is_authenticated')
369 authenticated = cookie_store.get('is_authenticated')
370
370
371 if not auth_user.is_authenticated and auth_user.user_id is not None:
371 if not auth_user.is_authenticated and auth_user.user_id is not None:
372 # user is not authenticated and not empty
372 # user is not authenticated and not empty
373 auth_user.set_authenticated(authenticated)
373 auth_user.set_authenticated(authenticated)
374
374
375 return auth_user
375 return auth_user
376
376
377 def __call__(self, environ, start_response):
377 def __call__(self, environ, start_response):
378 """Invoke the Controller"""
378 """Invoke the Controller"""
379
379
380 # WSGIController.__call__ dispatches to the Controller method
380 # WSGIController.__call__ dispatches to the Controller method
381 # the request is routed to. This routing information is
381 # the request is routed to. This routing information is
382 # available in environ['pylons.routes_dict']
382 # available in environ['pylons.routes_dict']
383 try:
383 try:
384 self.ip_addr = _get_ip_addr(environ)
384 self.ip_addr = _get_ip_addr(environ)
385 # make sure that we update permissions each time we call controller
385 # make sure that we update permissions each time we call controller
386
386
387 #set globals for auth user
387 #set globals for auth user
388 self.authuser = c.authuser = request.user = self._determine_auth_user(
388 self.authuser = c.authuser = request.user = self._determine_auth_user(
389 self.ip_addr,
390 request.GET.get('api_key'),
389 request.GET.get('api_key'),
391 session.get('authuser'),
390 session.get('authuser'),
392 )
391 )
393
392
394 log.info('IP: %s User: %s accessed %s',
393 log.info('IP: %s User: %s accessed %s',
395 self.ip_addr, self.authuser,
394 self.ip_addr, self.authuser,
396 safe_unicode(_get_access_path(environ)),
395 safe_unicode(_get_access_path(environ)),
397 )
396 )
398 return WSGIController.__call__(self, environ, start_response)
397 return WSGIController.__call__(self, environ, start_response)
399 finally:
398 finally:
400 meta.Session.remove()
399 meta.Session.remove()
401
400
402
401
403 class BaseRepoController(BaseController):
402 class BaseRepoController(BaseController):
404 """
403 """
405 Base class for controllers responsible for loading all needed data for
404 Base class for controllers responsible for loading all needed data for
406 repository loaded items are
405 repository loaded items are
407
406
408 c.db_repo_scm_instance: instance of scm repository
407 c.db_repo_scm_instance: instance of scm repository
409 c.db_repo: instance of db
408 c.db_repo: instance of db
410 c.repository_followers: number of followers
409 c.repository_followers: number of followers
411 c.repository_forks: number of forks
410 c.repository_forks: number of forks
412 c.repository_following: weather the current user is following the current repo
411 c.repository_following: weather the current user is following the current repo
413 """
412 """
414
413
415 def __before__(self):
414 def __before__(self):
416 super(BaseRepoController, self).__before__()
415 super(BaseRepoController, self).__before__()
417 if c.repo_name: # extracted from routes
416 if c.repo_name: # extracted from routes
418 _dbr = Repository.get_by_repo_name(c.repo_name)
417 _dbr = Repository.get_by_repo_name(c.repo_name)
419 if not _dbr:
418 if not _dbr:
420 return
419 return
421
420
422 log.debug('Found repository in database %s with state `%s`'
421 log.debug('Found repository in database %s with state `%s`'
423 % (safe_unicode(_dbr), safe_unicode(_dbr.repo_state)))
422 % (safe_unicode(_dbr), safe_unicode(_dbr.repo_state)))
424 route = getattr(request.environ.get('routes.route'), 'name', '')
423 route = getattr(request.environ.get('routes.route'), 'name', '')
425
424
426 # allow to delete repos that are somehow damages in filesystem
425 # allow to delete repos that are somehow damages in filesystem
427 if route in ['delete_repo']:
426 if route in ['delete_repo']:
428 return
427 return
429
428
430 if _dbr.repo_state in [Repository.STATE_PENDING]:
429 if _dbr.repo_state in [Repository.STATE_PENDING]:
431 if route in ['repo_creating_home']:
430 if route in ['repo_creating_home']:
432 return
431 return
433 check_url = url('repo_creating_home', repo_name=c.repo_name)
432 check_url = url('repo_creating_home', repo_name=c.repo_name)
434 return redirect(check_url)
433 return redirect(check_url)
435
434
436 dbr = c.db_repo = _dbr
435 dbr = c.db_repo = _dbr
437 c.db_repo_scm_instance = c.db_repo.scm_instance
436 c.db_repo_scm_instance = c.db_repo.scm_instance
438 if c.db_repo_scm_instance is None:
437 if c.db_repo_scm_instance is None:
439 log.error('%s this repository is present in database but it '
438 log.error('%s this repository is present in database but it '
440 'cannot be created as an scm instance', c.repo_name)
439 'cannot be created as an scm instance', c.repo_name)
441 from kallithea.lib import helpers as h
440 from kallithea.lib import helpers as h
442 h.flash(h.literal(_('Repository not found in the filesystem')),
441 h.flash(h.literal(_('Repository not found in the filesystem')),
443 category='error')
442 category='error')
444 raise paste.httpexceptions.HTTPNotFound()
443 raise paste.httpexceptions.HTTPNotFound()
445
444
446 # some globals counter for menu
445 # some globals counter for menu
447 c.repository_followers = self.scm_model.get_followers(dbr)
446 c.repository_followers = self.scm_model.get_followers(dbr)
448 c.repository_forks = self.scm_model.get_forks(dbr)
447 c.repository_forks = self.scm_model.get_forks(dbr)
449 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
448 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
450 c.repository_following = self.scm_model.is_following_repo(
449 c.repository_following = self.scm_model.is_following_repo(
451 c.repo_name, self.authuser.user_id)
450 c.repo_name, self.authuser.user_id)
452
451
453 @staticmethod
452 @staticmethod
454 def _get_ref_rev(repo, ref_type, ref_name, returnempty=False):
453 def _get_ref_rev(repo, ref_type, ref_name, returnempty=False):
455 """
454 """
456 Safe way to get changeset. If error occurs show error.
455 Safe way to get changeset. If error occurs show error.
457 """
456 """
458 from kallithea.lib import helpers as h
457 from kallithea.lib import helpers as h
459 try:
458 try:
460 return repo.scm_instance.get_ref_revision(ref_type, ref_name)
459 return repo.scm_instance.get_ref_revision(ref_type, ref_name)
461 except EmptyRepositoryError as e:
460 except EmptyRepositoryError as e:
462 if returnempty:
461 if returnempty:
463 return repo.scm_instance.EMPTY_CHANGESET
462 return repo.scm_instance.EMPTY_CHANGESET
464 h.flash(h.literal(_('There are no changesets yet')),
463 h.flash(h.literal(_('There are no changesets yet')),
465 category='error')
464 category='error')
466 raise webob.exc.HTTPNotFound()
465 raise webob.exc.HTTPNotFound()
467 except ChangesetDoesNotExistError as e:
466 except ChangesetDoesNotExistError as e:
468 h.flash(h.literal(_('Changeset not found')),
467 h.flash(h.literal(_('Changeset not found')),
469 category='error')
468 category='error')
470 raise webob.exc.HTTPNotFound()
469 raise webob.exc.HTTPNotFound()
471 except RepositoryError as e:
470 except RepositoryError as e:
472 log.error(traceback.format_exc())
471 log.error(traceback.format_exc())
473 h.flash(safe_str(e), category='error')
472 h.flash(safe_str(e), category='error')
474 raise webob.exc.HTTPBadRequest()
473 raise webob.exc.HTTPBadRequest()
@@ -1,75 +1,75 b''
1 ${h.form(url('my_account'), method='post')}
1 ${h.form(url('my_account'), method='post')}
2 <div class="form">
2 <div class="form">
3
3
4 <div class="field">
4 <div class="field">
5 <div class="gravatar_box">
5 <div class="gravatar_box">
6 <div class="gravatar">
6 <div class="gravatar">
7 ${h.gravatar(c.user.email)}
7 ${h.gravatar(c.user.email)}
8 </div>
8 </div>
9 <p>
9 <p>
10 %if c.visual.use_gravatar:
10 %if c.visual.use_gravatar:
11 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
11 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
12 <br/>${_('Using')} ${c.user.email}
12 <br/>${_('Using')} ${c.user.email}
13 %else:
13 %else:
14 <strong>${_('Avatars are disabled')}</strong>
14 <strong>${_('Avatars are disabled')}</strong>
15 <br/>${c.user.email or _('Missing email, please update your user email address.')}
15 <br/>${c.user.email or _('Missing email, please update your user email address.')}
16 [${_('Current IP')}: ${c.perm_user.ip_addr or "?"}]
16 [${_('Current IP')}: ${c.ip_addr}]
17 %endif
17 %endif
18 </p>
18 </p>
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <% readonly = None %>
22 <% readonly = None %>
23 <% disabled = "" %>
23 <% disabled = "" %>
24 <div class="fields">
24 <div class="fields">
25 %if c.extern_type != c.EXTERN_TYPE_INTERNAL:
25 %if c.extern_type != c.EXTERN_TYPE_INTERNAL:
26 <% readonly = "readonly" %>
26 <% readonly = "readonly" %>
27 <% disabled = " disabled" %>
27 <% disabled = " disabled" %>
28 <strong>${_('Your user is in an external Source of Record; some details cannot be managed here')}.</strong>
28 <strong>${_('Your user is in an external Source of Record; some details cannot be managed here')}.</strong>
29 %endif
29 %endif
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="username">${_('Username')}:</label>
32 <label for="username">${_('Username')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('username',class_='medium%s' % disabled, readonly=readonly)}
35 ${h.text('username',class_='medium%s' % disabled, readonly=readonly)}
36 ${h.hidden('extern_name', c.extern_name)}
36 ${h.hidden('extern_name', c.extern_name)}
37 ${h.hidden('extern_type', c.extern_type)}
37 ${h.hidden('extern_type', c.extern_type)}
38 </div>
38 </div>
39 </div>
39 </div>
40
40
41 <div class="field">
41 <div class="field">
42 <div class="label">
42 <div class="label">
43 <label for="name">${_('First Name')}:</label>
43 <label for="name">${_('First Name')}:</label>
44 </div>
44 </div>
45 <div class="input">
45 <div class="input">
46 ${h.text('firstname',class_="medium")}
46 ${h.text('firstname',class_="medium")}
47 </div>
47 </div>
48 </div>
48 </div>
49
49
50 <div class="field">
50 <div class="field">
51 <div class="label">
51 <div class="label">
52 <label for="lastname">${_('Last Name')}:</label>
52 <label for="lastname">${_('Last Name')}:</label>
53 </div>
53 </div>
54 <div class="input">
54 <div class="input">
55 ${h.text('lastname',class_="medium")}
55 ${h.text('lastname',class_="medium")}
56 </div>
56 </div>
57 </div>
57 </div>
58
58
59 <div class="field">
59 <div class="field">
60 <div class="label">
60 <div class="label">
61 <label for="email">${_('Email')}:</label>
61 <label for="email">${_('Email')}:</label>
62 </div>
62 </div>
63 <div class="input">
63 <div class="input">
64 ## we should be able to edit email !
64 ## we should be able to edit email !
65 ${h.text('email',class_="medium")}
65 ${h.text('email',class_="medium")}
66 </div>
66 </div>
67 </div>
67 </div>
68
68
69 <div class="buttons">
69 <div class="buttons">
70 ${h.submit('save',_('Save'),class_="btn")}
70 ${h.submit('save',_('Save'),class_="btn")}
71 ${h.reset('reset',_('Reset'),class_="btn")}
71 ${h.reset('reset',_('Reset'),class_="btn")}
72 </div>
72 </div>
73 </div>
73 </div>
74 </div>
74 </div>
75 ${h.end_form()}
75 ${h.end_form()}
@@ -1,127 +1,127 b''
1 ${h.form(url('update_user', id=c.user.user_id),method='put')}
1 ${h.form(url('update_user', id=c.user.user_id),method='put')}
2 <div class="form">
2 <div class="form">
3 <div class="field">
3 <div class="field">
4 <div class="gravatar_box">
4 <div class="gravatar_box">
5 <div class="gravatar">${h.gravatar(c.user.email)}</div>
5 <div class="gravatar">${h.gravatar(c.user.email)}</div>
6 <p>
6 <p>
7 %if c.visual.use_gravatar:
7 %if c.visual.use_gravatar:
8 <strong>${_('Change avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
8 <strong>${_('Change avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
9 <br/>${_('Using')} ${c.user.email}
9 <br/>${_('Using')} ${c.user.email}
10 %else:
10 %else:
11 <strong>${_('Avatars are disabled')}</strong>
11 <strong>${_('Avatars are disabled')}</strong>
12 <br/>${c.user.email or _('Missing email, please update this user email address.')}
12 <br/>${c.user.email or _('Missing email, please update this user email address.')}
13 ##show current ip just if we show ourself
13 ##show current ip just if we show ourself
14 %if c.authuser.username == c.user.username:
14 %if c.authuser.username == c.user.username:
15 [${_('Current IP')}: ${c.perm_user.ip_addr or "?"}]
15 [${_('Current IP')}: ${c.ip_addr}]
16 %endif
16 %endif
17 %endif
17 %endif
18 </div>
18 </div>
19 </div>
19 </div>
20 <% readonly = None %>
20 <% readonly = None %>
21 <% disabled = "" %>
21 <% disabled = "" %>
22 <div class="fields">
22 <div class="fields">
23 %if c.extern_type != c.EXTERN_TYPE_INTERNAL:
23 %if c.extern_type != c.EXTERN_TYPE_INTERNAL:
24 <div class="field">
24 <div class="field">
25 <% readonly = "readonly" %>
25 <% readonly = "readonly" %>
26 <% disabled = " disabled" %>
26 <% disabled = " disabled" %>
27 <strong>${_('This user is in an external Source of Record (%s); some details cannot be managed here.' % c.extern_type)}.</strong>
27 <strong>${_('This user is in an external Source of Record (%s); some details cannot be managed here.' % c.extern_type)}.</strong>
28 </div>
28 </div>
29 %endif
29 %endif
30
30
31 <div class="field">
31 <div class="field">
32 <div class="label">
32 <div class="label">
33 <label for="username">${_('Username')}:</label>
33 <label for="username">${_('Username')}:</label>
34 </div>
34 </div>
35 <div class="input">
35 <div class="input">
36 ${h.text('username',class_='medium%s' % disabled, readonly=readonly)}
36 ${h.text('username',class_='medium%s' % disabled, readonly=readonly)}
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label">
41 <div class="label">
42 <label for="email">${_('Email')}:</label>
42 <label for="email">${_('Email')}:</label>
43 </div>
43 </div>
44 <div class="input">
44 <div class="input">
45 ${h.text('email',class_='medium')}
45 ${h.text('email',class_='medium')}
46 </div>
46 </div>
47 </div>
47 </div>
48
48
49 <div class="field">
49 <div class="field">
50 <div class="label">
50 <div class="label">
51 <label for="extern_type">${_('Source of Record')}:</label>
51 <label for="extern_type">${_('Source of Record')}:</label>
52 </div>
52 </div>
53 <div class="input">
53 <div class="input">
54 ${h.text('extern_type',class_='medium disabled',readonly="readonly")}
54 ${h.text('extern_type',class_='medium disabled',readonly="readonly")}
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="field">
58 <div class="field">
59 <div class="label">
59 <div class="label">
60 <label for="extern_name">${_('Name in Source of Record')}:</label>
60 <label for="extern_name">${_('Name in Source of Record')}:</label>
61 </div>
61 </div>
62 <div class="input">
62 <div class="input">
63 ${h.text('extern_name',class_='medium disabled',readonly="readonly")}
63 ${h.text('extern_name',class_='medium disabled',readonly="readonly")}
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 <div class="field">
67 <div class="field">
68 <div class="label">
68 <div class="label">
69 <label for="new_password">${_('New password')}:</label>
69 <label for="new_password">${_('New password')}:</label>
70 </div>
70 </div>
71 <div class="input">
71 <div class="input">
72 ${h.password('new_password',class_='medium%s' % disabled,readonly=readonly)}
72 ${h.password('new_password',class_='medium%s' % disabled,readonly=readonly)}
73 </div>
73 </div>
74 </div>
74 </div>
75
75
76 <div class="field">
76 <div class="field">
77 <div class="label">
77 <div class="label">
78 <label for="password_confirmation">${_('New password confirmation')}:</label>
78 <label for="password_confirmation">${_('New password confirmation')}:</label>
79 </div>
79 </div>
80 <div class="input">
80 <div class="input">
81 ${h.password('password_confirmation',class_="medium%s" % disabled,readonly=readonly)}
81 ${h.password('password_confirmation',class_="medium%s" % disabled,readonly=readonly)}
82 </div>
82 </div>
83 </div>
83 </div>
84
84
85 <div class="field">
85 <div class="field">
86 <div class="label">
86 <div class="label">
87 <label for="firstname">${_('First Name')}:</label>
87 <label for="firstname">${_('First Name')}:</label>
88 </div>
88 </div>
89 <div class="input">
89 <div class="input">
90 ${h.text('firstname',class_='medium')}
90 ${h.text('firstname',class_='medium')}
91 </div>
91 </div>
92 </div>
92 </div>
93
93
94 <div class="field">
94 <div class="field">
95 <div class="label">
95 <div class="label">
96 <label for="lastname">${_('Last Name')}:</label>
96 <label for="lastname">${_('Last Name')}:</label>
97 </div>
97 </div>
98 <div class="input">
98 <div class="input">
99 ${h.text('lastname',class_='medium')}
99 ${h.text('lastname',class_='medium')}
100 </div>
100 </div>
101 </div>
101 </div>
102
102
103 <div class="field">
103 <div class="field">
104 <div class="label label-checkbox">
104 <div class="label label-checkbox">
105 <label for="active">${_('Active')}:</label>
105 <label for="active">${_('Active')}:</label>
106 </div>
106 </div>
107 <div class="checkboxes">
107 <div class="checkboxes">
108 ${h.checkbox('active',value=True)}
108 ${h.checkbox('active',value=True)}
109 </div>
109 </div>
110 </div>
110 </div>
111
111
112 <div class="field">
112 <div class="field">
113 <div class="label label-checkbox">
113 <div class="label label-checkbox">
114 <label for="admin">${_('Admin')}:</label>
114 <label for="admin">${_('Admin')}:</label>
115 </div>
115 </div>
116 <div class="checkboxes">
116 <div class="checkboxes">
117 ${h.checkbox('admin',value=True)}
117 ${h.checkbox('admin',value=True)}
118 </div>
118 </div>
119 </div>
119 </div>
120
120
121 <div class="buttons">
121 <div class="buttons">
122 ${h.submit('save',_('Save'),class_="btn")}
122 ${h.submit('save',_('Save'),class_="btn")}
123 ${h.reset('reset',_('Reset'),class_="btn")}
123 ${h.reset('reset',_('Reset'),class_="btn")}
124 </div>
124 </div>
125 </div>
125 </div>
126 </div>
126 </div>
127 ${h.end_form()}
127 ${h.end_form()}
General Comments 0
You need to be logged in to leave comments. Login now