Completed
Push — master ( 5003b8...b307ec )
by Andrew
27s
created

ProfileView   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 44
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
dl 0
loc 44
rs 10
c 1
b 0
f 1
wmc 12

2 Methods

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