Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 97 additions & 97 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from django.utils.decorators import method_decorator
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.conf import settings
from django.views import View
from django.db.models.functions import TruncMonth, TruncYear
from django.db.models import Count, Sum
Expand All @@ -26,7 +27,7 @@
from deployments.models import Heop
from notifications.models import Subscription
from notifications.notification import send_notification
from registrations.models import Recovery
from registrations.models import Recovery, Pending
from main.frontend import frontend_url


Expand Down Expand Up @@ -91,24 +92,14 @@ def post(self, request):
return Response({'data': 'Success'})


class PublicJsonRequestView(View):
http_method_names = ['get', 'head', 'options']

def handle_get(self, request, *args, **kwargs):
print(pretty_request(request))

def get(self, request, *args, **kwargs):
return self.handle_get(request, *args, **kwargs)


class EsPageHealth(PublicJsonRequestView):
def handle_get(self, request, *args, **kwargs):
class EsPageHealth(APIView):
def get(self, request):
health = ES_CLIENT.cluster.health()
return JsonResponse(health)


class EsPageSearch(PublicJsonRequestView):
def handle_get(self, request, *args, **kwargs):
class EsPageSearch(APIView):
def get(self, request):
page_type = request.GET.get('type', None)
phrase = request.GET.get('keyword', None)
if phrase is None:
Expand Down Expand Up @@ -151,8 +142,8 @@ def handle_get(self, request, *args, **kwargs):
return JsonResponse(results['hits'])


class AreaAggregate(PublicJsonRequestView):
def handle_get(self, request, *args, **kwargs):
class AreaAggregate(APIView):
def get(self, request):
region_type = request.GET.get('type', None)
region_id = request.GET.get('id', None)

Expand All @@ -169,8 +160,8 @@ def handle_get(self, request, *args, **kwargs):
return JsonResponse(dict(aggregate))


class AggregateByDtype(PublicJsonRequestView):
def handle_get(self, request, *args, **kwargs):
class AggregateByDtype(APIView):
def get(self, request):
models = {
'appeal': Appeal,
'event': Event,
Expand All @@ -191,8 +182,8 @@ def handle_get(self, request, *args, **kwargs):
return JsonResponse(dict(aggregate=list(aggregate)))


class AggregateByTime(PublicJsonRequestView):
def handle_get(self, request, *args, **kwargs):
class AggregateByTime(APIView):
def get(self, request):
models = {
'appeal': Appeal,
'event': Event,
Expand Down Expand Up @@ -273,59 +264,20 @@ def handle_get(self, request, *args, **kwargs):
return JsonResponse(dict(aggregate=list(aggregate)))


@method_decorator(csrf_exempt, name='dispatch')
class PublicJsonPostView(View):
http_method_names = ['post']

def decode_auth_header(self, auth_header):
parts = auth_header[7:].split(':')
return parts[0], parts[1]

def get_authenticated_user(self, request):
auth_header = request.META.get('HTTP_AUTHORIZATION')
if not auth_header:
return None

# Parse the authorization header
username, key = self.decode_auth_header(auth_header)
if not username or not key:
return None

# Query the user
try:
user = User.objects.get(username=username)
except ObjectDoesNotExist:
return None

# Query the key
try:
Token.objects.get(user=user, key=key)
except ObjectDoesNotExist:
return None

return user

def handle_post(self, request, *args, **kwargs):
print(pretty_request(request))

def post(self, request, *args, **kwargs):
if request.META.get('CONTENT_TYPE').find('application/json') == -1:
return bad_request('Content-type must be `application/json`')
return self.handle_post(request, *args, **kwargs)
class GetAuthToken(APIView):
permission_classes = []

def post(self, request):
username = request.data.get('username', None)
password = request.data.get('password', None)

class GetAuthToken(PublicJsonPostView):
def handle_post(self, request, *args, **kwargs):
body = json.loads(request.body.decode('utf-8'))
if 'username' not in body or 'password' not in body:
if username is None or password is None:
return bad_request('Body must contain `username` and `password`')
username = body['username']
password = body['password']

# Get the case-correct username for authenticate()
casecorr_uname = User.objects.filter(username__iexact=username).values_list('username', flat=True).first()
if not casecorr_uname:
return bad_request('Invalid username or password') # definately username issue
if casecorr_uname is None:
return bad_request('Invalid username or password') # definitely username issue

# User model's __str__ is its username
user = authenticate(username=casecorr_uname, password=password)
Expand Down Expand Up @@ -353,48 +305,52 @@ def handle_post(self, request, *args, **kwargs):
return bad_request('Invalid username or password') # most probably password issue


class ChangePassword(PublicJsonPostView):
def handle_post(self, request, *args, **kwargs):
body = json.loads(request.body.decode('utf-8'))
if 'username' not in body or ('password' not in body and 'token' not in body):
class ChangePassword(APIView):
permissions_classes = []

def post(self, request):
username = request.data.get('username', None)
password = request.data.get('password', None)
new_pass = request.data.get('new_password', None)
token = request.data.get('token', None)
if username is None or password is None:
return bad_request('Must include a `username` and either a `password` or `token`')

try:
user = User.objects.get(username__iexact=body['username'])
except ObjectDoesNotExist:
user = User.objects.filter(username__iexact=username).first()
if user is None:
return bad_request('Could not authenticate')

if 'password' in body and not user.check_password(body['password']):
if password and not user.check_password(password):
return bad_request('Could not authenticate')
elif 'token' in body:
try:
recovery = Recovery.objects.get(user=user)
except ObjectDoesNotExist:
elif token:
recovery = Recovery.objects.filter(user=user).first()
if recovery is None:
return bad_request('Could not authenticate')

if recovery.token != body['token']:
if recovery.token != token:
return bad_request('Could not authenticate')
recovery.delete()

# TODO validate password
if 'new_password' not in body:
if new_pass is None:
return bad_request('Must include a `new_password` property')

user.set_password(body['new_password'])
user.set_password(new_pass)
user.save()

return JsonResponse({'status': 'ok'})


class RecoverPassword(PublicJsonPostView):
def handle_post(self, request, *args, **kwargs):
body = json.loads(request.body.decode('utf-8'))
if 'email' not in body:
class RecoverPassword(APIView):
permission_classes = []

def post(self, request):
email = request.data.get('email', None)
if email is None:
return bad_request('Must include an `email` property')

try:
user = User.objects.get(email__iexact=body['email'])
except ObjectDoesNotExist:
user = User.objects.filter(email__iexact=email).first()
if user is None:
return bad_request('That email is not associated with a user')

token = get_random_string(length=32)
Expand All @@ -413,15 +369,16 @@ def handle_post(self, request, *args, **kwargs):
return JsonResponse({'status': 'ok'})


class ShowUsername(PublicJsonPostView):
def handle_post(self, request, *args, **kwargs):
body = json.loads(request.body.decode('utf-8'))
if 'email' not in body:
class ShowUsername(APIView):
permission_classes = []

def post(self, request):
email = request.data.get('email', None)
if email is None:
return bad_request('Must include an `email` property')

try:
user = User.objects.get(email__iexact=body['email'])
except ObjectDoesNotExist:
user = User.objects.filter(email__iexact=email).first()
if user is None:
return bad_request('That email is not associated with a user')

email_context = {
Expand All @@ -435,6 +392,49 @@ def handle_post(self, request, *args, **kwargs):
return JsonResponse({'status': 'ok'})


class ResendValidation(APIView):
permission_classes = []

def post(self, request):
username = request.data.get('username', None)

if username:
pending_user = Pending.objects.select_related('user').filter(user__username__iexact=username).first()
if pending_user:
if pending_user.user.is_active is True:
return bad_request('Your registration is already active, \
you can try logging in with your registered username and password')
if pending_user.created_at < timezone.now() - timedelta(days=1):
return bad_request('The verification period is expired. \
You must verify your email within 24 hours. \
Please contact your system administrator.')

# Construct and re-send the email
email_context = {
'confirmation_link': 'https://%s/verify_email/?token=%s&user=%s' % (
settings.BASE_URL, # on PROD it should point to goadmin...
pending_user.token,
username,
)
}

if pending_user.user.is_staff:
template = 'email/registration/verify-staff-email.html'
else:
template = 'email/registration/verify-outside-email.html'

send_notification('Validate your account',
[pending_user.user.email],
render_to_string(template, email_context),
'Validate account - ' + username)
return Response({'data': 'Success'})
else:
return bad_request('No pending registration found with the provided username. \
Please check your input.')
else:
return bad_request('Please provide your username in the request.')


class AddCronJobLog(APIView):
authentication_classes = (authentication.TokenAuthentication,)
permissions_classes = (permissions.IsAuthenticated,)
Expand Down
4 changes: 1 addition & 3 deletions deployments/drf_views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from collections import defaultdict
from rest_framework.authentication import (
TokenAuthentication,
)
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import action
from rest_framework.response import Response
Expand Down
6 changes: 4 additions & 2 deletions main/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@
AreaAggregate,
AddCronJobLog,
DummyHttpStatusError,
DummyExceptionError
DummyExceptionError,
ResendValidation
)
from registrations.views import (
NewRegistration,
VerifyEmail,
ValidateUser,
ValidateUser
)
from per.views import (
DraftSent,
Expand Down Expand Up @@ -145,6 +146,7 @@
url(r'^change_password', ChangePassword.as_view()),
url(r'^recover_password', RecoverPassword.as_view()),
url(r'^show_username', ShowUsername.as_view()),
url(r'^resend_validation', ResendValidation.as_view()),
url(r'^api/v2/', include(router.urls)),
url(r'^api/v2/event/(?P<pk>\d+)', api_views.EventViewset.as_view({'get': 'retrieve'})),
url(r'^api/v2/event/(?P<slug>[-\w]+)', api_views.EventViewset.as_view({'get': 'retrieve'}, lookup_field='slug')),
Expand Down
1 change: 1 addition & 0 deletions per/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def test_simple_form(self):
'code': 'A1',
'name': 'Nemo',
'language': 1,
'user_id': 1,
'unique_id': '1aad9295-ceb9-4ad5-9b10-84cc423e93f4',
'started_at': '2019-04-11 11:42:22.278796+00',
'submitted_at': '2019-04-11 09:42:52.278796+00',
Expand Down
Loading