User profile models

SMS OTP provider
Login template (without css)
This commit is contained in:
Kumi 2020-04-13 20:03:01 +02:00
parent 9798849aab
commit d74a4c1b8b
20 changed files with 183 additions and 23 deletions

21
core/classes/otp.py Normal file
View file

@ -0,0 +1,21 @@
class BaseOTPProvider:
'''OTP providers must be subclasses of BaseOTPProvider and implement at least validate_token().'''
@property
def get_name(self):
return "Base OTP Provider"
@property
def get_logo(self):
return ""
@property
def is_active(self):
'''Returns True if the provider is properly configured and ready to use.'''
raise NotImplementedError(f"{type(self)} does not implement is_active!")
def start_authentication(self, user):
return "Authentication started, please enter your 2FA token."
def validate_token(self, user, token):
raise NotImplementedError(f"{type(self)} does not implement validate_token()!")

View file

@ -1 +1,2 @@
from core.models.files import * from core.models.files import *
from core.models.profiles import *

12
core/models/profiles.py Normal file
View file

@ -0,0 +1,12 @@
from polymorphic.models import PolymorphicModel
from phonenumber_field.modelfields import PhoneNumberField
from django.db.models import OneToOneField, CASCADE, CharField
from django.contrib.auth import get_user_model
class Profile(PolymorphicModel):
user = OneToOneField(get_user_model(), CASCADE)
mobile = PhoneNumberField(blank=True)
class AdminProfile(Profile):
role = CharField(max_length=64)

13
core/modules/otp.py Normal file
View file

@ -0,0 +1,13 @@
import importlib
from django.conf import settings
providers = []
for module in settings.EXPEPHALON_MODULES:
try:
moo = importlib.import_module(f"{module}.otp")
for provider in moo.OTPPROVIDERS:
providers.append(provider)
except (AttributeError, ModuleNotFoundError):
continue

View file

@ -2,9 +2,7 @@ import importlib
from django.conf import settings from django.conf import settings
from core.classes.navigation import NavItem, NavSection, Navigation from dbsettings.functions import getValue
from dbsettings import getValue
providers = [] providers = []
@ -16,7 +14,7 @@ for module in settings.EXPEPHALON_MODULES:
for provider in mos.SMSPROVIDERS: for provider in mos.SMSPROVIDERS:
providers.append(provider) providers.append(provider)
if mos.CREATE: if mos.CREATE:
modules_available = mos.CREATE modules_available.append(mos.CREATE)
except (AttributeError, ModuleNotFoundError): except (AttributeError, ModuleNotFoundError):
continue continue

View file

@ -91,6 +91,7 @@ administration_section = NavSection("Administration", "")
user_administration_item = NavItem("Administrator Users", "fa-users-cog", "backend") user_administration_item = NavItem("Administrator Users", "fa-users-cog", "backend")
brand_administration_item = NavItem("Brands", "fa-code-branch", "backend") brand_administration_item = NavItem("Brands", "fa-code-branch", "backend")
sms_administration_item = NavItem("SMS Gateway", "fa-sms", "backend") sms_administration_item = NavItem("SMS Gateway", "fa-sms", "backend")
otp_administration_item = NavItem("Two-Factor Authentication", "fa-id-badge", "backend")
backup_administration_item = NavItem("Backups", "fa-shield-alt", "backend") backup_administration_item = NavItem("Backups", "fa-shield-alt", "backend")
product_administration_item = NavItem("Products", "fa-cube", "backend") product_administration_item = NavItem("Products", "fa-cube", "backend")
pgroup_administration_item = NavItem("Product Groups", "fa-cubes", "backend") pgroup_administration_item = NavItem("Product Groups", "fa-cubes", "backend")
@ -100,6 +101,7 @@ dbsettings_item = NavItem("Database Settings", "fa-database", "dbsettings")
administration_section.add_item(user_administration_item) administration_section.add_item(user_administration_item)
administration_section.add_item(brand_administration_item) administration_section.add_item(brand_administration_item)
administration_section.add_item(sms_administration_item) administration_section.add_item(sms_administration_item)
administration_section.add_item(otp_administration_item)
administration_section.add_item(backup_administration_item) administration_section.add_item(backup_administration_item)
administration_section.add_item(product_administration_item) administration_section.add_item(product_administration_item)
administration_section.add_item(pgroup_administration_item) administration_section.add_item(pgroup_administration_item)

View file

@ -1,15 +1,3 @@
"""
Django settings for expephalon project.
Generated by 'django-admin startproject' using Django 3.0.4.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os import os
from expephalon.custom_settings import * # pylint: disable=unused-wildcard-import from expephalon.custom_settings import * # pylint: disable=unused-wildcard-import
@ -26,6 +14,8 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'polymorphic',
'phonenumber_field',
'core', 'core',
'dbsettings', 'dbsettings',
] + EXPEPHALON_MODULES ] + EXPEPHALON_MODULES
@ -132,4 +122,4 @@ PASSWORD_HASHERS = [
# Media # Media
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_DEFAULT_ACL = None AWS_DEFAULT_ACL = None

View file

@ -2,3 +2,7 @@
* core * core
### smsotp
* core
* any SMS provider (e.g. playsms)

View file

@ -25,11 +25,11 @@ class PlaySMSServer(Model, BaseSMSProvider):
@staticmethod @staticmethod
def getError(status): def getError(status):
if status["error_message"]: if status["error_string"]:
return status["error_message"] return status["error_string"]
try: try:
if status["data"]["error"]: if int(status["data"][0]["error"]):
return status["data"]["error"] return int(status["data"][0]["error"])
finally: finally:
return return
@ -43,11 +43,13 @@ class PlaySMSServer(Model, BaseSMSProvider):
if isinstance(recipients, list): if isinstance(recipients, list):
recipients = ",".join(recipients) recipients = ",".join(recipients)
url = 'http%s://%s/index.php?app=ws&u="%s"&h="%s"&op=pv&to=%s&msg=%s' % ("s" if self.https else "", self.host, self.username, self.token, recipients, quote_plus(message)) url = 'http%s://%s/index.php?app=ws&u=%s&h=%s&op=pv&to=%s&msg=%s' % ("s" if self.https else "", self.host, self.username, self.token, recipients, quote_plus(message))
print(url)
response = urlopen(url) response = urlopen(url)
status = json.loads(response.read().decode()) status = json.loads(response.read().decode())
print(status)
error = PlaySMSServer.getError(status) error = PlaySMSServer.getError(status)

View file

@ -0,0 +1,3 @@
from playsms.models import PlaySMSServer
SMSPROVIDERS = list(PlaySMSServer.objects.all())

View file

@ -3,3 +3,5 @@ mysqlclient
django-storages django-storages
boto3 boto3
Pillow Pillow
django-polymorphic
django-phonenumber-field[phonenumbers]

0
smsotp/__init__.py Normal file
View file

3
smsotp/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
smsotp/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class SmsotpConfig(AppConfig):
name = 'smsotp'

6
smsotp/helpers.py Normal file
View file

@ -0,0 +1,6 @@
from dbsettings.functions import getValue
import random
def generate_token(len=getValue("smsotp.length", 6), chars=getValue("smsotp.chars", "0123456789")):
return "".join(random.choices(chars, k=int(len)))

26
smsotp/models.py Normal file
View file

@ -0,0 +1,26 @@
from django.db.models import Model, CharField, ForeignKey, CASCADE, DateTimeField
from django.contrib.auth import get_user_model
from dbsettings.functions import getValue
from smsotp.helpers import generate_token
from core.modules.sms import get_default_sms_provider
# Create your models here.
class OTPToken(Model):
token = CharField(max_length=32, default=generate_token)
user = ForeignKey(get_user_model(), CASCADE)
creation = DateTimeField(auto_now_add=True)
def send_sms(self, text=getValue("smsotp.text", "Your authentication token is:"), number=None):
if "_TOKEN_" in text:
text = text.replace("_TOKEN_", self.token)
else:
text = f"{text} {self.token}"
number = number or self.user.profile.mobile
provider = get_default_sms_provider()
provider.sendSMS(number, text)

40
smsotp/otp.py Normal file
View file

@ -0,0 +1,40 @@
from core.classes.otp import BaseOTPProvider
from smsotp.models import OTPToken
from core.modules.sms import get_default_sms_provider
from dbsettings.functions import getValue
from django.utils import timezone
class SMSOTP(BaseOTPProvider):
@property
def get_name(self):
return "SMS OTP"
def create_token(self, user):
token = OTPToken.objects.create(user=user)
try:
token.send_sms()
return True
except:
return False
@property
def is_active(self):
return bool(get_default_sms_provider())
def start_authentication(self, user):
if self.create_token(user):
return "We have sent you an SMS containing your authentication token."
else:
return "An error has occurred, please try again later or contact the administrator."
def validate_token(self, user, token):
try:
max_age = timezone.now() - timezone.timedelta(seconds=int(getValue("smsotp.max_age", "300")))
OTPToken.objects.get(user=user, token=token, creation__gte=max_age).delete()
return True
except OTPToken.DoesNotExist:
return False
OTPPROVIDERS = [SMSOTP]

3
smsotp/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
smsotp/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

@ -0,0 +1,26 @@
<html>
<head>
<link rel="stylesheet" href="{% static "backend/css/login.css">
<link href="https://fontproxy.kumi.systems/css?family=Ubuntu" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://fa.kumi.systems/css/all.css">
<title>Sign in</title>
</head>
<body>
<div class="main">
<p class="sign" align="center">Sign in</p>
{% csrf_token %}
<form class="form1">
<input class="un " type="text" align="center" name="username" placeholder="Username">
<input class="pass" type="password" align="center" name="password" placeholder="Password">
<a class="submit" align="center">Sign in</a>
<p class="forgot" align="center"><a href="#">Forgot Password?</p>
</div>
</body>
</html>