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