*(minor): small refactor
This commit is contained in:
parent
d21ec6c9a5
commit
f2acc154cd
|
@ -90,23 +90,9 @@ data:
|
||||||
# create_users: true
|
# create_users: true
|
||||||
# # Reset LDAP password when user reset their password
|
# # Reset LDAP password when user reset their password
|
||||||
# reset_password: true
|
# reset_password: true
|
||||||
oauth_client:
|
|
||||||
# List of python packages with sources types to load.
|
|
||||||
types:
|
|
||||||
- passbook.oauth_client.source_types.discord
|
|
||||||
- passbook.oauth_client.source_types.facebook
|
|
||||||
- passbook.oauth_client.source_types.github
|
|
||||||
- passbook.oauth_client.source_types.google
|
|
||||||
- passbook.oauth_client.source_types.reddit
|
|
||||||
- passbook.oauth_client.source_types.supervisr
|
|
||||||
- passbook.oauth_client.source_types.twitter
|
|
||||||
- passbook.oauth_client.source_types.azure_ad
|
|
||||||
saml_idp:
|
saml_idp:
|
||||||
signing: true
|
signing: true
|
||||||
autosubmit: false
|
autosubmit: false
|
||||||
issuer: passbook
|
issuer: passbook
|
||||||
assertion_valid_for: 86400
|
assertion_valid_for: 86400
|
||||||
# List of python packages with provider types to load.
|
# List of python packages with provider types to load.
|
||||||
types:
|
|
||||||
- passbook.saml_idp.processors.generic
|
|
||||||
- passbook.saml_idp.processors.salesforce
|
|
||||||
|
|
|
@ -29,9 +29,13 @@ spec:
|
||||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- ./manage.py
|
- celery
|
||||||
args:
|
args:
|
||||||
- worker
|
- worker
|
||||||
|
- --autoscale=10,3
|
||||||
|
- -E
|
||||||
|
- -B
|
||||||
|
- -A passbook.root.celery
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: {{ include "passbook.fullname" . }}-config
|
name: {{ include "passbook.fullname" . }}-config
|
||||||
|
|
|
@ -3,8 +3,8 @@ from django.core.cache import cache
|
||||||
from django.shortcuts import redirect, reverse
|
from django.shortcuts import redirect, reverse
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from passbook import __version__
|
||||||
from passbook.admin.mixins import AdminRequiredMixin
|
from passbook.admin.mixins import AdminRequiredMixin
|
||||||
from passbook.core import __version__
|
|
||||||
from passbook.core.models import (Application, Factor, Invitation, Policy,
|
from passbook.core.models import (Application, Factor, Invitation, Policy,
|
||||||
Provider, Source, User)
|
Provider, Source, User)
|
||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
|
|
|
@ -12,7 +12,7 @@ from passbook.admin.forms.policies import PolicyTestForm
|
||||||
from passbook.admin.mixins import AdminRequiredMixin
|
from passbook.admin.mixins import AdminRequiredMixin
|
||||||
from passbook.core.models import Policy
|
from passbook.core.models import Policy
|
||||||
from passbook.lib.utils.reflection import path_to_class
|
from passbook.lib.utils.reflection import path_to_class
|
||||||
from passbook.policy.engine import PolicyEngine
|
from passbook.policies.engine import PolicyEngine
|
||||||
|
|
||||||
|
|
||||||
class PolicyListView(AdminRequiredMixin, ListView):
|
class PolicyListView(AdminRequiredMixin, ListView):
|
||||||
|
|
Binary file not shown.
|
@ -1,11 +0,0 @@
|
||||||
"""passbook Application Security Gateway app"""
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class PassbookApplicationApplicationGatewayConfig(AppConfig):
|
|
||||||
"""passbook app_gw app"""
|
|
||||||
|
|
||||||
name = 'passbook.app_gw'
|
|
||||||
label = 'passbook_app_gw'
|
|
||||||
verbose_name = 'passbook Application Security Gateway'
|
|
||||||
# mountpoint = 'app_gw/'
|
|
Binary file not shown.
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-03-21 15:21
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_app_gw', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='rewriterule',
|
|
||||||
name='conditions',
|
|
||||||
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.2 on 2019-04-11 13:14
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_app_gw', '0002_auto_20190321_1521'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='applicationgatewayprovider',
|
|
||||||
name='authentication_header',
|
|
||||||
field=models.TextField(blank=True, default='X-Remote-User'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,7 +1,8 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-16 09:13
|
# Generated by Django 2.2.6 on 2019-10-07 14:07
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -23,7 +24,7 @@ class Migration(migrations.Migration):
|
||||||
('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])),
|
('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])),
|
||||||
('date', models.DateTimeField(auto_now_add=True)),
|
('date', models.DateTimeField(auto_now_add=True)),
|
||||||
('app', models.TextField()),
|
('app', models.TextField()),
|
||||||
('_context', models.TextField()),
|
('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
|
||||||
('request_ip', models.GenericIPAddressField()),
|
('request_ip', models.GenericIPAddressField()),
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||||
|
@ -33,19 +34,4 @@ class Migration(migrations.Migration):
|
||||||
'verbose_name_plural': 'Audit Entries',
|
'verbose_name_plural': 'Audit Entries',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='LoginAttempt',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', models.DateField(auto_now_add=True)),
|
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
|
||||||
('target_uid', models.CharField(max_length=254)),
|
|
||||||
('request_ip', models.GenericIPAddressField()),
|
|
||||||
('attempts', models.IntegerField(default=1)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='loginattempt',
|
|
||||||
unique_together={('target_uid', 'request_ip', 'created')},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-21 12:01
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_audit', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='loginattempt',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(auto_now_add=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-21 12:40
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields.jsonb
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_audit', '0002_auto_20190221_1201'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='auditentry',
|
|
||||||
name='_context',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='auditentry',
|
|
||||||
name='context',
|
|
||||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-03-08 14:53
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_audit', '0003_auto_20190221_1240'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='LoginAttempt',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,10 +0,0 @@
|
||||||
"""passbook captcha app"""
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class PassbookCaptchaFactorConfig(AppConfig):
|
|
||||||
"""passbook captcha app"""
|
|
||||||
|
|
||||||
name = 'passbook.captcha_factor'
|
|
||||||
label = 'passbook_captcha_factor'
|
|
||||||
verbose_name = 'passbook Captcha'
|
|
|
@ -1,2 +0,0 @@
|
||||||
"""passbook core"""
|
|
||||||
__version__ = '0.2.6-beta'
|
|
|
@ -2,12 +2,12 @@
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.conf import settings
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class PassbookCoreConfig(AppConfig):
|
class PassbookCoreConfig(AppConfig):
|
||||||
"""passbook core app config"""
|
"""passbook core app config"""
|
||||||
|
|
||||||
|
@ -17,9 +17,7 @@ class PassbookCoreConfig(AppConfig):
|
||||||
mountpoint = ''
|
mountpoint = ''
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import_module('passbook.policy.engine')
|
for factors_to_load in settings.PASSBOOK_CORE_FACTORS:
|
||||||
factors_to_load = CONFIG.y('passbook.factors', [])
|
|
||||||
for factors_to_load in factors_to_load:
|
|
||||||
try:
|
try:
|
||||||
import_module(factors_to_load)
|
import_module(factors_to_load)
|
||||||
LOGGER.info("Loaded factor", factor_class=factors_to_load)
|
LOGGER.info("Loaded factor", factor_class=factors_to_load)
|
||||||
|
|
|
@ -16,7 +16,7 @@ class ApplicationForm(forms.ModelForm):
|
||||||
|
|
||||||
model = Application
|
model = Application
|
||||||
fields = ['name', 'slug', 'launch_url', 'icon_url',
|
fields = ['name', 'slug', 'launch_url', 'icon_url',
|
||||||
'policies', 'provider', 'skip_authorization']
|
'provider', 'policies', 'skip_authorization']
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput(),
|
'name': forms.TextInput(),
|
||||||
'launch_url': forms.TextInput(),
|
'launch_url': forms.TextInput(),
|
||||||
|
|
|
@ -3,40 +3,8 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from passbook.core.models import (DebugPolicy, FieldMatcherPolicy,
|
from passbook.core.models import DebugPolicy
|
||||||
GroupMembershipPolicy, PasswordPolicy,
|
from passbook.policies.forms import GENERAL_FIELDS
|
||||||
SSOLoginPolicy, WebhookPolicy)
|
|
||||||
|
|
||||||
GENERAL_FIELDS = ['name', 'action', 'negate', 'order', 'timeout']
|
|
||||||
|
|
||||||
class FieldMatcherPolicyForm(forms.ModelForm):
|
|
||||||
"""FieldMatcherPolicy Form"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = FieldMatcherPolicy
|
|
||||||
fields = GENERAL_FIELDS + ['user_field', 'match_action', 'value', ]
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'value': forms.TextInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class WebhookPolicyForm(forms.ModelForm):
|
|
||||||
"""WebhookPolicyForm Form"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = WebhookPolicy
|
|
||||||
fields = GENERAL_FIELDS + ['url', 'method', 'json_body', 'json_headers',
|
|
||||||
'result_jsonpath', 'result_json_value', ]
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'json_body': forms.TextInput(),
|
|
||||||
'json_headers': forms.TextInput(),
|
|
||||||
'result_jsonpath': forms.TextInput(),
|
|
||||||
'result_json_value': forms.TextInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DebugPolicyForm(forms.ModelForm):
|
class DebugPolicyForm(forms.ModelForm):
|
||||||
|
@ -52,49 +20,3 @@ class DebugPolicyForm(forms.ModelForm):
|
||||||
labels = {
|
labels = {
|
||||||
'result': _('Allow user')
|
'result': _('Allow user')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class GroupMembershipPolicyForm(forms.ModelForm):
|
|
||||||
"""GroupMembershipPolicy Form"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = GroupMembershipPolicy
|
|
||||||
fields = GENERAL_FIELDS + ['group', ]
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'order': forms.NumberInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
class SSOLoginPolicyForm(forms.ModelForm):
|
|
||||||
"""Edit SSOLoginPolicy instances"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = SSOLoginPolicy
|
|
||||||
fields = GENERAL_FIELDS
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'order': forms.NumberInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
class PasswordPolicyForm(forms.ModelForm):
|
|
||||||
"""PasswordPolicy Form"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = PasswordPolicy
|
|
||||||
fields = GENERAL_FIELDS + ['amount_uppercase', 'amount_lowercase',
|
|
||||||
'amount_symbols', 'length_min', 'symbol_charset',
|
|
||||||
'error_message']
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'symbol_charset': forms.TextInput(),
|
|
||||||
'error_message': forms.TextInput(),
|
|
||||||
}
|
|
||||||
labels = {
|
|
||||||
'amount_uppercase': _('Minimum amount of Uppercase Characters'),
|
|
||||||
'amount_lowercase': _('Minimum amount of Lowercase Characters'),
|
|
||||||
'amount_symbols': _('Minimum amount of Symbols Characters'),
|
|
||||||
'length_min': _('Minimum Length'),
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
"""passbook import_users management command"""
|
|
||||||
from csv import DictReader
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.core.validators import EmailValidator, ValidationError
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.core.models import User
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""Import users from CSV file"""
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
# Positional arguments
|
|
||||||
parser.add_argument('file', nargs='+', type=str)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""Create Users from CSV file"""
|
|
||||||
for file in options.get('file'):
|
|
||||||
with open(file, 'r') as _file:
|
|
||||||
reader = DictReader(_file)
|
|
||||||
for user in reader:
|
|
||||||
LOGGER.debug('User %s', user.get('username'))
|
|
||||||
try:
|
|
||||||
# only import users with valid email addresses
|
|
||||||
if user.get('email'):
|
|
||||||
validator = EmailValidator()
|
|
||||||
validator(user.get('email'))
|
|
||||||
# use combination of username and email to check for existing user
|
|
||||||
if User.objects.filter(
|
|
||||||
username=user.get('username'),
|
|
||||||
email=user.get('email')).exists():
|
|
||||||
LOGGER.debug('User %s exists already, skipping', user.get('username'))
|
|
||||||
# Create user
|
|
||||||
User.objects.create(
|
|
||||||
username=user.get('username'),
|
|
||||||
email=user.get('email'),
|
|
||||||
name=user.get('name'),
|
|
||||||
password=user.get('password'))
|
|
||||||
LOGGER.debug('Created User %s', user.get('username'))
|
|
||||||
except ValidationError as exc:
|
|
||||||
LOGGER.warning('User %s caused %r, skipping', user.get('username'), exc)
|
|
||||||
continue
|
|
|
@ -1,35 +0,0 @@
|
||||||
"""passbook Webserver management command"""
|
|
||||||
|
|
||||||
import cherrypy
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
|
||||||
from passbook.root.wsgi import application
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""Run CherryPy webserver"""
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""passbook cherrypy server"""
|
|
||||||
cherrypy.config.update(CONFIG.y('web'))
|
|
||||||
cherrypy.tree.graft(application, '/')
|
|
||||||
# Mount NullObject to serve static files
|
|
||||||
cherrypy.tree.mount(None, settings.STATIC_URL, config={
|
|
||||||
'/': {
|
|
||||||
'tools.staticdir.on': True,
|
|
||||||
'tools.staticdir.dir': settings.STATIC_ROOT,
|
|
||||||
'tools.expires.on': True,
|
|
||||||
'tools.expires.secs': 86400,
|
|
||||||
'tools.gzip.on': True,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
cherrypy.engine.start()
|
|
||||||
for file in CONFIG.loaded_file:
|
|
||||||
cherrypy.engine.autoreload.files.add(file)
|
|
||||||
LOGGER.info("Added '%s' to autoreload triggers", file)
|
|
||||||
cherrypy.engine.block()
|
|
|
@ -1,22 +0,0 @@
|
||||||
"""passbook Worker management command"""
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.utils import autoreload
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.root.celery import CELERY_APP
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""Run Celery Worker"""
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""celery worker"""
|
|
||||||
autoreload.run_with_reloader(self.celery_worker)
|
|
||||||
|
|
||||||
def celery_worker(self):
|
|
||||||
"""Run celery worker within autoreload"""
|
|
||||||
autoreload.raise_last_exception()
|
|
||||||
CELERY_APP.worker_main(['worker', '--autoscale=10,3', '-E', '-B'])
|
|
|
@ -1,21 +1,24 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-16 09:10
|
# Generated by Django 2.2.6 on 2019-10-07 14:06
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.contrib.auth.validators
|
import django.contrib.auth.validators
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import passbook.core.models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('auth', '0009_alter_user_last_name_max_length'),
|
('auth', '0011_update_proxy_permissions'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -34,6 +37,8 @@ class Migration(migrations.Migration):
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||||
|
('name', models.TextField()),
|
||||||
|
('password_change_date', models.DateTimeField(auto_now_add=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'user',
|
'verbose_name': 'user',
|
||||||
|
@ -44,39 +49,17 @@ class Migration(migrations.Migration):
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='Group',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=80, verbose_name='name')),
|
|
||||||
('extra_data', models.TextField(blank=True)),
|
|
||||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Invitation',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('expires', models.DateTimeField(blank=True, default=None, null=True)),
|
|
||||||
('fixed_username', models.TextField(blank=True, default=None)),
|
|
||||||
('fixed_email', models.TextField(blank=True, default=None)),
|
|
||||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Invitation',
|
|
||||||
'verbose_name_plural': 'Invitations',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Policy',
|
name='Policy',
|
||||||
fields=[
|
fields=[
|
||||||
('created', models.DateField(auto_now_add=True)),
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
('last_updated', models.DateTimeField(auto_now=True)),
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
('name', models.TextField(blank=True, null=True)),
|
('name', models.TextField(blank=True, null=True)),
|
||||||
('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)),
|
('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)),
|
||||||
('negate', models.BooleanField(default=False)),
|
('negate', models.BooleanField(default=False)),
|
||||||
('order', models.IntegerField(default=0)),
|
('order', models.IntegerField(default=0)),
|
||||||
|
('timeout', models.IntegerField(default=30)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
|
@ -85,28 +68,136 @@ class Migration(migrations.Migration):
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='PolicyModel',
|
name='PolicyModel',
|
||||||
fields=[
|
fields=[
|
||||||
('created', models.DateField(auto_now_add=True)),
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
('last_updated', models.DateTimeField(auto_now=True)),
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PropertyMapping',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('name', models.TextField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Property Mapping',
|
||||||
|
'verbose_name_plural': 'Property Mappings',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DebugPolicy',
|
||||||
|
fields=[
|
||||||
|
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
||||||
|
('result', models.BooleanField(default=False)),
|
||||||
|
('wait_min', models.IntegerField(default=5)),
|
||||||
|
('wait_max', models.IntegerField(default=30)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Debug Policy',
|
||||||
|
'verbose_name_plural': 'Debug Policies',
|
||||||
|
},
|
||||||
|
bases=('passbook_core.policy',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Factor',
|
||||||
|
fields=[
|
||||||
|
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||||
|
('name', models.TextField()),
|
||||||
|
('slug', models.SlugField(unique=True)),
|
||||||
|
('order', models.IntegerField()),
|
||||||
|
('enabled', models.BooleanField(default=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('passbook_core.policymodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Source',
|
||||||
|
fields=[
|
||||||
|
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||||
|
('name', models.TextField()),
|
||||||
|
('slug', models.SlugField()),
|
||||||
|
('enabled', models.BooleanField(default=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('passbook_core.policymodel',),
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Provider',
|
name='Provider',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('property_mappings', models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Nonce',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)),
|
||||||
|
('expiring', models.BooleanField(default=True)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Nonce',
|
||||||
|
'verbose_name_plural': 'Nonces',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Invitation',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('expires', models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
('fixed_username', models.TextField(blank=True, default=None)),
|
||||||
|
('fixed_email', models.TextField(blank=True, default=None)),
|
||||||
|
('needs_confirmation', models.BooleanField(default=True)),
|
||||||
|
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Invitation',
|
||||||
|
'verbose_name_plural': 'Invitations',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Group',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=80, verbose_name='name')),
|
||||||
|
('tags', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
|
||||||
|
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('name', 'parent')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='groups',
|
||||||
|
field=models.ManyToManyField(to='passbook_core.Group'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='user_permissions',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UserSourceConnection',
|
name='UserSourceConnection',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('created', models.DateField(auto_now_add=True)),
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
('last_updated', models.DateTimeField(auto_now=True)),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('user', 'source')},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Application',
|
name='Application',
|
||||||
|
@ -124,131 +215,9 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
bases=('passbook_core.policymodel',),
|
bases=('passbook_core.policymodel',),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='DebugPolicy',
|
|
||||||
fields=[
|
|
||||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
|
||||||
('result', models.BooleanField(default=False)),
|
|
||||||
('wait_min', models.IntegerField(default=5)),
|
|
||||||
('wait_max', models.IntegerField(default=30)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Debug Policy',
|
|
||||||
'verbose_name_plural': 'Debug Policys',
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policy',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Factor',
|
|
||||||
fields=[
|
|
||||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
|
||||||
('name', models.TextField()),
|
|
||||||
('slug', models.SlugField(unique=True)),
|
|
||||||
('order', models.IntegerField()),
|
|
||||||
('type', models.TextField(unique=True)),
|
|
||||||
('enabled', models.BooleanField(default=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policymodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='FieldMatcherPolicy',
|
|
||||||
fields=[
|
|
||||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
|
||||||
('user_field', models.TextField(choices=[('username', 'Username'), ('first_name', 'First Name'), ('last_name', 'Last Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')])),
|
|
||||||
('match_action', models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('endswith', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50)),
|
|
||||||
('value', models.TextField()),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Field matcher Policy',
|
|
||||||
'verbose_name_plural': 'Field matcher Policys',
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policy',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PasswordPolicyPolicy',
|
|
||||||
fields=[
|
|
||||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
|
||||||
('amount_uppercase', models.IntegerField(default=0)),
|
|
||||||
('amount_lowercase', models.IntegerField(default=0)),
|
|
||||||
('amount_symbols', models.IntegerField(default=0)),
|
|
||||||
('length_min', models.IntegerField(default=0)),
|
|
||||||
('symbol_charset', models.TextField(default='!\\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Password Policy Policy',
|
|
||||||
'verbose_name_plural': 'Password Policy Policys',
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policy',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Source',
|
|
||||||
fields=[
|
|
||||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
|
||||||
('name', models.TextField()),
|
|
||||||
('slug', models.SlugField()),
|
|
||||||
('enabled', models.BooleanField(default=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policymodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='WebhookPolicy',
|
|
||||||
fields=[
|
|
||||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
|
||||||
('url', models.URLField()),
|
|
||||||
('method', models.CharField(choices=[('GET', 'GET'), ('POST', 'POST'), ('PATCH', 'PATCH'), ('DELETE', 'DELETE'), ('PUT', 'PUT')], max_length=10)),
|
|
||||||
('json_body', models.TextField()),
|
|
||||||
('json_headers', models.TextField()),
|
|
||||||
('result_jsonpath', models.TextField()),
|
|
||||||
('result_json_value', models.TextField()),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Webhook Policy',
|
|
||||||
'verbose_name_plural': 'Webhook Policys',
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policy',),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='policymodel',
|
|
||||||
name='policies',
|
|
||||||
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='groups',
|
|
||||||
field=models.ManyToManyField(to='passbook_core.Group'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='user_permissions',
|
|
||||||
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='usersourceconnection',
|
|
||||||
name='source',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source'),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='group',
|
|
||||||
unique_together={('name', 'parent')},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='applications',
|
|
||||||
field=models.ManyToManyField(to='passbook_core.Application'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='user',
|
model_name='user',
|
||||||
name='sources',
|
name='sources',
|
||||||
field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'),
|
field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'),
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='usersourceconnection',
|
|
||||||
unique_together={('user', 'source')},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-16 10:02
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='debugpolicy',
|
|
||||||
options={'verbose_name': 'Debug Policy', 'verbose_name_plural': 'Debug Policies'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='fieldmatcherpolicy',
|
|
||||||
options={'verbose_name': 'Field matcher Policy', 'verbose_name_plural': 'Field matcher Policies'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='passwordpolicypolicy',
|
|
||||||
options={'verbose_name': 'Password Policy Policy', 'verbose_name_plural': 'Password Policy Policies'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='webhookpolicy',
|
|
||||||
options={'verbose_name': 'Webhook Policy', 'verbose_name_plural': 'Webhook Policies'},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-16 10:04
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0002_auto_20190216_1002'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameModel(
|
|
||||||
old_name='PasswordPolicyPolicy',
|
|
||||||
new_name='PasswordPolicy',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-16 10:13
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0003_auto_20190216_1004'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='passwordpolicy',
|
|
||||||
options={'verbose_name': 'Password Policy', 'verbose_name_plural': 'Password Policies'},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,28 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-21 12:01
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0004_auto_20190216_1013'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='policy',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(auto_now_add=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='policymodel',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(auto_now_add=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='usersourceconnection',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(auto_now_add=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-21 12:32
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields.jsonb
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0005_auto_20190221_1201'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='factor',
|
|
||||||
name='arguments',
|
|
||||||
field=django.contrib.postgres.fields.jsonb.JSONField(default=dict),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-21 12:33
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields.jsonb
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0006_factor_arguments'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='factor',
|
|
||||||
name='arguments',
|
|
||||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-21 15:16
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0007_auto_20190221_1233'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='fieldmatcherpolicy',
|
|
||||||
name='match_action',
|
|
||||||
field=models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('contains', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,44 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-24 09:50
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0008_auto_20190221_1516'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='DummyFactor',
|
|
||||||
fields=[
|
|
||||||
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('passbook_core.factor',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PasswordFactor',
|
|
||||||
fields=[
|
|
||||||
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')),
|
|
||||||
('backends', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('passbook_core.factor',),
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='factor',
|
|
||||||
name='arguments',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='factor',
|
|
||||||
name='type',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,21 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-24 10:16
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0009_auto_20190224_0950'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='dummyfactor',
|
|
||||||
options={'verbose_name': 'Dummy Factor', 'verbose_name_plural': 'Dummy Factors'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='passwordfactor',
|
|
||||||
options={'verbose_name': 'Password Factor', 'verbose_name_plural': 'Password Factors'},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-25 14:38
|
|
||||||
|
|
||||||
import django.utils.timezone
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0010_auto_20190224_1016'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='passwordfactor',
|
|
||||||
name='password_policies',
|
|
||||||
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='password_change_date',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,31 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-25 19:12
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
import passbook.core.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0011_auto_20190225_1438'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Nonce',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Nonce',
|
|
||||||
'verbose_name_plural': 'Nonces',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-25 19:57
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0012_nonce'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='invitation',
|
|
||||||
name='needs_confirmation',
|
|
||||||
field=models.BooleanField(default=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-26 14:28
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0014_auto_20190226_0850'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='passwordpolicy',
|
|
||||||
name='error_message',
|
|
||||||
field=models.TextField(default=''),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,38 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-27 13:55
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_names(apps, schema_editor):
|
|
||||||
"""migrate first_name and last_name to name"""
|
|
||||||
User = apps.get_model("passbook_core", "User")
|
|
||||||
for user in User.objects.all():
|
|
||||||
user.name = '%s %s' % (user.first_name, user.last_name)
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0015_passwordpolicy_error_message'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='name',
|
|
||||||
field=models.TextField(default=''),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.RunPython(migrate_names),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='user',
|
|
||||||
name='name',
|
|
||||||
field=models.TextField(),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='fieldmatcherpolicy',
|
|
||||||
name='user_field',
|
|
||||||
field=models.TextField(choices=[('username', 'Username'), ('name', 'Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')]),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,26 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-03-08 10:40
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0016_auto_20190227_1355'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PropertyMapping',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('name', models.TextField()),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Property Mapping',
|
|
||||||
'verbose_name_plural': 'Property Mappings',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-03-08 10:50
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0017_propertymapping'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='provider',
|
|
||||||
name='property_mappings',
|
|
||||||
field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-03-10 16:15
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields.hstore
|
|
||||||
from django.contrib.postgres.operations import HStoreExtension
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0018_provider_property_mappings'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='group',
|
|
||||||
name='extra_data',
|
|
||||||
),
|
|
||||||
HStoreExtension(),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='group',
|
|
||||||
name='tags',
|
|
||||||
field=django.contrib.postgres.fields.hstore.HStoreField(default=dict),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-03-21 12:03
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0020_groupmembershippolicy'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='policy',
|
|
||||||
name='timeout',
|
|
||||||
field=models.IntegerField(default=30),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-04-04 19:42
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0021_policy_timeout'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='nonce',
|
|
||||||
name='expiring',
|
|
||||||
field=models.BooleanField(default=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 2.2 on 2019-04-13 15:51
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0022_nonce_expiring'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='user',
|
|
||||||
name='applications',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,12 +1,11 @@
|
||||||
"""passbook core models"""
|
"""passbook core models"""
|
||||||
import re
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.postgres.fields import ArrayField, HStoreField
|
from django.contrib.postgres.fields import JSONField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
@ -16,8 +15,8 @@ from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.signals import password_changed
|
from passbook.core.signals import password_changed
|
||||||
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
|
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
|
||||||
from passbook.policy.exceptions import PolicyException
|
from passbook.policies.exceptions import PolicyException
|
||||||
from passbook.policy.struct import PolicyRequest, PolicyResult
|
from passbook.policies.struct import PolicyRequest, PolicyResult
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
@ -32,10 +31,10 @@ class Group(UUIDModel):
|
||||||
name = models.CharField(_('name'), max_length=80)
|
name = models.CharField(_('name'), max_length=80)
|
||||||
parent = models.ForeignKey('Group', blank=True, null=True,
|
parent = models.ForeignKey('Group', blank=True, null=True,
|
||||||
on_delete=models.SET_NULL, related_name='children')
|
on_delete=models.SET_NULL, related_name='children')
|
||||||
tags = HStoreField(default=dict)
|
tags = JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Group %s" % self.name
|
return f"Group {self.name}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
|
@ -94,48 +93,8 @@ class Factor(PolicyModel):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Factor %s" % self.slug
|
return f"Factor {self.slug}"
|
||||||
|
|
||||||
class PasswordFactor(Factor):
|
|
||||||
"""Password-based Django-backend Authentication Factor"""
|
|
||||||
|
|
||||||
backends = ArrayField(models.TextField())
|
|
||||||
password_policies = models.ManyToManyField('Policy', blank=True)
|
|
||||||
|
|
||||||
type = 'passbook.core.auth.factors.password.PasswordFactor'
|
|
||||||
form = 'passbook.core.forms.factors.PasswordFactorForm'
|
|
||||||
|
|
||||||
def has_user_settings(self):
|
|
||||||
return _('Change Password'), 'pficon-key', 'passbook_core:user-change-password'
|
|
||||||
|
|
||||||
def password_passes(self, user: User) -> bool:
|
|
||||||
"""Return true if user's password passes, otherwise False or raise Exception"""
|
|
||||||
for policy in self.policies.all():
|
|
||||||
if not policy.passes(user):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Password Factor %s" % self.slug
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('Password Factor')
|
|
||||||
verbose_name_plural = _('Password Factors')
|
|
||||||
|
|
||||||
class DummyFactor(Factor):
|
|
||||||
"""Dummy factor, mostly used to debug"""
|
|
||||||
|
|
||||||
type = 'passbook.core.auth.factors.dummy.DummyFactor'
|
|
||||||
form = 'passbook.core.forms.factors.DummyFactorForm'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Dummy Factor %s" % self.slug
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('Dummy Factor')
|
|
||||||
verbose_name_plural = _('Dummy Factors')
|
|
||||||
|
|
||||||
class Application(PolicyModel):
|
class Application(PolicyModel):
|
||||||
"""Every Application which uses passbook for authentication/identification/authorization
|
"""Every Application which uses passbook for authentication/identification/authorization
|
||||||
|
@ -161,6 +120,7 @@ class Application(PolicyModel):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Source(PolicyModel):
|
class Source(PolicyModel):
|
||||||
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
|
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
|
||||||
|
|
||||||
|
@ -196,6 +156,7 @@ class Source(PolicyModel):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class UserSourceConnection(CreatedUpdatedModel):
|
class UserSourceConnection(CreatedUpdatedModel):
|
||||||
"""Connection between User and Source."""
|
"""Connection between User and Source."""
|
||||||
|
|
||||||
|
@ -206,6 +167,7 @@ class UserSourceConnection(CreatedUpdatedModel):
|
||||||
|
|
||||||
unique_together = (('user', 'source'),)
|
unique_together = (('user', 'source'),)
|
||||||
|
|
||||||
|
|
||||||
class Policy(UUIDModel, CreatedUpdatedModel):
|
class Policy(UUIDModel, CreatedUpdatedModel):
|
||||||
"""Policies which specify if a user is authorized to use an Application. Can be overridden by
|
"""Policies which specify if a user is authorized to use an Application. Can be overridden by
|
||||||
other types to add other fields, more logic, etc."""
|
other types to add other fields, more logic, etc."""
|
||||||
|
@ -228,148 +190,12 @@ class Policy(UUIDModel, CreatedUpdatedModel):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.name:
|
if self.name:
|
||||||
return self.name
|
return self.name
|
||||||
return "%s action %s" % (self.name, self.action)
|
return f"{self.name} action {self.action}"
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||||
"""Check if user instance passes this policy"""
|
"""Check if user instance passes this policy"""
|
||||||
raise PolicyException()
|
raise PolicyException()
|
||||||
|
|
||||||
class FieldMatcherPolicy(Policy):
|
|
||||||
"""Policy which checks if a field of the User model matches/doesn't match a
|
|
||||||
certain pattern"""
|
|
||||||
|
|
||||||
MATCH_STARTSWITH = 'startswith'
|
|
||||||
MATCH_ENDSWITH = 'endswith'
|
|
||||||
MATCH_CONTAINS = 'contains'
|
|
||||||
MATCH_REGEXP = 'regexp'
|
|
||||||
MATCH_EXACT = 'exact'
|
|
||||||
|
|
||||||
MATCHES = (
|
|
||||||
(MATCH_STARTSWITH, _('Starts with')),
|
|
||||||
(MATCH_ENDSWITH, _('Ends with')),
|
|
||||||
(MATCH_CONTAINS, _('Contains')),
|
|
||||||
(MATCH_REGEXP, _('Regexp')),
|
|
||||||
(MATCH_EXACT, _('Exact')),
|
|
||||||
)
|
|
||||||
|
|
||||||
USER_FIELDS = (
|
|
||||||
('username', _('Username'),),
|
|
||||||
('name', _('Name'),),
|
|
||||||
('email', _('E-Mail'),),
|
|
||||||
('is_staff', _('Is staff'),),
|
|
||||||
('is_active', _('Is active'),),
|
|
||||||
('data_joined', _('Date joined'),),
|
|
||||||
)
|
|
||||||
|
|
||||||
user_field = models.TextField(choices=USER_FIELDS)
|
|
||||||
match_action = models.CharField(max_length=50, choices=MATCHES)
|
|
||||||
value = models.TextField()
|
|
||||||
|
|
||||||
form = 'passbook.core.forms.policies.FieldMatcherPolicyForm'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
description = "%s, user.%s %s '%s'" % (self.name, self.user_field,
|
|
||||||
self.match_action, self.value)
|
|
||||||
if self.name:
|
|
||||||
description = "%s: %s" % (self.name, description)
|
|
||||||
return description
|
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
|
||||||
"""Check if user instance passes this role"""
|
|
||||||
if not hasattr(request.user, self.user_field):
|
|
||||||
raise ValueError("Field does not exist")
|
|
||||||
user_field_value = getattr(request.user, self.user_field, None)
|
|
||||||
LOGGER.debug("Checking field", value=user_field_value,
|
|
||||||
action=self.match_action, should_be=self.value)
|
|
||||||
passes = False
|
|
||||||
if self.match_action == FieldMatcherPolicy.MATCH_STARTSWITH:
|
|
||||||
passes = user_field_value.startswith(self.value)
|
|
||||||
if self.match_action == FieldMatcherPolicy.MATCH_ENDSWITH:
|
|
||||||
passes = user_field_value.endswith(self.value)
|
|
||||||
if self.match_action == FieldMatcherPolicy.MATCH_CONTAINS:
|
|
||||||
passes = self.value in user_field_value
|
|
||||||
if self.match_action == FieldMatcherPolicy.MATCH_REGEXP:
|
|
||||||
pattern = re.compile(self.value)
|
|
||||||
passes = bool(pattern.match(user_field_value))
|
|
||||||
if self.match_action == FieldMatcherPolicy.MATCH_EXACT:
|
|
||||||
passes = user_field_value == self.value
|
|
||||||
return PolicyResult(passes)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('Field matcher Policy')
|
|
||||||
verbose_name_plural = _('Field matcher Policies')
|
|
||||||
|
|
||||||
class PasswordPolicy(Policy):
|
|
||||||
"""Policy to make sure passwords have certain properties"""
|
|
||||||
|
|
||||||
amount_uppercase = models.IntegerField(default=0)
|
|
||||||
amount_lowercase = models.IntegerField(default=0)
|
|
||||||
amount_symbols = models.IntegerField(default=0)
|
|
||||||
length_min = models.IntegerField(default=0)
|
|
||||||
symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ")
|
|
||||||
error_message = models.TextField()
|
|
||||||
|
|
||||||
form = 'passbook.core.forms.policies.PasswordPolicyForm'
|
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
|
||||||
# Only check if password is being set
|
|
||||||
if not hasattr(request.user, '__password__'):
|
|
||||||
return PolicyResult(True)
|
|
||||||
password = getattr(request.user, '__password__')
|
|
||||||
|
|
||||||
filter_regex = r''
|
|
||||||
if self.amount_lowercase > 0:
|
|
||||||
filter_regex += r'[a-z]{%d,}' % self.amount_lowercase
|
|
||||||
if self.amount_uppercase > 0:
|
|
||||||
filter_regex += r'[A-Z]{%d,}' % self.amount_uppercase
|
|
||||||
if self.amount_symbols > 0:
|
|
||||||
filter_regex += r'[%s]{%d,}' % (self.symbol_charset, self.amount_symbols)
|
|
||||||
result = bool(re.compile(filter_regex).match(password))
|
|
||||||
if not result:
|
|
||||||
return PolicyResult(result, self.error_message)
|
|
||||||
return PolicyResult(result)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('Password Policy')
|
|
||||||
verbose_name_plural = _('Password Policies')
|
|
||||||
|
|
||||||
|
|
||||||
class WebhookPolicy(Policy):
|
|
||||||
"""Policy that asks webhook"""
|
|
||||||
|
|
||||||
METHOD_GET = 'GET'
|
|
||||||
METHOD_POST = 'POST'
|
|
||||||
METHOD_PATCH = 'PATCH'
|
|
||||||
METHOD_DELETE = 'DELETE'
|
|
||||||
METHOD_PUT = 'PUT'
|
|
||||||
|
|
||||||
METHODS = (
|
|
||||||
(METHOD_GET, METHOD_GET),
|
|
||||||
(METHOD_POST, METHOD_POST),
|
|
||||||
(METHOD_PATCH, METHOD_PATCH),
|
|
||||||
(METHOD_DELETE, METHOD_DELETE),
|
|
||||||
(METHOD_PUT, METHOD_PUT),
|
|
||||||
)
|
|
||||||
|
|
||||||
url = models.URLField()
|
|
||||||
method = models.CharField(max_length=10, choices=METHODS)
|
|
||||||
json_body = models.TextField()
|
|
||||||
json_headers = models.TextField()
|
|
||||||
result_jsonpath = models.TextField()
|
|
||||||
result_json_value = models.TextField()
|
|
||||||
|
|
||||||
form = 'passbook.core.forms.policies.WebhookPolicyForm'
|
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
|
||||||
"""Call webhook asynchronously and report back"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('Webhook Policy')
|
|
||||||
verbose_name_plural = _('Webhook Policies')
|
|
||||||
|
|
||||||
class DebugPolicy(Policy):
|
class DebugPolicy(Policy):
|
||||||
"""Policy used for debugging the PolicyEngine. Returns a fixed result,
|
"""Policy used for debugging the PolicyEngine. Returns a fixed result,
|
||||||
|
@ -393,36 +219,6 @@ class DebugPolicy(Policy):
|
||||||
verbose_name = _('Debug Policy')
|
verbose_name = _('Debug Policy')
|
||||||
verbose_name_plural = _('Debug Policies')
|
verbose_name_plural = _('Debug Policies')
|
||||||
|
|
||||||
class GroupMembershipPolicy(Policy):
|
|
||||||
"""Policy to check if the user is member in a certain group"""
|
|
||||||
|
|
||||||
group = models.ForeignKey('Group', on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
form = 'passbook.core.forms.policies.GroupMembershipPolicyForm'
|
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
|
||||||
return PolicyResult(self.group.user_set.filter(pk=request.user.pk).exists())
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('Group Membership Policy')
|
|
||||||
verbose_name_plural = _('Group Membership Policies')
|
|
||||||
|
|
||||||
class SSOLoginPolicy(Policy):
|
|
||||||
"""Policy that applies to users that have authenticated themselves through SSO"""
|
|
||||||
|
|
||||||
form = 'passbook.core.forms.policies.SSOLoginPolicyForm'
|
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
|
||||||
"""Check if user instance passes this policy"""
|
|
||||||
from passbook.core.auth.view import AuthenticationView
|
|
||||||
is_sso_login = request.user.session.get(AuthenticationView.SESSION_IS_SSO_LOGIN, False)
|
|
||||||
return PolicyResult(is_sso_login)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('SSO Login Policy')
|
|
||||||
verbose_name_plural = _('SSO Login Policies')
|
|
||||||
|
|
||||||
class Invitation(UUIDModel):
|
class Invitation(UUIDModel):
|
||||||
"""Single-use invitation link"""
|
"""Single-use invitation link"""
|
||||||
|
@ -436,10 +232,10 @@ class Invitation(UUIDModel):
|
||||||
@property
|
@property
|
||||||
def link(self):
|
def link(self):
|
||||||
"""Get link to use invitation"""
|
"""Get link to use invitation"""
|
||||||
return reverse_lazy('passbook_core:auth-sign-up') + '?invitation=%s' % self.uuid
|
return reverse_lazy('passbook_core:auth-sign-up') + f'?invitation={self.uuid.hex}'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Invitation %s created by %s" % (self.uuid, self.created_by)
|
return f"Invitation {self.uuid.hex} created by {self.created_by}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
|
@ -454,7 +250,7 @@ class Nonce(UUIDModel):
|
||||||
expiring = models.BooleanField(default=True)
|
expiring = models.BooleanField(default=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Nonce %s (expires=%s)" % (self.uuid.hex, self.expires)
|
return f"Nonce f{self.uuid.hex} (expires={self.expires})"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
|
@ -470,7 +266,7 @@ class PropertyMapping(UUIDModel):
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Property Mapping %s" % self.name
|
return f"Property Mapping {self.name}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""core settings"""
|
||||||
|
|
||||||
|
PASSBOOK_CORE_FACTORS = [
|
||||||
|
|
||||||
|
]
|
|
@ -5,8 +5,6 @@ from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.exceptions import PasswordPolicyInvalid
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
user_signed_up = Signal(providing_args=['request', 'user'])
|
user_signed_up = Signal(providing_args=['request', 'user'])
|
||||||
|
@ -14,24 +12,9 @@ invitation_created = Signal(providing_args=['request', 'invitation'])
|
||||||
invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
|
invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
|
||||||
password_changed = Signal(providing_args=['user', 'password'])
|
password_changed = Signal(providing_args=['user', 'password'])
|
||||||
|
|
||||||
@receiver(password_changed)
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def password_policy_checker(sender, password, **kwargs):
|
|
||||||
"""Run password through all password policies which are applied to the user"""
|
|
||||||
from passbook.core.models import PasswordFactor
|
|
||||||
from passbook.policy.engine import PolicyEngine
|
|
||||||
setattr(sender, '__password__', password)
|
|
||||||
_all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order')
|
|
||||||
for factor in _all_factors:
|
|
||||||
policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses())
|
|
||||||
policy_engine.for_user(sender).build()
|
|
||||||
passing, messages = policy_engine.result
|
|
||||||
if not passing:
|
|
||||||
raise PasswordPolicyInvalid(*messages)
|
|
||||||
|
|
||||||
@receiver(post_save)
|
@receiver(post_save)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def invalidate_policy_cache(sender, instance, **kwargs):
|
def invalidate_policy_cache(sender, instance, **_):
|
||||||
"""Invalidate Policy cache when policy is updated"""
|
"""Invalidate Policy cache when policy is updated"""
|
||||||
from passbook.core.models import Policy
|
from passbook.core.models import Policy
|
||||||
if isinstance(instance, Policy):
|
if isinstance(instance, Policy):
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
"""passbook user settings template tags"""
|
"""passbook user settings template tags"""
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
from django.template.context import RequestContext
|
||||||
|
|
||||||
from passbook.core.models import Factor, Source
|
from passbook.core.models import Factor, Source
|
||||||
from passbook.policy.engine import PolicyEngine
|
from passbook.policies.engine import PolicyEngine
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def user_factors(context):
|
def user_factors(context: RequestContext):
|
||||||
"""Return list of all factors which apply to user"""
|
"""Return list of all factors which apply to user"""
|
||||||
user = context.get('request').user
|
user = context.get('request').user
|
||||||
_all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses()
|
_all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses()
|
||||||
|
@ -22,7 +23,7 @@ def user_factors(context):
|
||||||
return matching_factors
|
return matching_factors
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def user_sources(context):
|
def user_sources(context: RequestContext):
|
||||||
"""Return a list of all sources which are enabled for the user"""
|
"""Return a list of all sources which are enabled for the user"""
|
||||||
user = context.get('request').user
|
user = context.get('request').user
|
||||||
_all_sources = Source.objects.filter(enabled=True).select_subclasses()
|
_all_sources = Source.objects.filter(enabled=True).select_subclasses()
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth import view
|
|
||||||
from passbook.core.views import authentication, overview, user
|
from passbook.core.views import authentication, overview, user
|
||||||
|
from passbook.factors import view
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.translation import gettext as _
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Application, Provider, User
|
from passbook.core.models import Application, Provider, User
|
||||||
from passbook.policy.engine import PolicyEngine
|
from passbook.policies.engine import PolicyEngine
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,12 @@ from django.views import View
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth.view import AuthenticationView, _redirect_with_qs
|
|
||||||
from passbook.core.exceptions import PasswordPolicyInvalid
|
|
||||||
from passbook.core.forms.authentication import LoginForm, SignUpForm
|
from passbook.core.forms.authentication import LoginForm, SignUpForm
|
||||||
from passbook.core.models import Invitation, Nonce, Source, User
|
from passbook.core.models import Invitation, Nonce, Source, User
|
||||||
from passbook.core.signals import invitation_used, user_signed_up
|
from passbook.core.signals import invitation_used, user_signed_up
|
||||||
from passbook.core.tasks import send_email
|
from passbook.core.tasks import send_email
|
||||||
|
from passbook.factors.password.exceptions import PasswordPolicyInvalid
|
||||||
|
from passbook.factors.view import AuthenticationView, _redirect_with_qs
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from passbook.core.models import Application
|
from passbook.core.models import Application
|
||||||
from passbook.policy.engine import PolicyEngine
|
from passbook.policies.engine import PolicyEngine
|
||||||
|
|
||||||
|
|
||||||
class OverviewView(LoginRequiredMixin, TemplateView):
|
class OverviewView(LoginRequiredMixin, TemplateView):
|
||||||
|
|
|
@ -9,8 +9,8 @@ from django.urls import reverse_lazy
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import DeleteView, FormView, UpdateView
|
from django.views.generic import DeleteView, FormView, UpdateView
|
||||||
|
|
||||||
from passbook.core.exceptions import PasswordPolicyInvalid
|
|
||||||
from passbook.core.forms.users import PasswordChangeForm, UserDetailForm
|
from passbook.core.forms.users import PasswordChangeForm, UserDetailForm
|
||||||
|
from passbook.factors.password.exceptions import PasswordPolicyInvalid
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,25 @@
|
||||||
"""passbook multi-factor authentication engine"""
|
"""passbook multi-factor authentication engine"""
|
||||||
|
from django.forms import ModelForm
|
||||||
|
from django.http import HttpRequest
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from passbook.core.models import User
|
||||||
|
from passbook.factors.view import AuthenticationView
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationFactor(TemplateView):
|
class AuthenticationFactor(TemplateView):
|
||||||
"""Abstract Authentication factor, inherits TemplateView but can be combined with FormView"""
|
"""Abstract Authentication factor, inherits TemplateView but can be combined with FormView"""
|
||||||
|
|
||||||
form = None
|
form: ModelForm = None
|
||||||
required = True
|
required: bool = True
|
||||||
authenticator = None
|
authenticator: AuthenticationView = None
|
||||||
pending_user = None
|
pending_user: User = None
|
||||||
request = None
|
request: HttpRequest = None
|
||||||
template_name = 'login/form_with_user.html'
|
template_name = 'login/form_with_user.html'
|
||||||
|
|
||||||
def __init__(self, authenticator):
|
def __init__(self, authenticator: AuthenticationView):
|
||||||
self.authenticator = authenticator
|
self.authenticator = authenticator
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
|
@ -0,0 +1,10 @@
|
||||||
|
"""passbook captcha app"""
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PassbookFactorCaptchaConfig(AppConfig):
|
||||||
|
"""passbook captcha app"""
|
||||||
|
|
||||||
|
name = 'passbook.factors.captcha'
|
||||||
|
label = 'passbook_factors_captcha'
|
||||||
|
verbose_name = 'passbook Factors.Captcha'
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
|
|
||||||
from passbook.captcha_factor.forms import CaptchaForm
|
from passbook.factors.base import AuthenticationFactor
|
||||||
from passbook.core.auth.factor import AuthenticationFactor
|
from passbook.factors.captcha.forms import CaptchaForm
|
||||||
|
|
||||||
|
|
||||||
class CaptchaFactor(FormView, AuthenticationFactor):
|
class CaptchaFactor(FormView, AuthenticationFactor):
|
||||||
|
@ -16,6 +16,7 @@ class CaptchaFactor(FormView, AuthenticationFactor):
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
form = CaptchaForm(**self.get_form_kwargs())
|
form = CaptchaForm(**self.get_form_kwargs())
|
||||||
|
# TODO: uuuhm
|
||||||
form.fields['captcha'].public_key = '6Lfi1w8TAAAAAELH-YiWp0OFItmMzvjGmw2xkvUN'
|
form.fields['captcha'].public_key = '6Lfi1w8TAAAAAELH-YiWp0OFItmMzvjGmw2xkvUN'
|
||||||
form.fields['captcha'].private_key = '6Lfi1w8TAAAAAMQI3f86tGMvd1QkcqqVQyBWI23D'
|
form.fields['captcha'].private_key = '6Lfi1w8TAAAAAMQI3f86tGMvd1QkcqqVQyBWI23D'
|
||||||
form.fields['captcha'].widget.attrs["data-sitekey"] = form.fields['captcha'].public_key
|
form.fields['captcha'].widget.attrs["data-sitekey"] = form.fields['captcha'].public_key
|
|
@ -2,8 +2,8 @@
|
||||||
from captcha.fields import ReCaptchaField
|
from captcha.fields import ReCaptchaField
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from passbook.captcha_factor.models import CaptchaFactor
|
from passbook.factors.captcha.models import CaptchaFactor
|
||||||
from passbook.core.forms.factors import GENERAL_FIELDS
|
from passbook.factors.forms import GENERAL_FIELDS
|
||||||
|
|
||||||
|
|
||||||
class CaptchaForm(forms.Form):
|
class CaptchaForm(forms.Form):
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-24 21:35
|
# Generated by Django 2.2.6 on 2019-10-07 14:07
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -9,7 +9,7 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_core', '0010_auto_20190224_1016'),
|
('passbook_core', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
|
@ -11,11 +11,11 @@ class CaptchaFactor(Factor):
|
||||||
public_key = models.TextField()
|
public_key = models.TextField()
|
||||||
private_key = models.TextField()
|
private_key = models.TextField()
|
||||||
|
|
||||||
type = 'passbook.captcha_factor.factor.CaptchaFactor'
|
type = 'passbook.factors.captcha.factor.CaptchaFactor'
|
||||||
form = 'passbook.captcha_factor.forms.CaptchaFactorForm'
|
form = 'passbook.factors.captcha.forms.CaptchaFactorForm'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Captcha Factor %s" % self.slug
|
return f"Captcha Factor {self.slug}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""passbook captcha_facot settings"""
|
"""passbook captcha_factor settings"""
|
||||||
|
# https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do
|
||||||
RECAPTCHA_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
|
RECAPTCHA_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
|
||||||
RECAPTCHA_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
|
RECAPTCHA_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
|
||||||
|
|
||||||
NOCAPTCHA = True
|
NOCAPTCHA = True
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'captcha'
|
'captcha'
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""dummy factor admin"""
|
||||||
|
|
||||||
|
from passbook.lib.admin import admin_autoregister
|
||||||
|
|
||||||
|
admin_autoregister('passbook_factors_dummy')
|
|
@ -0,0 +1,11 @@
|
||||||
|
"""passbook dummy factor config"""
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PassbookFactorDummyConfig(AppConfig):
|
||||||
|
"""passbook dummy factor config"""
|
||||||
|
|
||||||
|
name = 'passbook.factors.dummy'
|
||||||
|
label = 'passbook_factors_dummy'
|
||||||
|
verbose_name = 'passbook Factors.Dummy'
|
|
@ -1,14 +1,12 @@
|
||||||
"""passbook multi-factor authentication engine"""
|
"""passbook multi-factor authentication engine"""
|
||||||
from structlog import get_logger
|
from django.http import HttpRequest
|
||||||
|
|
||||||
from passbook.core.auth.factor import AuthenticationFactor
|
from passbook.factors.base import AuthenticationFactor
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class DummyFactor(AuthenticationFactor):
|
class DummyFactor(AuthenticationFactor):
|
||||||
"""Dummy factor for testing with multiple factors"""
|
"""Dummy factor for testing with multiple factors"""
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request: HttpRequest):
|
||||||
"""Just redirect to next factor"""
|
"""Just redirect to next factor"""
|
||||||
return self.authenticator.user_ok()
|
return self.authenticator.user_ok()
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""passbook administration forms"""
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from passbook.factors.dummy.models import DummyFactor
|
||||||
|
from passbook.factors.forms import GENERAL_FIELDS
|
||||||
|
|
||||||
|
|
||||||
|
class DummyFactorForm(forms.ModelForm):
|
||||||
|
"""Form to create/edit Dummy Factor"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
model = DummyFactor
|
||||||
|
fields = GENERAL_FIELDS
|
||||||
|
widgets = {
|
||||||
|
'name': forms.TextInput(),
|
||||||
|
'order': forms.NumberInput(),
|
||||||
|
'policies': FilteredSelectMultiple(_('policies'), False)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 2.2.6 on 2019-10-07 14:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('passbook_core', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DummyFactor',
|
||||||
|
fields=[
|
||||||
|
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Dummy Factor',
|
||||||
|
'verbose_name_plural': 'Dummy Factors',
|
||||||
|
},
|
||||||
|
bases=('passbook_core.factor',),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
"""dummy factor models"""
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from passbook.core.models import Factor
|
||||||
|
|
||||||
|
|
||||||
|
class DummyFactor(Factor):
|
||||||
|
"""Dummy factor, mostly used to debug"""
|
||||||
|
|
||||||
|
type = 'passbook.factors.dummy.factor.DummyFactor'
|
||||||
|
form = 'passbook.factors.dummy.forms.DummyFactorForm'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Dummy Factor {self.slug}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
verbose_name = _('Dummy Factor')
|
||||||
|
verbose_name_plural = _('Dummy Factors')
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""factor forms"""
|
||||||
|
|
||||||
|
GENERAL_FIELDS = ['name', 'slug', 'order', 'policies', 'enabled']
|
|
@ -0,0 +1,12 @@
|
||||||
|
"""passbook OTP AppConfig"""
|
||||||
|
|
||||||
|
from django.apps.config import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PassbookFactorOTPConfig(AppConfig):
|
||||||
|
"""passbook OTP AppConfig"""
|
||||||
|
|
||||||
|
name = 'passbook.factors.otp'
|
||||||
|
label = 'passbook_factors_otp'
|
||||||
|
verbose_name = 'passbook Factors.OTP'
|
||||||
|
mountpoint = 'user/otp/'
|
|
@ -5,9 +5,9 @@ from django.views.generic import FormView
|
||||||
from django_otp import match_token, user_has_device
|
from django_otp import match_token, user_has_device
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth.factor import AuthenticationFactor
|
from passbook.factors.base import AuthenticationFactor
|
||||||
from passbook.otp.forms import OTPVerifyForm
|
from passbook.factors.otp.forms import OTPVerifyForm
|
||||||
from passbook.otp.views import OTP_SETTING_UP_KEY, EnableView
|
from passbook.factors.otp.views import OTP_SETTING_UP_KEY, EnableView
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
|
@ -5,9 +5,10 @@ from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django_otp.models import Device
|
||||||
|
|
||||||
from passbook.core.forms.factors import GENERAL_FIELDS
|
from passbook.factors.forms import GENERAL_FIELDS
|
||||||
from passbook.otp.models import OTPFactor
|
from passbook.factors.otp.models import OTPFactor
|
||||||
|
|
||||||
OTP_CODE_VALIDATOR = RegexValidator(r'^[0-9a-z]{6,8}$',
|
OTP_CODE_VALIDATOR = RegexValidator(r'^[0-9a-z]{6,8}$',
|
||||||
_('Only alpha-numeric characters are allowed.'))
|
_('Only alpha-numeric characters are allowed.'))
|
||||||
|
@ -17,7 +18,7 @@ class PictureWidget(forms.widgets.Widget):
|
||||||
"""Widget to render value as img-tag"""
|
"""Widget to render value as img-tag"""
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, renderer=None):
|
def render(self, name, value, attrs=None, renderer=None):
|
||||||
return mark_safe("<img src=\"%s\" />" % value) # nosec
|
return mark_safe(f'<img src="{value}" />') # nosec
|
||||||
|
|
||||||
|
|
||||||
class OTPVerifyForm(forms.Form):
|
class OTPVerifyForm(forms.Form):
|
||||||
|
@ -33,13 +34,14 @@ class OTPVerifyForm(forms.Form):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# This is a little helper so the field is focused by default
|
# This is a little helper so the field is focused by default
|
||||||
|
# TODO: Tell browser to not suggest any values
|
||||||
self.fields['code'].widget.attrs.update({'autofocus': 'autofocus'})
|
self.fields['code'].widget.attrs.update({'autofocus': 'autofocus'})
|
||||||
|
|
||||||
|
|
||||||
class OTPSetupForm(forms.Form):
|
class OTPSetupForm(forms.Form):
|
||||||
"""OTP Setup form"""
|
"""OTP Setup form"""
|
||||||
title = _('Set up OTP')
|
title = _('Set up OTP')
|
||||||
device = None
|
device: Device = None
|
||||||
qr_code = forms.CharField(widget=PictureWidget, disabled=True, required=False,
|
qr_code = forms.CharField(widget=PictureWidget, disabled=True, required=False,
|
||||||
label=_('Scan this Code with your OTP App.'))
|
label=_('Scan this Code with your OTP App.'))
|
||||||
code = forms.CharField(label=_('Code'), validators=[OTP_CODE_VALIDATOR],
|
code = forms.CharField(label=_('Code'), validators=[OTP_CODE_VALIDATOR],
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-25 09:42
|
# Generated by Django 2.2.6 on 2019-10-07 14:07
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -9,7 +9,7 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_core', '0010_auto_20190224_1016'),
|
('passbook_core', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
|
@ -12,14 +12,14 @@ class OTPFactor(Factor):
|
||||||
enforced = models.BooleanField(default=False, help_text=('Enforce enabled OTP for Users '
|
enforced = models.BooleanField(default=False, help_text=('Enforce enabled OTP for Users '
|
||||||
'this factor applies to.'))
|
'this factor applies to.'))
|
||||||
|
|
||||||
type = 'passbook.otp.factors.OTPFactor'
|
type = 'passbook.factors.otp.factors.OTPFactor'
|
||||||
form = 'passbook.otp.forms.OTPFactorForm'
|
form = 'passbook.factors.otp.forms.OTPFactorForm'
|
||||||
|
|
||||||
def has_user_settings(self):
|
def has_user_settings(self):
|
||||||
return _('OTP'), 'pficon-locked', 'passbook_otp:otp-user-settings'
|
return _('OTP'), 'pficon-locked', 'passbook_otp:otp-user-settings'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "OTP Factor %s" % self.slug
|
return f"OTP Factor {self.slug}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from passbook.otp import views
|
from passbook.factors.otp import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.UserSettingsView.as_view(), name='otp-user-settings'),
|
path('', views.UserSettingsView.as_view(), name='otp-user-settings'),
|
|
@ -16,10 +16,10 @@ from qrcode import make
|
||||||
from qrcode.image.svg import SvgPathImage
|
from qrcode.image.svg import SvgPathImage
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
|
from passbook.factors.otp.forms import OTPSetupForm
|
||||||
|
from passbook.factors.otp.utils import otpauth_url
|
||||||
from passbook.lib.boilerplate import NeverCacheMixin
|
from passbook.lib.boilerplate import NeverCacheMixin
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
from passbook.otp.forms import OTPSetupForm
|
|
||||||
from passbook.otp.utils import otpauth_url
|
|
||||||
|
|
||||||
OTP_SESSION_KEY = 'passbook_otp_key'
|
OTP_SESSION_KEY = 'passbook_otp_key'
|
||||||
OTP_SETTING_UP_KEY = 'passbook_otp_setup'
|
OTP_SETTING_UP_KEY = 'passbook_otp_setup'
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""password factor admin"""
|
||||||
|
|
||||||
|
from passbook.lib.admin import admin_autoregister
|
||||||
|
|
||||||
|
admin_autoregister('passbook_factors_password')
|
|
@ -0,0 +1,15 @@
|
||||||
|
"""passbook core app config"""
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PassbookFactorPasswordConfig(AppConfig):
|
||||||
|
"""passbook password factor config"""
|
||||||
|
|
||||||
|
name = 'passbook.factors.password'
|
||||||
|
label = 'passbook_factors_password'
|
||||||
|
verbose_name = 'passbook Factors.Password'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import_module('passbook.factors.password.signals')
|
|
@ -1,4 +1,4 @@
|
||||||
"""passbook core exceptions"""
|
"""passbook password policy exceptions"""
|
||||||
|
|
||||||
class PasswordPolicyInvalid(Exception):
|
class PasswordPolicyInvalid(Exception):
|
||||||
"""Exception raised when a Password Policy fails"""
|
"""Exception raised when a Password Policy fails"""
|
|
@ -11,11 +11,11 @@ from django.utils.translation import gettext as _
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth.factor import AuthenticationFactor
|
|
||||||
from passbook.core.auth.view import AuthenticationView
|
|
||||||
from passbook.core.forms.authentication import PasswordFactorForm
|
from passbook.core.forms.authentication import PasswordFactorForm
|
||||||
from passbook.core.models import Nonce
|
from passbook.core.models import Nonce
|
||||||
from passbook.core.tasks import send_email
|
from passbook.core.tasks import send_email
|
||||||
|
from passbook.factors.base import AuthenticationFactor
|
||||||
|
from passbook.factors.view import AuthenticationView
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
from passbook.lib.utils.reflection import path_to_class
|
from passbook.lib.utils.reflection import path_to_class
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ LOGGER = get_logger()
|
||||||
|
|
||||||
def authenticate(request, backends, **credentials):
|
def authenticate(request, backends, **credentials):
|
||||||
"""If the given credentials are valid, return a User object.
|
"""If the given credentials are valid, return a User object.
|
||||||
|
|
||||||
Customized version of django's authenticate, which accepts a list of backends"""
|
Customized version of django's authenticate, which accepts a list of backends"""
|
||||||
for backend_path in backends:
|
for backend_path in backends:
|
||||||
backend = path_to_class(backend_path)()
|
backend = path_to_class(backend_path)()
|
|
@ -4,10 +4,10 @@ from django.conf import settings
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from passbook.core.models import DummyFactor, PasswordFactor
|
from passbook.factors.forms import GENERAL_FIELDS
|
||||||
|
from passbook.factors.password.models import PasswordFactor
|
||||||
from passbook.lib.utils.reflection import path_to_class
|
from passbook.lib.utils.reflection import path_to_class
|
||||||
|
|
||||||
GENERAL_FIELDS = ['name', 'slug', 'order', 'policies', 'enabled']
|
|
||||||
|
|
||||||
def get_authentication_backends():
|
def get_authentication_backends():
|
||||||
"""Return all available authentication backends as tuple set"""
|
"""Return all available authentication backends as tuple set"""
|
||||||
|
@ -15,6 +15,7 @@ def get_authentication_backends():
|
||||||
klass = path_to_class(backend)
|
klass = path_to_class(backend)
|
||||||
yield backend, getattr(klass(), 'name', '%s (%s)' % (klass.__name__, klass.__module__))
|
yield backend, getattr(klass(), 'name', '%s (%s)' % (klass.__name__, klass.__module__))
|
||||||
|
|
||||||
|
|
||||||
class PasswordFactorForm(forms.ModelForm):
|
class PasswordFactorForm(forms.ModelForm):
|
||||||
"""Form to create/edit Password Factors"""
|
"""Form to create/edit Password Factors"""
|
||||||
|
|
||||||
|
@ -30,16 +31,3 @@ class PasswordFactorForm(forms.ModelForm):
|
||||||
choices=get_authentication_backends()),
|
choices=get_authentication_backends()),
|
||||||
'password_policies': FilteredSelectMultiple(_('password policies'), False),
|
'password_policies': FilteredSelectMultiple(_('password policies'), False),
|
||||||
}
|
}
|
||||||
|
|
||||||
class DummyFactorForm(forms.ModelForm):
|
|
||||||
"""Form to create/edit Dummy Factor"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = DummyFactor
|
|
||||||
fields = GENERAL_FIELDS
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'order': forms.NumberInput(),
|
|
||||||
'policies': FilteredSelectMultiple(_('policies'), False)
|
|
||||||
}
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 2.2.6 on 2019-10-07 14:07
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('passbook_core', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PasswordFactor',
|
||||||
|
fields=[
|
||||||
|
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')),
|
||||||
|
('backends', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)),
|
||||||
|
('password_policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Password Factor',
|
||||||
|
'verbose_name_plural': 'Password Factors',
|
||||||
|
},
|
||||||
|
bases=('passbook_core.factor',),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,11 +1,11 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-26 08:50
|
# Generated by Django 2.2.6 on 2019-10-07 14:11
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
def create_initial_factor(apps, schema_editor):
|
def create_initial_factor(apps, schema_editor):
|
||||||
"""Create initial PasswordFactor if none exists"""
|
"""Create initial PasswordFactor if none exists"""
|
||||||
PasswordFactor = apps.get_model("passbook_core", "PasswordFactor")
|
PasswordFactor = apps.get_model("passbook_factors_password", "PasswordFactor")
|
||||||
if not PasswordFactor.objects.exists():
|
if not PasswordFactor.objects.exists():
|
||||||
PasswordFactor.objects.create(
|
PasswordFactor.objects.create(
|
||||||
name='password',
|
name='password',
|
||||||
|
@ -17,7 +17,7 @@ def create_initial_factor(apps, schema_editor):
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_core', '0013_invitation_needs_confirmation'),
|
('passbook_factors_password', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
|
@ -0,0 +1,34 @@
|
||||||
|
"""password factor models"""
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from passbook.core.models import Factor, Policy, User
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordFactor(Factor):
|
||||||
|
"""Password-based Django-backend Authentication Factor"""
|
||||||
|
|
||||||
|
backends = ArrayField(models.TextField())
|
||||||
|
password_policies = models.ManyToManyField(Policy, blank=True)
|
||||||
|
|
||||||
|
type = 'passbook.factors.password.factor.PasswordFactor'
|
||||||
|
form = 'passbook.factors.password.forms.PasswordFactorForm'
|
||||||
|
|
||||||
|
def has_user_settings(self):
|
||||||
|
return _('Change Password'), 'pficon-key', 'passbook_core:user-change-password'
|
||||||
|
|
||||||
|
def password_passes(self, user: User) -> bool:
|
||||||
|
"""Return true if user's password passes, otherwise False or raise Exception"""
|
||||||
|
for policy in self.policies.all():
|
||||||
|
if not policy.passes(user):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Password Factor %s" % self.slug
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
verbose_name = _('Password Factor')
|
||||||
|
verbose_name_plural = _('Password Factors')
|
|
@ -0,0 +1,20 @@
|
||||||
|
"""passbook password factor signals"""
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from passbook.core.signals import password_changed
|
||||||
|
from passbook.factors.password.exceptions import PasswordPolicyInvalid
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(password_changed)
|
||||||
|
def password_policy_checker(sender, password, **_):
|
||||||
|
"""Run password through all password policies which are applied to the user"""
|
||||||
|
from passbook.factors.password.models import PasswordFactor
|
||||||
|
from passbook.policies.engine import PolicyEngine
|
||||||
|
setattr(sender, '__password__', password)
|
||||||
|
_all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order')
|
||||||
|
for factor in _all_factors:
|
||||||
|
policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses())
|
||||||
|
policy_engine.for_user(sender).build()
|
||||||
|
passing, messages = policy_engine.result
|
||||||
|
if not passing:
|
||||||
|
raise PasswordPolicyInvalid(*messages)
|
|
@ -7,8 +7,10 @@ from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.test import RequestFactory, TestCase
|
from django.test import RequestFactory, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from passbook.core.auth.view import AuthenticationView
|
from passbook.core.models import User
|
||||||
from passbook.core.models import DummyFactor, PasswordFactor, User
|
from passbook.factors.dummy.models import DummyFactor
|
||||||
|
from passbook.factors.password.models import PasswordFactor
|
||||||
|
from passbook.factors.view import AuthenticationView
|
||||||
|
|
||||||
|
|
||||||
class TestFactorAuthentication(TestCase):
|
class TestFactorAuthentication(TestCase):
|
|
@ -12,7 +12,7 @@ from passbook.core.models import Factor, User
|
||||||
from passbook.core.views.utils import PermissionDeniedView
|
from passbook.core.views.utils import PermissionDeniedView
|
||||||
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
||||||
from passbook.lib.utils.urls import is_url_absolute
|
from passbook.lib.utils.urls import is_url_absolute
|
||||||
from passbook.policy.engine import PolicyEngine
|
from passbook.policies.engine import PolicyEngine
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
@ -23,6 +23,10 @@ def _redirect_with_qs(view, get_query_set=None):
|
||||||
target += '?' + urlencode({key: value for key, value in get_query_set.items()})
|
target += '?' + urlencode({key: value for key, value in get_query_set.items()})
|
||||||
return redirect(target)
|
return redirect(target)
|
||||||
|
|
||||||
|
|
||||||
|
# Argument used to redirect user after login
|
||||||
|
NEXT_ARG_NAME = 'next'
|
||||||
|
|
||||||
class AuthenticationView(UserPassesTestMixin, View):
|
class AuthenticationView(UserPassesTestMixin, View):
|
||||||
"""Wizard-like Multi-factor authenticator"""
|
"""Wizard-like Multi-factor authenticator"""
|
||||||
|
|
||||||
|
@ -45,8 +49,8 @@ class AuthenticationView(UserPassesTestMixin, View):
|
||||||
|
|
||||||
def handle_no_permission(self):
|
def handle_no_permission(self):
|
||||||
# Function from UserPassesTestMixin
|
# Function from UserPassesTestMixin
|
||||||
if 'next' in self.request.GET:
|
if NEXT_ARG_NAME in self.request.GET:
|
||||||
return redirect(self.request.GET.get('next'))
|
return redirect(self.request.GET.get(NEXT_ARG_NAME))
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
return _redirect_with_qs('passbook_core:overview', self.request.GET)
|
return _redirect_with_qs('passbook_core:overview', self.request.GET)
|
||||||
return _redirect_with_qs('passbook_core:auth-login', self.request.GET)
|
return _redirect_with_qs('passbook_core:auth-login', self.request.GET)
|
||||||
|
@ -147,7 +151,7 @@ class AuthenticationView(UserPassesTestMixin, View):
|
||||||
LOGGER.debug("Logged in", user=self.pending_user)
|
LOGGER.debug("Logged in", user=self.pending_user)
|
||||||
# Cleanup
|
# Cleanup
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
next_param = self.request.GET.get('next', None)
|
next_param = self.request.GET.get(NEXT_ARG_NAME, None)
|
||||||
if next_param and not is_url_absolute(next_param):
|
if next_param and not is_url_absolute(next_param):
|
||||||
return redirect(next_param)
|
return redirect(next_param)
|
||||||
return _redirect_with_qs('passbook_core:overview')
|
return _redirect_with_qs('passbook_core:overview')
|
|
@ -1,11 +0,0 @@
|
||||||
"""Passbook hibp app config"""
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class PassbookHIBPConfig(AppConfig):
|
|
||||||
"""Passbook hibp app config"""
|
|
||||||
|
|
||||||
name = 'passbook.hibp_policy'
|
|
||||||
label = 'passbook_hibp_policy'
|
|
||||||
verbose_name = 'passbook HaveIBeenPwned Policy'
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 2.1.7 on 2019-02-25 19:12
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_hibp_policy', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='haveibeenpwendpolicy',
|
|
||||||
options={'verbose_name': 'have i been pwned Policy', 'verbose_name_plural': 'have i been pwned Policies'},
|
|
||||||
),
|
|
||||||
]
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue