Completed
Push — master ( 5850a4...5772bc )
by Andrew
01:00
created

get_html_restore_pass()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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