Completed
Push — master ( 6ca66a...a573ac )
by Andrew
01:29
created

RestorePassword.get()   A

Complexity

Conditions 2

Size

Total Lines 14

Duplication

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