Check user license in Api, save

This commit is contained in:
Christian Cueni 2020-02-17 15:08:06 +01:00
parent d5276d5adc
commit c9c42e2296
16 changed files with 118 additions and 17 deletions

View File

@ -23,7 +23,7 @@ describe('Email Verifcation', () => {
} }
}); });
cy.visit('/verify-email?confirmation=abcd1234&userId=12'); cy.visit('/verify-email?confirmation=abcd1234&id=12');
// user should be logged in at that stage. As the cookie cannot be set at the right time // user should be logged in at that stage. As the cookie cannot be set at the right time
// we just check if the user gets redirected to the login page as we can't log her in // we just check if the user gets redirected to the login page as we can't log her in
@ -53,7 +53,7 @@ describe('Email Verifcation', () => {
} }
}); });
cy.visit('/verify-email?confirmation=abcd1234&userId=12'); cy.visit('/verify-email?confirmation=abcd1234&id=12');
cy.get('[data-cy="code-nok-msg"]').contains('Der angegebene Verifizierungscode ist falsch oder abgelaufen.'); cy.get('[data-cy="code-nok-msg"]').contains('Der angegebene Verifizierungscode ist falsch oder abgelaufen.');
cy.get('[data-cy="code-ok-msg"]').should('not.exist'); cy.get('[data-cy="code-ok-msg"]').should('not.exist');
@ -81,7 +81,7 @@ describe('Email Verifcation', () => {
} }
}); });
cy.visit('/verify-email?confirmation=abcd1234&userId=12'); cy.visit('/verify-email?confirmation=abcd1234&id=12');
cy.get('[data-cy="code-nok-msg"]').contains('Ein Fehler ist aufgetreten. Bitte kontaktieren Sie den Administrator.'); cy.get('[data-cy="code-nok-msg"]').contains('Ein Fehler ist aufgetreten. Bitte kontaktieren Sie den Administrator.');
}); });
@ -108,7 +108,7 @@ describe('Email Verifcation', () => {
} }
}); });
cy.visit('/verify-email?confirmation=abcd1234&userId=12'); cy.visit('/verify-email?confirmation=abcd1234&id=12');
// user should be logged in at that stage. As the cookie cannot be set at the right time // user should be logged in at that stage. As the cookie cannot be set at the right time
// we just check if the user gets redirected to the coupon page as we can't log her in // we just check if the user gets redirected to the coupon page as we can't log her in

View File

@ -7,7 +7,7 @@ export function register(registrationData) {
} }
export function login(username, password) { export function login(username, password) {
return axios.post(`${hepBaseUrl}/rest/deutsch/V1/customers`, {username, password}); return axios.post(`${hepBaseUrl}/rest/deutsch/V1/integration/customer/token`, {username, password});
} }
export function emailExists(email) { export function emailExists(email) {

View File

@ -42,7 +42,7 @@ export default {
variables: { variables: {
input: { input: {
confirmationKeyInput: this.$route.query.confirmation, confirmationKeyInput: this.$route.query.confirmation,
userIdInput: this.$route.query.userId userIdInput: this.$route.query.id
} }
}, },
fetchPolicy: 'no-cache' fetchPolicy: 'no-cache'

View File

@ -27,9 +27,6 @@
data-cy="password-errors" data-cy="password-errors"
>{{ error }}</small> >{{ error }}</small>
</div> </div>
<div class="skillboxform-input">
<small class="skillboxform-input__error" data-cy="login-error" v-if="loginError">{{loginError}}</small>
</div>
<div class="actions"> <div class="actions">
<button class="button button--primary button--big actions__submit" data-cy="login-button">Anmelden</button> <button class="button button--primary button--big actions__submit" data-cy="login-button">Anmelden</button>
<a class="actions__reset text-link" href="/accounts/password_reset/">Passwort vergessen?</a> <a class="actions__reset text-link" href="/accounts/password_reset/">Passwort vergessen?</a>

View File

@ -1,10 +1,12 @@
import re import re
from django.conf import settings from django.conf import settings
from django.http import Http404, HttpResponsePermanentRedirect from django.http import Http404, HttpResponsePermanentRedirect, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from core.utils import is_private_api_call_allowed
try: try:
from threading import local from threading import local
except ImportError: except ImportError:
@ -95,3 +97,13 @@ class UserLoggedInCookieMiddleWare(MiddlewareMixin):
#else if if no user and cookie remove user cookie, logout #else if if no user and cookie remove user cookie, logout
response.delete_cookie(self.cookie_name) response.delete_cookie(self.cookie_name)
return response return response
class UserHasLicenseeMiddleWare(MiddlewareMixin):
def process_response(self, request, response):
if request.path == '/api/graphql/':
if not is_private_api_call_allowed(request.user, request.body):
return HttpResponse('License required', status=402)
return response

View File

@ -120,6 +120,7 @@ MIDDLEWARE += [
'core.middleware.ThreadLocalMiddleware', 'core.middleware.ThreadLocalMiddleware',
'core.middleware.CommonRedirectMiddleware', 'core.middleware.CommonRedirectMiddleware',
'core.middleware.UserLoggedInCookieMiddleWare', 'core.middleware.UserLoggedInCookieMiddleWare',
'core.middleware.UserHasLicenseeMiddleWare',
] ]
ROOT_URLCONF = 'core.urls' ROOT_URLCONF = 'core.urls'

View File

@ -1,4 +1,4 @@
from django.test import TestCase, Client from django.test import TestCase
from django.core import management from django.core import management
from users.models import User, Role from users.models import User, Role

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2020 ITerativ GmbH. All rights reserved.
#
# Created on 17.02.20
# @author: chrigu <christian.cueni@iterativ.ch>
from datetime import timedelta
from django.test import TestCase
from django.utils import timezone
from core.factories import UserFactory
from core.utils import is_private_api_call_allowed
class MiddlewareTestCase(TestCase):
def test_user_with_license_can_see_private_api(self):
tomorrow = timezone.now() + timedelta(1)
user = UserFactory(username='aschiman@ch.ch')
user.license_expiry_date = tomorrow
body = b'"{mutation {\\n addRoom}"'
self.assertTrue(is_private_api_call_allowed(user, body))
def test_user_without_valid_license_cannot_see_private_api(self):
yesterday = timezone.now() - timedelta(1)
user = UserFactory(username='aschiman@ch.ch')
user.license_expiry_date = yesterday
body = b'"{mutation {\\n addRoom}"'
self.assertFalse(is_private_api_call_allowed(user, body))
def test_logout_is_allowed_without_valid_license(self):
yesterday = timezone.now() - timedelta(1)
user = UserFactory(username='aschiman@ch.ch')
user.license_expiry_date = yesterday
body = b'"{mutation { logout {"'
self.assertTrue(is_private_api_call_allowed(user, body))

View File

@ -1,3 +1,7 @@
import re
from django.utils import timezone
from api.utils import get_object from api.utils import get_object
from users.models import SchoolClass from users.models import SchoolClass
@ -18,3 +22,18 @@ def set_visible_for(block, visibility_list):
block.visible_for.remove(school_class) block.visible_for.remove(school_class)
else: else:
block.visible_for.add(school_class) block.visible_for.add(school_class)
def is_private_api_call_allowed(user, body):
body_unicode = body.decode('utf-8')
if re.search(r"mutation\s*.*\s*logout\s*{", body_unicode):
return True
license_expiry = user.license_expiry_date
if license_expiry is None or license_expiry < timezone.now():
return False
return True

View File

@ -8,7 +8,7 @@
# Created on 27.01.20 # Created on 27.01.20
# @author: chrigu <christian.cueni@iterativ.ch> # @author: chrigu <christian.cueni@iterativ.ch>
from datetime import timedelta from datetime import timedelta
from django.utils import timezone
from django.db import models from django.db import models
from core.hep_client import TEACHER_EDITION_DURATION, STUDENT_EDITION_DURATION from core.hep_client import TEACHER_EDITION_DURATION, STUDENT_EDITION_DURATION
@ -29,3 +29,7 @@ class LicenseManager(models.Manager):
def _create_license_for_role(self, licensee, expiry_date, raw, role): def _create_license_for_role(self, licensee, expiry_date, raw, role):
return self.create(licensee=licensee, expire_date=expiry_date, raw=raw, for_role=role) return self.create(licensee=licensee, expire_date=expiry_date, raw=raw, for_role=role)
def get_active_licenses_for_user(self, user):
return self.filter(licensee=user, expire_date__gte=timezone.now())\
.filter(expire_date__lt=timezone.now()).order_by('-expire_date')

View File

@ -11,9 +11,10 @@ import graphene
from django.contrib.auth import login from django.contrib.auth import login
from graphene import relay from graphene import relay
from core.hep_client import HepClient, HepClientException, HepClientUnauthorizedException from core.hep_client import HepClient, HepClientException
from core.models import AdminData from core.models import AdminData
from users.user_signup_login_handler import handle_user_and_verify_products, UNKNOWN_ERROR from users.user_signup_login_handler import handle_user_and_verify_products, UNKNOWN_ERROR
from users.models import User
class RegistrationError(graphene.ObjectType): class RegistrationError(graphene.ObjectType):
@ -39,7 +40,7 @@ class Registration(relay.ClientIDMutation):
try: try:
hep_client.customer_activate(confirmation_key, user_id) hep_client.customer_activate(confirmation_key, user_id)
user_data = hep_client.customers_by_id(admin_token) user_data = hep_client.customers_by_id(admin_token, user_id)
if 'confirmation' in user_data: if 'confirmation' in user_data:
return cls.return_registration_msg('invalid_key') return cls.return_registration_msg('invalid_key')
except HepClientException: except HepClientException:

View File

@ -0,0 +1,18 @@
# Generated by Django 2.1.15 on 2020-02-17 13:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0010_auto_20200204_1331'),
]
operations = [
migrations.AddField(
model_name='user',
name='license_expiry_date',
field=models.DateField(default=None, null=True),
),
]

View File

@ -17,6 +17,7 @@ class User(AbstractUser):
email = models.EmailField(_('email address'), unique=True) email = models.EmailField(_('email address'), unique=True)
hep_id = models.PositiveIntegerField(null=True, blank=False) hep_id = models.PositiveIntegerField(null=True, blank=False)
hep_group_id = models.PositiveIntegerField(null=True, blank=False) hep_group_id = models.PositiveIntegerField(null=True, blank=False)
license_expiry_date = models.DateField(blank=False, null=True, default=None)
objects = UserManager() objects = UserManager()

View File

@ -3,8 +3,6 @@ from graphene import relay
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
from assignments.models import StudentSubmission
from assignments.schema.types import StudentSubmissionNode
from basicknowledge.models import BasicKnowledge from basicknowledge.models import BasicKnowledge
from basicknowledge.queries import InstrumentNode from basicknowledge.queries import InstrumentNode
from books.models import Module from books.models import Module

View File

@ -14,7 +14,7 @@ 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 from django.test import TestCase, RequestFactory, override_settings
from django.utils import timezone from django.utils import timezone
from graphene.test import Client from graphene.test import Client

View File

@ -65,6 +65,8 @@ def check_and_create_licenses(hep_client, user):
if product: if product:
license = License.objects.create_license_for_role(user, product['activated'], license = License.objects.create_license_for_role(user, product['activated'],
product['raw'], product['edition']) product['raw'], product['edition'])
user.license_expiry_date = license.expire_date
user.save()
# todo handle no license case # todo handle no license case
else: else:
return None, NO_VALID_LICENSE return None, NO_VALID_LICENSE