ProfileView.change_password()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
dl 0
loc 8
rs 10
1
# -*- encoding: utf-8 -*-
2
import datetime
3
import json
4
import logging
5
import os
6
7
from django.contrib.auth import authenticate
8
from django.contrib.auth import login as djangologin
9
from django.contrib.auth import logout as djangologout
10
from django.contrib.auth.hashers import make_password
11
from django.core.mail import mail_admins
12
13
from chat.templatetags.md5url import md5url
14
from chat.tornado.message_creator import MessagesCreator
15
16
try:
17
	from django.template.context_processors import csrf
18
except ImportError:
19
	from django.core.context_processors import csrf
20
from django.core.exceptions import ObjectDoesNotExist, ValidationError, PermissionDenied
21
from django.db import transaction
22
from django.db.models import Count, Q
23
from django.http import Http404
24
from django.http import HttpResponse
25
from django.shortcuts import render_to_response
26
from django.template import RequestContext
27
from django.views.decorators.http import require_http_methods
28
from django.views.generic import View
29
from chat import utils
30
from chat.decorators import login_required_no_redirect, validation
31
from chat.forms import UserProfileForm, UserProfileReadOnlyForm
32
from chat.models import Issue, IssueDetails, IpAddress, UserProfile, Verification, Message, Subscription, \
33
	SubscriptionMessages, RoomUsers, Room, UserJoinedInfo, UploadedFile
34
from django.conf import settings
35
from chat.utils import hide_fields, check_user, check_password, check_email, extract_photo, send_sign_up_email, \
36
	create_user_model, check_captcha, send_reset_password_email, get_client_ip, get_or_create_ip, \
37
	send_password_changed, send_email_change, send_new_email_ver, get_message_images_videos
38
39
logger = logging.getLogger(__name__)
40
RECAPTCHA_PUBLIC_KEY = getattr(settings, "RECAPTCHA_PUBLIC_KEY", None)
41
RECAPTHCA_SITE_URL = getattr(settings, "RECAPTHCA_SITE_URL", None)
42
GOOGLE_OAUTH_2_CLIENT_ID = getattr(settings, "GOOGLE_OAUTH_2_CLIENT_ID", None)
43
GOOGLE_OAUTH_2_JS_URL = getattr(settings, "GOOGLE_OAUTH_2_JS_URL", None)
44
FACEBOOK_APP_ID = getattr(settings, "FACEBOOK_APP_ID", None)
45
FACEBOOK_JS_URL = getattr(settings, "FACEBOOK_JS_URL", None)
46
47
# TODO doesn't work
48
def handler404(request):
49
	return HttpResponse("Page not found", content_type='text/plain')
50
51
52
@require_http_methods(['POST'])
53
@validation
54
def validate_email(request):
55
	"""
56
	POST only, validates email during registration
57
	"""
58
	utils.check_email(request.POST.get('email'))
59
	return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')
60
61
62
@require_http_methods(['POST'])
63
@login_required_no_redirect(False)
64
@transaction.atomic
65
def upload_file(request):
66
	"""
67
	POST only, validates email during registration
68
	"""
69
	logger.debug('Uploading file %s', request.POST)
70
	res = []
71
	for file in request.FILES:
72
		uf = UploadedFile(symbol=file[1], user=request.user, file=request.FILES[file], type_enum=UploadedFile.UploadedFileChoices(file[0]))
73
		uf.save()
74
		res.append(uf.id)
75
	return HttpResponse(json.dumps(res), content_type='application/json')
76
77
78
@require_http_methods(['POST'])
79
@login_required_no_redirect(False)
80
@transaction.atomic
81
def save_room_settings(request):
82
	"""
83
	POST only, validates email during registration
84
	"""
85
	logger.debug('save_room_settings request,  %s', request.POST)
86
	room_id = request.POST['roomId']
87
	room_name = request.POST.get('roomName')
88
	updated = RoomUsers.objects.filter(room_id=room_id, user_id=request.user.id).update(
89
		volume=request.POST['volume'],
90
		notifications=request.POST['notifications'] == 'true',
91
	)
92
	if updated != 1:
93
		raise PermissionDenied
94
	if room_name is not None:
95
		room_name = room_name.strip()
96
		if room_name and int(room_id) != settings.ALL_ROOM_ID:
97
			Room.objects.filter(id=room_id).update(name = room_name)
98
	return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')
99
100
101
@require_http_methods('GET')
102
@transaction.atomic
103
def get_firebase_playback(request):
104
	registration_id = request.META['HTTP_AUTH']
105
	logger.debug('Firebase playback, id %s', registration_id)
106
	query_sub_message = SubscriptionMessages.objects.filter(subscription__registration_id=registration_id, received=False).order_by('-message__time')[:1]
107
	sub_message = query_sub_message[0]
108
	SubscriptionMessages.objects.filter(id=sub_message.id).update(received=True)
109
	message = Message.objects.select_related("sender__username", "room__name").get(id=sub_message.message_id)
110
	data = {
111
		'title': message.sender.username,
112
		'options': {
113
			'body': message.content,
114
			'icon': md5url('images/favicon.ico'),
115
			'data': {
116
				'id': sub_message.message_id,
117
				'sender': message.sender.username,
118
				'room': message.room.name,
119
				'roomId': message.room_id
120
			},
121
			'requireInteraction': True
122
		},
123
	}
124
	return HttpResponse(json.dumps(data), content_type='application/json')
125
126
127
def test(request):
128
	return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')
129
130
131
@require_http_methods('POST')
132
@login_required_no_redirect(False)
133
@validation
134
def search_messages(request):
135
	data = request.POST['data']
136
	room_id = request.POST['room']
137
	offset = int(request.POST['offset'])
138
	if not RoomUsers.objects.filter(room_id=room_id, user_id=request.user.id).exists():
139
		raise ValidationError("You can't access this room")
140
	messages = Message.objects.filter(content__icontains=data, room_id=room_id).order_by('-id')[offset:offset+settings.MESSAGES_PER_SEARCH]
141
	imv = get_message_images_videos(messages)
142
	result = []
143
	for message in messages:
144
		files = MessagesCreator.prepare_img_video(imv, message.id)
145
		prep_m = MessagesCreator.create_message(message, files)
146
		result.append(prep_m)
147
	response = json.dumps(result)
148
	return HttpResponse(response, content_type='application/json')
149
150
151
@require_http_methods('POST')
152
def register_subscription(request):
153
	logger.debug('Subscription request,  %s', request)
154
	registration_id = request.POST['registration_id']
155
	agent = request.POST['agent']
156
	is_mobile = request.POST['is_mobile']
157
	ip = get_or_create_ip(get_client_ip(request), logger)
158
	Subscription.objects.update_or_create(
159
		registration_id=registration_id,
160
		defaults={
161
			'user': request.user,
162
			'inactive': False,
163
			'updated': datetime.datetime.now(),
164
			'agent': agent,
165
			'is_mobile': is_mobile == 'true',
166
			'ip': ip
167
		}
168
	)
169
	return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')
170
171
@require_http_methods('POST')
172
@validation
173
def validate_user(request):
174
	"""
175
	Validates user during registration
176
	"""
177
	utils.check_user(request.POST.get('username'))
178
	# hardcoded ok check in register.js
179
	return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')
180
181
182
def get_service_worker(request):  # this stub is only for development, this is replaced in nginx for prod
183
	worker = open(os.path.join(settings.STATIC_ROOT, 'js', 'sw.js'), 'rb')
184
	response = HttpResponse(content=worker)
185
	response['Content-Type'] = 'application/javascript'
186
	return response
187
188
@require_http_methods('GET')
189
@login_required_no_redirect(False)
190
def home(request):
191
	"""
192
	Login or logout navbar is creates by means of create_nav_page
193
	@return:  the x intercept of the line M{y=m*x+b}.
194
	"""
195
	context = csrf(request)
196
	ip = get_client_ip(request)
197
	if not UserJoinedInfo.objects.filter(Q(ip__ip=ip) & Q(user=request.user)).exists():
198
		ip_obj = get_or_create_ip(ip, logger)
199
		UserJoinedInfo.objects.create(ip=ip_obj, user=request.user)
200
	up = UserProfile.objects.defer('suggestions', 'highlight_code', 'embedded_youtube', 'online_change_sound', 'incoming_file_call_sound', 'message_sound', 'theme').get(id=request.user.id)
201
	context['suggestions'] = up.suggestions
202
	context['highlight_code'] = up.highlight_code
203
	context['message_sound'] = up.message_sound
204
	context['incoming_file_call_sound'] = up.incoming_file_call_sound
205
	context['online_change_sound'] = up.online_change_sound
206
	context['theme'] = up.theme
207
	context['embedded_youtube'] = up.embedded_youtube
208
	context['extensionId'] = settings.EXTENSION_ID
209
	context['extensionUrl'] = settings.EXTENSION_INSTALL_URL
210
	context['defaultRoomId'] = settings.ALL_ROOM_ID
211
	context['pingCloseDelay'] = settings.PING_CLOSE_JS_DELAY
212
	context['pingServerCloseDelay'] = settings.CLIENT_NO_SERVER_PING_CLOSE_TIMEOUT
213
	context['MESSAGES_PER_SEARCH'] = settings.MESSAGES_PER_SEARCH
214
	context['manifest'] = hasattr(settings, 'FIREBASE_API_KEY')
215
	context['apiUrl'] = settings.API_ADDRESS_PATTERN % request.get_host().split(':')[0]
216
	context['sendLogs'] = up.send_logs
217
	return render_to_response('chat.html', context, context_instance=RequestContext(request))
218
219
220
@login_required_no_redirect(True)
221
def logout(request):
222
	"""
223
	POST. Logs out into system.
224
	"""
225
	registration_id = request.POST.get('registration_id')
226
	if registration_id is not None:
227
		Subscription.objects.filter(registration_id=registration_id).delete()
228
	djangologout(request)
229
	return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')
230
231
232
@require_http_methods(['POST'])
233
def auth(request):
234
	"""
235
	Logs in into system.
236
	"""
237
	username = request.POST.get('username')
238
	password = request.POST.get('password')
239
	user = authenticate(username=username, password=password)
240
	if user is not None:
241
		djangologin(request, user)
242
		message = settings.VALIDATION_IS_OK
243
	else:
244
		message = 'Login or password is wrong'
245
	logger.debug('Auth request %s ; Response: %s', hide_fields(request.POST, ('password',)), message)
246
	return HttpResponse(message, content_type='text/plain')
247
248
249
def send_restore_password(request):
250
	"""
251
	Sends email verification code
252
	"""
253
	logger.debug('Recover password request %s', request)
254
	try:
255
		username_or_password = request.POST.get('username_or_password')
256
		check_captcha(request)
257
		user_profile = UserProfile.objects.get(Q(username=username_or_password) | Q(email=username_or_password))
258
		if not user_profile.email:
259
			raise ValidationError("You didn't specify email address for this user")
260
		verification = Verification(type_enum=Verification.TypeChoices.password, user_id=user_profile.id)
261
		verification.save()
262
		send_reset_password_email(request, user_profile, verification)
263
		message = settings.VALIDATION_IS_OK
264
		logger.debug('Verification email has been send for token %s to user %s(id=%d)',
265
				verification.token, user_profile.username, user_profile.id)
266
	except UserProfile.DoesNotExist:
267
		message = "User with this email or username doesn't exist"
268
		logger.debug("Skipping password recovery request for nonexisting user")
269
	except (UserProfile.DoesNotExist, ValidationError) as e:
270
		logger.debug('Not sending verification email because %s', e)
271
		message = 'Unfortunately we were not able to send you restore password email because {}'.format(e)
272
	return HttpResponse(message, content_type='text/plain')
273
274
275
@require_http_methods(['GET'])
276
def proceed_email_changed(request):
277
	try:
278
		with transaction.atomic():
279
			token = request.GET['token']
280
			logger.debug('Proceed change email with token %s', token)
281
			user, verification = utils.get_user_by_code(token, Verification.TypeChoices.email)
282
			new_ver = send_new_email_ver(request, user, verification.email)
283
			user.email = verification.email
284
			user.email_verification = new_ver
285
			user.save(update_fields=('email', 'email_verification'))
286
			verification.verified = True
287
			verification.save(update_fields=('verified',))
288
			logger.info('Email has been change for token %s user %s(id=%d)', token, user.username, user.id)
289
			return render_to_response(
290
				'email_changed.html',
291
				{'text': 'Your email has been changed to {}.'.format(verification.email)},
292
				context_instance=RequestContext(request)
293
			)
294
	except Exception as e:
295
		return render_to_response(
296
			'email_changed.html',
297
			{'text': 'Unable to change your email because {}'.format(e.message)}
298
			, context_instance=RequestContext(request)
299
		)
300
301
302
class RestorePassword(View):
303
304
	@transaction.atomic
305
	@validation
306
	def post(self, request):
307
		"""
308
		Sends email verification token
309
		"""
310
		token = request.POST.get('token', False)
311
		logger.debug('Proceed Recover password with token %s', token)
312
		user, verification = utils.get_user_by_code(token, Verification.TypeChoices.password)
313
		password = request.POST.get('password')
314
		check_password(password)
315
		user.set_password(password)
316
		user.save(update_fields=('password',))
317
		verification.verified = True
318
		verification.save(update_fields=('verified',))
319
		logger.info('Password has been change for token %s user %s(id=%d)', token, user.username, user.id)
320
		return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')
321
322
	def get(self, request):
323
		token = request.GET.get('token', False)
324
		logger.debug('Rendering restore password page with token  %s', token)
325
		try:
326
			user = utils.get_user_by_code(token, Verification.TypeChoices.password)[0]
327
			response = {
328
				'message': settings.VALIDATION_IS_OK,
329
				'restore_user': user.username,
330
				'token': token
331
			}
332
		except ValidationError as e:
333
			logger.debug('Rejecting verification token %s because %s', token, e)
334
			response = {'message': "Unable to confirm email with token {} because {}".format(token, e)}
335
		return render_to_response('reset_password.html', response, context_instance=RequestContext(request))
336
337
338
@require_http_methods('GET')
339
def confirm_email(request):
340
	"""
341
	Accept the verification token sent to email
342
	"""
343
	token = request.GET.get('token', False)
344
	logger.debug('Processing email confirm with token  %s', token)
345
	try:
346
		try:
347
			v = Verification.objects.get(token=token)
348
		except Verification.DoesNotExist:
349
			raise ValidationError('Unknown verification token')
350
		if v.type_enum not in (Verification.TypeChoices.register, Verification.TypeChoices.confirm_email):
351
			raise ValidationError('This is not confirm email token')
352
		if v.verified:
353
			raise ValidationError('This verification token already accepted')
354
		user = UserProfile.objects.get(id=v.user_id)
355
		if user.email_verification_id != v.id:
356
			raise ValidationError('Verification token expired because you generated another one')
357
		v.verified = True
358
		v.save(update_fields=['verified'])
359
		message = settings.VALIDATION_IS_OK
360
		logger.info('Email verification token %s has been accepted for user %s(id=%d)', token, user.username, user.id)
361
	except Exception as e:
362
		logger.debug('Rejecting verification token %s because %s', token, e)
363
		message = ("Unable to confirm email with token {} because {}".format(token, e))
364
	response = {'message': message}
365
	return render_to_response('confirm_mail.html', response, context_instance=RequestContext(request))
366
367
368
@require_http_methods('GET')
369
def show_profile(request, profile_id):
370
	try:
371
		user_profile = UserProfile.objects.get(pk=profile_id)
372
		form = UserProfileReadOnlyForm(instance=user_profile)
373
		form.username = user_profile.username
374
		return render_to_response(
375
			'show_profile.html',
376
			{'form': form},
377
			context_instance=RequestContext(request)
378
		)
379
	except ObjectDoesNotExist:
380
		raise Http404
381
382
383
@require_http_methods('GET')
384
def statistics(request):
385
	pie_data = IpAddress.objects.values('country').filter(country__isnull=False).annotate(count=Count("country"))
386
	return HttpResponse(json.dumps(list(pie_data)), content_type='application/json')
387
388
389
@transaction.atomic
390
def report_issue(request):
391
	logger.info('Saving issue: %s', hide_fields(request.POST, ('log',), huge=True))
392
	issue_text = request.POST['issue']
393
	issue = Issue.objects.get_or_create(content=issue_text)[0]
394
	issue_details = IssueDetails(
395
		sender_id=request.user.id,
396
		browser=request.POST.get('browser'),
397
		issue=issue,
398
		log=request.POST.get('log')
399
	)
400
	try:
401
		mail_admins("{} reported issue".format(request.user.username), issue_text, fail_silently=True)
402
	except Exception as e:
403
		logging.error("Failed to send issue email because {}".format(e))
404
	issue_details.save()
405
	return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')
406
407
408
class ProfileView(View):
409
410
	@login_required_no_redirect()
411
	def get(self, request):
412
		user_profile = UserProfile.objects.get(pk=request.user.id)
413
		form = UserProfileForm(instance=user_profile)
414
		c = csrf(request)
415
		c['form'] = form
416
		c['date_format'] = settings.DATE_INPUT_FORMATS_JS
417
		return render_to_response('change_profile.html', c, context_instance=RequestContext(request))
418
419
420
	@transaction.atomic
421
	@login_required_no_redirect()
422
	@validation # should follow after transaciton.atomic, thus ValidationError doesn't rollback
423
	def post(self, request):
424
		logger.info('Saving profile: %s', hide_fields(request.POST, ("base64_image", ), huge=True))
425
		image_base64 = request.POST.get('base64_image')
426
		new_email = request.POST['email']
427
		passwd = request.POST['password']
428
		username = request.POST['username']
429
		user_profile = UserProfile.objects.get(pk=request.user.id)
430
		if new_email:
431
			utils.validate_email(new_email)
432
		utils.validate_user(username)
433
		if image_base64:
434
			image = extract_photo(image_base64)
435
			request.FILES['photo'] = image
436
		if passwd:
437
			self.change_password(passwd, request)
438
		form = UserProfileForm(request.POST, request.FILES, instance=user_profile)
439
		if form.is_valid():
440
			if not passwd:
441
				form.instance.password = form.initial['password']
442
			if new_email != form.initial['email']:
443
				self.send_email_if_needed(form, new_email, request, user_profile)
444
			profile = form.save()
445
			if passwd and form.initial['email']:
446
				send_password_changed(request, form.initial['email'])
447
			response = profile. photo.url if 'photo' in  request.FILES else settings.VALIDATION_IS_OK
448
		else:
449
			response = form.errors
450
		return HttpResponse(response, content_type='text/plain')
451
452
	@staticmethod
453
	def change_password(passwd, request):
454
		if request.user.password:
455
			is_valid = authenticate(username=request.user.username, password=request.POST['old_password'])
456
			if not is_valid:
457
				raise ValidationError("Invalid old password")
458
		utils.check_password(passwd)
459
		request.POST['password'] = make_password(passwd)
460
461
	@staticmethod
462
	def send_email_if_needed(form, new_email, request, user_profile):
463
		if form.initial['email'] and form.instance.email_verification and form.instance.email_verification.verified:
464
			verification = Verification(
465
				type_enum=Verification.TypeChoices.email,
466
				user_id=user_profile.id,
467
				email=new_email
468
			)
469
			verification.save()
470
			send_email_change(request, request.user.username, form.initial['email'], verification, new_email)
471
			raise ValidationError(
472
				"In order to change an email please confirm it from you current address. We send you an verification email to {}.".format(
473
					form.initial['email']))
474
		if new_email:
475
			new_ver = send_new_email_ver(request, request.user, new_email)
476
			form.instance.email_verification = new_ver
477
478
479
class RegisterView(View):
480
481
	def get(self, request):
482
		logger.debug(
483
			'Rendering register page with captcha site key %s and oauth key %s',
484
			RECAPTCHA_PUBLIC_KEY, GOOGLE_OAUTH_2_CLIENT_ID
485
		)
486
		c = csrf(request)
487
		c['captcha_key'] = RECAPTCHA_PUBLIC_KEY
488
		c['captcha_url'] = RECAPTHCA_SITE_URL
489
		c['oauth_url'] = GOOGLE_OAUTH_2_JS_URL
490
		c['oauth_token'] = GOOGLE_OAUTH_2_CLIENT_ID
491
		c['fb_app_id'] = FACEBOOK_APP_ID
492
		c['fb_js_url'] = FACEBOOK_JS_URL
493
		return render_to_response("register.html", c, context_instance=RequestContext(request))
494
495
	@transaction.atomic
496
	@validation
497
	def post(self, request):
498
		rp = request.POST
499
		logger.info('Got register request %s', hide_fields(rp, ('password', 'repeatpassword')))
500
		(username, password, email) = (rp.get('username'), rp.get('password'), rp.get('email'))
501
		check_user(username)
502
		check_password(password)
503
		check_email(email)
504
		user_profile = UserProfile(username=username, email=email, sex_str=rp.get('sex'))
505
		user_profile.set_password(password)
506
		create_user_model(user_profile)
507
		# You must call authenticate before you can call login
508
		auth_user = authenticate(username=username, password=password)
509
		if email:
510
			send_sign_up_email(user_profile, request.get_host(), request)
511
		djangologin(request, auth_user)
512
		return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')
513