Completed
Push — master ( 0b926c...019c64 )
by Andrew
24s
created

upload_photo()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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