Completed
Push — master ( b307ec...4c8bc3 )
by Andrew
32s
created

RestorePassword.get_user_by_code()   B

Complexity

Conditions 5

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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