Split token and local login

This commit is contained in:
Christian Cueni 2020-02-18 14:36:49 +01:00
parent 42d16caafc
commit 3c72c9559e
9 changed files with 276 additions and 58 deletions

View File

@ -1,4 +1,5 @@
{ {
"__schema": { "__schema": {
"queryType": { "queryType": {
"name": "Query" "name": "Query"
@ -357,17 +358,7 @@
"fields": null, "fields": null,
"inputFields": [ "inputFields": [
{ {
"name": "usernameInput", "name": "tokenInput",
"description": null,
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "passwordInput",
"description": null, "description": null,
"type": { "type": {
"kind": "SCALAR", "kind": "SCALAR",

View File

@ -0,0 +1,9 @@
mutation Login($input: LoginInput!) {
login(input: $input) {
success
message
errors {
field
}
}
}

View File

@ -165,7 +165,7 @@ router.beforeEach(async (to, from, next) => {
return; return;
} }
if (to.name !== 'licenseActivation' && await redirectUsersWithoutValidLicense()) { if (to.name !== 'licenseActivation' && loginRequired(to) && await redirectUsersWithoutValidLicense()) {
next({name: 'licenseActivation'}) next({name: 'licenseActivation'})
return; return;
} }

View File

@ -76,8 +76,7 @@ export default {
mutation: LOGIN_MUTATION, mutation: LOGIN_MUTATION,
variables: { variables: {
input: { input: {
usernameInput: that.helloEmail, tokenInput: token
passwordInput: token
} }
}, },
update( update(

View File

@ -370,8 +370,7 @@ TASKBASE_SUPERPASSWORD = os.environ.get("TASKBASE_SUPERPASSWORD")
TASKBASE_BASEURL = os.environ.get("TASKBASE_BASEURL") TASKBASE_BASEURL = os.environ.get("TASKBASE_BASEURL")
USE_LOCAL_REGISTRATION = False ALLOW_LOCAL_REGISTRATION = False
# HEP # HEP
HEP_ADMIN_USER = "myskillbox" HEP_ADMIN_USER = "myskillbox"

View File

@ -99,7 +99,7 @@
"row_invoiced": 0, "row_invoiced": 0,
"row_total": 44.88, "row_total": 44.88,
"row_total_incl_tax": 46, "row_total_incl_tax": 46,
"sku": "123-4-5678-9012-3", "sku": "978-3-0355-0708-9",
"store_id": 1, "store_id": 1,
"tax_amount": 1.12, "tax_amount": 1.12,
"tax_invoiced": 0, "tax_invoiced": 0,

View File

@ -32,40 +32,51 @@ class Login(relay.ClientIDMutation):
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **kwargs): def mutate_and_get_payload(cls, root, info, **kwargs):
if settings.ALLOW_LOCAL_REGISTRATION:
username = kwargs.get('username_input')
if settings.USE_LOCAL_REGISTRATION:
password = kwargs.get('password_input') password = kwargs.get('password_input')
username = kwargs.get('username_input')
user = authenticate(username=username, password=password) user = authenticate(username=username, password=password)
if user is None: if user is None:
return cls.return_login_message('invalid_credentials') return cls.return_login_message('invalid_credentials')
login(info.context, user) login(info.context, user)
else: return cls(success=False, errors=[{'field': 'not_implemented'}], message='')
hep_client = HepClient()
token = kwargs.get('password_input')
try:
user_data = hep_client.customer_me(token)
except HepClientUnauthorizedException:
return cls.return_login_message('invalid_credentials')
except HepClientException:
return cls.return_login_message(UNKNOWN_ERROR)
user, status_msg = handle_user_and_verify_products(user_data) class Login(relay.ClientIDMutation):
class Input:
token_input = graphene.String()
# sync email if user changed it on hep account success = graphene.Boolean()
if user.email != user_data['email']: message = graphene.String()
user.email = user_data['email'] errors = graphene.List(LoginError) # todo: change for consistency
user.username = user_data['email']
user.save()
if user and status_msg != EMAIL_NOT_VERIFIED: @classmethod
login(info.context, user) def mutate_and_get_payload(cls, root, info, **kwargs):
if status_msg: hep_client = HepClient()
return cls.return_login_message(status_msg) token = kwargs.get('token_input')
try:
user_data = hep_client.customer_me(token)
except HepClientUnauthorizedException:
return cls.return_login_message('invalid_credentials')
except HepClientException:
return cls.return_login_message(UNKNOWN_ERROR)
user, status_msg = handle_user_and_verify_products(user_data)
# sync email if user changed it on hep account
if user.email != user_data['email']:
user.email = user_data['email']
user.username = user_data['email']
user.save()
if user and status_msg != EMAIL_NOT_VERIFIED:
login(info.context, user)
if status_msg:
return cls.return_login_message(status_msg)
return cls(success=True, errors=[], message='success') return cls(success=True, errors=[], message='success')

View File

@ -0,0 +1,210 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
#
# Created on 2019-10-02
# @author: chrigu <christian.cueni@iterativ.ch>
import json
import os
from datetime import timedelta
from unittest.mock import patch
import requests
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory
from django.utils import timezone
from graphene.test import Client
from api.schema_public import schema
from core.factories import UserFactory
from core.hep_client import HepClient
from core.tests.mock_hep_data_factory import MockResponse, ME_DATA, VALID_STUDENT_ORDERS, VALID_TEACHERS_ORDERS, \
NOT_CONFIRMED_ME
from registration.factories import LicenseFactory
from registration.models import License
from users.models import Role, User, SchoolClass
TOKEN = 'abcd12345!'
class LoginTests(TestCase):
def setUp(self):
self.user = UserFactory(username=ME_DATA['id'], email=ME_DATA['id'])
Role.objects.create_default_roles()
self.teacher_role = Role.objects.get_default_teacher_role()
request = RequestFactory().post('/')
# adding session
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
self.client = Client(schema=schema, context_value=request)
def make_login_mutation(self, username, password):
mutation = '''
mutation Login($input: LoginInput!){
login(input: $input) {
success
message
errors {
field
}
}
}
'''
return self.client.execute(mutation, variables={
'input': {
'usernameInput': username,
'passwordInput': password
}
})
@patch.object(HepClient, 'customer_me', return_value=ME_DATA)
def test_user_can_login_with_local_user_and_valid_local_license(self, me_mock):
self.user.hep_id = ME_DATA['id']
self.user.save()
now = timezone.now()
expiry_date = now + timedelta(365)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
result = self.make_login_mutation(self.user.email, TOKEN)
self.assertTrue(result.get('data').get('login').get('success'))
self.assertTrue(self.user.is_authenticated)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA)
def test_user_can_login_with_updated_email(self, me_mock):
old_mail = 'aschi@iterativ.ch'
self.user.hep_id = ME_DATA['id']
self.user.email = old_mail
self.user.username = old_mail
self.user.save()
now = timezone.now()
expiry_date = now + timedelta(365)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
result = self.make_login_mutation(self.user.email, TOKEN)
user = User.objects.get(hep_id=self.user.hep_id)
self.assertEqual(user.username, ME_DATA['email'])
self.assertEqual(user.email, ME_DATA['email'])
self.assertTrue(result.get('data').get('login').get('success'))
self.assertTrue(self.user.is_authenticated)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA)
def test_user_can_login_with_local_user_and_valid_local_license(self, me_mock):
self.user.hep_id = ME_DATA['id']
self.user.save()
now = timezone.now()
expiry_date = now + timedelta(365)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
result = self.make_login_mutation(self.user.email, TOKEN)
self.assertTrue(result.get('data').get('login').get('success'))
self.assertTrue(self.user.is_authenticated)
@patch.object(HepClient, '_customer_orders', return_value=VALID_TEACHERS_ORDERS)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA)
@patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'})
def test_teacher_can_login_with_remote_user_and_remote_license(self, order_mock, me_mock, admin_token_mock):
result = self.make_login_mutation(ME_DATA['email'], TOKEN)
user = User.objects.get(email=ME_DATA['email'])
user_role_key = user.user_roles.get(user=user).role.key
self.assertEqual(user_role_key, Role.objects.TEACHER_KEY)
license = License.objects.get(licensee=user)
self.assertEqual(license.for_role.key, Role.objects.TEACHER_KEY)
school_class = SchoolClass.objects.get(users__in=[user])
self.assertIsNotNone(school_class)
self.assertTrue(result.get('data').get('login').get('success'))
self.assertTrue(self.user.is_authenticated)
@patch.object(HepClient, '_customer_orders', return_value=VALID_STUDENT_ORDERS)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA)
@patch.object(HepClient, 'fetch_admin_token', return_value={'token':'AABBCCDDEE**44566'})
def test_student_can_login_with_remote_user_and_remote_license(self, order_mock, me_mock, admin_token_mock):
result = self.make_login_mutation(ME_DATA['email'], TOKEN)
user = User.objects.get(email=ME_DATA['email'])
user_role_key = user.user_roles.get(user=user).role.key
self.assertEqual(user_role_key, Role.objects.STUDENT_KEY)
license = License.objects.get(licensee=user)
self.assertEqual(license.for_role.key, Role.objects.STUDENT_KEY)
self.assertTrue(result.get('data').get('login').get('success'))
self.assertTrue(self.user.is_authenticated)
@patch.object(requests, 'post', return_value=MockResponse(401))
def test_user_with_no_login_cannot_login(self, post_mock):
result = self.make_login_mutation('some@some.ch', 'some')
self.assertFalse(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'invalid_credentials')
@patch.object(HepClient, 'is_email_verified', return_value=False)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA)
def test_user_with_unconfirmed_email_cannot_login(self, me_mock, post_mock):
result = self.make_login_mutation(ME_DATA['email'], TOKEN)
User.objects.get(email=ME_DATA['email'])
self.assertFalse(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'email_not_verified')
@patch.object(HepClient, 'myskillbox_product_for_customer', return_value=None)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA)
@patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'})
def test_user_can_login_without_license(self, me_mock, product_mock, admin_token_mock):
result = self.make_login_mutation(self.user.email, TOKEN)
self.assertTrue(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('message'), 'no_valid_license')
self.assertTrue(self.user.is_authenticated)
@patch.object(HepClient, 'myskillbox_product_for_customer', return_value=None)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA)
@patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'})
def test_user_can_login_local_license_invalid(self, product_mock, me_mock, admin_token_mock):
now = timezone.now()
expiry_date = now - timedelta(1)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
result = self.make_login_mutation(self.user.email, TOKEN)
self.assertTrue(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('message'), 'no_valid_license')
self.assertTrue(self.user.is_authenticated)
@patch.object(HepClient, 'customer_me', return_value=NOT_CONFIRMED_ME)
def test_user_can_login_with_unconfirmed_email(self, me_mock):
result = self.make_login_mutation(self.user.email, TOKEN)
self.assertFalse(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'email_not_verified')
@patch.object(requests, 'get', return_value=MockResponse(500))
def test_user_gets_notified_if_server_error(self, post_mock):
result = self.make_login_mutation(ME_DATA['email'], TOKEN)
self.assertFalse(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'unknown_error')

View File

@ -9,12 +9,12 @@
# @author: chrigu <christian.cueni@iterativ.ch> # @author: chrigu <christian.cueni@iterativ.ch>
import json import json
import os import os
from datetime import timedelta, datetime from datetime import timedelta
from unittest.mock import patch from unittest.mock import patch
import requests import requests
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory, override_settings from django.test import TestCase, RequestFactory
from django.utils import timezone from django.utils import timezone
from graphene.test import Client from graphene.test import Client
@ -30,7 +30,7 @@ from users.models import Role, User, SchoolClass
TOKEN = 'abcd12345!' TOKEN = 'abcd12345!'
class PasswordResetTests(TestCase): class LoginTests(TestCase):
def setUp(self): def setUp(self):
self.user = UserFactory(username=ME_DATA['id'], email=ME_DATA['id']) self.user = UserFactory(username=ME_DATA['id'], email=ME_DATA['id'])
Role.objects.create_default_roles() Role.objects.create_default_roles()
@ -44,7 +44,7 @@ class PasswordResetTests(TestCase):
request.session.save() request.session.save()
self.client = Client(schema=schema, context_value=request) self.client = Client(schema=schema, context_value=request)
def make_login_mutation(self, username, password): def make_login_mutation(self, token):
mutation = ''' mutation = '''
mutation Login($input: LoginInput!){ mutation Login($input: LoginInput!){
login(input: $input) { login(input: $input) {
@ -59,8 +59,7 @@ class PasswordResetTests(TestCase):
return self.client.execute(mutation, variables={ return self.client.execute(mutation, variables={
'input': { 'input': {
'usernameInput': username, 'tokenInput': token
'passwordInput': password
} }
}) })
@ -74,7 +73,7 @@ class PasswordResetTests(TestCase):
expiry_date = now + timedelta(365) expiry_date = now + timedelta(365)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save() LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
result = self.make_login_mutation(self.user.email, TOKEN) result = self.make_login_mutation(TOKEN)
self.assertTrue(result.get('data').get('login').get('success')) self.assertTrue(result.get('data').get('login').get('success'))
self.assertTrue(self.user.is_authenticated) self.assertTrue(self.user.is_authenticated)
@ -93,7 +92,7 @@ class PasswordResetTests(TestCase):
expiry_date = now + timedelta(365) expiry_date = now + timedelta(365)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save() LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
result = self.make_login_mutation(self.user.email, TOKEN) result = self.make_login_mutation(TOKEN)
user = User.objects.get(hep_id=self.user.hep_id) user = User.objects.get(hep_id=self.user.hep_id)
@ -112,7 +111,7 @@ class PasswordResetTests(TestCase):
expiry_date = now + timedelta(365) expiry_date = now + timedelta(365)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save() LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
result = self.make_login_mutation(self.user.email, TOKEN) result = self.make_login_mutation(TOKEN)
self.assertTrue(result.get('data').get('login').get('success')) self.assertTrue(result.get('data').get('login').get('success'))
self.assertTrue(self.user.is_authenticated) self.assertTrue(self.user.is_authenticated)
@ -121,7 +120,7 @@ class PasswordResetTests(TestCase):
@patch.object(HepClient, 'customer_me', return_value=ME_DATA) @patch.object(HepClient, 'customer_me', return_value=ME_DATA)
@patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'}) @patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'})
def test_teacher_can_login_with_remote_user_and_remote_license(self, order_mock, me_mock, admin_token_mock): def test_teacher_can_login_with_remote_user_and_remote_license(self, order_mock, me_mock, admin_token_mock):
result = self.make_login_mutation(ME_DATA['email'], TOKEN) result = self.make_login_mutation(TOKEN)
user = User.objects.get(email=ME_DATA['email']) user = User.objects.get(email=ME_DATA['email'])
@ -139,10 +138,10 @@ class PasswordResetTests(TestCase):
@patch.object(HepClient, '_customer_orders', return_value=VALID_STUDENT_ORDERS) @patch.object(HepClient, '_customer_orders', return_value=VALID_STUDENT_ORDERS)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA) @patch.object(HepClient, 'customer_me', return_value=ME_DATA)
@patch.object(HepClient, 'fetch_admin_token', return_value={'token':'AABBCCDDEE**44566'}) @patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'})
def test_student_can_login_with_remote_user_and_remote_license(self, order_mock, me_mock, admin_token_mock): def test_student_can_login_with_remote_user_and_remote_license(self, order_mock, me_mock, admin_token_mock):
result = self.make_login_mutation(ME_DATA['email'], TOKEN) result = self.make_login_mutation(TOKEN)
user = User.objects.get(email=ME_DATA['email']) user = User.objects.get(email=ME_DATA['email'])
user_role_key = user.user_roles.get(user=user).role.key user_role_key = user.user_roles.get(user=user).role.key
@ -156,7 +155,7 @@ class PasswordResetTests(TestCase):
@patch.object(requests, 'post', return_value=MockResponse(401)) @patch.object(requests, 'post', return_value=MockResponse(401))
def test_user_with_no_login_cannot_login(self, post_mock): def test_user_with_no_login_cannot_login(self, post_mock):
result = self.make_login_mutation('some@some.ch', 'some') result = self.make_login_mutation('some')
self.assertFalse(result.get('data').get('login').get('success')) self.assertFalse(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'invalid_credentials') self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'invalid_credentials')
@ -164,7 +163,7 @@ class PasswordResetTests(TestCase):
@patch.object(HepClient, 'is_email_verified', return_value=False) @patch.object(HepClient, 'is_email_verified', return_value=False)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA) @patch.object(HepClient, 'customer_me', return_value=ME_DATA)
def test_user_with_unconfirmed_email_cannot_login(self, me_mock, post_mock): def test_user_with_unconfirmed_email_cannot_login(self, me_mock, post_mock):
result = self.make_login_mutation(ME_DATA['email'], TOKEN) result = self.make_login_mutation(TOKEN)
User.objects.get(email=ME_DATA['email']) User.objects.get(email=ME_DATA['email'])
@ -175,7 +174,7 @@ class PasswordResetTests(TestCase):
@patch.object(HepClient, 'customer_me', return_value=ME_DATA) @patch.object(HepClient, 'customer_me', return_value=ME_DATA)
@patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'}) @patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'})
def test_user_can_login_without_license(self, me_mock, product_mock, admin_token_mock): def test_user_can_login_without_license(self, me_mock, product_mock, admin_token_mock):
result = self.make_login_mutation(self.user.email, TOKEN) result = self.make_login_mutation(TOKEN)
self.assertTrue(result.get('data').get('login').get('success')) self.assertTrue(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('message'), 'no_valid_license') self.assertEqual(result.get('data').get('login').get('message'), 'no_valid_license')
@ -189,7 +188,7 @@ class PasswordResetTests(TestCase):
expiry_date = now - timedelta(1) expiry_date = now - timedelta(1)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save() LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
result = self.make_login_mutation(self.user.email, TOKEN) result = self.make_login_mutation(TOKEN)
self.assertTrue(result.get('data').get('login').get('success')) self.assertTrue(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('message'), 'no_valid_license') self.assertEqual(result.get('data').get('login').get('message'), 'no_valid_license')
@ -197,14 +196,14 @@ class PasswordResetTests(TestCase):
@patch.object(HepClient, 'customer_me', return_value=NOT_CONFIRMED_ME) @patch.object(HepClient, 'customer_me', return_value=NOT_CONFIRMED_ME)
def test_user_can_login_with_unconfirmed_email(self, me_mock): def test_user_can_login_with_unconfirmed_email(self, me_mock):
result = self.make_login_mutation(self.user.email, TOKEN) result = self.make_login_mutation(TOKEN)
self.assertFalse(result.get('data').get('login').get('success')) self.assertFalse(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'email_not_verified') self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'email_not_verified')
@patch.object(requests, 'get', return_value=MockResponse(500)) @patch.object(requests, 'get', return_value=MockResponse(500))
def test_user_gets_notified_if_server_error(self, post_mock): def test_user_gets_notified_if_server_error(self, post_mock):
result = self.make_login_mutation(ME_DATA['email'], TOKEN) result = self.make_login_mutation(TOKEN)
self.assertFalse(result.get('data').get('login').get('success')) self.assertFalse(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'unknown_error') self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'unknown_error')