Completed
Push — master ( ec6a41...752250 )
by Andrew
01:13
created

report_issue()   A

Complexity

Conditions 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
c 2
b 0
f 0
dl 0
loc 13
rs 9.4285
1
# -*- encoding: utf-8 -*-
2
import datetime
3
import json
4
5
from django.conf import settings
6
from django.contrib.auth import authenticate
7
from django.contrib.auth import login as djangologin
8
from django.contrib.auth import logout as djangologout
9
from django.core import serializers
10
from django.core.mail import send_mail
11
12
try:
13
	from django.template.context_processors import csrf
14
except ImportError:
15
	from django.core.context_processors import csrf
16
from django.core.exceptions import ObjectDoesNotExist, ValidationError
17
from django.db import transaction
18
from django.db.models import Count, Q
19
from django.http import Http404
20
from django.http import HttpResponse
21
from django.http import HttpResponseRedirect
22
from django.shortcuts import render_to_response
23
from django.template import RequestContext
24
from django.views.decorators.http import require_http_methods
25
from django.views.generic import View
26
27
from chat import utils
28
from chat.decorators import login_required_no_redirect
29
from chat.forms import UserProfileForm, UserProfileReadOnlyForm
30
from chat.models import Issue, IssueDetails, IpAddress, UserProfile, Verification
31
from chat.settings import VALIDATION_IS_OK, DATE_INPUT_FORMATS_JS, logging, SITE_PROTOCOL
32
from chat.utils import hide_fields, check_user, check_password, check_email, extract_photo, send_email_verification, \
33
	create_user_profile, check_captcha
34
35
logger = logging.getLogger(__name__)
36
37
38
# TODO doesn't work
39
def handler404(request):
40
	return HttpResponse("Page not found", content_type='text/plain')
41
42
43
@require_http_methods(['POST'])
44
def validate_email(request):
45
	"""
46
	POST only, validates email during registration
47
	"""
48
	email = request.POST.get('email')
49
	try:
50
		utils.check_email(email)
51
		response = VALIDATION_IS_OK
52
	except ValidationError as e:
53
		response = e.message
54
	return HttpResponse(response, content_type='text/plain')
55
56
57
@require_http_methods(['GET'])
58
def update_session_key(request):
59
	"""
60
	Creates a new session key, saves it to session store and to response
61
	"""
62
	old_key = request.session.session_key
63
	request.session.create()  # updates the session_key
64
	logger.info("Session key %s has been updated to %s", old_key, request.session.session_key)
65
	request.session.modified = True
66
	return HttpResponse(VALIDATION_IS_OK, content_type='text/plain')
67
68
69
@require_http_methods('POST')
70
def validate_user(request):
71
	"""
72
	Validates user during registration
73
	"""
74
	try:
75
		username = request.POST.get('username')
76
		utils.check_user(username)
77
		# hardcoded ok check in register.js
78
		message = VALIDATION_IS_OK
79
	except ValidationError as e:
80
		message = e.message
81
	return HttpResponse(message, content_type='text/plain')
82
83
84
@require_http_methods('GET')
85
@login_required_no_redirect(False)
86
def home(request):
87
	"""
88
	Login or logout navbar is creates by means of create_nav_page
89
	@return:  the x intercept of the line M{y=m*x+b}.
90
	"""
91
	context = csrf(request)
92
	context['suggestions'] = UserProfile.objects.get(id=request.user.id).suggestions
93
	return render_to_response('chat.html', context, context_instance=RequestContext(request))
94
95
96
@login_required_no_redirect(True)
97
def logout(request):
98
	"""
99
	POST. Logs out into system.
100
	"""
101
	djangologout(request)
102
	response = HttpResponseRedirect('/')
103
	return response
104
105
106
@require_http_methods(['POST'])
107
def auth(request):
108
	"""
109
	Logs in into system.
110
	"""
111
	username = request.POST.get('username')
112
	password = request.POST.get('password')
113
	user = authenticate(username=username, password=password)
114
	if user is not None:
115
		djangologin(request, user)
116
		message = VALIDATION_IS_OK
117
	else:
118
		message = 'Login or password is wrong'
119
	logger.debug('Auth request %s ; Response: %s', hide_fields(request.POST, 'password'), message)
120
	response = HttpResponse(message, content_type='text/plain')
121
	return response
122
123
124
def send_restore_password(request):
125
	"""
126
	Sends email verification code
127
	"""
128
	logger.debug('Recover password request %s', request)
129
	try:
130
		username_or_password = request.POST.get('username_or_password')
131
		check_captcha(request)
132
		user_profile = UserProfile.objects.get(Q(username=username_or_password) | Q(email=username_or_password))
133
		if not user_profile.email:
134
			raise ValidationError("You didn't specify email address for this user")
135
		if not user_profile.email_verification_id or not user_profile.email_verification.verified:
136
			raise ValidationError("You didn't verify the email after registration. "
137
					"Find you verification email and complete verification and after restore the password again")
138
		verification = Verification(type_enum=Verification.TypeChoices.password, user_id=user_profile.id)
139
		verification.save()
140
		message = "{},\n" \
141
			"You requested to change a password on site {}.\n" \
142
			"To proceed click on the link {}://{}/restore_password?token={}\n" \
143
			"If you didn't request the password change just ignore this mail" \
144
			.format(user_profile.username, request.get_host(), SITE_PROTOCOL, request.get_host(), verification.token)
145
		send_mail("Change password", message, request.get_host(), (user_profile.email,), fail_silently=False)
146
		message = VALIDATION_IS_OK
147
		logger.debug('Verification email has been send for token %s to user %s(id=%d)',
148
				verification.token, user_profile.username, user_profile.id)
149
	except UserProfile.DoesNotExist:
150
		message = "User with this email or username doesn't exist"
151
		logger.debug("Skipping password recovery request for nonexisting user")
152
	except (UserProfile.DoesNotExist, ValidationError) as e:
153
		logger.debug('Not sending verification email because %s', e)
154
		message = 'Unfortunately we were not able to send you restore password email because {}'.format(e)
155
	return HttpResponse(message, content_type='text/plain')
156
157
158
class RestorePassword(View):
159
160
	def get_user_by_code(self, token):
161
		"""
162
		:param token: token code to verify
163
		:type token: str
164
		:raises ValidationError: if token is not usable
165
		:return: UserProfile, Verification: if token is usable
166
		"""
167
		try:
168
			v = Verification.objects.get(token=token)
169
			if v.type_enum != Verification.TypeChoices.password:
170
				raise ValidationError("it's not for this operation ")
171
			if v.verified:
172
				raise ValidationError("it's already used")
173
			# TODO move to sql query or leave here?
174
			if v.time < datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=1):
175
				raise ValidationError("it's expired")
176
			return UserProfile.objects.get(id=v.user_id), v
177
		except Verification.DoesNotExist:
178
			raise ValidationError('Unknown verification token')
179
180
	@transaction.atomic
181
	def post(self, request):
182
		"""
183
		Sends email verification token
184
		"""
185
		token = request.POST.get('token', False)
186
		try:
187
			logger.debug('Proceed Recover password with token %s', token)
188
			user, verification = self.get_user_by_code(token)
189
			password = request.POST.get('password')
190
			check_password(password)
191
			user.set_password(password)
192
			user.save(update_fields=('password',))
193
			verification.verified = True
194
			verification.save(update_fields=('verified',))
195
			logger.info('Password has been change for token %s user %s(id=%d)', token, user.username, user.id)
196
			response = VALIDATION_IS_OK
197
		except ValidationError as e:
198
			logger.debug('Rejecting verification token %s because %s', token, e)
199
			response = "".join(("You can't reset password with this token because ", str(e)))
200
		return HttpResponse(response, content_type='text/plain')
201
202
	def get(self, request):
203
		token = request.GET.get('token', False)
204
		logger.debug('Rendering restore password page with token  %s', token)
205
		try:
206
			user, verification = self.get_user_by_code(token)
207
			response = {
208
				'message': VALIDATION_IS_OK,
209
				'restore_user': user.username,
210
				'token': token
211
			}
212
		except ValidationError as e:
213
			logger.debug('Rejecting verification token %s because %s', token, e)
214
			response = {'message': "Unable to confirm email with token {} because {}".format(token, e)}
215
		return render_to_response('reset_password.html', response, context_instance=RequestContext(request))
216
217
218
@require_http_methods('GET')
219
def confirm_email(request):
220
	"""
221
	Accept the verification token sent to email
222
	"""
223
	token = request.GET.get('token', False)
224
	logger.debug('Processing email confirm with token  %s', token)
225
	try:
226
		try:
227
			v = Verification.objects.get(token=token)
228
		except Verification.DoesNotExist:
229
			raise ValidationError('Unknown verification token')
230
		if v.type_enum != Verification.TypeChoices.register:
231
			raise ValidationError('This is not confirm email token')
232
		if v.verified:
233
			raise ValidationError('This verification token already accepted')
234
		user = UserProfile.objects.get(id=v.user_id)
235
		if user.email_verification_id != v.id:
236
			raise ValidationError('Verification token expired because you generated another one')
237
		v.verified = True
238
		v.save(update_fields=['verified'])
239
		message = VALIDATION_IS_OK
240
		logger.info('Email verification token %s has been accepted for user %s(id=%d)', token, user.username, user.id)
241
	except Exception as e:
242
		logger.debug('Rejecting verification token %s because %s', token, e)
243
		message = ("Unable to confirm email with token {} because {}".format(token, e))
244
	response = {'message': message}
245
	return render_to_response('confirm_mail.html', response, context_instance=RequestContext(request))
246
247
248
@require_http_methods('GET')
249
def show_profile(request, profile_id):
250
	try:
251
		user_profile = UserProfile.objects.get(pk=profile_id)
252
		form = UserProfileReadOnlyForm(instance=user_profile)
253
		form.username = user_profile.username
254
		return render_to_response(
255
			'show_profile.html',
256
			{'form': form},
257
			context_instance=RequestContext(request)
258
		)
259
	except ObjectDoesNotExist:
260
		raise Http404
261
262
263
@require_http_methods('GET')
264
def statistics(request):
265
	pie_data = IpAddress.objects.values('country').filter(country__isnull=False).annotate(count=Count("country"))
266
	return HttpResponse(json.dumps(list(pie_data)), content_type='application/json')
267
268
269
@login_required_no_redirect()
270
@transaction.atomic
271
def report_issue(self, request):
272
	logger.info('Saving issue: %s', hide_fields(request.POST, 'log', huge=True))
273
	issue = Issue.objects.get_or_create(content=request.POST['issue'])[0]
274
	issue_details = IssueDetails(
275
		sender_id=request.user.id,
276
		browser=request.POST.get('browser'),
277
		issue=issue,
278
		log=request.POST.get('log')
279
	)
280
	issue_details.save()
281
	return HttpResponse(VALIDATION_IS_OK, content_type='text/plain')
282
283
284
class ProfileView(View):
285
286
	@login_required_no_redirect(True)
287
	def get(self, request):
288
		user_profile = UserProfile.objects.get(pk=request.user.id)
289
		form = UserProfileForm(instance=user_profile)
290
		c = csrf(request)
291
		c['form'] = form
292
		c['date_format'] = DATE_INPUT_FORMATS_JS
293
		return render_to_response('change_profile.html', c, context_instance=RequestContext(request))
294
295
	@login_required_no_redirect()
296
	def post(self, request):
297
		logger.info('Saving profile: %s', hide_fields(request.POST, "base64_image", huge=True))
298
		user_profile = UserProfile.objects.get(pk=request.user.id)
299
		image_base64 = request.POST.get('base64_image')
300
301
		if image_base64 is not None:
302
			image = extract_photo(image_base64)
303
			request.FILES['photo'] = image
304
305
		form = UserProfileForm(request.POST, request.FILES, instance=user_profile)
306
		if form.is_valid():
307
			profile = form.save()
308
			response = profile. photo.url if 'photo' in  request.FILES else VALIDATION_IS_OK
309
		else:
310
			response = form.errors
311
		return HttpResponse(response, content_type='text/plain')
312
313
314
class RegisterView(View):
315
316
	def get(self, request):
317
		c = csrf(request)
318
		c['captcha'] = getattr(settings, "RECAPTCHA_SITE_KEY", None)
319
		logger.debug('Rendering register page with captcha site key %s', c['captcha'])
320
		c['captcha_url'] = getattr(settings, "RECAPTHCA_SITE_URL", None)
321
		return render_to_response("register.html", c, context_instance=RequestContext(request))
322
323
	@transaction.atomic
324
	def post(self, request):
325
		try:
326
			rp = request.POST
327
			logger.info('Got register request %s', hide_fields(rp, 'password', 'repeatpassword'))
328
			(username, password, email) = (rp.get('username'), rp.get('password'), rp.get('email'))
329
			check_user(username)
330
			check_password(password)
331
			check_email(email)
332
			user_profile = create_user_profile(email, password, rp.get('sex'), username)
333
			# You must call authenticate before you can call login
334
			auth_user = authenticate(username=username, password=password)
335
			message = VALIDATION_IS_OK  # redirect
336
			if email:
337
				send_email_verification(user_profile, request.get_host())
338
			djangologin(request, auth_user)
339
		except ValidationError as e:
340
			message = e.message
341
			logger.debug('Rejecting request because "%s"', message)
342
		return HttpResponse(message, content_type='text/plain')
343