##// END OF EJS Templates
emails: fixed password reset confirmation that was broken because of undefied variable.
marcink -
r531:91bf0444 default
parent child Browse files
Show More
@@ -1,340 +1,343 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22 22 import formencode
23 23 import logging
24 24 import urlparse
25 25
26 26 from pylons import url
27 27 from pyramid.httpexceptions import HTTPFound
28 28 from pyramid.view import view_config
29 29 from recaptcha.client.captcha import submit
30 30
31 31 from rhodecode.authentication.base import authenticate, HTTP_TYPE
32 32 from rhodecode.events import UserRegistered
33 33 from rhodecode.lib.auth import (
34 34 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
35 35 from rhodecode.lib.base import get_ip_addr
36 36 from rhodecode.lib.exceptions import UserCreationError
37 37 from rhodecode.lib.utils2 import safe_str
38 38 from rhodecode.model.db import User
39 39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
40 40 from rhodecode.model.login_session import LoginSession
41 41 from rhodecode.model.meta import Session
42 42 from rhodecode.model.settings import SettingsModel
43 43 from rhodecode.model.user import UserModel
44 44 from rhodecode.translation import _
45 45
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 def _store_user_in_session(session, username, remember=False):
51 51 user = User.get_by_username(username, case_insensitive=True)
52 52 auth_user = AuthUser(user.user_id)
53 53 auth_user.set_authenticated()
54 54 cs = auth_user.get_cookie_store()
55 55 session['rhodecode_user'] = cs
56 56 user.update_lastlogin()
57 57 Session().commit()
58 58
59 59 # If they want to be remembered, update the cookie
60 60 if remember:
61 61 _year = (datetime.datetime.now() +
62 62 datetime.timedelta(seconds=60 * 60 * 24 * 365))
63 63 session._set_cookie_expires(_year)
64 64
65 65 session.save()
66 66
67 67 log.info('user %s is now authenticated and stored in '
68 68 'session, session attrs %s', username, cs)
69 69
70 70 # dumps session attrs back to cookie
71 71 session._update_cookie_out()
72 72 # we set new cookie
73 73 headers = None
74 74 if session.request['set_cookie']:
75 75 # send set-cookie headers back to response to update cookie
76 76 headers = [('Set-Cookie', session.request['cookie_out'])]
77 77 return headers
78 78
79 79
80 80 def get_came_from(request):
81 81 came_from = safe_str(request.GET.get('came_from', ''))
82 82 parsed = urlparse.urlparse(came_from)
83 83 allowed_schemes = ['http', 'https']
84 84 if parsed.scheme and parsed.scheme not in allowed_schemes:
85 85 log.error('Suspicious URL scheme detected %s for url %s' %
86 86 (parsed.scheme, parsed))
87 87 came_from = url('home')
88 88 elif parsed.netloc and request.host != parsed.netloc:
89 89 log.error('Suspicious NETLOC detected %s for url %s server url '
90 90 'is: %s' % (parsed.netloc, parsed, request.host))
91 91 came_from = url('home')
92 92 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
93 93 log.error('Header injection detected `%s` for url %s server url ' %
94 94 (parsed.path, parsed))
95 95 came_from = url('home')
96 96
97 97 return came_from or url('home')
98 98
99 99
100 100 class LoginView(object):
101 101
102 102 def __init__(self, context, request):
103 103 self.request = request
104 104 self.context = context
105 105 self.session = request.session
106 106 self._rhodecode_user = request.user
107 107
108 108 def _get_template_context(self):
109 109 return {
110 110 'came_from': get_came_from(self.request),
111 111 'defaults': {},
112 112 'errors': {},
113 113 }
114 114
115 115 @view_config(
116 116 route_name='login', request_method='GET',
117 117 renderer='rhodecode:templates/login.html')
118 118 def login(self):
119 119 came_from = get_came_from(self.request)
120 120 user = self.request.user
121 121
122 122 # redirect if already logged in
123 123 if user.is_authenticated and not user.is_default and user.ip_allowed:
124 124 raise HTTPFound(came_from)
125 125
126 126 # check if we use headers plugin, and try to login using it.
127 127 try:
128 128 log.debug('Running PRE-AUTH for headers based authentication')
129 129 auth_info = authenticate(
130 130 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
131 131 if auth_info:
132 132 headers = _store_user_in_session(
133 133 self.session, auth_info.get('username'))
134 134 raise HTTPFound(came_from, headers=headers)
135 135 except UserCreationError as e:
136 136 log.error(e)
137 137 self.session.flash(e, queue='error')
138 138
139 139 return self._get_template_context()
140 140
141 141 @view_config(
142 142 route_name='login', request_method='POST',
143 143 renderer='rhodecode:templates/login.html')
144 144 def login_post(self):
145 145 came_from = get_came_from(self.request)
146 146 session = self.request.session
147 147 login_form = LoginForm()()
148 148
149 149 try:
150 150 session.invalidate()
151 151 form_result = login_form.to_python(self.request.params)
152 152 # form checks for username/password, now we're authenticated
153 153 headers = _store_user_in_session(
154 154 self.session,
155 155 username=form_result['username'],
156 156 remember=form_result['remember'])
157 157 log.debug('Redirecting to "%s" after login.', came_from)
158 158 raise HTTPFound(came_from, headers=headers)
159 159 except formencode.Invalid as errors:
160 160 defaults = errors.value
161 161 # remove password from filling in form again
162 162 del defaults['password']
163 163 render_ctx = self._get_template_context()
164 164 render_ctx.update({
165 165 'errors': errors.error_dict,
166 166 'defaults': defaults,
167 167 })
168 168 return render_ctx
169 169
170 170 except UserCreationError as e:
171 171 # headers auth or other auth functions that create users on
172 172 # the fly can throw this exception signaling that there's issue
173 173 # with user creation, explanation should be provided in
174 174 # Exception itself
175 175 session.flash(e, queue='error')
176 176 return self._get_template_context()
177 177
178 178 @CSRFRequired()
179 179 @view_config(route_name='logout', request_method='POST')
180 180 def logout(self):
181 181 LoginSession().destroy_user_session()
182 182 return HTTPFound(url('home'))
183 183
184 184 @HasPermissionAnyDecorator(
185 185 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
186 186 @view_config(
187 187 route_name='register', request_method='GET',
188 188 renderer='rhodecode:templates/register.html',)
189 189 def register(self, defaults=None, errors=None):
190 190 defaults = defaults or {}
191 191 errors = errors or {}
192 192
193 193 settings = SettingsModel().get_all_settings()
194 194 captcha_public_key = settings.get('rhodecode_captcha_public_key')
195 195 captcha_private_key = settings.get('rhodecode_captcha_private_key')
196 196 captcha_active = bool(captcha_private_key)
197 197 register_message = settings.get('rhodecode_register_message') or ''
198 198 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
199 199 .AuthUser.permissions['global']
200 200
201 201 render_ctx = self._get_template_context()
202 202 render_ctx.update({
203 203 'defaults': defaults,
204 204 'errors': errors,
205 205 'auto_active': auto_active,
206 206 'captcha_active': captcha_active,
207 207 'captcha_public_key': captcha_public_key,
208 208 'register_message': register_message,
209 209 })
210 210 return render_ctx
211 211
212 212 @HasPermissionAnyDecorator(
213 213 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
214 214 @view_config(
215 215 route_name='register', request_method='POST',
216 216 renderer='rhodecode:templates/register.html')
217 217 def register_post(self):
218 218 captcha_private_key = SettingsModel().get_setting_by_name(
219 219 'rhodecode_captcha_private_key')
220 220 captcha_active = bool(captcha_private_key)
221 221 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
222 222 .AuthUser.permissions['global']
223 223
224 224 register_form = RegisterForm()()
225 225 try:
226 226 form_result = register_form.to_python(self.request.params)
227 227 form_result['active'] = auto_active
228 228
229 229 if captcha_active:
230 230 response = submit(
231 231 self.request.params.get('recaptcha_challenge_field'),
232 232 self.request.params.get('recaptcha_response_field'),
233 233 private_key=captcha_private_key,
234 234 remoteip=get_ip_addr(self.request.environ))
235 235 if captcha_active and not response.is_valid:
236 236 _value = form_result
237 237 _msg = _('bad captcha')
238 238 error_dict = {'recaptcha_field': _msg}
239 239 raise formencode.Invalid(_msg, _value, None,
240 240 error_dict=error_dict)
241 241
242 242 new_user = UserModel().create_registration(form_result)
243 243 event = UserRegistered(user=new_user, session=self.session)
244 244 self.request.registry.notify(event)
245 245 self.session.flash(
246 246 _('You have successfully registered with RhodeCode'),
247 247 queue='success')
248 248 Session().commit()
249 249
250 250 redirect_ro = self.request.route_path('login')
251 251 raise HTTPFound(redirect_ro)
252 252
253 253 except formencode.Invalid as errors:
254 254 del errors.value['password']
255 255 del errors.value['password_confirmation']
256 256 return self.register(
257 257 defaults=errors.value, errors=errors.error_dict)
258 258
259 259 except UserCreationError as e:
260 260 # container auth or other auth functions that create users on
261 261 # the fly can throw this exception signaling that there's issue
262 262 # with user creation, explanation should be provided in
263 263 # Exception itself
264 264 self.session.flash(e, queue='error')
265 265 return self.register()
266 266
267 267 @view_config(
268 268 route_name='reset_password', request_method=('GET', 'POST'),
269 269 renderer='rhodecode:templates/password_reset.html')
270 270 def password_reset(self):
271 271 settings = SettingsModel().get_all_settings()
272 272 captcha_private_key = settings.get('rhodecode_captcha_private_key')
273 273 captcha_active = bool(captcha_private_key)
274 274 captcha_public_key = settings.get('rhodecode_captcha_public_key')
275 275
276 276 render_ctx = {
277 277 'captcha_active': captcha_active,
278 278 'captcha_public_key': captcha_public_key,
279 279 'defaults': {},
280 280 'errors': {},
281 281 }
282 282
283 283 if self.request.POST:
284 284 password_reset_form = PasswordResetForm()()
285 285 try:
286 286 form_result = password_reset_form.to_python(
287 287 self.request.params)
288 288 if captcha_active:
289 289 response = submit(
290 290 self.request.params.get('recaptcha_challenge_field'),
291 291 self.request.params.get('recaptcha_response_field'),
292 292 private_key=captcha_private_key,
293 293 remoteip=get_ip_addr(self.request.environ))
294 294 if captcha_active and not response.is_valid:
295 295 _value = form_result
296 296 _msg = _('bad captcha')
297 297 error_dict = {'recaptcha_field': _msg}
298 298 raise formencode.Invalid(_msg, _value, None,
299 299 error_dict=error_dict)
300 300
301 301 # Generate reset URL and send mail.
302 302 user_email = form_result['email']
303 303 user = User.get_by_email(user_email)
304 304 password_reset_url = self.request.route_url(
305 305 'reset_password_confirmation',
306 306 _query={'key': user.api_key})
307 307 UserModel().reset_password_link(
308 308 form_result, password_reset_url)
309 309
310 310 # Display success message and redirect.
311 311 self.session.flash(
312 312 _('Your password reset link was sent'),
313 313 queue='success')
314 314 return HTTPFound(self.request.route_path('login'))
315 315
316 316 except formencode.Invalid as errors:
317 317 render_ctx.update({
318 318 'defaults': errors.value,
319 319 'errors': errors.error_dict,
320 320 })
321 321
322 322 return render_ctx
323 323
324 324 @view_config(route_name='reset_password_confirmation',
325 325 request_method='GET')
326 326 def password_reset_confirmation(self):
327 327 if self.request.GET and self.request.GET.get('key'):
328 328 try:
329 329 user = User.get_by_auth_token(self.request.GET.get('key'))
330 password_reset_url = self.request.route_url(
331 'reset_password_confirmation',
332 _query={'key': user.api_key})
330 333 data = {'email': user.email}
331 UserModel().reset_password(data)
334 UserModel().reset_password(data, password_reset_url)
332 335 self.session.flash(
333 336 _('Your password reset was successful, '
334 337 'a new password has been sent to your email'),
335 338 queue='success')
336 339 except Exception as e:
337 340 log.error(e)
338 341 return HTTPFound(self.request.route_path('reset_password'))
339 342
340 343 return HTTPFound(self.request.route_path('login'))
@@ -1,838 +1,839 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 users model for RhodeCode
23 23 """
24 24
25 25 import logging
26 26 import traceback
27 27
28 28 import datetime
29 29 from pylons.i18n.translation import _
30 30
31 31 import ipaddress
32 32 from sqlalchemy.exc import DatabaseError
33 33 from sqlalchemy.sql.expression import true, false
34 34
35 35 from rhodecode import events
36 36 from rhodecode.lib.utils2 import (
37 37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
38 38 AttributeDict)
39 39 from rhodecode.lib.caching_query import FromCache
40 40 from rhodecode.model import BaseModel
41 41 from rhodecode.model.auth_token import AuthTokenModel
42 42 from rhodecode.model.db import (
43 43 User, UserToPerm, UserEmailMap, UserIpMap)
44 44 from rhodecode.lib.exceptions import (
45 45 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
46 46 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.repo_group import RepoGroupModel
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class UserModel(BaseModel):
55 55 cls = User
56 56
57 57 def get(self, user_id, cache=False):
58 58 user = self.sa.query(User)
59 59 if cache:
60 60 user = user.options(FromCache("sql_cache_short",
61 61 "get_user_%s" % user_id))
62 62 return user.get(user_id)
63 63
64 64 def get_user(self, user):
65 65 return self._get_user(user)
66 66
67 67 def get_by_username(self, username, cache=False, case_insensitive=False):
68 68
69 69 if case_insensitive:
70 70 user = self.sa.query(User).filter(User.username.ilike(username))
71 71 else:
72 72 user = self.sa.query(User)\
73 73 .filter(User.username == username)
74 74 if cache:
75 75 user = user.options(FromCache("sql_cache_short",
76 76 "get_user_%s" % username))
77 77 return user.scalar()
78 78
79 79 def get_by_email(self, email, cache=False, case_insensitive=False):
80 80 return User.get_by_email(email, case_insensitive, cache)
81 81
82 82 def get_by_auth_token(self, auth_token, cache=False):
83 83 return User.get_by_auth_token(auth_token, cache)
84 84
85 85 def get_active_user_count(self, cache=False):
86 86 return User.query().filter(
87 87 User.active == True).filter(
88 88 User.username != User.DEFAULT_USER).count()
89 89
90 90 def create(self, form_data, cur_user=None):
91 91 if not cur_user:
92 92 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
93 93
94 94 user_data = {
95 95 'username': form_data['username'],
96 96 'password': form_data['password'],
97 97 'email': form_data['email'],
98 98 'firstname': form_data['firstname'],
99 99 'lastname': form_data['lastname'],
100 100 'active': form_data['active'],
101 101 'extern_type': form_data['extern_type'],
102 102 'extern_name': form_data['extern_name'],
103 103 'admin': False,
104 104 'cur_user': cur_user
105 105 }
106 106
107 107 try:
108 108 if form_data.get('create_repo_group'):
109 109 user_data['create_repo_group'] = True
110 110 if form_data.get('password_change'):
111 111 user_data['force_password_change'] = True
112 112
113 113 return UserModel().create_or_update(**user_data)
114 114 except Exception:
115 115 log.error(traceback.format_exc())
116 116 raise
117 117
118 118 def update_user(self, user, skip_attrs=None, **kwargs):
119 119 from rhodecode.lib.auth import get_crypt_password
120 120
121 121 user = self._get_user(user)
122 122 if user.username == User.DEFAULT_USER:
123 123 raise DefaultUserException(
124 124 _("You can't Edit this user since it's"
125 125 " crucial for entire application"))
126 126
127 127 # first store only defaults
128 128 user_attrs = {
129 129 'updating_user_id': user.user_id,
130 130 'username': user.username,
131 131 'password': user.password,
132 132 'email': user.email,
133 133 'firstname': user.name,
134 134 'lastname': user.lastname,
135 135 'active': user.active,
136 136 'admin': user.admin,
137 137 'extern_name': user.extern_name,
138 138 'extern_type': user.extern_type,
139 139 'language': user.user_data.get('language')
140 140 }
141 141
142 142 # in case there's new_password, that comes from form, use it to
143 143 # store password
144 144 if kwargs.get('new_password'):
145 145 kwargs['password'] = kwargs['new_password']
146 146
147 147 # cleanups, my_account password change form
148 148 kwargs.pop('current_password', None)
149 149 kwargs.pop('new_password', None)
150 150 kwargs.pop('new_password_confirmation', None)
151 151
152 152 # cleanups, user edit password change form
153 153 kwargs.pop('password_confirmation', None)
154 154 kwargs.pop('password_change', None)
155 155
156 156 # create repo group on user creation
157 157 kwargs.pop('create_repo_group', None)
158 158
159 159 # legacy forms send name, which is the firstname
160 160 firstname = kwargs.pop('name', None)
161 161 if firstname:
162 162 kwargs['firstname'] = firstname
163 163
164 164 for k, v in kwargs.items():
165 165 # skip if we don't want to update this
166 166 if skip_attrs and k in skip_attrs:
167 167 continue
168 168
169 169 user_attrs[k] = v
170 170
171 171 try:
172 172 return self.create_or_update(**user_attrs)
173 173 except Exception:
174 174 log.error(traceback.format_exc())
175 175 raise
176 176
177 177 def create_or_update(
178 178 self, username, password, email, firstname='', lastname='',
179 179 active=True, admin=False, extern_type=None, extern_name=None,
180 180 cur_user=None, plugin=None, force_password_change=False,
181 181 allow_to_create_user=True, create_repo_group=False,
182 182 updating_user_id=None, language=None, strict_creation_check=True):
183 183 """
184 184 Creates a new instance if not found, or updates current one
185 185
186 186 :param username:
187 187 :param password:
188 188 :param email:
189 189 :param firstname:
190 190 :param lastname:
191 191 :param active:
192 192 :param admin:
193 193 :param extern_type:
194 194 :param extern_name:
195 195 :param cur_user:
196 196 :param plugin: optional plugin this method was called from
197 197 :param force_password_change: toggles new or existing user flag
198 198 for password change
199 199 :param allow_to_create_user: Defines if the method can actually create
200 200 new users
201 201 :param create_repo_group: Defines if the method should also
202 202 create an repo group with user name, and owner
203 203 :param updating_user_id: if we set it up this is the user we want to
204 204 update this allows to editing username.
205 205 :param language: language of user from interface.
206 206
207 207 :returns: new User object with injected `is_new_user` attribute.
208 208 """
209 209 if not cur_user:
210 210 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
211 211
212 212 from rhodecode.lib.auth import (
213 213 get_crypt_password, check_password, generate_auth_token)
214 214 from rhodecode.lib.hooks_base import (
215 215 log_create_user, check_allowed_create_user)
216 216
217 217 def _password_change(new_user, password):
218 218 # empty password
219 219 if not new_user.password:
220 220 return False
221 221
222 222 # password check is only needed for RhodeCode internal auth calls
223 223 # in case it's a plugin we don't care
224 224 if not plugin:
225 225
226 226 # first check if we gave crypted password back, and if it matches
227 227 # it's not password change
228 228 if new_user.password == password:
229 229 return False
230 230
231 231 password_match = check_password(password, new_user.password)
232 232 if not password_match:
233 233 return True
234 234
235 235 return False
236 236
237 237 user_data = {
238 238 'username': username,
239 239 'password': password,
240 240 'email': email,
241 241 'firstname': firstname,
242 242 'lastname': lastname,
243 243 'active': active,
244 244 'admin': admin
245 245 }
246 246
247 247 if updating_user_id:
248 248 log.debug('Checking for existing account in RhodeCode '
249 249 'database with user_id `%s` ' % (updating_user_id,))
250 250 user = User.get(updating_user_id)
251 251 else:
252 252 log.debug('Checking for existing account in RhodeCode '
253 253 'database with username `%s` ' % (username,))
254 254 user = User.get_by_username(username, case_insensitive=True)
255 255
256 256 if user is None:
257 257 # we check internal flag if this method is actually allowed to
258 258 # create new user
259 259 if not allow_to_create_user:
260 260 msg = ('Method wants to create new user, but it is not '
261 261 'allowed to do so')
262 262 log.warning(msg)
263 263 raise NotAllowedToCreateUserError(msg)
264 264
265 265 log.debug('Creating new user %s', username)
266 266
267 267 # only if we create user that is active
268 268 new_active_user = active
269 269 if new_active_user and strict_creation_check:
270 270 # raises UserCreationError if it's not allowed for any reason to
271 271 # create new active user, this also executes pre-create hooks
272 272 check_allowed_create_user(user_data, cur_user, strict_check=True)
273 273 events.trigger(events.UserPreCreate(user_data))
274 274 new_user = User()
275 275 edit = False
276 276 else:
277 277 log.debug('updating user %s', username)
278 278 events.trigger(events.UserPreUpdate(user, user_data))
279 279 new_user = user
280 280 edit = True
281 281
282 282 # we're not allowed to edit default user
283 283 if user.username == User.DEFAULT_USER:
284 284 raise DefaultUserException(
285 285 _("You can't edit this user (`%(username)s`) since it's "
286 286 "crucial for entire application") % {'username': user.username})
287 287
288 288 # inject special attribute that will tell us if User is new or old
289 289 new_user.is_new_user = not edit
290 290 # for users that didn's specify auth type, we use RhodeCode built in
291 291 from rhodecode.authentication.plugins import auth_rhodecode
292 292 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
293 293 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
294 294
295 295 try:
296 296 new_user.username = username
297 297 new_user.admin = admin
298 298 new_user.email = email
299 299 new_user.active = active
300 300 new_user.extern_name = safe_unicode(extern_name)
301 301 new_user.extern_type = safe_unicode(extern_type)
302 302 new_user.name = firstname
303 303 new_user.lastname = lastname
304 304
305 305 if not edit:
306 306 new_user.api_key = generate_auth_token(username)
307 307
308 308 # set password only if creating an user or password is changed
309 309 if not edit or _password_change(new_user, password):
310 310 reason = 'new password' if edit else 'new user'
311 311 log.debug('Updating password reason=>%s', reason)
312 312 new_user.password = get_crypt_password(password) if password else None
313 313
314 314 if force_password_change:
315 315 new_user.update_userdata(force_password_change=True)
316 316 if language:
317 317 new_user.update_userdata(language=language)
318 318
319 319 self.sa.add(new_user)
320 320
321 321 if not edit and create_repo_group:
322 322 # create new group same as username, and make this user an owner
323 323 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
324 324 RepoGroupModel().create(group_name=username,
325 325 group_description=desc,
326 326 owner=username, commit_early=False)
327 327 if not edit:
328 328 # add the RSS token
329 329 AuthTokenModel().create(username,
330 330 description='Generated feed token',
331 331 role=AuthTokenModel.cls.ROLE_FEED)
332 332 log_create_user(created_by=cur_user, **new_user.get_dict())
333 333 return new_user
334 334 except (DatabaseError,):
335 335 log.error(traceback.format_exc())
336 336 raise
337 337
338 338 def create_registration(self, form_data):
339 339 from rhodecode.model.notification import NotificationModel
340 340 from rhodecode.model.notification import EmailNotificationModel
341 341
342 342 try:
343 343 form_data['admin'] = False
344 344 form_data['extern_name'] = 'rhodecode'
345 345 form_data['extern_type'] = 'rhodecode'
346 346 new_user = self.create(form_data)
347 347
348 348 self.sa.add(new_user)
349 349 self.sa.flush()
350 350
351 351 user_data = new_user.get_dict()
352 352 kwargs = {
353 353 # use SQLALCHEMY safe dump of user data
354 354 'user': AttributeDict(user_data),
355 355 'date': datetime.datetime.now()
356 356 }
357 357 notification_type = EmailNotificationModel.TYPE_REGISTRATION
358 358 # pre-generate the subject for notification itself
359 359 (subject,
360 360 _h, _e, # we don't care about those
361 361 body_plaintext) = EmailNotificationModel().render_email(
362 362 notification_type, **kwargs)
363 363
364 364 # create notification objects, and emails
365 365 NotificationModel().create(
366 366 created_by=new_user,
367 367 notification_subject=subject,
368 368 notification_body=body_plaintext,
369 369 notification_type=notification_type,
370 370 recipients=None, # all admins
371 371 email_kwargs=kwargs,
372 372 )
373 373
374 374 return new_user
375 375 except Exception:
376 376 log.error(traceback.format_exc())
377 377 raise
378 378
379 379 def _handle_user_repos(self, username, repositories, handle_mode=None):
380 380 _superadmin = self.cls.get_first_super_admin()
381 381 left_overs = True
382 382
383 383 from rhodecode.model.repo import RepoModel
384 384
385 385 if handle_mode == 'detach':
386 386 for obj in repositories:
387 387 obj.user = _superadmin
388 388 # set description we know why we super admin now owns
389 389 # additional repositories that were orphaned !
390 390 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
391 391 self.sa.add(obj)
392 392 left_overs = False
393 393 elif handle_mode == 'delete':
394 394 for obj in repositories:
395 395 RepoModel().delete(obj, forks='detach')
396 396 left_overs = False
397 397
398 398 # if nothing is done we have left overs left
399 399 return left_overs
400 400
401 401 def _handle_user_repo_groups(self, username, repository_groups,
402 402 handle_mode=None):
403 403 _superadmin = self.cls.get_first_super_admin()
404 404 left_overs = True
405 405
406 406 from rhodecode.model.repo_group import RepoGroupModel
407 407
408 408 if handle_mode == 'detach':
409 409 for r in repository_groups:
410 410 r.user = _superadmin
411 411 # set description we know why we super admin now owns
412 412 # additional repositories that were orphaned !
413 413 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
414 414 self.sa.add(r)
415 415 left_overs = False
416 416 elif handle_mode == 'delete':
417 417 for r in repository_groups:
418 418 RepoGroupModel().delete(r)
419 419 left_overs = False
420 420
421 421 # if nothing is done we have left overs left
422 422 return left_overs
423 423
424 424 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
425 425 _superadmin = self.cls.get_first_super_admin()
426 426 left_overs = True
427 427
428 428 from rhodecode.model.user_group import UserGroupModel
429 429
430 430 if handle_mode == 'detach':
431 431 for r in user_groups:
432 432 for user_user_group_to_perm in r.user_user_group_to_perm:
433 433 if user_user_group_to_perm.user.username == username:
434 434 user_user_group_to_perm.user = _superadmin
435 435 r.user = _superadmin
436 436 # set description we know why we super admin now owns
437 437 # additional repositories that were orphaned !
438 438 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
439 439 self.sa.add(r)
440 440 left_overs = False
441 441 elif handle_mode == 'delete':
442 442 for r in user_groups:
443 443 UserGroupModel().delete(r)
444 444 left_overs = False
445 445
446 446 # if nothing is done we have left overs left
447 447 return left_overs
448 448
449 449 def delete(self, user, cur_user=None, handle_repos=None,
450 450 handle_repo_groups=None, handle_user_groups=None):
451 451 if not cur_user:
452 452 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
453 453 user = self._get_user(user)
454 454
455 455 try:
456 456 if user.username == User.DEFAULT_USER:
457 457 raise DefaultUserException(
458 458 _(u"You can't remove this user since it's"
459 459 u" crucial for entire application"))
460 460
461 461 left_overs = self._handle_user_repos(
462 462 user.username, user.repositories, handle_repos)
463 463 if left_overs and user.repositories:
464 464 repos = [x.repo_name for x in user.repositories]
465 465 raise UserOwnsReposException(
466 466 _(u'user "%s" still owns %s repositories and cannot be '
467 467 u'removed. Switch owners or remove those repositories:%s')
468 468 % (user.username, len(repos), ', '.join(repos)))
469 469
470 470 left_overs = self._handle_user_repo_groups(
471 471 user.username, user.repository_groups, handle_repo_groups)
472 472 if left_overs and user.repository_groups:
473 473 repo_groups = [x.group_name for x in user.repository_groups]
474 474 raise UserOwnsRepoGroupsException(
475 475 _(u'user "%s" still owns %s repository groups and cannot be '
476 476 u'removed. Switch owners or remove those repository groups:%s')
477 477 % (user.username, len(repo_groups), ', '.join(repo_groups)))
478 478
479 479 left_overs = self._handle_user_user_groups(
480 480 user.username, user.user_groups, handle_user_groups)
481 481 if left_overs and user.user_groups:
482 482 user_groups = [x.users_group_name for x in user.user_groups]
483 483 raise UserOwnsUserGroupsException(
484 484 _(u'user "%s" still owns %s user groups and cannot be '
485 485 u'removed. Switch owners or remove those user groups:%s')
486 486 % (user.username, len(user_groups), ', '.join(user_groups)))
487 487
488 488 # we might change the user data with detach/delete, make sure
489 489 # the object is marked as expired before actually deleting !
490 490 self.sa.expire(user)
491 491 self.sa.delete(user)
492 492 from rhodecode.lib.hooks_base import log_delete_user
493 493 log_delete_user(deleted_by=cur_user, **user.get_dict())
494 494 except Exception:
495 495 log.error(traceback.format_exc())
496 496 raise
497 497
498 498 def reset_password_link(self, data, pwd_reset_url):
499 499 from rhodecode.lib.celerylib import tasks, run_task
500 500 from rhodecode.model.notification import EmailNotificationModel
501 501 user_email = data['email']
502 502 try:
503 503 user = User.get_by_email(user_email)
504 504 if user:
505 505 log.debug('password reset user found %s', user)
506 506
507 507 email_kwargs = {
508 508 'password_reset_url': pwd_reset_url,
509 509 'user': user,
510 510 'email': user_email,
511 511 'date': datetime.datetime.now()
512 512 }
513 513
514 514 (subject, headers, email_body,
515 515 email_body_plaintext) = EmailNotificationModel().render_email(
516 516 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
517 517
518 518 recipients = [user_email]
519 519
520 520 action_logger_generic(
521 521 'sending password reset email to user: {}'.format(
522 522 user), namespace='security.password_reset')
523 523
524 524 run_task(tasks.send_email, recipients, subject,
525 525 email_body_plaintext, email_body)
526 526
527 527 else:
528 528 log.debug("password reset email %s not found", user_email)
529 529 except Exception:
530 530 log.error(traceback.format_exc())
531 531 return False
532 532
533 533 return True
534 534
535 def reset_password(self, data):
535 def reset_password(self, data, pwd_reset_url):
536 536 from rhodecode.lib.celerylib import tasks, run_task
537 537 from rhodecode.model.notification import EmailNotificationModel
538 538 from rhodecode.lib import auth
539 539 user_email = data['email']
540 540 pre_db = True
541 541 try:
542 542 user = User.get_by_email(user_email)
543 543 new_passwd = auth.PasswordGenerator().gen_password(
544 544 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
545 545 if user:
546 546 user.password = auth.get_crypt_password(new_passwd)
547 547 # also force this user to reset his password !
548 548 user.update_userdata(force_password_change=True)
549 549
550 550 Session().add(user)
551 551 Session().commit()
552 552 log.info('change password for %s', user_email)
553 553 if new_passwd is None:
554 554 raise Exception('unable to generate new password')
555 555
556 556 pre_db = False
557 557
558 558 email_kwargs = {
559 559 'new_password': new_passwd,
560 'password_reset_url': pwd_reset_url,
560 561 'user': user,
561 562 'email': user_email,
562 563 'date': datetime.datetime.now()
563 564 }
564 565
565 566 (subject, headers, email_body,
566 567 email_body_plaintext) = EmailNotificationModel().render_email(
567 568 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
568 569
569 570 recipients = [user_email]
570 571
571 572 action_logger_generic(
572 573 'sent new password to user: {} with email: {}'.format(
573 574 user, user_email), namespace='security.password_reset')
574 575
575 576 run_task(tasks.send_email, recipients, subject,
576 577 email_body_plaintext, email_body)
577 578
578 579 except Exception:
579 580 log.error('Failed to update user password')
580 581 log.error(traceback.format_exc())
581 582 if pre_db:
582 583 # we rollback only if local db stuff fails. If it goes into
583 584 # run_task, we're pass rollback state this wouldn't work then
584 585 Session().rollback()
585 586
586 587 return True
587 588
588 589 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
589 590 """
590 591 Fetches auth_user by user_id,or api_key if present.
591 592 Fills auth_user attributes with those taken from database.
592 593 Additionally set's is_authenitated if lookup fails
593 594 present in database
594 595
595 596 :param auth_user: instance of user to set attributes
596 597 :param user_id: user id to fetch by
597 598 :param api_key: api key to fetch by
598 599 :param username: username to fetch by
599 600 """
600 601 if user_id is None and api_key is None and username is None:
601 602 raise Exception('You need to pass user_id, api_key or username')
602 603
603 604 log.debug(
604 605 'doing fill data based on: user_id:%s api_key:%s username:%s',
605 606 user_id, api_key, username)
606 607 try:
607 608 dbuser = None
608 609 if user_id:
609 610 dbuser = self.get(user_id)
610 611 elif api_key:
611 612 dbuser = self.get_by_auth_token(api_key)
612 613 elif username:
613 614 dbuser = self.get_by_username(username)
614 615
615 616 if not dbuser:
616 617 log.warning(
617 618 'Unable to lookup user by id:%s api_key:%s username:%s',
618 619 user_id, api_key, username)
619 620 return False
620 621 if not dbuser.active:
621 622 log.debug('User `%s` is inactive, skipping fill data', username)
622 623 return False
623 624
624 625 log.debug('filling user:%s data', dbuser)
625 626
626 627 # TODO: johbo: Think about this and find a clean solution
627 628 user_data = dbuser.get_dict()
628 629 user_data.update(dbuser.get_api_data(include_secrets=True))
629 630
630 631 for k, v in user_data.iteritems():
631 632 # properties of auth user we dont update
632 633 if k not in ['auth_tokens', 'permissions']:
633 634 setattr(auth_user, k, v)
634 635
635 636 # few extras
636 637 setattr(auth_user, 'feed_token', dbuser.feed_token)
637 638 except Exception:
638 639 log.error(traceback.format_exc())
639 640 auth_user.is_authenticated = False
640 641 return False
641 642
642 643 return True
643 644
644 645 def has_perm(self, user, perm):
645 646 perm = self._get_perm(perm)
646 647 user = self._get_user(user)
647 648
648 649 return UserToPerm.query().filter(UserToPerm.user == user)\
649 650 .filter(UserToPerm.permission == perm).scalar() is not None
650 651
651 652 def grant_perm(self, user, perm):
652 653 """
653 654 Grant user global permissions
654 655
655 656 :param user:
656 657 :param perm:
657 658 """
658 659 user = self._get_user(user)
659 660 perm = self._get_perm(perm)
660 661 # if this permission is already granted skip it
661 662 _perm = UserToPerm.query()\
662 663 .filter(UserToPerm.user == user)\
663 664 .filter(UserToPerm.permission == perm)\
664 665 .scalar()
665 666 if _perm:
666 667 return
667 668 new = UserToPerm()
668 669 new.user = user
669 670 new.permission = perm
670 671 self.sa.add(new)
671 672 return new
672 673
673 674 def revoke_perm(self, user, perm):
674 675 """
675 676 Revoke users global permissions
676 677
677 678 :param user:
678 679 :param perm:
679 680 """
680 681 user = self._get_user(user)
681 682 perm = self._get_perm(perm)
682 683
683 684 obj = UserToPerm.query()\
684 685 .filter(UserToPerm.user == user)\
685 686 .filter(UserToPerm.permission == perm)\
686 687 .scalar()
687 688 if obj:
688 689 self.sa.delete(obj)
689 690
690 691 def add_extra_email(self, user, email):
691 692 """
692 693 Adds email address to UserEmailMap
693 694
694 695 :param user:
695 696 :param email:
696 697 """
697 698 from rhodecode.model import forms
698 699 form = forms.UserExtraEmailForm()()
699 700 data = form.to_python({'email': email})
700 701 user = self._get_user(user)
701 702
702 703 obj = UserEmailMap()
703 704 obj.user = user
704 705 obj.email = data['email']
705 706 self.sa.add(obj)
706 707 return obj
707 708
708 709 def delete_extra_email(self, user, email_id):
709 710 """
710 711 Removes email address from UserEmailMap
711 712
712 713 :param user:
713 714 :param email_id:
714 715 """
715 716 user = self._get_user(user)
716 717 obj = UserEmailMap.query().get(email_id)
717 718 if obj:
718 719 self.sa.delete(obj)
719 720
720 721 def parse_ip_range(self, ip_range):
721 722 ip_list = []
722 723 def make_unique(value):
723 724 seen = []
724 725 return [c for c in value if not (c in seen or seen.append(c))]
725 726
726 727 # firsts split by commas
727 728 for ip_range in ip_range.split(','):
728 729 if not ip_range:
729 730 continue
730 731 ip_range = ip_range.strip()
731 732 if '-' in ip_range:
732 733 start_ip, end_ip = ip_range.split('-', 1)
733 734 start_ip = ipaddress.ip_address(start_ip.strip())
734 735 end_ip = ipaddress.ip_address(end_ip.strip())
735 736 parsed_ip_range = []
736 737
737 738 for index in xrange(int(start_ip), int(end_ip) + 1):
738 739 new_ip = ipaddress.ip_address(index)
739 740 parsed_ip_range.append(str(new_ip))
740 741 ip_list.extend(parsed_ip_range)
741 742 else:
742 743 ip_list.append(ip_range)
743 744
744 745 return make_unique(ip_list)
745 746
746 747 def add_extra_ip(self, user, ip, description=None):
747 748 """
748 749 Adds ip address to UserIpMap
749 750
750 751 :param user:
751 752 :param ip:
752 753 """
753 754 from rhodecode.model import forms
754 755 form = forms.UserExtraIpForm()()
755 756 data = form.to_python({'ip': ip})
756 757 user = self._get_user(user)
757 758
758 759 obj = UserIpMap()
759 760 obj.user = user
760 761 obj.ip_addr = data['ip']
761 762 obj.description = description
762 763 self.sa.add(obj)
763 764 return obj
764 765
765 766 def delete_extra_ip(self, user, ip_id):
766 767 """
767 768 Removes ip address from UserIpMap
768 769
769 770 :param user:
770 771 :param ip_id:
771 772 """
772 773 user = self._get_user(user)
773 774 obj = UserIpMap.query().get(ip_id)
774 775 if obj:
775 776 self.sa.delete(obj)
776 777
777 778 def get_accounts_in_creation_order(self, current_user=None):
778 779 """
779 780 Get accounts in order of creation for deactivation for license limits
780 781
781 782 pick currently logged in user, and append to the list in position 0
782 783 pick all super-admins in order of creation date and add it to the list
783 784 pick all other accounts in order of creation and add it to the list.
784 785
785 786 Based on that list, the last accounts can be disabled as they are
786 787 created at the end and don't include any of the super admins as well
787 788 as the current user.
788 789
789 790 :param current_user: optionally current user running this operation
790 791 """
791 792
792 793 if not current_user:
793 794 current_user = get_current_rhodecode_user()
794 795 active_super_admins = [
795 796 x.user_id for x in User.query()
796 797 .filter(User.user_id != current_user.user_id)
797 798 .filter(User.active == true())
798 799 .filter(User.admin == true())
799 800 .order_by(User.created_on.asc())]
800 801
801 802 active_regular_users = [
802 803 x.user_id for x in User.query()
803 804 .filter(User.user_id != current_user.user_id)
804 805 .filter(User.active == true())
805 806 .filter(User.admin == false())
806 807 .order_by(User.created_on.asc())]
807 808
808 809 list_of_accounts = [current_user.user_id]
809 810 list_of_accounts += active_super_admins
810 811 list_of_accounts += active_regular_users
811 812
812 813 return list_of_accounts
813 814
814 815 def deactivate_last_users(self, expected_users):
815 816 """
816 817 Deactivate accounts that are over the license limits.
817 818 Algorithm of which accounts to disabled is based on the formula:
818 819
819 820 Get current user, then super admins in creation order, then regular
820 821 active users in creation order.
821 822
822 823 Using that list we mark all accounts from the end of it as inactive.
823 824 This way we block only latest created accounts.
824 825
825 826 :param expected_users: list of users in special order, we deactivate
826 827 the end N ammoun of users from that list
827 828 """
828 829
829 830 list_of_accounts = self.get_accounts_in_creation_order()
830 831
831 832 for acc_id in list_of_accounts[expected_users + 1:]:
832 833 user = User.get(acc_id)
833 834 log.info('Deactivating account %s for license unlock', user)
834 835 user.active = False
835 836 Session().add(user)
836 837 Session().commit()
837 838
838 839 return
General Comments 0
You need to be logged in to leave comments. Login now