diff --git a/helm/passbook/templates/configmap.yaml b/helm/passbook/templates/configmap.yaml index 6ee203ae6..1e2a81bed 100644 --- a/helm/passbook/templates/configmap.yaml +++ b/helm/passbook/templates/configmap.yaml @@ -90,23 +90,9 @@ data: # create_users: true # # Reset LDAP password when user reset their password # 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: signing: true autosubmit: false issuer: passbook assertion_valid_for: 86400 # List of python packages with provider types to load. - types: - - passbook.saml_idp.processors.generic - - passbook.saml_idp.processors.salesforce diff --git a/helm/passbook/templates/worker-deployment.yaml b/helm/passbook/templates/worker-deployment.yaml index 1164e65b3..409fc757d 100644 --- a/helm/passbook/templates/worker-deployment.yaml +++ b/helm/passbook/templates/worker-deployment.yaml @@ -29,9 +29,13 @@ spec: image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}" imagePullPolicy: IfNotPresent command: - - ./manage.py + - celery args: - worker + - --autoscale=10,3 + - -E + - -B + - -A passbook.root.celery envFrom: - configMapRef: name: {{ include "passbook.fullname" . }}-config diff --git a/passbook/admin/views/overview.py b/passbook/admin/views/overview.py index c7ffd41e4..679792828 100644 --- a/passbook/admin/views/overview.py +++ b/passbook/admin/views/overview.py @@ -3,8 +3,8 @@ from django.core.cache import cache from django.shortcuts import redirect, reverse from django.views.generic import TemplateView +from passbook import __version__ from passbook.admin.mixins import AdminRequiredMixin -from passbook.core import __version__ from passbook.core.models import (Application, Factor, Invitation, Policy, Provider, Source, User) from passbook.root.celery import CELERY_APP diff --git a/passbook/admin/views/policy.py b/passbook/admin/views/policy.py index 355717126..541d70e09 100644 --- a/passbook/admin/views/policy.py +++ b/passbook/admin/views/policy.py @@ -12,7 +12,7 @@ from passbook.admin.forms.policies import PolicyTestForm from passbook.admin.mixins import AdminRequiredMixin from passbook.core.models import Policy 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): diff --git a/passbook/app_gw/.DS_Store b/passbook/app_gw/.DS_Store deleted file mode 100644 index 45dfba929..000000000 Binary files a/passbook/app_gw/.DS_Store and /dev/null differ diff --git a/passbook/app_gw/apps.py b/passbook/app_gw/apps.py deleted file mode 100644 index b3c8cec2b..000000000 --- a/passbook/app_gw/apps.py +++ /dev/null @@ -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/' diff --git a/passbook/app_gw/migrations/.DS_Store b/passbook/app_gw/migrations/.DS_Store deleted file mode 100644 index 5008ddfcf..000000000 Binary files a/passbook/app_gw/migrations/.DS_Store and /dev/null differ diff --git a/passbook/app_gw/migrations/0002_auto_20190321_1521.py b/passbook/app_gw/migrations/0002_auto_20190321_1521.py deleted file mode 100644 index 3a9dbe301..000000000 --- a/passbook/app_gw/migrations/0002_auto_20190321_1521.py +++ /dev/null @@ -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'), - ), - ] diff --git a/passbook/app_gw/migrations/0003_auto_20190411_1314.py b/passbook/app_gw/migrations/0003_auto_20190411_1314.py deleted file mode 100644 index 28434b016..000000000 --- a/passbook/app_gw/migrations/0003_auto_20190411_1314.py +++ /dev/null @@ -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'), - ), - ] diff --git a/passbook/audit/migrations/0001_initial.py b/passbook/audit/migrations/0001_initial.py index 568b3d7d7..32d1bd814 100644 --- a/passbook/audit/migrations/0001_initial.py +++ b/passbook/audit/migrations/0001_initial.py @@ -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 django.contrib.postgres.fields.jsonb import django.db.models.deletion from django.conf import settings 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')])), ('date', models.DateTimeField(auto_now_add=True)), ('app', models.TextField()), - ('_context', models.TextField()), + ('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)), ('request_ip', models.GenericIPAddressField()), ('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)), @@ -33,19 +34,4 @@ class Migration(migrations.Migration): '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')}, - ), ] diff --git a/passbook/audit/migrations/0002_auto_20190221_1201.py b/passbook/audit/migrations/0002_auto_20190221_1201.py deleted file mode 100644 index 2327a386d..000000000 --- a/passbook/audit/migrations/0002_auto_20190221_1201.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/audit/migrations/0003_auto_20190221_1240.py b/passbook/audit/migrations/0003_auto_20190221_1240.py deleted file mode 100644 index d8e0a87f6..000000000 --- a/passbook/audit/migrations/0003_auto_20190221_1240.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/audit/migrations/0004_delete_loginattempt.py b/passbook/audit/migrations/0004_delete_loginattempt.py deleted file mode 100644 index 96ca0e8dc..000000000 --- a/passbook/audit/migrations/0004_delete_loginattempt.py +++ /dev/null @@ -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', - ), - ] diff --git a/passbook/captcha_factor/apps.py b/passbook/captcha_factor/apps.py deleted file mode 100644 index 8c1c3294e..000000000 --- a/passbook/captcha_factor/apps.py +++ /dev/null @@ -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' diff --git a/passbook/core/__init__.py b/passbook/core/__init__.py index fdd22d84b..e69de29bb 100644 --- a/passbook/core/__init__.py +++ b/passbook/core/__init__.py @@ -1,2 +0,0 @@ -"""passbook core""" -__version__ = '0.2.6-beta' diff --git a/passbook/core/apps.py b/passbook/core/apps.py index cc38e1fb6..70c0e791d 100644 --- a/passbook/core/apps.py +++ b/passbook/core/apps.py @@ -2,12 +2,12 @@ from importlib import import_module from django.apps import AppConfig +from django.conf import settings from structlog import get_logger -from passbook.lib.config import CONFIG - LOGGER = get_logger() + class PassbookCoreConfig(AppConfig): """passbook core app config""" @@ -17,9 +17,7 @@ class PassbookCoreConfig(AppConfig): mountpoint = '' def ready(self): - import_module('passbook.policy.engine') - factors_to_load = CONFIG.y('passbook.factors', []) - for factors_to_load in factors_to_load: + for factors_to_load in settings.PASSBOOK_CORE_FACTORS: try: import_module(factors_to_load) LOGGER.info("Loaded factor", factor_class=factors_to_load) diff --git a/passbook/core/forms/applications.py b/passbook/core/forms/applications.py index 254c999e6..812cf724b 100644 --- a/passbook/core/forms/applications.py +++ b/passbook/core/forms/applications.py @@ -16,7 +16,7 @@ class ApplicationForm(forms.ModelForm): model = Application fields = ['name', 'slug', 'launch_url', 'icon_url', - 'policies', 'provider', 'skip_authorization'] + 'provider', 'policies', 'skip_authorization'] widgets = { 'name': forms.TextInput(), 'launch_url': forms.TextInput(), diff --git a/passbook/core/forms/policies.py b/passbook/core/forms/policies.py index 7a3293c58..d8ca19127 100644 --- a/passbook/core/forms/policies.py +++ b/passbook/core/forms/policies.py @@ -3,40 +3,8 @@ from django import forms from django.utils.translation import gettext as _ -from passbook.core.models import (DebugPolicy, FieldMatcherPolicy, - GroupMembershipPolicy, PasswordPolicy, - 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(), - } +from passbook.core.models import DebugPolicy +from passbook.policies.forms import GENERAL_FIELDS class DebugPolicyForm(forms.ModelForm): @@ -52,49 +20,3 @@ class DebugPolicyForm(forms.ModelForm): labels = { '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'), - } diff --git a/passbook/core/management/commands/import_users.py b/passbook/core/management/commands/import_users.py deleted file mode 100644 index 889c5e5d7..000000000 --- a/passbook/core/management/commands/import_users.py +++ /dev/null @@ -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 diff --git a/passbook/core/management/commands/web.py b/passbook/core/management/commands/web.py deleted file mode 100644 index 34e4f69e0..000000000 --- a/passbook/core/management/commands/web.py +++ /dev/null @@ -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() diff --git a/passbook/core/management/commands/worker.py b/passbook/core/management/commands/worker.py deleted file mode 100644 index cb11f59ea..000000000 --- a/passbook/core/management/commands/worker.py +++ /dev/null @@ -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']) diff --git a/passbook/core/migrations/0001_initial.py b/passbook/core/migrations/0001_initial.py index 94e90987e..bbc44d1dc 100644 --- a/passbook/core/migrations/0001_initial.py +++ b/passbook/core/migrations/0001_initial.py @@ -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 django.contrib.auth.models import django.contrib.auth.validators +import django.contrib.postgres.fields.jsonb import django.db.models.deletion import django.utils.timezone from django.conf import settings from django.db import migrations, models +import passbook.core.models + class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0009_alter_user_last_name_max_length'), + ('auth', '0011_update_proxy_permissions'), ] 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')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('uuid', models.UUIDField(default=uuid.uuid4, editable=False)), + ('name', models.TextField()), + ('password_change_date', models.DateTimeField(auto_now_add=True)), ], options={ 'verbose_name': 'user', @@ -44,39 +49,17 @@ class Migration(migrations.Migration): ('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( name='Policy', fields=[ - ('created', models.DateField(auto_now_add=True)), + ('created', models.DateTimeField(auto_now_add=True)), ('last_updated', models.DateTimeField(auto_now=True)), ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('name', models.TextField(blank=True, null=True)), ('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)), ('negate', models.BooleanField(default=False)), ('order', models.IntegerField(default=0)), + ('timeout', models.IntegerField(default=30)), ], options={ 'abstract': False, @@ -85,28 +68,136 @@ class Migration(migrations.Migration): migrations.CreateModel( name='PolicyModel', fields=[ - ('created', models.DateField(auto_now_add=True)), + ('created', models.DateTimeField(auto_now_add=True)), ('last_updated', models.DateTimeField(auto_now=True)), ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')), ], options={ '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( name='Provider', fields=[ ('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( name='UserSourceConnection', fields=[ ('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)), ('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( name='Application', @@ -124,131 +215,9 @@ class Migration(migrations.Migration): }, 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( model_name='user', name='sources', field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'), ), - migrations.AlterUniqueTogether( - name='usersourceconnection', - unique_together={('user', 'source')}, - ), ] diff --git a/passbook/core/migrations/0002_auto_20190216_1002.py b/passbook/core/migrations/0002_auto_20190216_1002.py deleted file mode 100644 index 09704b03a..000000000 --- a/passbook/core/migrations/0002_auto_20190216_1002.py +++ /dev/null @@ -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'}, - ), - ] diff --git a/passbook/core/migrations/0003_auto_20190216_1004.py b/passbook/core/migrations/0003_auto_20190216_1004.py deleted file mode 100644 index 88363038d..000000000 --- a/passbook/core/migrations/0003_auto_20190216_1004.py +++ /dev/null @@ -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', - ), - ] diff --git a/passbook/core/migrations/0004_auto_20190216_1013.py b/passbook/core/migrations/0004_auto_20190216_1013.py deleted file mode 100644 index 29b8ce4f7..000000000 --- a/passbook/core/migrations/0004_auto_20190216_1013.py +++ /dev/null @@ -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'}, - ), - ] diff --git a/passbook/core/migrations/0005_auto_20190221_1201.py b/passbook/core/migrations/0005_auto_20190221_1201.py deleted file mode 100644 index db97c81b0..000000000 --- a/passbook/core/migrations/0005_auto_20190221_1201.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/core/migrations/0006_factor_arguments.py b/passbook/core/migrations/0006_factor_arguments.py deleted file mode 100644 index 356d25935..000000000 --- a/passbook/core/migrations/0006_factor_arguments.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/core/migrations/0007_auto_20190221_1233.py b/passbook/core/migrations/0007_auto_20190221_1233.py deleted file mode 100644 index 377c31dd3..000000000 --- a/passbook/core/migrations/0007_auto_20190221_1233.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/core/migrations/0008_auto_20190221_1516.py b/passbook/core/migrations/0008_auto_20190221_1516.py deleted file mode 100644 index 4c7e00ded..000000000 --- a/passbook/core/migrations/0008_auto_20190221_1516.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/core/migrations/0009_auto_20190224_0950.py b/passbook/core/migrations/0009_auto_20190224_0950.py deleted file mode 100644 index 6e0e39e7f..000000000 --- a/passbook/core/migrations/0009_auto_20190224_0950.py +++ /dev/null @@ -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', - ), - ] diff --git a/passbook/core/migrations/0010_auto_20190224_1016.py b/passbook/core/migrations/0010_auto_20190224_1016.py deleted file mode 100644 index b0da3563b..000000000 --- a/passbook/core/migrations/0010_auto_20190224_1016.py +++ /dev/null @@ -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'}, - ), - ] diff --git a/passbook/core/migrations/0011_auto_20190225_1438.py b/passbook/core/migrations/0011_auto_20190225_1438.py deleted file mode 100644 index 690c6f4e7..000000000 --- a/passbook/core/migrations/0011_auto_20190225_1438.py +++ /dev/null @@ -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, - ), - ] diff --git a/passbook/core/migrations/0012_nonce.py b/passbook/core/migrations/0012_nonce.py deleted file mode 100644 index 335ffb3db..000000000 --- a/passbook/core/migrations/0012_nonce.py +++ /dev/null @@ -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', - }, - ), - ] diff --git a/passbook/core/migrations/0013_invitation_needs_confirmation.py b/passbook/core/migrations/0013_invitation_needs_confirmation.py deleted file mode 100644 index 4b09597e1..000000000 --- a/passbook/core/migrations/0013_invitation_needs_confirmation.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/core/migrations/0015_passwordpolicy_error_message.py b/passbook/core/migrations/0015_passwordpolicy_error_message.py deleted file mode 100644 index 7181f95f5..000000000 --- a/passbook/core/migrations/0015_passwordpolicy_error_message.py +++ /dev/null @@ -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, - ), - ] diff --git a/passbook/core/migrations/0016_auto_20190227_1355.py b/passbook/core/migrations/0016_auto_20190227_1355.py deleted file mode 100644 index 8a6a801b9..000000000 --- a/passbook/core/migrations/0016_auto_20190227_1355.py +++ /dev/null @@ -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')]), - ), - ] diff --git a/passbook/core/migrations/0017_propertymapping.py b/passbook/core/migrations/0017_propertymapping.py deleted file mode 100644 index c53c910c1..000000000 --- a/passbook/core/migrations/0017_propertymapping.py +++ /dev/null @@ -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', - }, - ), - ] diff --git a/passbook/core/migrations/0018_provider_property_mappings.py b/passbook/core/migrations/0018_provider_property_mappings.py deleted file mode 100644 index a845d1c81..000000000 --- a/passbook/core/migrations/0018_provider_property_mappings.py +++ /dev/null @@ -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'), - ), - ] diff --git a/passbook/core/migrations/0019_auto_20190310_1615.py b/passbook/core/migrations/0019_auto_20190310_1615.py deleted file mode 100644 index 8b59ed40e..000000000 --- a/passbook/core/migrations/0019_auto_20190310_1615.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/core/migrations/0021_policy_timeout.py b/passbook/core/migrations/0021_policy_timeout.py deleted file mode 100644 index acf278b69..000000000 --- a/passbook/core/migrations/0021_policy_timeout.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/core/migrations/0022_nonce_expiring.py b/passbook/core/migrations/0022_nonce_expiring.py deleted file mode 100644 index 01862a037..000000000 --- a/passbook/core/migrations/0022_nonce_expiring.py +++ /dev/null @@ -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), - ), - ] diff --git a/passbook/core/migrations/0023_remove_user_applications.py b/passbook/core/migrations/0023_remove_user_applications.py deleted file mode 100644 index daaaefcd2..000000000 --- a/passbook/core/migrations/0023_remove_user_applications.py +++ /dev/null @@ -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', - ), - ] diff --git a/passbook/core/models.py b/passbook/core/models.py index 7b8cbe0f2..82bdbf9e3 100644 --- a/passbook/core/models.py +++ b/passbook/core/models.py @@ -1,12 +1,11 @@ """passbook core models""" -import re from datetime import timedelta from random import SystemRandom from time import sleep from uuid import uuid4 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.urls import reverse_lazy from django.utils.timezone import now @@ -16,8 +15,8 @@ from structlog import get_logger from passbook.core.signals import password_changed from passbook.lib.models import CreatedUpdatedModel, UUIDModel -from passbook.policy.exceptions import PolicyException -from passbook.policy.struct import PolicyRequest, PolicyResult +from passbook.policies.exceptions import PolicyException +from passbook.policies.struct import PolicyRequest, PolicyResult LOGGER = get_logger() @@ -32,10 +31,10 @@ class Group(UUIDModel): name = models.CharField(_('name'), max_length=80) parent = models.ForeignKey('Group', blank=True, null=True, on_delete=models.SET_NULL, related_name='children') - tags = HStoreField(default=dict) + tags = JSONField(default=dict, blank=True) def __str__(self): - return "Group %s" % self.name + return f"Group {self.name}" class Meta: @@ -94,48 +93,8 @@ class Factor(PolicyModel): return False 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): """Every Application which uses passbook for authentication/identification/authorization @@ -161,6 +120,7 @@ class Application(PolicyModel): def __str__(self): return self.name + class Source(PolicyModel): """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" @@ -196,6 +156,7 @@ class Source(PolicyModel): def __str__(self): return self.name + class UserSourceConnection(CreatedUpdatedModel): """Connection between User and Source.""" @@ -206,6 +167,7 @@ class UserSourceConnection(CreatedUpdatedModel): unique_together = (('user', 'source'),) + class Policy(UUIDModel, CreatedUpdatedModel): """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.""" @@ -228,148 +190,12 @@ class Policy(UUIDModel, CreatedUpdatedModel): def __str__(self): if 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: """Check if user instance passes this policy""" 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): """Policy used for debugging the PolicyEngine. Returns a fixed result, @@ -393,36 +219,6 @@ class DebugPolicy(Policy): verbose_name = _('Debug Policy') 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): """Single-use invitation link""" @@ -436,10 +232,10 @@ class Invitation(UUIDModel): @property def link(self): """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): - return "Invitation %s created by %s" % (self.uuid, self.created_by) + return f"Invitation {self.uuid.hex} created by {self.created_by}" class Meta: @@ -454,7 +250,7 @@ class Nonce(UUIDModel): expiring = models.BooleanField(default=True) 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: @@ -470,7 +266,7 @@ class PropertyMapping(UUIDModel): objects = InheritanceManager() def __str__(self): - return "Property Mapping %s" % self.name + return f"Property Mapping {self.name}" class Meta: diff --git a/passbook/core/settings.py b/passbook/core/settings.py new file mode 100644 index 000000000..3cd27d42f --- /dev/null +++ b/passbook/core/settings.py @@ -0,0 +1,5 @@ +"""core settings""" + +PASSBOOK_CORE_FACTORS = [ + +] diff --git a/passbook/core/signals.py b/passbook/core/signals.py index 66842f1e8..741af6094 100644 --- a/passbook/core/signals.py +++ b/passbook/core/signals.py @@ -5,8 +5,6 @@ from django.db.models.signals import post_save from django.dispatch import receiver from structlog import get_logger -from passbook.core.exceptions import PasswordPolicyInvalid - LOGGER = get_logger() 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']) 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) # pylint: disable=unused-argument -def invalidate_policy_cache(sender, instance, **kwargs): +def invalidate_policy_cache(sender, instance, **_): """Invalidate Policy cache when policy is updated""" from passbook.core.models import Policy if isinstance(instance, Policy): diff --git a/passbook/core/templatetags/passbook_user_settings.py b/passbook/core/templatetags/passbook_user_settings.py index 05bfc0ae6..f560d03d3 100644 --- a/passbook/core/templatetags/passbook_user_settings.py +++ b/passbook/core/templatetags/passbook_user_settings.py @@ -1,14 +1,15 @@ """passbook user settings template tags""" from django import template +from django.template.context import RequestContext from passbook.core.models import Factor, Source -from passbook.policy.engine import PolicyEngine +from passbook.policies.engine import PolicyEngine register = template.Library() @register.simple_tag(takes_context=True) -def user_factors(context): +def user_factors(context: RequestContext): """Return list of all factors which apply to user""" user = context.get('request').user _all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses() @@ -22,7 +23,7 @@ def user_factors(context): return matching_factors @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""" user = context.get('request').user _all_sources = Source.objects.filter(enabled=True).select_subclasses() diff --git a/passbook/core/urls.py b/passbook/core/urls.py index 560f292be..7857a41e0 100644 --- a/passbook/core/urls.py +++ b/passbook/core/urls.py @@ -2,8 +2,8 @@ from django.urls import path from structlog import get_logger -from passbook.core.auth import view from passbook.core.views import authentication, overview, user +from passbook.factors import view LOGGER = get_logger() diff --git a/passbook/core/views/access.py b/passbook/core/views/access.py index 9d1155460..fa9bad3e0 100644 --- a/passbook/core/views/access.py +++ b/passbook/core/views/access.py @@ -7,7 +7,7 @@ from django.utils.translation import gettext as _ from structlog import get_logger from passbook.core.models import Application, Provider, User -from passbook.policy.engine import PolicyEngine +from passbook.policies.engine import PolicyEngine LOGGER = get_logger() diff --git a/passbook/core/views/authentication.py b/passbook/core/views/authentication.py index 0f957d797..c2767467e 100644 --- a/passbook/core/views/authentication.py +++ b/passbook/core/views/authentication.py @@ -12,12 +12,12 @@ from django.views import View from django.views.generic import FormView 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.models import Invitation, Nonce, Source, User from passbook.core.signals import invitation_used, user_signed_up 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 LOGGER = get_logger() diff --git a/passbook/core/views/overview.py b/passbook/core/views/overview.py index ad788b607..efe37e17c 100644 --- a/passbook/core/views/overview.py +++ b/passbook/core/views/overview.py @@ -4,7 +4,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import TemplateView from passbook.core.models import Application -from passbook.policy.engine import PolicyEngine +from passbook.policies.engine import PolicyEngine class OverviewView(LoginRequiredMixin, TemplateView): diff --git a/passbook/core/views/user.py b/passbook/core/views/user.py index 6de4ad003..4f90a1421 100644 --- a/passbook/core/views/user.py +++ b/passbook/core/views/user.py @@ -9,8 +9,8 @@ from django.urls import reverse_lazy from django.utils.translation import gettext as _ from django.views.generic import DeleteView, FormView, UpdateView -from passbook.core.exceptions import PasswordPolicyInvalid from passbook.core.forms.users import PasswordChangeForm, UserDetailForm +from passbook.factors.password.exceptions import PasswordPolicyInvalid from passbook.lib.config import CONFIG diff --git a/passbook/app_gw/__init__.py b/passbook/factors/__init__.py similarity index 100% rename from passbook/app_gw/__init__.py rename to passbook/factors/__init__.py diff --git a/passbook/core/auth/factor.py b/passbook/factors/base.py similarity index 66% rename from passbook/core/auth/factor.py rename to passbook/factors/base.py index 98b26232b..f4754bf53 100644 --- a/passbook/core/auth/factor.py +++ b/passbook/factors/base.py @@ -1,21 +1,25 @@ """passbook multi-factor authentication engine""" +from django.forms import ModelForm +from django.http import HttpRequest from django.utils.translation import gettext as _ from django.views.generic import TemplateView +from passbook.core.models import User +from passbook.factors.view import AuthenticationView from passbook.lib.config import CONFIG class AuthenticationFactor(TemplateView): """Abstract Authentication factor, inherits TemplateView but can be combined with FormView""" - form = None - required = True - authenticator = None - pending_user = None - request = None + form: ModelForm = None + required: bool = True + authenticator: AuthenticationView = None + pending_user: User = None + request: HttpRequest = None template_name = 'login/form_with_user.html' - def __init__(self, authenticator): + def __init__(self, authenticator: AuthenticationView): self.authenticator = authenticator def get_context_data(self, **kwargs): diff --git a/passbook/app_gw/migrations/__init__.py b/passbook/factors/captcha/__init__.py similarity index 100% rename from passbook/app_gw/migrations/__init__.py rename to passbook/factors/captcha/__init__.py diff --git a/passbook/factors/captcha/apps.py b/passbook/factors/captcha/apps.py new file mode 100644 index 000000000..5b4e0c722 --- /dev/null +++ b/passbook/factors/captcha/apps.py @@ -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' diff --git a/passbook/captcha_factor/factor.py b/passbook/factors/captcha/factor.py similarity index 84% rename from passbook/captcha_factor/factor.py rename to passbook/factors/captcha/factor.py index 9ce564cff..9017bc674 100644 --- a/passbook/captcha_factor/factor.py +++ b/passbook/factors/captcha/factor.py @@ -2,8 +2,8 @@ from django.views.generic import FormView -from passbook.captcha_factor.forms import CaptchaForm -from passbook.core.auth.factor import AuthenticationFactor +from passbook.factors.base import AuthenticationFactor +from passbook.factors.captcha.forms import CaptchaForm class CaptchaFactor(FormView, AuthenticationFactor): @@ -16,6 +16,7 @@ class CaptchaFactor(FormView, AuthenticationFactor): def get_form(self, form_class=None): form = CaptchaForm(**self.get_form_kwargs()) + # TODO: uuuhm form.fields['captcha'].public_key = '6Lfi1w8TAAAAAELH-YiWp0OFItmMzvjGmw2xkvUN' form.fields['captcha'].private_key = '6Lfi1w8TAAAAAMQI3f86tGMvd1QkcqqVQyBWI23D' form.fields['captcha'].widget.attrs["data-sitekey"] = form.fields['captcha'].public_key diff --git a/passbook/captcha_factor/forms.py b/passbook/factors/captcha/forms.py similarity index 84% rename from passbook/captcha_factor/forms.py rename to passbook/factors/captcha/forms.py index ed48968ff..687b44a46 100644 --- a/passbook/captcha_factor/forms.py +++ b/passbook/factors/captcha/forms.py @@ -2,8 +2,8 @@ from captcha.fields import ReCaptchaField from django import forms -from passbook.captcha_factor.models import CaptchaFactor -from passbook.core.forms.factors import GENERAL_FIELDS +from passbook.factors.captcha.models import CaptchaFactor +from passbook.factors.forms import GENERAL_FIELDS class CaptchaForm(forms.Form): diff --git a/passbook/captcha_factor/migrations/0001_initial.py b/passbook/factors/captcha/migrations/0001_initial.py similarity index 88% rename from passbook/captcha_factor/migrations/0001_initial.py rename to passbook/factors/captcha/migrations/0001_initial.py index 97d2be499..e08a8efdd 100644 --- a/passbook/captcha_factor/migrations/0001_initial.py +++ b/passbook/factors/captcha/migrations/0001_initial.py @@ -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 from django.db import migrations, models @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('passbook_core', '0010_auto_20190224_1016'), + ('passbook_core', '0001_initial'), ] operations = [ diff --git a/passbook/captcha_factor/__init__.py b/passbook/factors/captcha/migrations/__init__.py similarity index 100% rename from passbook/captcha_factor/__init__.py rename to passbook/factors/captcha/migrations/__init__.py diff --git a/passbook/captcha_factor/models.py b/passbook/factors/captcha/models.py similarity index 71% rename from passbook/captcha_factor/models.py rename to passbook/factors/captcha/models.py index cb0319ac2..9b45bbb57 100644 --- a/passbook/captcha_factor/models.py +++ b/passbook/factors/captcha/models.py @@ -11,11 +11,11 @@ class CaptchaFactor(Factor): public_key = models.TextField() private_key = models.TextField() - type = 'passbook.captcha_factor.factor.CaptchaFactor' - form = 'passbook.captcha_factor.forms.CaptchaFactorForm' + type = 'passbook.factors.captcha.factor.CaptchaFactor' + form = 'passbook.factors.captcha.forms.CaptchaFactorForm' def __str__(self): - return "Captcha Factor %s" % self.slug + return f"Captcha Factor {self.slug}" class Meta: diff --git a/passbook/captcha_factor/settings.py b/passbook/factors/captcha/settings.py similarity index 61% rename from passbook/captcha_factor/settings.py rename to passbook/factors/captcha/settings.py index 0b888f511..01e98b632 100644 --- a/passbook/captcha_factor/settings.py +++ b/passbook/factors/captcha/settings.py @@ -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_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe' + NOCAPTCHA = True INSTALLED_APPS = [ 'captcha' diff --git a/passbook/captcha_factor/migrations/__init__.py b/passbook/factors/dummy/__init__.py similarity index 100% rename from passbook/captcha_factor/migrations/__init__.py rename to passbook/factors/dummy/__init__.py diff --git a/passbook/factors/dummy/admin.py b/passbook/factors/dummy/admin.py new file mode 100644 index 000000000..0c3f336fc --- /dev/null +++ b/passbook/factors/dummy/admin.py @@ -0,0 +1,5 @@ +"""dummy factor admin""" + +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_factors_dummy') diff --git a/passbook/factors/dummy/apps.py b/passbook/factors/dummy/apps.py new file mode 100644 index 000000000..2a5eb82c3 --- /dev/null +++ b/passbook/factors/dummy/apps.py @@ -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' diff --git a/passbook/core/auth/factors/dummy.py b/passbook/factors/dummy/factor.py similarity index 62% rename from passbook/core/auth/factors/dummy.py rename to passbook/factors/dummy/factor.py index ebfd29ed6..63fce66c1 100644 --- a/passbook/core/auth/factors/dummy.py +++ b/passbook/factors/dummy/factor.py @@ -1,14 +1,12 @@ """passbook multi-factor authentication engine""" -from structlog import get_logger +from django.http import HttpRequest -from passbook.core.auth.factor import AuthenticationFactor - -LOGGER = get_logger() +from passbook.factors.base import AuthenticationFactor class DummyFactor(AuthenticationFactor): """Dummy factor for testing with multiple factors""" - def post(self, request): + def post(self, request: HttpRequest): """Just redirect to next factor""" return self.authenticator.user_ok() diff --git a/passbook/factors/dummy/forms.py b/passbook/factors/dummy/forms.py new file mode 100644 index 000000000..cf5a9145e --- /dev/null +++ b/passbook/factors/dummy/forms.py @@ -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) + } diff --git a/passbook/factors/dummy/migrations/0001_initial.py b/passbook/factors/dummy/migrations/0001_initial.py new file mode 100644 index 000000000..59abab393 --- /dev/null +++ b/passbook/factors/dummy/migrations/0001_initial.py @@ -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',), + ), + ] diff --git a/passbook/core/auth/__init__.py b/passbook/factors/dummy/migrations/__init__.py similarity index 100% rename from passbook/core/auth/__init__.py rename to passbook/factors/dummy/migrations/__init__.py diff --git a/passbook/factors/dummy/models.py b/passbook/factors/dummy/models.py new file mode 100644 index 000000000..bbf4c6b36 --- /dev/null +++ b/passbook/factors/dummy/models.py @@ -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') diff --git a/passbook/factors/forms.py b/passbook/factors/forms.py new file mode 100644 index 000000000..1f65394ac --- /dev/null +++ b/passbook/factors/forms.py @@ -0,0 +1,3 @@ +"""factor forms""" + +GENERAL_FIELDS = ['name', 'slug', 'order', 'policies', 'enabled'] diff --git a/passbook/core/auth/factors/__init__.py b/passbook/factors/otp/__init__.py similarity index 100% rename from passbook/core/auth/factors/__init__.py rename to passbook/factors/otp/__init__.py diff --git a/passbook/factors/otp/apps.py b/passbook/factors/otp/apps.py new file mode 100644 index 000000000..1fc1be8e6 --- /dev/null +++ b/passbook/factors/otp/apps.py @@ -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/' diff --git a/passbook/otp/factors.py b/passbook/factors/otp/factors.py similarity index 92% rename from passbook/otp/factors.py rename to passbook/factors/otp/factors.py index bab0cb796..9cae91761 100644 --- a/passbook/otp/factors.py +++ b/passbook/factors/otp/factors.py @@ -5,9 +5,9 @@ from django.views.generic import FormView from django_otp import match_token, user_has_device from structlog import get_logger -from passbook.core.auth.factor import AuthenticationFactor -from passbook.otp.forms import OTPVerifyForm -from passbook.otp.views import OTP_SETTING_UP_KEY, EnableView +from passbook.factors.base import AuthenticationFactor +from passbook.factors.otp.forms import OTPVerifyForm +from passbook.factors.otp.views import OTP_SETTING_UP_KEY, EnableView LOGGER = get_logger() diff --git a/passbook/otp/forms.py b/passbook/factors/otp/forms.py similarity index 89% rename from passbook/otp/forms.py rename to passbook/factors/otp/forms.py index bfe41e0bc..16a37908a 100644 --- a/passbook/otp/forms.py +++ b/passbook/factors/otp/forms.py @@ -5,9 +5,10 @@ from django.contrib.admin.widgets import FilteredSelectMultiple from django.core.validators import RegexValidator from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ +from django_otp.models import Device -from passbook.core.forms.factors import GENERAL_FIELDS -from passbook.otp.models import OTPFactor +from passbook.factors.forms import GENERAL_FIELDS +from passbook.factors.otp.models import OTPFactor OTP_CODE_VALIDATOR = RegexValidator(r'^[0-9a-z]{6,8}$', _('Only alpha-numeric characters are allowed.')) @@ -17,7 +18,7 @@ class PictureWidget(forms.widgets.Widget): """Widget to render value as img-tag""" def render(self, name, value, attrs=None, renderer=None): - return mark_safe("" % value) # nosec + return mark_safe(f'') # nosec class OTPVerifyForm(forms.Form): @@ -33,13 +34,14 @@ class OTPVerifyForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 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'}) class OTPSetupForm(forms.Form): """OTP Setup form""" title = _('Set up OTP') - device = None + device: Device = None qr_code = forms.CharField(widget=PictureWidget, disabled=True, required=False, label=_('Scan this Code with your OTP App.')) code = forms.CharField(label=_('Code'), validators=[OTP_CODE_VALIDATOR], diff --git a/passbook/otp/migrations/0001_initial.py b/passbook/factors/otp/migrations/0001_initial.py similarity index 88% rename from passbook/otp/migrations/0001_initial.py rename to passbook/factors/otp/migrations/0001_initial.py index 9e7483c43..0acab2c9d 100644 --- a/passbook/otp/migrations/0001_initial.py +++ b/passbook/factors/otp/migrations/0001_initial.py @@ -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 from django.db import migrations, models @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('passbook_core', '0010_auto_20190224_1016'), + ('passbook_core', '0001_initial'), ] operations = [ diff --git a/passbook/core/management/__init__.py b/passbook/factors/otp/migrations/__init__.py similarity index 100% rename from passbook/core/management/__init__.py rename to passbook/factors/otp/migrations/__init__.py diff --git a/passbook/otp/models.py b/passbook/factors/otp/models.py similarity index 80% rename from passbook/otp/models.py rename to passbook/factors/otp/models.py index 15d2249e7..74e2a54a6 100644 --- a/passbook/otp/models.py +++ b/passbook/factors/otp/models.py @@ -12,14 +12,14 @@ class OTPFactor(Factor): enforced = models.BooleanField(default=False, help_text=('Enforce enabled OTP for Users ' 'this factor applies to.')) - type = 'passbook.otp.factors.OTPFactor' - form = 'passbook.otp.forms.OTPFactorForm' + type = 'passbook.factors.otp.factors.OTPFactor' + form = 'passbook.factors.otp.forms.OTPFactorForm' def has_user_settings(self): return _('OTP'), 'pficon-locked', 'passbook_otp:otp-user-settings' def __str__(self): - return "OTP Factor %s" % self.slug + return f"OTP Factor {self.slug}" class Meta: diff --git a/passbook/otp/settings.py b/passbook/factors/otp/settings.py similarity index 100% rename from passbook/otp/settings.py rename to passbook/factors/otp/settings.py diff --git a/passbook/otp/templates/otp/factor.html b/passbook/factors/otp/templates/otp/factor.html similarity index 100% rename from passbook/otp/templates/otp/factor.html rename to passbook/factors/otp/templates/otp/factor.html diff --git a/passbook/otp/templates/otp/setup.html b/passbook/factors/otp/templates/otp/setup.html similarity index 100% rename from passbook/otp/templates/otp/setup.html rename to passbook/factors/otp/templates/otp/setup.html diff --git a/passbook/otp/templates/otp/user_settings.html b/passbook/factors/otp/templates/otp/user_settings.html similarity index 100% rename from passbook/otp/templates/otp/user_settings.html rename to passbook/factors/otp/templates/otp/user_settings.html diff --git a/passbook/otp/urls.py b/passbook/factors/otp/urls.py similarity index 89% rename from passbook/otp/urls.py rename to passbook/factors/otp/urls.py index 96574c117..6da05b400 100644 --- a/passbook/otp/urls.py +++ b/passbook/factors/otp/urls.py @@ -2,7 +2,7 @@ from django.urls import path -from passbook.otp import views +from passbook.factors.otp import views urlpatterns = [ path('', views.UserSettingsView.as_view(), name='otp-user-settings'), diff --git a/passbook/otp/utils.py b/passbook/factors/otp/utils.py similarity index 100% rename from passbook/otp/utils.py rename to passbook/factors/otp/utils.py diff --git a/passbook/otp/views.py b/passbook/factors/otp/views.py similarity index 98% rename from passbook/otp/views.py rename to passbook/factors/otp/views.py index 3c01bd851..84f3b7a0a 100644 --- a/passbook/otp/views.py +++ b/passbook/factors/otp/views.py @@ -16,10 +16,10 @@ from qrcode import make from qrcode.image.svg import SvgPathImage 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.config import CONFIG -from passbook.otp.forms import OTPSetupForm -from passbook.otp.utils import otpauth_url OTP_SESSION_KEY = 'passbook_otp_key' OTP_SETTING_UP_KEY = 'passbook_otp_setup' diff --git a/passbook/core/management/commands/__init__.py b/passbook/factors/password/__init__.py similarity index 100% rename from passbook/core/management/commands/__init__.py rename to passbook/factors/password/__init__.py diff --git a/passbook/factors/password/admin.py b/passbook/factors/password/admin.py new file mode 100644 index 000000000..1ff90534f --- /dev/null +++ b/passbook/factors/password/admin.py @@ -0,0 +1,5 @@ +"""password factor admin""" + +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_factors_password') diff --git a/passbook/factors/password/apps.py b/passbook/factors/password/apps.py new file mode 100644 index 000000000..2ab933c0d --- /dev/null +++ b/passbook/factors/password/apps.py @@ -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') diff --git a/passbook/core/exceptions.py b/passbook/factors/password/exceptions.py similarity index 83% rename from passbook/core/exceptions.py rename to passbook/factors/password/exceptions.py index b5f5b5277..b04d2668f 100644 --- a/passbook/core/exceptions.py +++ b/passbook/factors/password/exceptions.py @@ -1,4 +1,4 @@ -"""passbook core exceptions""" +"""passbook password policy exceptions""" class PasswordPolicyInvalid(Exception): """Exception raised when a Password Policy fails""" diff --git a/passbook/core/auth/factors/password.py b/passbook/factors/password/factor.py similarity index 97% rename from passbook/core/auth/factors/password.py rename to passbook/factors/password/factor.py index 948687c64..afccdc242 100644 --- a/passbook/core/auth/factors/password.py +++ b/passbook/factors/password/factor.py @@ -11,11 +11,11 @@ from django.utils.translation import gettext as _ from django.views.generic import FormView 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.models import Nonce 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.utils.reflection import path_to_class @@ -24,6 +24,7 @@ LOGGER = get_logger() def authenticate(request, backends, **credentials): """If the given credentials are valid, return a User object. + Customized version of django's authenticate, which accepts a list of backends""" for backend_path in backends: backend = path_to_class(backend_path)() diff --git a/passbook/core/forms/factors.py b/passbook/factors/password/forms.py similarity index 71% rename from passbook/core/forms/factors.py rename to passbook/factors/password/forms.py index b304eb966..c790c4d43 100644 --- a/passbook/core/forms/factors.py +++ b/passbook/factors/password/forms.py @@ -4,10 +4,10 @@ from django.conf import settings from django.contrib.admin.widgets import FilteredSelectMultiple 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 -GENERAL_FIELDS = ['name', 'slug', 'order', 'policies', 'enabled'] def get_authentication_backends(): """Return all available authentication backends as tuple set""" @@ -15,6 +15,7 @@ def get_authentication_backends(): klass = path_to_class(backend) yield backend, getattr(klass(), 'name', '%s (%s)' % (klass.__name__, klass.__module__)) + class PasswordFactorForm(forms.ModelForm): """Form to create/edit Password Factors""" @@ -30,16 +31,3 @@ class PasswordFactorForm(forms.ModelForm): choices=get_authentication_backends()), '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) - } diff --git a/passbook/factors/password/migrations/0001_initial.py b/passbook/factors/password/migrations/0001_initial.py new file mode 100644 index 000000000..68cfaa8a0 --- /dev/null +++ b/passbook/factors/password/migrations/0001_initial.py @@ -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',), + ), + ] diff --git a/passbook/core/migrations/0014_auto_20190226_0850.py b/passbook/factors/password/migrations/0002_auto_20191007_1411.py similarity index 73% rename from passbook/core/migrations/0014_auto_20190226_0850.py rename to passbook/factors/password/migrations/0002_auto_20191007_1411.py index 1b9ed8c68..b7e15c08c 100644 --- a/passbook/core/migrations/0014_auto_20190226_0850.py +++ b/passbook/factors/password/migrations/0002_auto_20191007_1411.py @@ -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 def create_initial_factor(apps, schema_editor): """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(): PasswordFactor.objects.create( name='password', @@ -17,7 +17,7 @@ def create_initial_factor(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('passbook_core', '0013_invitation_needs_confirmation'), + ('passbook_factors_password', '0001_initial'), ] operations = [ diff --git a/passbook/hibp_policy/__init__.py b/passbook/factors/password/migrations/__init__.py similarity index 100% rename from passbook/hibp_policy/__init__.py rename to passbook/factors/password/migrations/__init__.py diff --git a/passbook/factors/password/models.py b/passbook/factors/password/models.py new file mode 100644 index 000000000..b255e87e6 --- /dev/null +++ b/passbook/factors/password/models.py @@ -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') diff --git a/passbook/factors/password/signals.py b/passbook/factors/password/signals.py new file mode 100644 index 000000000..cf7cea028 --- /dev/null +++ b/passbook/factors/password/signals.py @@ -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) diff --git a/passbook/hibp_policy/migrations/__init__.py b/passbook/factors/tests/__init__.py similarity index 100% rename from passbook/hibp_policy/migrations/__init__.py rename to passbook/factors/tests/__init__.py diff --git a/passbook/core/tests/test_auth_view.py b/passbook/factors/tests/test_auth_view.py similarity index 96% rename from passbook/core/tests/test_auth_view.py rename to passbook/factors/tests/test_auth_view.py index 430936029..df447d930 100644 --- a/passbook/core/tests/test_auth_view.py +++ b/passbook/factors/tests/test_auth_view.py @@ -7,8 +7,10 @@ from django.contrib.sessions.middleware import SessionMiddleware from django.test import RequestFactory, TestCase from django.urls import reverse -from passbook.core.auth.view import AuthenticationView -from passbook.core.models import DummyFactor, PasswordFactor, User +from passbook.core.models import User +from passbook.factors.dummy.models import DummyFactor +from passbook.factors.password.models import PasswordFactor +from passbook.factors.view import AuthenticationView class TestFactorAuthentication(TestCase): diff --git a/passbook/core/auth/view.py b/passbook/factors/view.py similarity index 96% rename from passbook/core/auth/view.py rename to passbook/factors/view.py index 5516380ae..d53d38502 100644 --- a/passbook/core/auth/view.py +++ b/passbook/factors/view.py @@ -12,7 +12,7 @@ from passbook.core.models import Factor, User from passbook.core.views.utils import PermissionDeniedView from passbook.lib.utils.reflection import class_to_path, path_to_class from passbook.lib.utils.urls import is_url_absolute -from passbook.policy.engine import PolicyEngine +from passbook.policies.engine import PolicyEngine 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()}) return redirect(target) + +# Argument used to redirect user after login +NEXT_ARG_NAME = 'next' + class AuthenticationView(UserPassesTestMixin, View): """Wizard-like Multi-factor authenticator""" @@ -45,8 +49,8 @@ class AuthenticationView(UserPassesTestMixin, View): def handle_no_permission(self): # Function from UserPassesTestMixin - if 'next' in self.request.GET: - return redirect(self.request.GET.get('next')) + if NEXT_ARG_NAME in self.request.GET: + return redirect(self.request.GET.get(NEXT_ARG_NAME)) if self.request.user.is_authenticated: return _redirect_with_qs('passbook_core:overview', 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) # 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): return redirect(next_param) return _redirect_with_qs('passbook_core:overview') diff --git a/passbook/hibp_policy/apps.py b/passbook/hibp_policy/apps.py deleted file mode 100644 index 7e71824bc..000000000 --- a/passbook/hibp_policy/apps.py +++ /dev/null @@ -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' diff --git a/passbook/hibp_policy/migrations/0002_auto_20190225_1912.py b/passbook/hibp_policy/migrations/0002_auto_20190225_1912.py deleted file mode 100644 index 6d7f9eabf..000000000 --- a/passbook/hibp_policy/migrations/0002_auto_20190225_1912.py +++ /dev/null @@ -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'}, - ), - ] diff --git a/passbook/hibp_policy/migrations/0003_auto_20190227_1505.py b/passbook/hibp_policy/migrations/0003_auto_20190227_1505.py deleted file mode 100644 index f37bc6a90..000000000 --- a/passbook/hibp_policy/migrations/0003_auto_20190227_1505.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.1.7 on 2019-02-27 15:05 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('passbook_hibp_policy', '0002_auto_20190225_1912'), - ] - - operations = [ - migrations.AlterModelOptions( - name='haveibeenpwendpolicy', - options={'verbose_name': 'Have I Been Pwned Policy', 'verbose_name_plural': 'Have I Been Pwned Policies'}, - ), - ] diff --git a/passbook/ldap/apps.py b/passbook/ldap/apps.py deleted file mode 100644 index 909e61360..000000000 --- a/passbook/ldap/apps.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Passbook ldap app config""" - -from django.apps import AppConfig - - -class PassbookLdapConfig(AppConfig): - """Passbook ldap app config""" - - name = 'passbook.ldap' - label = 'passbook_ldap' - verbose_name = 'passbook LDAP' diff --git a/passbook/ldap/migrations/0002_ldapgroupmembershippolicy.py b/passbook/ldap/migrations/0002_ldapgroupmembershippolicy.py deleted file mode 100644 index a7f2bed7e..000000000 --- a/passbook/ldap/migrations/0002_ldapgroupmembershippolicy.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-10 18:38 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('passbook_core', '0020_groupmembershippolicy'), - ('passbook_ldap', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='LDAPGroupMembershipPolicy', - 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')), - ('dn', models.TextField()), - ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_ldap.LDAPSource')), - ], - options={ - 'verbose_name': 'LDAP Group Membership Policy', - 'verbose_name_plural': 'LDAP Group Membership Policys', - }, - bases=('passbook_core.policy',), - ), - ] diff --git a/passbook/lib/default.yml b/passbook/lib/default.yml index c7f09fb21..8c2bedaf6 100644 --- a/passbook/lib/default.yml +++ b/passbook/lib/default.yml @@ -61,22 +61,6 @@ ldap: username: "%(sAMAccountName)s" email: "%(mail)s" name: "%(displayName)" -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: - # List of python packages with provider types to load. - types: - - passbook.saml_idp.processors.generic - - passbook.saml_idp.processors.salesforce app_gw: listen: 0.0.0.0 port: 8000 diff --git a/passbook/oauth_client/settings.py b/passbook/oauth_client/settings.py deleted file mode 100644 index 82f40f720..000000000 --- a/passbook/oauth_client/settings.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Oauth2 Client Settings -""" - -AUTHENTICATION_BACKENDS = [ - 'passbook.oauth_client.backends.AuthorizedServiceBackend', -] diff --git a/passbook/oidc_provider/migrations/0002_auto_20190709_1416.py b/passbook/oidc_provider/migrations/0002_auto_20190709_1416.py deleted file mode 100644 index 6bc8bc225..000000000 --- a/passbook/oidc_provider/migrations/0002_auto_20190709_1416.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.3 on 2019-07-09 14:16 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('passbook_oidc_provider', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='openidprovider', - options={'verbose_name': 'OpenID Provider', 'verbose_name_plural': 'OpenID Providers'}, - ), - ] diff --git a/passbook/oidc_provider/settings.py b/passbook/oidc_provider/settings.py deleted file mode 100644 index dfde0f058..000000000 --- a/passbook/oidc_provider/settings.py +++ /dev/null @@ -1,7 +0,0 @@ -"""passbook OIDC Provider""" - -INSTALLED_APPS = [ - 'oidc_provider', -] - -OIDC_AFTER_USERLOGIN_HOOK = "passbook.oidc_provider.lib.check_permissions" diff --git a/passbook/otp/apps.py b/passbook/otp/apps.py deleted file mode 100644 index b7e7e1fa8..000000000 --- a/passbook/otp/apps.py +++ /dev/null @@ -1,12 +0,0 @@ -"""passbook OTP AppConfig""" - -from django.apps.config import AppConfig - - -class PassbookOTPConfig(AppConfig): - """passbook OTP AppConfig""" - - name = 'passbook.otp' - label = 'passbook_otp' - verbose_name = 'passbook OTP' - mountpoint = 'user/otp/' diff --git a/passbook/password_expiry_policy/admin.py b/passbook/password_expiry_policy/admin.py deleted file mode 100644 index 67192491d..000000000 --- a/passbook/password_expiry_policy/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Passbook password_expiry_policy Admin""" - -from passbook.lib.admin import admin_autoregister - -admin_autoregister('passbook_password_expiry_policy') diff --git a/passbook/password_expiry_policy/apps.py b/passbook/password_expiry_policy/apps.py deleted file mode 100644 index c335659f9..000000000 --- a/passbook/password_expiry_policy/apps.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Passbook password_expiry_policy app config""" - -from django.apps import AppConfig - - -class PassbookPasswordExpiryPolicyConfig(AppConfig): - """Passbook password_expiry_policy app config""" - - name = 'passbook.password_expiry_policy' - label = 'passbook_password_expiry_policy' - verbose_name = 'passbook Password Expiry Policy' diff --git a/passbook/ldap/__init__.py b/passbook/policies/__init__.py similarity index 100% rename from passbook/ldap/__init__.py rename to passbook/policies/__init__.py diff --git a/passbook/policy/engine.py b/passbook/policies/engine.py similarity index 93% rename from passbook/policy/engine.py rename to passbook/policies/engine.py index 2c418ffb6..bc661bea1 100644 --- a/passbook/policy/engine.py +++ b/passbook/policies/engine.py @@ -8,14 +8,11 @@ from django.http import HttpRequest from structlog import get_logger from passbook.core.models import Policy, User -from passbook.policy.process import PolicyProcess -from passbook.policy.struct import PolicyRequest, PolicyResult +from passbook.policies.process import PolicyProcess, cache_key +from passbook.policies.struct import PolicyRequest, PolicyResult LOGGER = get_logger() -def _cache_key(policy, user): - return f"policy_{policy.pk}#{user.pk}" - class PolicyProcessInfo: """Dataclass to hold all information and communication channels to a process""" @@ -69,7 +66,7 @@ class PolicyEngine: request = PolicyRequest(self.__user) request.http_request = self.__request for policy in self._select_subclasses(): - cached_policy = cache.get(_cache_key(policy, self.__user), None) + cached_policy = cache.get(cache_key(policy, self.__user), None) if cached_policy: LOGGER.debug("Taking result from cache", policy=policy) cached_policies.append(cached_policy) diff --git a/passbook/policy/exceptions.py b/passbook/policies/exceptions.py similarity index 100% rename from passbook/policy/exceptions.py rename to passbook/policies/exceptions.py diff --git a/passbook/ldap/migrations/__init__.py b/passbook/policies/expiry/__init__.py similarity index 100% rename from passbook/ldap/migrations/__init__.py rename to passbook/policies/expiry/__init__.py diff --git a/passbook/policies/expiry/admin.py b/passbook/policies/expiry/admin.py new file mode 100644 index 000000000..2623d7989 --- /dev/null +++ b/passbook/policies/expiry/admin.py @@ -0,0 +1,5 @@ +"""Passbook passbook expiry policy Admin""" + +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_policies_expiry') diff --git a/passbook/policies/expiry/apps.py b/passbook/policies/expiry/apps.py new file mode 100644 index 000000000..b218da9d9 --- /dev/null +++ b/passbook/policies/expiry/apps.py @@ -0,0 +1,11 @@ +"""Passbook policy_expiry app config""" + +from django.apps import AppConfig + + +class PassbookPolicyExpiryConfig(AppConfig): + """Passbook policy_expiry app config""" + + name = 'passbook.policies.expiry' + label = 'passbook_policies_expiry' + verbose_name = 'passbook Policies.Expiry' diff --git a/passbook/password_expiry_policy/forms.py b/passbook/policies/expiry/forms.py similarity index 91% rename from passbook/password_expiry_policy/forms.py rename to passbook/policies/expiry/forms.py index be496cd0b..0ead5c233 100644 --- a/passbook/password_expiry_policy/forms.py +++ b/passbook/policies/expiry/forms.py @@ -5,7 +5,7 @@ from django.contrib.admin.widgets import FilteredSelectMultiple from django.utils.translation import gettext as _ from passbook.core.forms.policies import GENERAL_FIELDS -from passbook.password_expiry_policy.models import PasswordExpiryPolicy +from passbook.policies.expiry.models import PasswordExpiryPolicy class PasswordExpiryPolicyForm(forms.ModelForm): diff --git a/passbook/password_expiry_policy/migrations/0001_initial.py b/passbook/policies/expiry/migrations/0001_initial.py similarity index 89% rename from passbook/password_expiry_policy/migrations/0001_initial.py rename to passbook/policies/expiry/migrations/0001_initial.py index 50f28fe06..38809d9cd 100644 --- a/passbook/password_expiry_policy/migrations/0001_initial.py +++ b/passbook/policies/expiry/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.7 on 2019-03-03 13:46 +# Generated by Django 2.2.6 on 2019-10-07 14:07 import django.db.models.deletion from django.db import migrations, models @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('passbook_core', '0016_auto_20190227_1355'), + ('passbook_core', '0001_initial'), ] operations = [ diff --git a/passbook/oauth_client/__init__.py b/passbook/policies/expiry/migrations/__init__.py similarity index 100% rename from passbook/oauth_client/__init__.py rename to passbook/policies/expiry/migrations/__init__.py diff --git a/passbook/password_expiry_policy/models.py b/passbook/policies/expiry/models.py similarity index 92% rename from passbook/password_expiry_policy/models.py rename to passbook/policies/expiry/models.py index ed5d7fa9d..b7d3271ea 100644 --- a/passbook/password_expiry_policy/models.py +++ b/passbook/policies/expiry/models.py @@ -7,7 +7,7 @@ from django.utils.translation import gettext as _ from structlog import get_logger from passbook.core.models import Policy -from passbook.policy.struct import PolicyRequest, PolicyResult +from passbook.policies.struct import PolicyRequest, PolicyResult LOGGER = get_logger() @@ -19,7 +19,7 @@ class PasswordExpiryPolicy(Policy): deny_only = models.BooleanField(default=False) days = models.IntegerField() - form = 'passbook.password_expiry_policy.forms.PasswordExpiryPolicyForm' + form = 'passbook.policies.expiry.forms.PasswordExpiryPolicyForm' def passes(self, request: PolicyRequest) -> PolicyResult: """If password change date is more than x days in the past, call set_unusable_password diff --git a/passbook/policies/forms.py b/passbook/policies/forms.py new file mode 100644 index 000000000..19bf89588 --- /dev/null +++ b/passbook/policies/forms.py @@ -0,0 +1,3 @@ +"""General fields""" + +GENERAL_FIELDS = ['name', 'action', 'negate', 'order', 'timeout'] diff --git a/passbook/oauth_client/migrations/__init__.py b/passbook/policies/group/__init__.py similarity index 100% rename from passbook/oauth_client/migrations/__init__.py rename to passbook/policies/group/__init__.py diff --git a/passbook/policies/group/admin.py b/passbook/policies/group/admin.py new file mode 100644 index 000000000..7206844ec --- /dev/null +++ b/passbook/policies/group/admin.py @@ -0,0 +1,4 @@ +"""autodiscover admin""" +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_policies_group') diff --git a/passbook/policies/group/apps.py b/passbook/policies/group/apps.py new file mode 100644 index 000000000..f27b34954 --- /dev/null +++ b/passbook/policies/group/apps.py @@ -0,0 +1,11 @@ +"""passbook Group policy app config""" + +from django.apps import AppConfig + + +class PassbookPoliciesGroupConfig(AppConfig): + """passbook Group policy app config""" + + name = 'passbook.policies.group' + label = 'passbook_policies_group' + verbose_name = 'passbook Policies.Group' diff --git a/passbook/policies/group/forms.py b/passbook/policies/group/forms.py new file mode 100644 index 000000000..5bb43c0d4 --- /dev/null +++ b/passbook/policies/group/forms.py @@ -0,0 +1,19 @@ +"""passbook Policy forms""" + +from django import forms + +from passbook.policies.forms import GENERAL_FIELDS +from passbook.policies.group.models import GroupMembershipPolicy + + +class GroupMembershipPolicyForm(forms.ModelForm): + """GroupMembershipPolicy Form""" + + class Meta: + + model = GroupMembershipPolicy + fields = GENERAL_FIELDS + ['group', ] + widgets = { + 'name': forms.TextInput(), + 'order': forms.NumberInput(), + } diff --git a/passbook/core/migrations/0020_groupmembershippolicy.py b/passbook/policies/group/migrations/0001_initial.py similarity index 88% rename from passbook/core/migrations/0020_groupmembershippolicy.py rename to passbook/policies/group/migrations/0001_initial.py index f120f4908..a2b255184 100644 --- a/passbook/core/migrations/0020_groupmembershippolicy.py +++ b/passbook/policies/group/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.7 on 2019-03-10 18:25 +# Generated by Django 2.2.6 on 2019-10-07 14:07 import django.db.models.deletion from django.db import migrations, models @@ -6,8 +6,10 @@ from django.db import migrations, models class Migration(migrations.Migration): + initial = True + dependencies = [ - ('passbook_core', '0019_auto_20190310_1615'), + ('passbook_core', '0001_initial'), ] operations = [ diff --git a/passbook/oauth_client/source_types/__init__.py b/passbook/policies/group/migrations/__init__.py similarity index 100% rename from passbook/oauth_client/source_types/__init__.py rename to passbook/policies/group/migrations/__init__.py diff --git a/passbook/policies/group/models.py b/passbook/policies/group/models.py new file mode 100644 index 000000000..149eb2c0d --- /dev/null +++ b/passbook/policies/group/models.py @@ -0,0 +1,22 @@ +"""passbook group models models""" +from django.db import models +from django.utils.translation import gettext as _ + +from passbook.core.models import Group, Policy +from passbook.policies.struct import PolicyRequest, PolicyResult + + +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.policies.group.forms.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') diff --git a/passbook/oauth_client/views/__init__.py b/passbook/policies/hibp/__init__.py similarity index 100% rename from passbook/oauth_client/views/__init__.py rename to passbook/policies/hibp/__init__.py diff --git a/passbook/hibp_policy/admin.py b/passbook/policies/hibp/admin.py similarity index 63% rename from passbook/hibp_policy/admin.py rename to passbook/policies/hibp/admin.py index b8f5efb9d..a8f568fd5 100644 --- a/passbook/hibp_policy/admin.py +++ b/passbook/policies/hibp/admin.py @@ -2,4 +2,4 @@ from passbook.lib.admin import admin_autoregister -admin_autoregister('passbook_hibp_policy') +admin_autoregister('passbook_policies_hibp') diff --git a/passbook/policies/hibp/apps.py b/passbook/policies/hibp/apps.py new file mode 100644 index 000000000..090b3cbf7 --- /dev/null +++ b/passbook/policies/hibp/apps.py @@ -0,0 +1,11 @@ +"""Passbook hibp app config""" + +from django.apps import AppConfig + + +class PassbookPolicyHIBPConfig(AppConfig): + """Passbook hibp app config""" + + name = 'passbook.policies.hibp' + label = 'passbook_policies_hibp' + verbose_name = 'passbook Policies.HaveIBeenPwned' diff --git a/passbook/hibp_policy/forms.py b/passbook/policies/hibp/forms.py similarity index 90% rename from passbook/hibp_policy/forms.py rename to passbook/policies/hibp/forms.py index 643bf4791..5557097f6 100644 --- a/passbook/hibp_policy/forms.py +++ b/passbook/policies/hibp/forms.py @@ -5,7 +5,7 @@ from django.contrib.admin.widgets import FilteredSelectMultiple from django.utils.translation import gettext as _ from passbook.core.forms.policies import GENERAL_FIELDS -from passbook.hibp_policy.models import HaveIBeenPwendPolicy +from passbook.policies.hibp.models import HaveIBeenPwendPolicy class HaveIBeenPwnedPolicyForm(forms.ModelForm): diff --git a/passbook/hibp_policy/migrations/0001_initial.py b/passbook/policies/hibp/migrations/0001_initial.py similarity index 74% rename from passbook/hibp_policy/migrations/0001_initial.py rename to passbook/policies/hibp/migrations/0001_initial.py index f183157c7..b6069d17b 100644 --- a/passbook/hibp_policy/migrations/0001_initial.py +++ b/passbook/policies/hibp/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.7 on 2019-02-25 15:50 +# Generated by Django 2.2.6 on 2019-10-07 14:07 import django.db.models.deletion from django.db import migrations, models @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('passbook_core', '0011_auto_20190225_1438'), + ('passbook_core', '0001_initial'), ] operations = [ @@ -20,8 +20,8 @@ class Migration(migrations.Migration): ('allowed_count', models.IntegerField(default=0)), ], options={ - 'verbose_name': 'HaveIBeenPwned Policy', - 'verbose_name_plural': 'HaveIBeenPwned Policies', + 'verbose_name': 'Have I Been Pwned Policy', + 'verbose_name_plural': 'Have I Been Pwned Policies', }, bases=('passbook_core.policy',), ), diff --git a/passbook/oauth_provider/__init__.py b/passbook/policies/hibp/migrations/__init__.py similarity index 100% rename from passbook/oauth_provider/__init__.py rename to passbook/policies/hibp/migrations/__init__.py diff --git a/passbook/hibp_policy/models.py b/passbook/policies/hibp/models.py similarity index 96% rename from passbook/hibp_policy/models.py rename to passbook/policies/hibp/models.py index eb8c3bb06..b737b6b2b 100644 --- a/passbook/hibp_policy/models.py +++ b/passbook/policies/hibp/models.py @@ -16,7 +16,7 @@ class HaveIBeenPwendPolicy(Policy): allowed_count = models.IntegerField(default=0) - form = 'passbook.hibp_policy.forms.HaveIBeenPwnedPolicyForm' + form = 'passbook.policies.hibp.forms.HaveIBeenPwnedPolicyForm' def passes(self, user: User) -> PolicyResult: """Check if password is in HIBP DB. Hashes given Password with SHA1, uses the first 5 diff --git a/passbook/oauth_provider/migrations/__init__.py b/passbook/policies/matcher/__init__.py similarity index 100% rename from passbook/oauth_provider/migrations/__init__.py rename to passbook/policies/matcher/__init__.py diff --git a/passbook/policies/matcher/admin.py b/passbook/policies/matcher/admin.py new file mode 100644 index 000000000..c81b555a6 --- /dev/null +++ b/passbook/policies/matcher/admin.py @@ -0,0 +1,4 @@ +"""autodiscover admin""" +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_policies_matcher') diff --git a/passbook/policies/matcher/apps.py b/passbook/policies/matcher/apps.py new file mode 100644 index 000000000..8d19ddd19 --- /dev/null +++ b/passbook/policies/matcher/apps.py @@ -0,0 +1,11 @@ +"""passbook Matcher policy app config""" + +from django.apps import AppConfig + + +class PassbookPoliciesMatcherConfig(AppConfig): + """passbook Matcher policy app config""" + + name = 'passbook.policies.matcher' + label = 'passbook_policies_matcher' + verbose_name = 'passbook Policies.Matcher' diff --git a/passbook/policies/matcher/forms.py b/passbook/policies/matcher/forms.py new file mode 100644 index 000000000..13b4e4f36 --- /dev/null +++ b/passbook/policies/matcher/forms.py @@ -0,0 +1,19 @@ +"""passbook Policy forms""" + +from django import forms + +from passbook.policies.forms import GENERAL_FIELDS +from passbook.policies.matcher.models import FieldMatcherPolicy + + +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(), + } diff --git a/passbook/policies/matcher/migrations/0001_initial.py b/passbook/policies/matcher/migrations/0001_initial.py new file mode 100644 index 000000000..c1da71ea1 --- /dev/null +++ b/passbook/policies/matcher/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# 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='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'), ('name', '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'), ('contains', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50)), + ('value', models.TextField()), + ], + options={ + 'verbose_name': 'Field matcher Policy', + 'verbose_name_plural': 'Field matcher Policies', + }, + bases=('passbook_core.policy',), + ), + ] diff --git a/passbook/oauth_provider/views/__init__.py b/passbook/policies/matcher/migrations/__init__.py similarity index 100% rename from passbook/oauth_provider/views/__init__.py rename to passbook/policies/matcher/migrations/__init__.py diff --git a/passbook/policies/matcher/models.py b/passbook/policies/matcher/models.py new file mode 100644 index 000000000..dec2f4b1c --- /dev/null +++ b/passbook/policies/matcher/models.py @@ -0,0 +1,76 @@ +"""user field matcher models""" +import re + +from django.db import models +from django.utils.translation import gettext as _ +from structlog import get_logger + +from passbook.core.models import Policy +from passbook.policies.struct import PolicyRequest, PolicyResult + +LOGGER = get_logger() + +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.policies.matcher.forms.FieldMatcherPolicyForm' + + def __str__(self): + description = f"{self.name}, user.{self.user_field} {self.match_action} '{self.value}'" + if self.name: + description = f"{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') diff --git a/passbook/oidc_provider/__init__.py b/passbook/policies/password/__init__.py similarity index 100% rename from passbook/oidc_provider/__init__.py rename to passbook/policies/password/__init__.py diff --git a/passbook/policies/password/admin.py b/passbook/policies/password/admin.py new file mode 100644 index 000000000..919eeae08 --- /dev/null +++ b/passbook/policies/password/admin.py @@ -0,0 +1,4 @@ +"""autodiscover admin""" +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_policies_password') diff --git a/passbook/policies/password/apps.py b/passbook/policies/password/apps.py new file mode 100644 index 000000000..e8a3abc47 --- /dev/null +++ b/passbook/policies/password/apps.py @@ -0,0 +1,11 @@ +"""passbook Password policy app config""" + +from django.apps import AppConfig + + +class PassbookPoliciesPasswordConfig(AppConfig): + """passbook Password policy app config""" + + name = 'passbook.policies.password' + label = 'passbook_policies_password' + verbose_name = 'passbook Policies.Password' diff --git a/passbook/policies/password/forms.py b/passbook/policies/password/forms.py new file mode 100644 index 000000000..ba505f7b3 --- /dev/null +++ b/passbook/policies/password/forms.py @@ -0,0 +1,29 @@ +"""passbook Policy forms""" + +from django import forms +from django.utils.translation import gettext as _ + +from passbook.policies.forms import GENERAL_FIELDS +from passbook.policies.password.models import PasswordPolicy + + +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'), + } diff --git a/passbook/policies/password/migrations/0001_initial.py b/passbook/policies/password/migrations/0001_initial.py new file mode 100644 index 000000000..389ed060e --- /dev/null +++ b/passbook/policies/password/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# 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='PasswordPolicy', + 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='!\\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ')), + ('error_message', models.TextField()), + ], + options={ + 'verbose_name': 'Password Policy', + 'verbose_name_plural': 'Password Policies', + }, + bases=('passbook_core.policy',), + ), + ] diff --git a/passbook/oidc_provider/migrations/__init__.py b/passbook/policies/password/migrations/__init__.py similarity index 100% rename from passbook/oidc_provider/migrations/__init__.py rename to passbook/policies/password/migrations/__init__.py diff --git a/passbook/policies/password/models.py b/passbook/policies/password/models.py new file mode 100644 index 000000000..b7f72be32 --- /dev/null +++ b/passbook/policies/password/models.py @@ -0,0 +1,47 @@ +"""user field matcher models""" +import re + +from django.db import models +from django.utils.translation import gettext as _ +from structlog import get_logger + +from passbook.core.models import Policy +from passbook.policies.struct import PolicyRequest, PolicyResult + +LOGGER = get_logger() + + +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.policies.password.forms.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') diff --git a/passbook/policy/process.py b/passbook/policies/process.py similarity index 75% rename from passbook/policy/process.py rename to passbook/policies/process.py index 9427e93f2..c12375359 100644 --- a/passbook/policy/process.py +++ b/passbook/policies/process.py @@ -2,17 +2,19 @@ from multiprocessing import Process from multiprocessing.connection import Connection +from django.core.cache import cache from structlog import get_logger from passbook.core.models import Policy -from passbook.policy.exceptions import PolicyException -from passbook.policy.struct import PolicyRequest, PolicyResult +from passbook.policies.exceptions import PolicyException +from passbook.policies.struct import PolicyRequest, PolicyResult LOGGER = get_logger() -def _cache_key(policy, user): - return "policy_%s#%s" % (policy.uuid, user.pk) +def cache_key(policy, user): + """Generate Cache key for policy""" + return f"policy_{policy.pk}#{user.pk}" class PolicyProcess(Process): """Evaluate a single policy within a seprate process""" @@ -41,7 +43,7 @@ class PolicyProcess(Process): policy_result = not policy_result LOGGER.debug("Got result", policy=self.policy, result=policy_result, process="PolicyProcess") - # cache_key = _cache_key(self.policy, self.request.user) - # cache.set(cache_key, (self.policy.action, policy_result, message)) - # LOGGER.debug("Cached entry as %s", cache_key) + key = cache_key(self.policy, self.request.user) + cache.set(key, policy_result) + LOGGER.debug("Cached policy evaluation", key=key) self.connection.send(policy_result) diff --git a/passbook/otp/__init__.py b/passbook/policies/reputation/__init__.py similarity index 100% rename from passbook/otp/__init__.py rename to passbook/policies/reputation/__init__.py diff --git a/passbook/policies/reputation/admin.py b/passbook/policies/reputation/admin.py new file mode 100644 index 000000000..354368fc4 --- /dev/null +++ b/passbook/policies/reputation/admin.py @@ -0,0 +1,5 @@ +"""Passbook reputation Admin""" + +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_policies_reputation') diff --git a/passbook/policies/reputation/apps.py b/passbook/policies/reputation/apps.py new file mode 100644 index 000000000..663b8e307 --- /dev/null +++ b/passbook/policies/reputation/apps.py @@ -0,0 +1,15 @@ +"""Passbook reputation_policy app config""" +from importlib import import_module + +from django.apps import AppConfig + + +class PassbookPolicyReputationConfig(AppConfig): + """Passbook reputation app config""" + + name = 'passbook.policies.reputation' + label = 'passbook_policies_reputation' + verbose_name = 'passbook Policies.Reputation' + + def ready(self): + import_module('passbook.policies.reputation.signals') diff --git a/passbook/suspicious_policy/forms.py b/passbook/policies/reputation/forms.py similarity index 53% rename from passbook/suspicious_policy/forms.py rename to passbook/policies/reputation/forms.py index 45758aafc..53d2e53eb 100644 --- a/passbook/suspicious_policy/forms.py +++ b/passbook/policies/reputation/forms.py @@ -1,16 +1,16 @@ -"""passbook suspicious request forms""" +"""passbook reputation request forms""" from django import forms from passbook.core.forms.policies import GENERAL_FIELDS -from passbook.suspicious_policy.models import SuspiciousRequestPolicy +from passbook.policies.reputation.models import ReputationPolicy -class SuspiciousRequestPolicyForm(forms.ModelForm): - """Form to edit SuspiciousRequestPolicy""" +class ReputationPolicyForm(forms.ModelForm): + """Form to edit ReputationPolicy""" class Meta: - model = SuspiciousRequestPolicy + model = ReputationPolicy fields = GENERAL_FIELDS + ['check_ip', 'check_username', 'threshold'] widgets = { 'name': forms.TextInput(), diff --git a/passbook/suspicious_policy/migrations/0001_initial.py b/passbook/policies/reputation/migrations/0001_initial.py similarity index 74% rename from passbook/suspicious_policy/migrations/0001_initial.py rename to passbook/policies/reputation/migrations/0001_initial.py index f48ae499c..ffddca276 100644 --- a/passbook/suspicious_policy/migrations/0001_initial.py +++ b/passbook/policies/reputation/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.7 on 2019-03-03 18:17 +# Generated by Django 2.2.6 on 2019-10-07 14:07 import django.db.models.deletion from django.conf import settings @@ -10,22 +10,22 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('passbook_core', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('passbook_core', '0016_auto_20190227_1355'), ] operations = [ migrations.CreateModel( - name='IPScore', + name='IPReputation', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('ip', models.GenericIPAddressField()), + ('ip', models.GenericIPAddressField(unique=True)), ('score', models.IntegerField(default=0)), ('updated', models.DateTimeField(auto_now=True)), ], ), migrations.CreateModel( - name='SuspiciousRequestPolicy', + name='ReputationPolicy', 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')), ('check_ip', models.BooleanField(default=True)), @@ -33,17 +33,18 @@ class Migration(migrations.Migration): ('threshold', models.IntegerField(default=-5)), ], options={ - 'abstract': False, + 'verbose_name': 'Reputation Policy', + 'verbose_name_plural': 'Reputation Policies', }, bases=('passbook_core.policy',), ), migrations.CreateModel( - name='UserScore', + name='UserReputation', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('score', models.IntegerField(default=0)), ('updated', models.DateTimeField(auto_now=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), ] diff --git a/passbook/otp/migrations/__init__.py b/passbook/policies/reputation/migrations/__init__.py similarity index 100% rename from passbook/otp/migrations/__init__.py rename to passbook/policies/reputation/migrations/__init__.py diff --git a/passbook/suspicious_policy/models.py b/passbook/policies/reputation/models.py similarity index 62% rename from passbook/suspicious_policy/models.py rename to passbook/policies/reputation/models.py index a7a52501c..d41711dc5 100644 --- a/passbook/suspicious_policy/models.py +++ b/passbook/policies/reputation/models.py @@ -1,38 +1,40 @@ -"""passbook suspicious request policy""" +"""passbook reputation request policy""" from django.db import models from django.utils.translation import gettext as _ from ipware import get_client_ip from passbook.core.models import Policy, User -from passbook.policy.struct import PolicyRequest, PolicyResult +from passbook.policies.struct import PolicyRequest, PolicyResult -class SuspiciousRequestPolicy(Policy): +class ReputationPolicy(Policy): """Return true if request IP/target username's score is below a certain threshold""" check_ip = models.BooleanField(default=True) check_username = models.BooleanField(default=True) threshold = models.IntegerField(default=-5) - form = 'passbook.suspicious_policy.forms.SuspiciousRequestPolicyForm' + form = 'passbook.policies.reputation.forms.ReputationPolicyForm' def passes(self, request: PolicyRequest) -> PolicyResult: remote_ip, _ = get_client_ip(request.http_request) passing = True if self.check_ip: - ip_scores = IPScore.objects.filter(ip=remote_ip, score__lte=self.threshold) + ip_scores = IPReputation.objects.filter(ip=remote_ip, score__lte=self.threshold) passing = passing and ip_scores.exists() if self.check_username: - user_scores = UserScore.objects.filter(user=request.user, score__lte=self.threshold) + user_scores = UserReputation.objects.filter(user=request.user, + score__lte=self.threshold) passing = passing and user_scores.exists() return PolicyResult(passing) class Meta: - verbose_name = _('Suspicious Request Policy') - verbose_name_plural = _('Suspicious Request Policies') + verbose_name = _('Reputation Policy') + verbose_name_plural = _('Reputation Policies') -class IPScore(models.Model): + +class IPReputation(models.Model): """Store score coming from the same IP""" ip = models.GenericIPAddressField(unique=True) @@ -40,9 +42,10 @@ class IPScore(models.Model): updated = models.DateTimeField(auto_now=True) def __str__(self): - return "IPScore for %s @ %d" % (self.ip, self.score) + return f"IPReputation for {self.ip} @ {self.score}" -class UserScore(models.Model): + +class UserReputation(models.Model): """Store score attempting to log in as the same username""" user = models.OneToOneField(User, on_delete=models.CASCADE) @@ -50,4 +53,4 @@ class UserScore(models.Model): updated = models.DateTimeField(auto_now=True) def __str__(self): - return "UserScore for %s @ %d" % (self.user, self.score) + return f"UserReputation for {self.user} @ {self.score}" diff --git a/passbook/suspicious_policy/signals.py b/passbook/policies/reputation/signals.py similarity index 84% rename from passbook/suspicious_policy/signals.py rename to passbook/policies/reputation/signals.py index a0ca5058a..dea151573 100644 --- a/passbook/suspicious_policy/signals.py +++ b/passbook/policies/reputation/signals.py @@ -1,11 +1,11 @@ -"""passbook suspicious request signals""" +"""passbook reputation request signals""" from django.contrib.auth.signals import user_logged_in, user_login_failed from django.dispatch import receiver from ipware import get_client_ip from structlog import get_logger from passbook.core.models import User -from passbook.suspicious_policy.models import IPScore, UserScore +from passbook.policies.reputation.models import IPReputation, UserReputation LOGGER = get_logger() @@ -25,14 +25,14 @@ def get_remote_ip(request): def update_score(request, username, amount): """Update score for IP and User""" remote_ip = get_remote_ip(request) - ip_score, _ = IPScore.objects.update_or_create(ip=remote_ip) + ip_score, _ = IPReputation.objects.update_or_create(ip=remote_ip) ip_score.score += amount ip_score.save() LOGGER.debug("Updated score", amount=amount, for_ip=remote_ip) user = User.objects.filter(username=username) if not user.exists(): return - user_score, _ = UserScore.objects.update_or_create(user=user.first()) + user_score, _ = UserReputation.objects.update_or_create(user=user.first()) user_score.score += amount user_score.save() LOGGER.debug("Updated score", amount=amount, for_user=username) diff --git a/passbook/password_expiry_policy/__init__.py b/passbook/policies/sso/__init__.py similarity index 100% rename from passbook/password_expiry_policy/__init__.py rename to passbook/policies/sso/__init__.py diff --git a/passbook/policies/sso/admin.py b/passbook/policies/sso/admin.py new file mode 100644 index 000000000..a5146d89f --- /dev/null +++ b/passbook/policies/sso/admin.py @@ -0,0 +1,4 @@ +"""autodiscover admin""" +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_policies_sso') diff --git a/passbook/policies/sso/apps.py b/passbook/policies/sso/apps.py new file mode 100644 index 000000000..18ae2fc53 --- /dev/null +++ b/passbook/policies/sso/apps.py @@ -0,0 +1,11 @@ +"""passbook sso policy app config""" + +from django.apps import AppConfig + + +class PassbookPoliciesSSOConfig(AppConfig): + """passbook sso policy app config""" + + name = 'passbook.policies.sso' + label = 'passbook_policies_sso' + verbose_name = 'passbook Policies.SSO' diff --git a/passbook/policies/sso/forms.py b/passbook/policies/sso/forms.py new file mode 100644 index 000000000..52fc0e50b --- /dev/null +++ b/passbook/policies/sso/forms.py @@ -0,0 +1,19 @@ +"""passbook Policy forms""" + +from django import forms + +from passbook.policies.forms import GENERAL_FIELDS +from passbook.policies.sso.models import SSOLoginPolicy + + +class SSOLoginPolicyForm(forms.ModelForm): + """Edit SSOLoginPolicy instances""" + + class Meta: + + model = SSOLoginPolicy + fields = GENERAL_FIELDS + widgets = { + 'name': forms.TextInput(), + 'order': forms.NumberInput(), + } diff --git a/passbook/core/migrations/0024_ssologinpolicy.py b/passbook/policies/sso/migrations/0001_initial.py similarity index 85% rename from passbook/core/migrations/0024_ssologinpolicy.py rename to passbook/policies/sso/migrations/0001_initial.py index f783d2841..4d0a3cf7d 100644 --- a/passbook/core/migrations/0024_ssologinpolicy.py +++ b/passbook/policies/sso/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2 on 2019-04-29 21:14 +# Generated by Django 2.2.6 on 2019-10-07 14:07 import django.db.models.deletion from django.db import migrations, models @@ -6,8 +6,10 @@ from django.db import migrations, models class Migration(migrations.Migration): + initial = True + dependencies = [ - ('passbook_core', '0023_remove_user_applications'), + ('passbook_core', '0001_initial'), ] operations = [ diff --git a/passbook/password_expiry_policy/migrations/__init__.py b/passbook/policies/sso/migrations/__init__.py similarity index 100% rename from passbook/password_expiry_policy/migrations/__init__.py rename to passbook/policies/sso/migrations/__init__.py diff --git a/passbook/policies/sso/models.py b/passbook/policies/sso/models.py new file mode 100644 index 000000000..560d2597c --- /dev/null +++ b/passbook/policies/sso/models.py @@ -0,0 +1,22 @@ +"""sso models""" +from django.utils.translation import gettext as _ + +from passbook.core.models import Policy +from passbook.policies.struct import PolicyRequest, PolicyResult + + +class SSOLoginPolicy(Policy): + """Policy that applies to users that have authenticated themselves through SSO""" + + form = 'passbook.policies.sso.forms.SSOLoginPolicyForm' + + def passes(self, request: PolicyRequest) -> PolicyResult: + """Check if user instance passes this policy""" + from passbook.factors.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') diff --git a/passbook/policy/struct.py b/passbook/policies/struct.py similarity index 88% rename from passbook/policy/struct.py rename to passbook/policies/struct.py index 0d13f00d3..83add6e3d 100644 --- a/passbook/policy/struct.py +++ b/passbook/policies/struct.py @@ -1,4 +1,5 @@ """policy structs""" +from __future__ import annotations from typing import TYPE_CHECKING, List from django.http import HttpRequest @@ -9,10 +10,10 @@ if TYPE_CHECKING: class PolicyRequest: """Data-class to hold policy request data""" - user: 'User' + user: User http_request: HttpRequest - def __init__(self, user: 'User'): + def __init__(self, user: User): self.user = user def __str__(self): diff --git a/passbook/policy/__init__.py b/passbook/policies/webhook/__init__.py similarity index 100% rename from passbook/policy/__init__.py rename to passbook/policies/webhook/__init__.py diff --git a/passbook/policies/webhook/admin.py b/passbook/policies/webhook/admin.py new file mode 100644 index 000000000..cf14a6054 --- /dev/null +++ b/passbook/policies/webhook/admin.py @@ -0,0 +1,4 @@ +"""autodiscover admin""" +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_policies_webhook') diff --git a/passbook/policies/webhook/apps.py b/passbook/policies/webhook/apps.py new file mode 100644 index 000000000..38738930d --- /dev/null +++ b/passbook/policies/webhook/apps.py @@ -0,0 +1,11 @@ +"""passbook Webhook policy app config""" + +from django.apps import AppConfig + + +class PassbookPoliciesWebhookConfig(AppConfig): + """passbook Webhook policy app config""" + + name = 'passbook.policies.webhook' + label = 'passbook_policies_webhook' + verbose_name = 'passbook Policies.Webhook' diff --git a/passbook/policies/webhook/forms.py b/passbook/policies/webhook/forms.py new file mode 100644 index 000000000..bbf7f1f5a --- /dev/null +++ b/passbook/policies/webhook/forms.py @@ -0,0 +1,23 @@ +"""passbook Policy forms""" + +from django import forms + +from passbook.policies.forms import GENERAL_FIELDS +from passbook.policies.webhook.models import WebhookPolicy + + +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(), + } diff --git a/passbook/policies/webhook/migrations/0001_initial.py b/passbook/policies/webhook/migrations/0001_initial.py new file mode 100644 index 000000000..871215c8f --- /dev/null +++ b/passbook/policies/webhook/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# 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='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 Policies', + }, + bases=('passbook_core.policy',), + ), + ] diff --git a/passbook/pretend/__init__.py b/passbook/policies/webhook/migrations/__init__.py similarity index 100% rename from passbook/pretend/__init__.py rename to passbook/policies/webhook/migrations/__init__.py diff --git a/passbook/policies/webhook/models.py b/passbook/policies/webhook/models.py new file mode 100644 index 000000000..91cf59608 --- /dev/null +++ b/passbook/policies/webhook/models.py @@ -0,0 +1,42 @@ +"""webhook models""" +from django.db import models +from django.utils.translation import gettext as _ + +from passbook.core.models import Policy +from passbook.policies.struct import PolicyRequest, PolicyResult + + +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.policies.webhook.forms.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') diff --git a/passbook/pretend/apps.py b/passbook/pretend/apps.py deleted file mode 100644 index 3a823b13f..000000000 --- a/passbook/pretend/apps.py +++ /dev/null @@ -1,11 +0,0 @@ -"""passbook pretend config""" -from django.apps import AppConfig - - -class PassbookPretendConfig(AppConfig): - """passbook pretend config""" - - name = 'passbook.pretend' - label = 'passbook_pretend' - verbose_name = 'passbook Pretender' - mountpoint = '' diff --git a/passbook/pretend/urls.py b/passbook/pretend/urls.py deleted file mode 100644 index e140c9f9a..000000000 --- a/passbook/pretend/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -"""passbook pretend urls""" -from django.urls import include, path -from oauth2_provider.views import TokenView - -from passbook.oauth_provider.views.oauth2 import PassbookAuthorizationView -from passbook.pretend.views.github import GitHubUserView - -github_urlpatterns = [ - path('login/oauth/authorize', PassbookAuthorizationView.as_view(), name='github-authorize'), - path('login/oauth/access_token', TokenView.as_view(), name='github-access-token'), - path('user', GitHubUserView.as_view(), name='github-user'), -] - -urlpatterns = [ - path('', include(github_urlpatterns)) -] diff --git a/passbook/pretend/views/__init__.py b/passbook/providers/__init__.py similarity index 100% rename from passbook/pretend/views/__init__.py rename to passbook/providers/__init__.py diff --git a/passbook/saml_idp/__init__.py b/passbook/providers/app_gw/__init__.py similarity index 100% rename from passbook/saml_idp/__init__.py rename to passbook/providers/app_gw/__init__.py diff --git a/passbook/app_gw/admin.py b/passbook/providers/app_gw/admin.py similarity index 69% rename from passbook/app_gw/admin.py rename to passbook/providers/app_gw/admin.py index 9391932a6..80e51f8b4 100644 --- a/passbook/app_gw/admin.py +++ b/passbook/providers/app_gw/admin.py @@ -2,4 +2,4 @@ from passbook.lib.admin import admin_autoregister -admin_autoregister('passbook_app_gw') +admin_autoregister('passbook_providers_app_gw') diff --git a/passbook/providers/app_gw/apps.py b/passbook/providers/app_gw/apps.py new file mode 100644 index 000000000..0e5c803e9 --- /dev/null +++ b/passbook/providers/app_gw/apps.py @@ -0,0 +1,11 @@ +"""passbook Application Security Gateway app""" +from django.apps import AppConfig + + +class PassbookApplicationApplicationGatewayConfig(AppConfig): + """passbook app_gw app""" + + name = 'passbook.providers.app_gw' + label = 'passbook_providers_app_gw' + verbose_name = 'passbook Providers.Application Security Gateway' + mountpoint = 'application/gateway/' diff --git a/passbook/app_gw/forms.py b/passbook/providers/app_gw/forms.py similarity index 94% rename from passbook/app_gw/forms.py rename to passbook/providers/app_gw/forms.py index fb3df1507..23eadd2d6 100644 --- a/passbook/app_gw/forms.py +++ b/passbook/providers/app_gw/forms.py @@ -6,8 +6,9 @@ from django.contrib.admin.widgets import FilteredSelectMultiple from django.forms import ValidationError from django.utils.translation import gettext as _ -from passbook.app_gw.models import ApplicationGatewayProvider, RewriteRule from passbook.lib.fields import DynamicArrayField +from passbook.providers.app_gw.models import (ApplicationGatewayProvider, + RewriteRule) class ApplicationGatewayProviderForm(forms.ModelForm): diff --git a/passbook/providers/app_gw/middleware.py b/passbook/providers/app_gw/middleware.py new file mode 100644 index 000000000..9aa94c506 --- /dev/null +++ b/passbook/providers/app_gw/middleware.py @@ -0,0 +1,55 @@ +import time + +from django.conf import settings +from django.contrib.sessions.middleware import SessionMiddleware +from django.utils.cache import patch_vary_headers +from django.utils.http import cookie_date +from structlog import get_logger + +from passbook.factors.view import AuthenticationView + +LOGGER = get_logger() + +class SessionHostDomainMiddleware(SessionMiddleware): + + def process_request(self, request): + session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) + request.session = self.SessionStore(session_key) + + def process_response(self, request, response): + """ + If request.session was modified, or if the configuration is to save the + session every time, save the changes and set a session cookie. + """ + try: + accessed = request.session.accessed + modified = request.session.modified + except AttributeError: + pass + else: + if accessed: + patch_vary_headers(response, ('Cookie',)) + if modified or settings.SESSION_SAVE_EVERY_REQUEST: + if request.session.get_expire_at_browser_close(): + max_age = None + expires = None + else: + max_age = request.session.get_expiry_age() + expires_time = time.time() + max_age + expires = cookie_date(expires_time) + # Save the session data and refresh the client cookie. + # Skip session save for 500 responses, refs #3881. + if response.status_code != 500: + request.session.save() + hosts = [request.get_host().split(':')[0]] + if AuthenticationView.SESSION_FORCE_COOKIE_HOSTNAME in request.session: + hosts.append(request.session[AuthenticationView.SESSION_FORCE_COOKIE_HOSTNAME]) + LOGGER.debug("Setting hosts for session", hosts=hosts) + for host in hosts: + response.set_cookie(settings.SESSION_COOKIE_NAME, + request.session.session_key, max_age=max_age, + expires=expires, domain=host, + path=settings.SESSION_COOKIE_PATH, + secure=settings.SESSION_COOKIE_SECURE or None, + httponly=settings.SESSION_COOKIE_HTTPONLY or None) + return response diff --git a/passbook/app_gw/migrations/0001_initial.py b/passbook/providers/app_gw/migrations/0001_initial.py similarity index 87% rename from passbook/app_gw/migrations/0001_initial.py rename to passbook/providers/app_gw/migrations/0001_initial.py index bdbf88084..3bcdb5907 100644 --- a/passbook/app_gw/migrations/0001_initial.py +++ b/passbook/providers/app_gw/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.7 on 2019-03-20 21:38 +# Generated by Django 2.2.6 on 2019-10-07 14:07 import django.contrib.postgres.fields import django.db.models.deletion @@ -10,7 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('passbook_core', '0020_groupmembershippolicy'), + ('passbook_core', '0001_initial'), ] operations = [ @@ -21,7 +21,7 @@ class Migration(migrations.Migration): ('server_name', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)), ('upstream', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)), ('enabled', models.BooleanField(default=True)), - ('authentication_header', models.TextField(default='X-Remote-User')), + ('authentication_header', models.TextField(blank=True, default='X-Remote-User')), ('default_content_type', models.TextField(default='application/octet-stream')), ('upstream_ssl_verification', models.BooleanField(default=True)), ], @@ -39,7 +39,7 @@ class Migration(migrations.Migration): ('halt', models.BooleanField(default=False)), ('replacement', models.TextField()), ('redirect', models.CharField(choices=[('internal', 'Internal'), (301, 'Moved Permanently'), (302, 'Found')], max_length=50)), - ('conditions', models.ManyToManyField(to='passbook_core.Policy')), + ('conditions', models.ManyToManyField(blank=True, to='passbook_core.Policy')), ], options={ 'verbose_name': 'Rewrite Rule', diff --git a/passbook/saml_idp/migrations/__init__.py b/passbook/providers/app_gw/migrations/__init__.py similarity index 100% rename from passbook/saml_idp/migrations/__init__.py rename to passbook/providers/app_gw/migrations/__init__.py diff --git a/passbook/app_gw/models.py b/passbook/providers/app_gw/models.py similarity index 94% rename from passbook/app_gw/models.py rename to passbook/providers/app_gw/models.py index dec46bdc0..febb7c9e6 100644 --- a/passbook/app_gw/models.py +++ b/passbook/providers/app_gw/models.py @@ -19,7 +19,7 @@ class ApplicationGatewayProvider(Provider): default_content_type = models.TextField(default='application/octet-stream') upstream_ssl_verification = models.BooleanField(default=True) - form = 'passbook.app_gw.forms.ApplicationGatewayProviderForm' + form = 'passbook.providers.app_gw.forms.ApplicationGatewayProviderForm' @property def name(self): @@ -54,7 +54,7 @@ class RewriteRule(PropertyMapping): replacement = models.TextField() # python formatted strings, use {match.1} redirect = models.CharField(max_length=50, choices=REDIRECTS) - form = 'passbook.app_gw.forms.RewriteRuleForm' + form = 'passbook.providers.app_gw.forms.RewriteRuleForm' _matcher = None diff --git a/passbook/saml_idp/processors/__init__.py b/passbook/providers/app_gw/provider/__init__.py similarity index 100% rename from passbook/saml_idp/processors/__init__.py rename to passbook/providers/app_gw/provider/__init__.py diff --git a/passbook/suspicious_policy/__init__.py b/passbook/providers/app_gw/provider/kubernetes/__init__.py similarity index 100% rename from passbook/suspicious_policy/__init__.py rename to passbook/providers/app_gw/provider/kubernetes/__init__.py diff --git a/passbook/providers/app_gw/urls.py b/passbook/providers/app_gw/urls.py new file mode 100644 index 000000000..62cbc1ed9 --- /dev/null +++ b/passbook/providers/app_gw/urls.py @@ -0,0 +1,8 @@ +"""passbook app_gw urls""" +from django.urls import path + +from passbook.providers.app_gw.views import NginxCheckView + +urlpatterns = [ + path('nginx/', NginxCheckView.as_view()) +] diff --git a/passbook/providers/app_gw/views.py b/passbook/providers/app_gw/views.py new file mode 100644 index 000000000..5090bf61f --- /dev/null +++ b/passbook/providers/app_gw/views.py @@ -0,0 +1,36 @@ +"""passbook app_gw views""" +from pprint import pprint +from urllib.parse import urlparse + +from django.http import HttpRequest, HttpResponse +from django.views import View +from structlog import get_logger + +from passbook.core.views.access import AccessMixin +from passbook.providers.app_gw.models import ApplicationGatewayProvider + +ORIGINAL_URL = 'HTTP_X_ORIGINAL_URL' +LOGGER = get_logger() + + +class NginxCheckView(AccessMixin, View): + + def dispatch(self, request: HttpRequest) -> HttpResponse: + pprint(request.META) + parsed_url = urlparse(request.META.get(ORIGINAL_URL)) + # request.session[AuthenticationView.SESSION_ALLOW_ABSOLUTE_NEXT] = True + # request.session[AuthenticationView.SESSION_FORCE_COOKIE_HOSTNAME] = parsed_url.hostname + print(request.user) + if not request.user.is_authenticated: + return HttpResponse(status=401) + matching = ApplicationGatewayProvider.objects.filter( + server_name__contains=[parsed_url.hostname]) + if not matching.exists(): + LOGGER.debug("Couldn't find matching application", host=parsed_url.hostname) + return HttpResponse(status=403) + application = self.provider_to_application(matching.first()) + has_access, _ = self.user_has_access(application, request.user) + if has_access: + return HttpResponse(status=202) + LOGGER.debug("User not passing", user=request.user) + return HttpResponse(status=401) diff --git a/passbook/suspicious_policy/migrations/__init__.py b/passbook/providers/oauth/__init__.py similarity index 100% rename from passbook/suspicious_policy/migrations/__init__.py rename to passbook/providers/oauth/__init__.py diff --git a/passbook/providers/oauth/admin.py b/passbook/providers/oauth/admin.py new file mode 100644 index 000000000..6f0f106db --- /dev/null +++ b/passbook/providers/oauth/admin.py @@ -0,0 +1,4 @@ +"""oauth provider admin""" +# from passbook.lib.admin import admin_autoregister + +# admin_autoregister('passbook_providers_oauth') diff --git a/passbook/oauth_provider/apps.py b/passbook/providers/oauth/apps.py similarity index 50% rename from passbook/oauth_provider/apps.py rename to passbook/providers/oauth/apps.py index d29c696fc..2974f1223 100644 --- a/passbook/oauth_provider/apps.py +++ b/passbook/providers/oauth/apps.py @@ -3,10 +3,10 @@ from django.apps import AppConfig -class PassbookOAuthProviderConfig(AppConfig): +class PassbookProviderOAuthConfig(AppConfig): """passbook auth oauth provider app config""" - name = 'passbook.oauth_provider' - label = 'passbook_oauth_provider' - verbose_name = 'passbook OAuth Provider' + name = 'passbook.providers.oauth' + label = 'passbook_providers_oauth' + verbose_name = 'passbook Providers.OAuth' mountpoint = 'application/oauth/' diff --git a/passbook/oauth_provider/forms.py b/passbook/providers/oauth/forms.py similarity index 84% rename from passbook/oauth_provider/forms.py rename to passbook/providers/oauth/forms.py index 895291ff6..dffca09d5 100644 --- a/passbook/oauth_provider/forms.py +++ b/passbook/providers/oauth/forms.py @@ -2,7 +2,7 @@ from django import forms -from passbook.oauth_provider.models import OAuth2Provider +from passbook.providers.oauth.models import OAuth2Provider class OAuth2ProviderForm(forms.ModelForm): diff --git a/passbook/oauth_provider/locale/de/LC_MESSAGES/django.po b/passbook/providers/oauth/locale/de/LC_MESSAGES/django.po similarity index 100% rename from passbook/oauth_provider/locale/de/LC_MESSAGES/django.po rename to passbook/providers/oauth/locale/de/LC_MESSAGES/django.po diff --git a/passbook/oauth_provider/locale/en/LC_MESSAGES/django.po b/passbook/providers/oauth/locale/en/LC_MESSAGES/django.po similarity index 100% rename from passbook/oauth_provider/locale/en/LC_MESSAGES/django.po rename to passbook/providers/oauth/locale/en/LC_MESSAGES/django.po diff --git a/passbook/oauth_provider/locale/es/LC_MESSAGES/django.po b/passbook/providers/oauth/locale/es/LC_MESSAGES/django.po similarity index 100% rename from passbook/oauth_provider/locale/es/LC_MESSAGES/django.po rename to passbook/providers/oauth/locale/es/LC_MESSAGES/django.po diff --git a/passbook/oauth_provider/locale/fr/LC_MESSAGES/django.po b/passbook/providers/oauth/locale/fr/LC_MESSAGES/django.po similarity index 100% rename from passbook/oauth_provider/locale/fr/LC_MESSAGES/django.po rename to passbook/providers/oauth/locale/fr/LC_MESSAGES/django.po diff --git a/passbook/oauth_provider/migrations/0001_initial.py b/passbook/providers/oauth/migrations/0001_initial.py similarity index 94% rename from passbook/oauth_provider/migrations/0001_initial.py rename to passbook/providers/oauth/migrations/0001_initial.py index b25fedbca..98174e732 100644 --- a/passbook/oauth_provider/migrations/0001_initial.py +++ b/passbook/providers/oauth/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# 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 django.db.models.deletion import oauth2_provider.generators @@ -33,7 +33,7 @@ class Migration(migrations.Migration): ('skip_authorization', models.BooleanField(default=False)), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), - ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='passbook_oauth_provider_oauth2provider', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='passbook_providers_oauth_oauth2provider', to=settings.AUTH_USER_MODEL)), ], options={ 'verbose_name': 'OAuth2 Provider', diff --git a/passbook/providers/oauth/migrations/__init__.py b/passbook/providers/oauth/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/oauth_provider/models.py b/passbook/providers/oauth/models.py similarity index 79% rename from passbook/oauth_provider/models.py rename to passbook/providers/oauth/models.py index 9dff16202..9f148e57f 100644 --- a/passbook/oauth_provider/models.py +++ b/passbook/providers/oauth/models.py @@ -10,19 +10,19 @@ from passbook.core.models import Provider class OAuth2Provider(Provider, AbstractApplication): """Associate an OAuth2 Application with a Product""" - form = 'passbook.oauth_provider.forms.OAuth2ProviderForm' + form = 'passbook.providers.oauth.forms.OAuth2ProviderForm' def __str__(self): - return "OAuth2 Provider %s" % self.name + return f"OAuth2 Provider {self.name}" def html_setup_urls(self, request): """return template and context modal with URLs for authorize, token, openid-config, etc""" return "oauth2_provider/setup_url_modal.html", { 'provider': self, 'authorize_url': request.build_absolute_uri( - reverse('passbook_oauth_provider:oauth2-authorize')), + reverse('passbook_providers_oauth:oauth2-authorize')), 'token_url': request.build_absolute_uri( - reverse('passbook_oauth_provider:token')), + reverse('passbook_providers_oauth:token')), 'userinfo_url': request.build_absolute_uri( reverse('passbook_api:openid')), } diff --git a/passbook/oauth_provider/settings.py b/passbook/providers/oauth/settings.py similarity index 90% rename from passbook/oauth_provider/settings.py rename to passbook/providers/oauth/settings.py index 7d7178119..f8b077bc1 100644 --- a/passbook/oauth_provider/settings.py +++ b/passbook/providers/oauth/settings.py @@ -15,7 +15,7 @@ AUTHENTICATION_BACKENDS = [ 'oauth2_provider.backends.OAuth2Backend', ] -OAUTH2_PROVIDER_APPLICATION_MODEL = 'passbook_oauth_provider.OAuth2Provider' +OAUTH2_PROVIDER_APPLICATION_MODEL = 'passbook_providers_oauth.OAuth2Provider' OAUTH2_PROVIDER = { # this is the list of available scopes diff --git a/passbook/oauth_provider/templates/oauth2_provider/authorize.html b/passbook/providers/oauth/templates/oauth2_provider/authorize.html similarity index 100% rename from passbook/oauth_provider/templates/oauth2_provider/authorize.html rename to passbook/providers/oauth/templates/oauth2_provider/authorize.html diff --git a/passbook/oauth_provider/templates/oauth2_provider/base.html b/passbook/providers/oauth/templates/oauth2_provider/base.html similarity index 100% rename from passbook/oauth_provider/templates/oauth2_provider/base.html rename to passbook/providers/oauth/templates/oauth2_provider/base.html diff --git a/passbook/oauth_provider/templates/oauth2_provider/setup_url_modal.html b/passbook/providers/oauth/templates/oauth2_provider/setup_url_modal.html similarity index 100% rename from passbook/oauth_provider/templates/oauth2_provider/setup_url_modal.html rename to passbook/providers/oauth/templates/oauth2_provider/setup_url_modal.html diff --git a/passbook/oauth_provider/urls.py b/passbook/providers/oauth/urls.py similarity index 63% rename from passbook/oauth_provider/urls.py rename to passbook/providers/oauth/urls.py index 1fd14cc2d..854569116 100644 --- a/passbook/oauth_provider/urls.py +++ b/passbook/providers/oauth/urls.py @@ -3,7 +3,7 @@ from django.urls import path from oauth2_provider import views -from passbook.oauth_provider.views import oauth2 +from passbook.providers.oauth.views import github, oauth2 urlpatterns = [ # Custom OAuth 2 Authorize View @@ -18,3 +18,14 @@ urlpatterns = [ path("revoke_token/", views.RevokeTokenView.as_view(), name="revoke-token"), path("introspect/", views.IntrospectTokenView.as_view(), name="introspect"), ] + +github_urlpatterns = [ + path('login/oauth/authorize', + oauth2.PassbookAuthorizationView.as_view(), name='github-authorize'), + path('login/oauth/access_token', views.TokenView.as_view(), name='github-access-token'), + path('user', github.GitHubUserView.as_view(), name='github-user'), +] + +# urlpatterns = [ +# path('', include(github_urlpatterns)) +# ] diff --git a/passbook/providers/oauth/views/__init__.py b/passbook/providers/oauth/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/pretend/views/github.py b/passbook/providers/oauth/views/github.py similarity index 100% rename from passbook/pretend/views/github.py rename to passbook/providers/oauth/views/github.py diff --git a/passbook/oauth_provider/views/oauth2.py b/passbook/providers/oauth/views/oauth2.py similarity index 88% rename from passbook/oauth_provider/views/oauth2.py rename to passbook/providers/oauth/views/oauth2.py index e42886a0f..0ec663220 100644 --- a/passbook/oauth_provider/views/oauth2.py +++ b/passbook/providers/oauth/views/oauth2.py @@ -12,7 +12,7 @@ from passbook.audit.models import AuditEntry from passbook.core.models import Application from passbook.core.views.access import AccessMixin from passbook.core.views.utils import LoadingView, PermissionDeniedView -from passbook.oauth_provider.models import OAuth2Provider +from passbook.providers.oauth.models import OAuth2Provider LOGGER = get_logger() @@ -24,7 +24,7 @@ class PassbookAuthorizationLoadingView(LoginRequiredMixin, LoadingView): def get_url(self): querystring = urlencode(self.request.GET) - return reverse('passbook_oauth_provider:oauth2-ok-authorize')+'?'+querystring + return reverse('passbook_providers_oauth:oauth2-ok-authorize')+'?'+querystring class OAuthPermissionDenied(PermissionDeniedView): @@ -41,7 +41,7 @@ class PassbookAuthorizationView(AccessMixin, AuthorizationView): LOGGER.debug("response_type not set, defaulting to 'code'") querystring = urlencode(self.request.GET) querystring += '&response_type=code' - return redirect(reverse('passbook_oauth_provider:oauth2-ok-authorize') + '?' + querystring) + return redirect(reverse('passbook_providers_oauth:oauth2-ok-authorize') + '?' + querystring) def dispatch(self, request, *args, **kwargs): """Update OAuth2Provider's skip_authorization state""" @@ -51,7 +51,7 @@ class PassbookAuthorizationView(AccessMixin, AuthorizationView): try: application = self.provider_to_application(provider) except Application.DoesNotExist: - return redirect('passbook_oauth_provider:oauth2-permission-denied') + return redirect('passbook_providers_oauth:oauth2-permission-denied') # Update field here so oauth-toolkit does work for us provider.skip_authorization = application.skip_authorization provider.save() @@ -61,7 +61,7 @@ class PassbookAuthorizationView(AccessMixin, AuthorizationView): if not passing: for policy_message in policy_messages: messages.error(request, policy_message) - return redirect('passbook_oauth_provider:oauth2-permission-denied') + return redirect('passbook_providers_oauth:oauth2-permission-denied') # Some clients don't pass response_type, so we default to code if 'response_type' not in request.GET: return self._inject_response_type() diff --git a/passbook/providers/oidc/__init__.py b/passbook/providers/oidc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/providers/oidc/admin.py b/passbook/providers/oidc/admin.py new file mode 100644 index 000000000..0da5dad89 --- /dev/null +++ b/passbook/providers/oidc/admin.py @@ -0,0 +1,5 @@ +"""oidc provider admin""" + +from passbook.lib.admin import admin_autoregister + +admin_autoregister('passbook_providers_oidc') diff --git a/passbook/oidc_provider/apps.py b/passbook/providers/oidc/apps.py similarity index 84% rename from passbook/oidc_provider/apps.py rename to passbook/providers/oidc/apps.py index 34130de33..0a8466b36 100644 --- a/passbook/oidc_provider/apps.py +++ b/passbook/providers/oidc/apps.py @@ -6,12 +6,12 @@ from structlog import get_logger LOGGER = get_logger() -class PassbookOIDCProviderConfig(AppConfig): +class PassbookProviderOIDCConfig(AppConfig): """passbook auth oidc provider app config""" - name = 'passbook.oidc_provider' - label = 'passbook_oidc_provider' - verbose_name = 'passbook OIDC Provider' + name = 'passbook.providers.oidc' + label = 'passbook_providers_oidc' + verbose_name = 'passbook Providers.OIDC' def ready(self): try: diff --git a/passbook/oidc_provider/forms.py b/passbook/providers/oidc/forms.py similarity index 95% rename from passbook/oidc_provider/forms.py rename to passbook/providers/oidc/forms.py index 23651bc3a..cff2c21e0 100644 --- a/passbook/oidc_provider/forms.py +++ b/passbook/providers/oidc/forms.py @@ -5,7 +5,7 @@ from oauth2_provider.generators import (generate_client_id, generate_client_secret) from oidc_provider.models import Client -from passbook.oidc_provider.models import OpenIDProvider +from passbook.providers.oidc.models import OpenIDProvider class OIDCProviderForm(forms.ModelForm): diff --git a/passbook/oidc_provider/lib.py b/passbook/providers/oidc/lib.py similarity index 73% rename from passbook/oidc_provider/lib.py rename to passbook/providers/oidc/lib.py index 19b33eef5..ce82d100f 100644 --- a/passbook/oidc_provider/lib.py +++ b/passbook/providers/oidc/lib.py @@ -4,7 +4,7 @@ from django.shortcuts import redirect from structlog import get_logger from passbook.core.models import Application -from passbook.policy.engine import PolicyEngine +from passbook.policies.engine import PolicyEngine LOGGER = get_logger() @@ -15,8 +15,8 @@ def check_permissions(request, user, client): try: application = client.openidprovider.application except Application.DoesNotExist: - return redirect('passbook_oauth_provider:oauth2-permission-denied') - LOGGER.debug("Checking permissions of %s on application %s...", user, application) + return redirect('passbook_providers_oauth:oauth2-permission-denied') + LOGGER.debug("Checking permissions for application", user=user, application=application) policy_engine = PolicyEngine(application.policies.all()) policy_engine.for_user(user).with_request(request).build() @@ -25,5 +25,5 @@ def check_permissions(request, user, client): if not passing: for policy_message in policy_messages: messages.error(request, policy_message) - return redirect('passbook_oauth_provider:oauth2-permission-denied') + return redirect('passbook_providers_oauth:oauth2-permission-denied') return None diff --git a/passbook/oidc_provider/migrations/0001_initial.py b/passbook/providers/oidc/migrations/0001_initial.py similarity index 75% rename from passbook/oidc_provider/migrations/0001_initial.py rename to passbook/providers/oidc/migrations/0001_initial.py index 0e810eed7..c9924dce3 100644 --- a/passbook/oidc_provider/migrations/0001_initial.py +++ b/passbook/providers/oidc/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.3 on 2019-07-05 12:16 +# Generated by Django 2.2.6 on 2019-10-07 14:07 import django.db.models.deletion from django.db import migrations, models @@ -9,8 +9,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('passbook_core', '0001_initial'), ('oidc_provider', '0026_client_multiple_response_types'), - ('passbook_core', '0024_ssologinpolicy'), ] operations = [ @@ -20,6 +20,10 @@ class Migration(migrations.Migration): ('provider_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Provider')), ('oidc_client', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.Client')), ], + options={ + 'verbose_name': 'OpenID Provider', + 'verbose_name_plural': 'OpenID Providers', + }, bases=('passbook_core.provider',), ), ] diff --git a/passbook/providers/oidc/migrations/__init__.py b/passbook/providers/oidc/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/oidc_provider/models.py b/passbook/providers/oidc/models.py similarity index 96% rename from passbook/oidc_provider/models.py rename to passbook/providers/oidc/models.py index 091b1ef8b..9dea3dc4a 100644 --- a/passbook/oidc_provider/models.py +++ b/passbook/providers/oidc/models.py @@ -15,7 +15,7 @@ class OpenIDProvider(Provider): oidc_client = models.OneToOneField(Client, on_delete=models.CASCADE) - form = 'passbook.oidc_provider.forms.OIDCProviderForm' + form = 'passbook.providers.oidc.forms.OIDCProviderForm' @property def name(self): diff --git a/passbook/providers/oidc/settings.py b/passbook/providers/oidc/settings.py new file mode 100644 index 000000000..6e27f4457 --- /dev/null +++ b/passbook/providers/oidc/settings.py @@ -0,0 +1,7 @@ +"""passbook OIDC Provider""" + +INSTALLED_APPS = [ + 'oidc_provider', +] + +OIDC_AFTER_USERLOGIN_HOOK = "passbook.providers.oidc.lib.check_permissions" diff --git a/passbook/oidc_provider/templates/oidc_provider/authorize.html b/passbook/providers/oidc/templates/oidc_provider/authorize.html similarity index 100% rename from passbook/oidc_provider/templates/oidc_provider/authorize.html rename to passbook/providers/oidc/templates/oidc_provider/authorize.html diff --git a/passbook/oidc_provider/templates/oidc_provider/setup_url_modal.html b/passbook/providers/oidc/templates/oidc_provider/setup_url_modal.html similarity index 100% rename from passbook/oidc_provider/templates/oidc_provider/setup_url_modal.html rename to passbook/providers/oidc/templates/oidc_provider/setup_url_modal.html diff --git a/passbook/providers/saml/__init__.py b/passbook/providers/saml/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/saml_idp/admin.py b/passbook/providers/saml/admin.py similarity index 61% rename from passbook/saml_idp/admin.py rename to passbook/providers/saml/admin.py index b222d2128..8813bd64b 100644 --- a/passbook/saml_idp/admin.py +++ b/passbook/providers/saml/admin.py @@ -2,4 +2,4 @@ from passbook.lib.admin import admin_autoregister -admin_autoregister('passbook_saml_idp') +admin_autoregister('passbook_providers_saml') diff --git a/passbook/saml_idp/apps.py b/passbook/providers/saml/apps.py similarity index 64% rename from passbook/saml_idp/apps.py rename to passbook/providers/saml/apps.py index 12b146238..8aeb55316 100644 --- a/passbook/saml_idp/apps.py +++ b/passbook/providers/saml/apps.py @@ -2,24 +2,22 @@ from importlib import import_module from django.apps import AppConfig +from django.conf import settings from structlog import get_logger -from passbook.lib.config import CONFIG - LOGGER = get_logger() -class PassbookSAMLIDPConfig(AppConfig): +class PassbookProviderSAMLConfig(AppConfig): """passbook saml_idp app config""" - name = 'passbook.saml_idp' - label = 'passbook_saml_idp' - verbose_name = 'passbook SAML IDP' + name = 'passbook.providers.saml' + label = 'passbook_providers_saml' + verbose_name = 'passbook Providers.SAML' mountpoint = 'application/saml/' def ready(self): """Load source_types from config file""" - source_types_to_load = CONFIG.y('saml_idp.types', []) - for source_type in source_types_to_load: + for source_type in settings.PASSBOOK_PROVIDERS_SAML_PROCESSORS: try: import_module(source_type) LOGGER.info("Loaded SAML Processor", processor_class=source_type) diff --git a/passbook/saml_idp/base.py b/passbook/providers/saml/base.py similarity index 98% rename from passbook/saml_idp/base.py rename to passbook/providers/saml/base.py index 1fd182928..e70329c4f 100644 --- a/passbook/saml_idp/base.py +++ b/passbook/providers/saml/base.py @@ -6,7 +6,7 @@ import uuid from defusedxml import ElementTree from structlog import get_logger -from passbook.saml_idp import exceptions, utils, xml_render +from passbook.providers.saml import exceptions, utils, xml_render MINUTES = 60 HOURS = 60 * MINUTES @@ -165,7 +165,7 @@ class Processor: 'Value': self._django_request.user.username, }, ] - from passbook.saml_idp.models import SAMLPropertyMapping + from passbook.providers.saml.models import SAMLPropertyMapping for mapping in self._remote.property_mappings.all().select_subclasses(): if isinstance(mapping, SAMLPropertyMapping): mapping_payload = { diff --git a/passbook/saml_idp/exceptions.py b/passbook/providers/saml/exceptions.py similarity index 100% rename from passbook/saml_idp/exceptions.py rename to passbook/providers/saml/exceptions.py diff --git a/passbook/saml_idp/forms.py b/passbook/providers/saml/forms.py similarity index 89% rename from passbook/saml_idp/forms.py rename to passbook/providers/saml/forms.py index 6f04d5344..dc2ea0f72 100644 --- a/passbook/saml_idp/forms.py +++ b/passbook/providers/saml/forms.py @@ -5,9 +5,9 @@ from django.contrib.admin.widgets import FilteredSelectMultiple from django.utils.translation import gettext as _ from passbook.lib.fields import DynamicArrayField -from passbook.saml_idp.models import (SAMLPropertyMapping, SAMLProvider, - get_provider_choices) -from passbook.saml_idp.utils import CertificateBuilder +from passbook.providers.saml.models import (SAMLPropertyMapping, SAMLProvider, + get_provider_choices) +from passbook.providers.saml.utils import CertificateBuilder class SAMLProviderForm(forms.ModelForm): diff --git a/passbook/saml_idp/migrations/0001_initial.py b/passbook/providers/saml/migrations/0001_initial.py similarity index 55% rename from passbook/saml_idp/migrations/0001_initial.py rename to passbook/providers/saml/migrations/0001_initial.py index 4a210df76..8839bb5a9 100644 --- a/passbook/saml_idp/migrations/0001_initial.py +++ b/passbook/providers/saml/migrations/0001_initial.py @@ -1,5 +1,6 @@ -# 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 django.contrib.postgres.fields import django.db.models.deletion from django.db import migrations, models @@ -13,12 +14,27 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='SAMLPropertyMapping', + fields=[ + ('propertymapping_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PropertyMapping')), + ('saml_name', models.TextField()), + ('friendly_name', models.TextField(blank=True, default=None, null=True)), + ('values', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)), + ], + options={ + 'verbose_name': 'SAML Property Mapping', + 'verbose_name_plural': 'SAML Property Mappings', + }, + bases=('passbook_core.propertymapping',), + ), migrations.CreateModel( name='SAMLProvider', fields=[ ('provider_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Provider')), ('name', models.TextField()), ('acs_url', models.URLField()), + ('audience', models.TextField(default='')), ('processor_path', models.CharField(max_length=255)), ('issuer', models.TextField()), ('assertion_valid_for', models.IntegerField(default=86400)), diff --git a/passbook/providers/saml/migrations/__init__.py b/passbook/providers/saml/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/saml_idp/models.py b/passbook/providers/saml/models.py similarity index 93% rename from passbook/saml_idp/models.py rename to passbook/providers/saml/models.py index 51c730c50..609b5fe6d 100644 --- a/passbook/saml_idp/models.py +++ b/passbook/providers/saml/models.py @@ -7,7 +7,7 @@ from structlog import get_logger from passbook.core.models import PropertyMapping, Provider from passbook.lib.utils.reflection import class_to_path, path_to_class -from passbook.saml_idp.base import Processor +from passbook.providers.saml.base import Processor LOGGER = get_logger() @@ -25,7 +25,7 @@ class SAMLProvider(Provider): signing_cert = models.TextField() signing_key = models.TextField() - form = 'passbook.saml_idp.forms.SAMLProviderForm' + form = 'passbook.providers.saml.forms.SAMLProviderForm' _processor = None def __init__(self, *args, **kwargs): @@ -68,7 +68,7 @@ class SAMLPropertyMapping(PropertyMapping): friendly_name = models.TextField(default=None, blank=True, null=True) values = ArrayField(models.TextField()) - form = 'passbook.saml_idp.forms.SAMLPropertyMappingForm' + form = 'passbook.providers.saml.forms.SAMLPropertyMappingForm' def __str__(self): return "SAML Property Mapping %s" % self.saml_name diff --git a/passbook/providers/saml/processors/__init__.py b/passbook/providers/saml/processors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/saml_idp/processors/generic.py b/passbook/providers/saml/processors/generic.py similarity index 73% rename from passbook/saml_idp/processors/generic.py rename to passbook/providers/saml/processors/generic.py index 14c060c82..142ce8428 100644 --- a/passbook/saml_idp/processors/generic.py +++ b/passbook/providers/saml/processors/generic.py @@ -1,6 +1,6 @@ """Generic Processor""" -from passbook.saml_idp.base import Processor +from passbook.providers.saml.base import Processor class GenericProcessor(Processor): diff --git a/passbook/saml_idp/processors/salesforce.py b/passbook/providers/saml/processors/salesforce.py similarity index 77% rename from passbook/saml_idp/processors/salesforce.py rename to passbook/providers/saml/processors/salesforce.py index 7a86a5301..178773372 100644 --- a/passbook/saml_idp/processors/salesforce.py +++ b/passbook/providers/saml/processors/salesforce.py @@ -1,7 +1,7 @@ """Salesforce Processor""" -from passbook.saml_idp.base import Processor -from passbook.saml_idp.xml_render import get_assertion_xml +from passbook.providers.saml.base import Processor +from passbook.providers.saml.xml_render import get_assertion_xml class SalesForceProcessor(Processor): diff --git a/passbook/providers/saml/settings.py b/passbook/providers/saml/settings.py new file mode 100644 index 000000000..0d639b84c --- /dev/null +++ b/passbook/providers/saml/settings.py @@ -0,0 +1,6 @@ +"""saml provider settings""" + +PASSBOOK_PROVIDERS_SAML_PROCESSORS = [ + 'passbook.providers.saml.processors.generic', + 'passbook.providers.saml.processors.salesforce', +] diff --git a/passbook/saml_idp/templates/saml/idp/invalid_user.html b/passbook/providers/saml/templates/saml/idp/invalid_user.html similarity index 100% rename from passbook/saml_idp/templates/saml/idp/invalid_user.html rename to passbook/providers/saml/templates/saml/idp/invalid_user.html diff --git a/passbook/saml_idp/templates/saml/idp/logged_out.html b/passbook/providers/saml/templates/saml/idp/logged_out.html similarity index 100% rename from passbook/saml_idp/templates/saml/idp/logged_out.html rename to passbook/providers/saml/templates/saml/idp/logged_out.html diff --git a/passbook/saml_idp/templates/saml/idp/login.html b/passbook/providers/saml/templates/saml/idp/login.html similarity index 100% rename from passbook/saml_idp/templates/saml/idp/login.html rename to passbook/providers/saml/templates/saml/idp/login.html diff --git a/passbook/saml_idp/templates/saml/idp/settings.html b/passbook/providers/saml/templates/saml/idp/settings.html similarity index 100% rename from passbook/saml_idp/templates/saml/idp/settings.html rename to passbook/providers/saml/templates/saml/idp/settings.html diff --git a/passbook/saml_idp/templates/saml/xml/assertions/generic.xml b/passbook/providers/saml/templates/saml/xml/assertions/generic.xml similarity index 100% rename from passbook/saml_idp/templates/saml/xml/assertions/generic.xml rename to passbook/providers/saml/templates/saml/xml/assertions/generic.xml diff --git a/passbook/saml_idp/templates/saml/xml/assertions/google_apps.xml b/passbook/providers/saml/templates/saml/xml/assertions/google_apps.xml similarity index 100% rename from passbook/saml_idp/templates/saml/xml/assertions/google_apps.xml rename to passbook/providers/saml/templates/saml/xml/assertions/google_apps.xml diff --git a/passbook/saml_idp/templates/saml/xml/assertions/salesforce.xml b/passbook/providers/saml/templates/saml/xml/assertions/salesforce.xml similarity index 100% rename from passbook/saml_idp/templates/saml/xml/assertions/salesforce.xml rename to passbook/providers/saml/templates/saml/xml/assertions/salesforce.xml diff --git a/passbook/saml_idp/templates/saml/xml/attributes.xml b/passbook/providers/saml/templates/saml/xml/attributes.xml similarity index 100% rename from passbook/saml_idp/templates/saml/xml/attributes.xml rename to passbook/providers/saml/templates/saml/xml/attributes.xml diff --git a/passbook/saml_idp/templates/saml/xml/metadata.xml b/passbook/providers/saml/templates/saml/xml/metadata.xml similarity index 100% rename from passbook/saml_idp/templates/saml/xml/metadata.xml rename to passbook/providers/saml/templates/saml/xml/metadata.xml diff --git a/passbook/saml_idp/templates/saml/xml/response.xml b/passbook/providers/saml/templates/saml/xml/response.xml similarity index 100% rename from passbook/saml_idp/templates/saml/xml/response.xml rename to passbook/providers/saml/templates/saml/xml/response.xml diff --git a/passbook/saml_idp/templates/saml/xml/signature.xml b/passbook/providers/saml/templates/saml/xml/signature.xml similarity index 100% rename from passbook/saml_idp/templates/saml/xml/signature.xml rename to passbook/providers/saml/templates/saml/xml/signature.xml diff --git a/passbook/saml_idp/templates/saml/xml/subject.xml b/passbook/providers/saml/templates/saml/xml/subject.xml similarity index 100% rename from passbook/saml_idp/templates/saml/xml/subject.xml rename to passbook/providers/saml/templates/saml/xml/subject.xml diff --git a/passbook/saml_idp/urls.py b/passbook/providers/saml/urls.py similarity index 94% rename from passbook/saml_idp/urls.py rename to passbook/providers/saml/urls.py index 33a67a66c..4e7d8fdd2 100644 --- a/passbook/saml_idp/urls.py +++ b/passbook/providers/saml/urls.py @@ -1,7 +1,7 @@ """passbook SAML IDP URLs""" from django.urls import path -from passbook.saml_idp import views +from passbook.providers.saml import views urlpatterns = [ path('/login/', diff --git a/passbook/saml_idp/utils.py b/passbook/providers/saml/utils.py similarity index 100% rename from passbook/saml_idp/utils.py rename to passbook/providers/saml/utils.py diff --git a/passbook/saml_idp/views.py b/passbook/providers/saml/views.py similarity index 98% rename from passbook/saml_idp/views.py rename to passbook/providers/saml/views.py index 5cafc866b..0d76601a0 100644 --- a/passbook/saml_idp/views.py +++ b/passbook/providers/saml/views.py @@ -17,9 +17,9 @@ from passbook.audit.models import AuditEntry from passbook.core.models import Application from passbook.lib.mixins import CSRFExemptMixin from passbook.lib.utils.template import render_to_string -from passbook.policy.engine import PolicyEngine -from passbook.saml_idp import exceptions -from passbook.saml_idp.models import SAMLProvider +from passbook.policies.engine import PolicyEngine +from passbook.providers.saml import exceptions +from passbook.providers.saml.models import SAMLProvider LOGGER = get_logger() URL_VALIDATOR = URLValidator(schemes=('http', 'https')) diff --git a/passbook/saml_idp/xml_render.py b/passbook/providers/saml/xml_render.py similarity index 88% rename from passbook/saml_idp/xml_render.py rename to passbook/providers/saml/xml_render.py index edce64110..460d60cca 100644 --- a/passbook/saml_idp/xml_render.py +++ b/passbook/providers/saml/xml_render.py @@ -1,9 +1,15 @@ """Functions for creating XML output.""" +from __future__ import annotations +from typing import TYPE_CHECKING from structlog import get_logger from passbook.lib.utils.template import render_to_string -from passbook.saml_idp.xml_signing import get_signature_xml, sign_with_signxml +from passbook.providers.saml.xml_signing import (get_signature_xml, + sign_with_signxml) + +if TYPE_CHECKING: + from passbook.providers.saml.models import SAMLProvider LOGGER = get_logger() @@ -63,7 +69,7 @@ def get_assertion_xml(template, parameters, signed=False): return render_to_string(template, params) -def get_response_xml(parameters, saml_provider: 'SAMLProvider', assertion_id=''): +def get_response_xml(parameters, saml_provider: SAMLProvider, assertion_id=''): """Returns XML for response, with signatures, if signed is True.""" # Reset signatures. params = {} diff --git a/passbook/saml_idp/xml_signing.py b/passbook/providers/saml/xml_signing.py similarity index 100% rename from passbook/saml_idp/xml_signing.py rename to passbook/providers/saml/xml_signing.py diff --git a/passbook/root/settings.py b/passbook/root/settings.py index 073074fa0..a8455198b 100644 --- a/passbook/root/settings.py +++ b/passbook/root/settings.py @@ -68,20 +68,30 @@ INSTALLED_APPS = [ 'passbook.core.apps.PassbookCoreConfig', 'passbook.admin.apps.PassbookAdminConfig', 'passbook.api.apps.PassbookAPIConfig', - 'passbook.audit.apps.PassbookAuditConfig', 'passbook.lib.apps.PassbookLibConfig', - 'passbook.ldap.apps.PassbookLdapConfig', - 'passbook.oauth_client.apps.PassbookOAuthClientConfig', - 'passbook.oauth_provider.apps.PassbookOAuthProviderConfig', - 'passbook.oidc_provider.apps.PassbookOIDCProviderConfig', - 'passbook.saml_idp.apps.PassbookSAMLIDPConfig', - 'passbook.otp.apps.PassbookOTPConfig', - 'passbook.captcha_factor.apps.PassbookCaptchaFactorConfig', - 'passbook.hibp_policy.apps.PassbookHIBPConfig', - 'passbook.pretend.apps.PassbookPretendConfig', - 'passbook.password_expiry_policy.apps.PassbookPasswordExpiryPolicyConfig', - 'passbook.suspicious_policy.apps.PassbookSuspiciousPolicyConfig', - 'passbook.app_gw.apps.PassbookApplicationApplicationGatewayConfig', + 'passbook.audit.apps.PassbookAuditConfig', + + 'passbook.sources.ldap.apps.PassbookSourceLDAPConfig', + 'passbook.sources.oauth.apps.PassbookSourceOAuthConfig', + + 'passbook.providers.app_gw.apps.PassbookApplicationApplicationGatewayConfig', + 'passbook.providers.oauth.apps.PassbookProviderOAuthConfig', + 'passbook.providers.oidc.apps.PassbookProviderOIDCConfig', + 'passbook.providers.saml.apps.PassbookProviderSAMLConfig', + + 'passbook.factors.otp.apps.PassbookFactorOTPConfig', + 'passbook.factors.captcha.apps.PassbookFactorCaptchaConfig', + 'passbook.factors.password.apps.PassbookFactorPasswordConfig', + 'passbook.factors.dummy.apps.PassbookFactorDummyConfig', + + 'passbook.policies.expiry.apps.PassbookPolicyExpiryConfig', + 'passbook.policies.reputation.apps.PassbookPolicyReputationConfig', + 'passbook.policies.hibp.apps.PassbookPolicyHIBPConfig', + 'passbook.policies.group.apps.PassbookPoliciesGroupConfig', + 'passbook.policies.matcher.apps.PassbookPoliciesMatcherConfig', + 'passbook.policies.password.apps.PassbookPoliciesPasswordConfig', + 'passbook.policies.sso.apps.PassbookPoliciesSSOConfig', + 'passbook.policies.webhook.apps.PassbookPoliciesWebhookConfig', ] REST_FRAMEWORK = { @@ -292,8 +302,7 @@ if any('test' in arg for arg in sys.argv): _DISALLOWED_ITEMS = ['INSTALLED_APPS', 'MIDDLEWARE', 'AUTHENTICATION_BACKENDS'] # Load subapps's INSTALLED_APPS for _app in INSTALLED_APPS: - if _app.startswith('passbook') and \ - not _app.startswith('passbook.core'): + if _app.startswith('passbook'): if 'apps' in _app: _app = '.'.join(_app.split('.')[:-2]) try: diff --git a/passbook/saml_idp/migrations/0002_samlpropertymapping.py b/passbook/saml_idp/migrations/0002_samlpropertymapping.py deleted file mode 100644 index 7fe8de720..000000000 --- a/passbook/saml_idp/migrations/0002_samlpropertymapping.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-08 10:40 - -import django.contrib.postgres.fields -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('passbook_core', '0017_propertymapping'), - ('passbook_saml_idp', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='SAMLPropertyMapping', - fields=[ - ('propertymapping_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PropertyMapping')), - ('saml_name', models.TextField()), - ('friendly_name', models.TextField(blank=True, default=None, null=True)), - ('values', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)), - ], - options={ - 'verbose_name': 'SAML Property Mapping', - 'verbose_name_plural': 'SAML Property Mappings', - }, - bases=('passbook_core.propertymapping',), - ), - ] diff --git a/passbook/saml_idp/migrations/0003_samlprovider_audience.py b/passbook/saml_idp/migrations/0003_samlprovider_audience.py deleted file mode 100644 index f183d53a8..000000000 --- a/passbook/saml_idp/migrations/0003_samlprovider_audience.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2019-04-18 09:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('passbook_saml_idp', '0002_samlpropertymapping'), - ] - - operations = [ - migrations.AddField( - model_name='samlprovider', - name='audience', - field=models.TextField(blank=True, default=''), - ), - ] diff --git a/passbook/saml_idp/migrations/0004_auto_20190418_0918.py b/passbook/saml_idp/migrations/0004_auto_20190418_0918.py deleted file mode 100644 index a12725415..000000000 --- a/passbook/saml_idp/migrations/0004_auto_20190418_0918.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2019-04-18 09:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('passbook_saml_idp', '0003_samlprovider_audience'), - ] - - operations = [ - migrations.AlterField( - model_name='samlprovider', - name='audience', - field=models.TextField(default=''), - ), - ] diff --git a/passbook/sources/ldap/__init__.py b/passbook/sources/ldap/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/ldap/admin.py b/passbook/sources/ldap/admin.py similarity index 63% rename from passbook/ldap/admin.py rename to passbook/sources/ldap/admin.py index 09facac6b..8dd111cd0 100644 --- a/passbook/ldap/admin.py +++ b/passbook/sources/ldap/admin.py @@ -2,4 +2,4 @@ from passbook.lib.admin import admin_autoregister -admin_autoregister('passbook_ldap') +admin_autoregister('passbook_sources_ldap') diff --git a/passbook/sources/ldap/apps.py b/passbook/sources/ldap/apps.py new file mode 100644 index 000000000..7a56777dc --- /dev/null +++ b/passbook/sources/ldap/apps.py @@ -0,0 +1,11 @@ +"""Passbook ldap app config""" + +from django.apps import AppConfig + + +class PassbookSourceLDAPConfig(AppConfig): + """Passbook ldap app config""" + + name = 'passbook.sources.ldap' + label = 'passbook_sources_ldap' + verbose_name = 'passbook Sources.LDAP' diff --git a/passbook/ldap/auth.py b/passbook/sources/ldap/auth.py similarity index 84% rename from passbook/ldap/auth.py rename to passbook/sources/ldap/auth.py index 6c7b59d5a..86508b2b3 100644 --- a/passbook/ldap/auth.py +++ b/passbook/sources/ldap/auth.py @@ -2,8 +2,8 @@ from django.contrib.auth.backends import ModelBackend from structlog import get_logger -from passbook.ldap.ldap_connector import LDAPConnector -from passbook.ldap.models import LDAPSource +from passbook.sources.ldap.ldap_connector import LDAPConnector +from passbook.sources.ldap.models import LDAPSource LOGGER = get_logger() diff --git a/passbook/ldap/forms.py b/passbook/sources/ldap/forms.py similarity index 94% rename from passbook/ldap/forms.py rename to passbook/sources/ldap/forms.py index c663a1c00..2a2aad74a 100644 --- a/passbook/ldap/forms.py +++ b/passbook/sources/ldap/forms.py @@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _ from passbook.admin.forms.source import SOURCE_FORM_FIELDS from passbook.core.forms.policies import GENERAL_FIELDS -from passbook.ldap.models import LDAPGroupMembershipPolicy, LDAPSource +from passbook.sources.ldap.models import LDAPGroupMembershipPolicy, LDAPSource class LDAPSourceForm(forms.ModelForm): diff --git a/passbook/ldap/ldap_connector.py b/passbook/sources/ldap/ldap_connector.py similarity index 99% rename from passbook/ldap/ldap_connector.py rename to passbook/sources/ldap/ldap_connector.py index 29e9efb75..925652c83 100644 --- a/passbook/ldap/ldap_connector.py +++ b/passbook/sources/ldap/ldap_connector.py @@ -6,8 +6,8 @@ import ldap3.core.exceptions from structlog import get_logger from passbook.core.models import User -from passbook.ldap.models import LDAPSource from passbook.lib.config import CONFIG +from passbook.sources.ldap.models import LDAPSource LOGGER = get_logger() diff --git a/passbook/ldap/migrations/0001_initial.py b/passbook/sources/ldap/migrations/0001_initial.py similarity index 61% rename from passbook/ldap/migrations/0001_initial.py rename to passbook/sources/ldap/migrations/0001_initial.py index 4d432c79a..0226b33da 100644 --- a/passbook/ldap/migrations/0001_initial.py +++ b/passbook/sources/ldap/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# 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 django.db.models.deletion from django.db import migrations, models @@ -32,4 +32,17 @@ class Migration(migrations.Migration): }, bases=('passbook_core.source',), ), + migrations.CreateModel( + name='LDAPGroupMembershipPolicy', + 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')), + ('dn', models.TextField()), + ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_sources_ldap.LDAPSource')), + ], + options={ + 'verbose_name': 'LDAP Group Membership Policy', + 'verbose_name_plural': 'LDAP Group Membership Policys', + }, + bases=('passbook_core.policy',), + ), ] diff --git a/passbook/sources/ldap/migrations/__init__.py b/passbook/sources/ldap/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/ldap/models.py b/passbook/sources/ldap/models.py similarity index 91% rename from passbook/ldap/models.py rename to passbook/sources/ldap/models.py index 190da2ebe..a7c6f0b5b 100644 --- a/passbook/ldap/models.py +++ b/passbook/sources/ldap/models.py @@ -26,7 +26,7 @@ class LDAPSource(Source): create_user = models.BooleanField(default=False) reset_password = models.BooleanField(default=True) - form = 'passbook.ldap.forms.LDAPSourceForm' + form = 'passbook.sources.ldap.forms.LDAPSourceForm' @property def get_login_button(self): @@ -43,7 +43,7 @@ class LDAPGroupMembershipPolicy(Policy): dn = models.TextField() source = models.ForeignKey('LDAPSource', on_delete=models.CASCADE) - form = 'passbook.ldap.forms.LDAPGroupMembershipPolicyForm' + form = 'passbook.sources.ldap.forms.LDAPGroupMembershipPolicyForm' def passes(self, user: User): """Check if user instance passes this policy""" diff --git a/passbook/ldap/settings.py b/passbook/sources/ldap/settings.py similarity index 52% rename from passbook/ldap/settings.py rename to passbook/sources/ldap/settings.py index 500815b02..cb69c276b 100644 --- a/passbook/ldap/settings.py +++ b/passbook/sources/ldap/settings.py @@ -1,5 +1,5 @@ """LDAP Settings""" AUTHENTICATION_BACKENDS = [ - 'passbook.ldap.auth.LDAPBackend', + 'passbook.sources.ldap.auth.LDAPBackend', ] diff --git a/passbook/ldap/templates/ldap/settings.html b/passbook/sources/ldap/templates/ldap/settings.html similarity index 100% rename from passbook/ldap/templates/ldap/settings.html rename to passbook/sources/ldap/templates/ldap/settings.html diff --git a/passbook/ldap/urls.py b/passbook/sources/ldap/urls.py similarity index 100% rename from passbook/ldap/urls.py rename to passbook/sources/ldap/urls.py diff --git a/passbook/ldap/views.py b/passbook/sources/ldap/views.py similarity index 95% rename from passbook/ldap/views.py rename to passbook/sources/ldap/views.py index 5eb73d82e..7e3e2c6a5 100644 --- a/passbook/ldap/views.py +++ b/passbook/sources/ldap/views.py @@ -8,7 +8,7 @@ # from django.urls import reverse # from django.utils.translation import ugettext as _ -# from passbook.ldap.forms import (AuthenticationBackendSettings, +# from passbook.sources.ldap.forms import (AuthenticationBackendSettings, # ConnectionSettings, # CreateUsersSettings, # GeneralSettingsForm) diff --git a/passbook/sources/oauth/__init__.py b/passbook/sources/oauth/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/oauth_client/admin.py b/passbook/sources/oauth/admin.py similarity index 65% rename from passbook/oauth_client/admin.py rename to passbook/sources/oauth/admin.py index a2f52168b..50cec0a49 100644 --- a/passbook/oauth_client/admin.py +++ b/passbook/sources/oauth/admin.py @@ -2,4 +2,4 @@ from passbook.lib.admin import admin_autoregister -admin_autoregister('passbook_oauth_client') +admin_autoregister('passbook_sources_oauth') diff --git a/passbook/oauth_client/apps.py b/passbook/sources/oauth/apps.py similarity index 57% rename from passbook/oauth_client/apps.py rename to passbook/sources/oauth/apps.py index a8e12e668..1672ed344 100644 --- a/passbook/oauth_client/apps.py +++ b/passbook/sources/oauth/apps.py @@ -2,24 +2,22 @@ from importlib import import_module from django.apps import AppConfig +from django.conf import settings from structlog import get_logger -from passbook.lib.config import CONFIG - LOGGER = get_logger() -class PassbookOAuthClientConfig(AppConfig): - """passbook oauth_client config""" +class PassbookSourceOAuthConfig(AppConfig): + """passbook source.oauth config""" - name = 'passbook.oauth_client' - label = 'passbook_oauth_client' - verbose_name = 'passbook OAuth Client' + name = 'passbook.sources.oauth' + label = 'passbook_sources_oauth' + verbose_name = 'passbook Sources.OAuth' mountpoint = 'source/oauth/' def ready(self): """Load source_types from config file""" - source_types_to_load = CONFIG.y('oauth_client.types', []) - for source_type in source_types_to_load: + for source_type in settings.PASSBOOK_SOURCES_OAUTH_TYPES: try: import_module(source_type) LOGGER.info("Loaded source_type", source_class=source_type) diff --git a/passbook/oauth_client/backends.py b/passbook/sources/oauth/backends.py similarity index 85% rename from passbook/oauth_client/backends.py rename to passbook/sources/oauth/backends.py index 647fc048f..055aa8a69 100644 --- a/passbook/oauth_client/backends.py +++ b/passbook/sources/oauth/backends.py @@ -3,7 +3,8 @@ from django.contrib.auth.backends import ModelBackend from django.db.models import Q -from passbook.oauth_client.models import OAuthSource, UserOAuthSourceConnection +from passbook.sources.oauth.models import (OAuthSource, + UserOAuthSourceConnection) class AuthorizedServiceBackend(ModelBackend): diff --git a/passbook/oauth_client/clients.py b/passbook/sources/oauth/clients.py similarity index 100% rename from passbook/oauth_client/clients.py rename to passbook/sources/oauth/clients.py diff --git a/passbook/oauth_client/forms.py b/passbook/sources/oauth/forms.py similarity index 97% rename from passbook/oauth_client/forms.py rename to passbook/sources/oauth/forms.py index a4e49ff00..8c6fb78d9 100644 --- a/passbook/oauth_client/forms.py +++ b/passbook/sources/oauth/forms.py @@ -5,8 +5,8 @@ from django.contrib.admin.widgets import FilteredSelectMultiple from django.utils.translation import gettext as _ from passbook.admin.forms.source import SOURCE_FORM_FIELDS -from passbook.oauth_client.models import OAuthSource -from passbook.oauth_client.source_types.manager import MANAGER +from passbook.sources.oauth.models import OAuthSource +from passbook.sources.oauth.types.manager import MANAGER class OAuthSourceForm(forms.ModelForm): diff --git a/passbook/oauth_client/locale/de/LC_MESSAGES/django.po b/passbook/sources/oauth/locale/de/LC_MESSAGES/django.po similarity index 100% rename from passbook/oauth_client/locale/de/LC_MESSAGES/django.po rename to passbook/sources/oauth/locale/de/LC_MESSAGES/django.po diff --git a/passbook/oauth_client/locale/en/LC_MESSAGES/django.po b/passbook/sources/oauth/locale/en/LC_MESSAGES/django.po similarity index 100% rename from passbook/oauth_client/locale/en/LC_MESSAGES/django.po rename to passbook/sources/oauth/locale/en/LC_MESSAGES/django.po diff --git a/passbook/oauth_client/locale/es/LC_MESSAGES/django.po b/passbook/sources/oauth/locale/es/LC_MESSAGES/django.po similarity index 100% rename from passbook/oauth_client/locale/es/LC_MESSAGES/django.po rename to passbook/sources/oauth/locale/es/LC_MESSAGES/django.po diff --git a/passbook/oauth_client/locale/fr/LC_MESSAGES/django.po b/passbook/sources/oauth/locale/fr/LC_MESSAGES/django.po similarity index 100% rename from passbook/oauth_client/locale/fr/LC_MESSAGES/django.po rename to passbook/sources/oauth/locale/fr/LC_MESSAGES/django.po diff --git a/passbook/oauth_client/migrations/0001_initial.py b/passbook/sources/oauth/migrations/0001_initial.py similarity index 97% rename from passbook/oauth_client/migrations/0001_initial.py rename to passbook/sources/oauth/migrations/0001_initial.py index 5642bec33..6e61b2815 100644 --- a/passbook/oauth_client/migrations/0001_initial.py +++ b/passbook/sources/oauth/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# 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 django.db.models.deletion from django.db import migrations, models diff --git a/passbook/sources/oauth/migrations/__init__.py b/passbook/sources/oauth/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/oauth_client/models.py b/passbook/sources/oauth/models.py similarity index 84% rename from passbook/oauth_client/models.py rename to passbook/sources/oauth/models.py index 99f37db42..053889194 100644 --- a/passbook/oauth_client/models.py +++ b/passbook/sources/oauth/models.py @@ -5,7 +5,7 @@ from django.urls import reverse, reverse_lazy from django.utils.translation import gettext as _ from passbook.core.models import Source, UserSourceConnection -from passbook.oauth_client.clients import get_client +from passbook.sources.oauth.clients import get_client class OAuthSource(Source): @@ -19,7 +19,7 @@ class OAuthSource(Source): consumer_key = models.TextField() consumer_secret = models.TextField() - form = 'passbook.oauth_client.forms.OAuthSourceForm' + form = 'passbook.sources.oauth.forms.OAuthSourceForm' @property def is_link(self): @@ -27,14 +27,14 @@ class OAuthSource(Source): @property def get_login_button(self): - url = reverse_lazy('passbook_oauth_client:oauth-client-login', + url = reverse_lazy('passbook_sources_oauth:oauth-client-login', kwargs={'source_slug': self.slug}) return url, self.provider_type, self.name @property def additional_info(self): return "Callback URL:
%s
" % \ - reverse_lazy('passbook_oauth_client:oauth-client-callback', + reverse_lazy('passbook_sources_oauth:oauth-client-callback', kwargs={'source_slug': self.slug}) def has_user_settings(self): @@ -45,7 +45,7 @@ class OAuthSource(Source): if icon_type == 'azure ad': icon_type = 'windows' icon_class = 'fa fa-%s' % icon_type - view_name = 'passbook_oauth_client:oauth-client-user' + view_name = 'passbook_sources_oauth:oauth-client-user' return self.name, icon_class, reverse((view_name), kwargs={ 'source_slug': self.slug }) @@ -59,7 +59,7 @@ class OAuthSource(Source): class GitHubOAuthSource(OAuthSource): """Abstract subclass of OAuthSource to specify GitHub Form""" - form = 'passbook.oauth_client.forms.GitHubOAuthSourceForm' + form = 'passbook.sources.oauth.forms.GitHubOAuthSourceForm' class Meta: @@ -71,7 +71,7 @@ class GitHubOAuthSource(OAuthSource): class TwitterOAuthSource(OAuthSource): """Abstract subclass of OAuthSource to specify Twitter Form""" - form = 'passbook.oauth_client.forms.TwitterOAuthSourceForm' + form = 'passbook.sources.oauth.forms.TwitterOAuthSourceForm' class Meta: @@ -83,7 +83,7 @@ class TwitterOAuthSource(OAuthSource): class FacebookOAuthSource(OAuthSource): """Abstract subclass of OAuthSource to specify Facebook Form""" - form = 'passbook.oauth_client.forms.FacebookOAuthSourceForm' + form = 'passbook.sources.oauth.forms.FacebookOAuthSourceForm' class Meta: @@ -95,7 +95,7 @@ class FacebookOAuthSource(OAuthSource): class DiscordOAuthSource(OAuthSource): """Abstract subclass of OAuthSource to specify Discord Form""" - form = 'passbook.oauth_client.forms.DiscordOAuthSourceForm' + form = 'passbook.sources.oauth.forms.DiscordOAuthSourceForm' class Meta: @@ -107,7 +107,7 @@ class DiscordOAuthSource(OAuthSource): class GoogleOAuthSource(OAuthSource): """Abstract subclass of OAuthSource to specify Google Form""" - form = 'passbook.oauth_client.forms.GoogleOAuthSourceForm' + form = 'passbook.sources.oauth.forms.GoogleOAuthSourceForm' class Meta: @@ -119,7 +119,7 @@ class GoogleOAuthSource(OAuthSource): class AzureADOAuthSource(OAuthSource): """Abstract subclass of OAuthSource to specify AzureAD Form""" - form = 'passbook.oauth_client.forms.AzureADOAuthSourceForm' + form = 'passbook.sources.oauth.forms.AzureADOAuthSourceForm' class Meta: diff --git a/passbook/sources/oauth/settings.py b/passbook/sources/oauth/settings.py new file mode 100644 index 000000000..2158129e0 --- /dev/null +++ b/passbook/sources/oauth/settings.py @@ -0,0 +1,16 @@ +"""Oauth2 Client Settings""" + +AUTHENTICATION_BACKENDS = [ + 'passbook.sources.oauth.backends.AuthorizedServiceBackend', +] + +PASSBOOK_SOURCES_OAUTH_TYPES = [ + 'passbook.sources.oauth.types.discord', + 'passbook.sources.oauth.types.facebook', + 'passbook.sources.oauth.types.github', + 'passbook.sources.oauth.types.google', + 'passbook.sources.oauth.types.reddit', + 'passbook.sources.oauth.types.supervisr', + 'passbook.sources.oauth.types.twitter', + 'passbook.sources.oauth.types.azure_ad', +] diff --git a/passbook/oauth_client/templates/oauth_client/user.html b/passbook/sources/oauth/templates/oauth_client/user.html similarity index 100% rename from passbook/oauth_client/templates/oauth_client/user.html rename to passbook/sources/oauth/templates/oauth_client/user.html diff --git a/passbook/sources/oauth/types/__init__.py b/passbook/sources/oauth/types/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/oauth_client/source_types/azure_ad.py b/passbook/sources/oauth/types/azure_ad.py similarity index 85% rename from passbook/oauth_client/source_types/azure_ad.py rename to passbook/sources/oauth/types/azure_ad.py index b35c6b163..f9d2cdafe 100644 --- a/passbook/oauth_client/source_types/azure_ad.py +++ b/passbook/sources/oauth/types/azure_ad.py @@ -5,10 +5,10 @@ import uuid from requests.exceptions import RequestException from structlog import get_logger -from passbook.oauth_client.clients import OAuth2Client -from passbook.oauth_client.source_types.manager import MANAGER, RequestKind -from passbook.oauth_client.utils import user_get_or_create -from passbook.oauth_client.views.core import OAuthCallback +from passbook.sources.oauth.clients import OAuth2Client +from passbook.sources.oauth.types.manager import MANAGER, RequestKind +from passbook.sources.oauth.utils import user_get_or_create +from passbook.sources.oauth.views.core import OAuthCallback LOGGER = get_logger() diff --git a/passbook/oauth_client/source_types/discord.py b/passbook/sources/oauth/types/discord.py similarity index 86% rename from passbook/oauth_client/source_types/discord.py rename to passbook/sources/oauth/types/discord.py index c8c9e714e..9361d44c0 100644 --- a/passbook/oauth_client/source_types/discord.py +++ b/passbook/sources/oauth/types/discord.py @@ -4,10 +4,10 @@ import json from requests.exceptions import RequestException from structlog import get_logger -from passbook.oauth_client.clients import OAuth2Client -from passbook.oauth_client.source_types.manager import MANAGER, RequestKind -from passbook.oauth_client.utils import user_get_or_create -from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect +from passbook.sources.oauth.clients import OAuth2Client +from passbook.sources.oauth.types.manager import MANAGER, RequestKind +from passbook.sources.oauth.utils import user_get_or_create +from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect LOGGER = get_logger() diff --git a/passbook/oauth_client/source_types/facebook.py b/passbook/sources/oauth/types/facebook.py similarity index 77% rename from passbook/oauth_client/source_types/facebook.py rename to passbook/sources/oauth/types/facebook.py index 00b387177..0f6fd3a9c 100644 --- a/passbook/oauth_client/source_types/facebook.py +++ b/passbook/sources/oauth/types/facebook.py @@ -1,8 +1,8 @@ """Facebook OAuth Views""" -from passbook.oauth_client.source_types.manager import MANAGER, RequestKind -from passbook.oauth_client.utils import user_get_or_create -from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect +from passbook.sources.oauth.types.manager import MANAGER, RequestKind +from passbook.sources.oauth.utils import user_get_or_create +from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect @MANAGER.source(kind=RequestKind.redirect, name='Facebook') diff --git a/passbook/oauth_client/source_types/github.py b/passbook/sources/oauth/types/github.py similarity index 71% rename from passbook/oauth_client/source_types/github.py rename to passbook/sources/oauth/types/github.py index cfdcc3a7c..bfa0580dc 100644 --- a/passbook/oauth_client/source_types/github.py +++ b/passbook/sources/oauth/types/github.py @@ -1,8 +1,8 @@ """GitHub OAuth Views""" -from passbook.oauth_client.source_types.manager import MANAGER, RequestKind -from passbook.oauth_client.utils import user_get_or_create -from passbook.oauth_client.views.core import OAuthCallback +from passbook.sources.oauth.types.manager import MANAGER, RequestKind +from passbook.sources.oauth.utils import user_get_or_create +from passbook.sources.oauth.views.core import OAuthCallback @MANAGER.source(kind=RequestKind.callback, name='GitHub') diff --git a/passbook/oauth_client/source_types/google.py b/passbook/sources/oauth/types/google.py similarity index 77% rename from passbook/oauth_client/source_types/google.py rename to passbook/sources/oauth/types/google.py index 7cc49c74f..b244359b3 100644 --- a/passbook/oauth_client/source_types/google.py +++ b/passbook/sources/oauth/types/google.py @@ -1,7 +1,7 @@ """Google OAuth Views""" -from passbook.oauth_client.source_types.manager import MANAGER, RequestKind -from passbook.oauth_client.utils import user_get_or_create -from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect +from passbook.sources.oauth.types.manager import MANAGER, RequestKind +from passbook.sources.oauth.utils import user_get_or_create +from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect @MANAGER.source(kind=RequestKind.redirect, name='Google') diff --git a/passbook/oauth_client/source_types/manager.py b/passbook/sources/oauth/types/manager.py similarity index 95% rename from passbook/oauth_client/source_types/manager.py rename to passbook/sources/oauth/types/manager.py index 389f12c0f..be4d366f7 100644 --- a/passbook/oauth_client/source_types/manager.py +++ b/passbook/sources/oauth/types/manager.py @@ -3,7 +3,7 @@ from enum import Enum from structlog import get_logger -from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect +from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect LOGGER = get_logger() diff --git a/passbook/oauth_client/source_types/reddit.py b/passbook/sources/oauth/types/reddit.py similarity index 88% rename from passbook/oauth_client/source_types/reddit.py rename to passbook/sources/oauth/types/reddit.py index b2dd3a71b..0b349bbf7 100644 --- a/passbook/oauth_client/source_types/reddit.py +++ b/passbook/sources/oauth/types/reddit.py @@ -5,10 +5,10 @@ from requests.auth import HTTPBasicAuth from requests.exceptions import RequestException from structlog import get_logger -from passbook.oauth_client.clients import OAuth2Client -from passbook.oauth_client.source_types.manager import MANAGER, RequestKind -from passbook.oauth_client.utils import user_get_or_create -from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect +from passbook.sources.oauth.clients import OAuth2Client +from passbook.sources.oauth.types.manager import MANAGER, RequestKind +from passbook.sources.oauth.utils import user_get_or_create +from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect LOGGER = get_logger() diff --git a/passbook/oauth_client/source_types/supervisr.py b/passbook/sources/oauth/types/supervisr.py similarity index 85% rename from passbook/oauth_client/source_types/supervisr.py rename to passbook/sources/oauth/types/supervisr.py index 4a30cba7f..7f3670b1e 100644 --- a/passbook/oauth_client/source_types/supervisr.py +++ b/passbook/sources/oauth/types/supervisr.py @@ -5,10 +5,10 @@ import json from requests.exceptions import RequestException from structlog import get_logger -from passbook.oauth_client.clients import OAuth2Client -from passbook.oauth_client.source_types.manager import MANAGER, RequestKind -from passbook.oauth_client.utils import user_get_or_create -from passbook.oauth_client.views.core import OAuthCallback +from passbook.sources.oauth.clients import OAuth2Client +from passbook.sources.oauth.types.manager import MANAGER, RequestKind +from passbook.sources.oauth.utils import user_get_or_create +from passbook.sources.oauth.views.core import OAuthCallback LOGGER = get_logger() diff --git a/passbook/oauth_client/source_types/twitter.py b/passbook/sources/oauth/types/twitter.py similarity index 82% rename from passbook/oauth_client/source_types/twitter.py rename to passbook/sources/oauth/types/twitter.py index fb49f9d4d..b43a0cfc5 100644 --- a/passbook/oauth_client/source_types/twitter.py +++ b/passbook/sources/oauth/types/twitter.py @@ -3,10 +3,10 @@ from requests.exceptions import RequestException from structlog import get_logger -from passbook.oauth_client.clients import OAuthClient -from passbook.oauth_client.source_types.manager import MANAGER, RequestKind -from passbook.oauth_client.utils import user_get_or_create -from passbook.oauth_client.views.core import OAuthCallback +from passbook.sources.oauth.clients import OAuthClient +from passbook.sources.oauth.types.manager import MANAGER, RequestKind +from passbook.sources.oauth.utils import user_get_or_create +from passbook.sources.oauth.views.core import OAuthCallback LOGGER = get_logger() diff --git a/passbook/oauth_client/urls.py b/passbook/sources/oauth/urls.py similarity index 81% rename from passbook/oauth_client/urls.py rename to passbook/sources/oauth/urls.py index e633f0a3a..b15f27314 100644 --- a/passbook/oauth_client/urls.py +++ b/passbook/sources/oauth/urls.py @@ -2,8 +2,8 @@ from django.urls import path -from passbook.oauth_client.source_types.manager import RequestKind -from passbook.oauth_client.views import core, dispatcher, user +from passbook.sources.oauth.types.manager import RequestKind +from passbook.sources.oauth.views import core, dispatcher, user urlpatterns = [ path('login//', dispatcher.DispatcherView.as_view( diff --git a/passbook/oauth_client/utils.py b/passbook/sources/oauth/utils.py similarity index 100% rename from passbook/oauth_client/utils.py rename to passbook/sources/oauth/utils.py diff --git a/passbook/sources/oauth/views/__init__.py b/passbook/sources/oauth/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/oauth_client/views/core.py b/passbook/sources/oauth/views/core.py similarity index 97% rename from passbook/oauth_client/views/core.py rename to passbook/sources/oauth/views/core.py index b137fec8f..da47a96de 100644 --- a/passbook/oauth_client/views/core.py +++ b/passbook/sources/oauth/views/core.py @@ -11,10 +11,11 @@ from django.utils.translation import ugettext as _ from django.views.generic import RedirectView, View from structlog import get_logger -from passbook.core.auth.view import AuthenticationView, _redirect_with_qs +from passbook.factors.view import AuthenticationView, _redirect_with_qs from passbook.lib.utils.reflection import app -from passbook.oauth_client.clients import get_client -from passbook.oauth_client.models import OAuthSource, UserOAuthSourceConnection +from passbook.sources.oauth.clients import get_client +from passbook.sources.oauth.models import (OAuthSource, + UserOAuthSourceConnection) LOGGER = get_logger() diff --git a/passbook/oauth_client/views/dispatcher.py b/passbook/sources/oauth/views/dispatcher.py similarity index 83% rename from passbook/oauth_client/views/dispatcher.py rename to passbook/sources/oauth/views/dispatcher.py index 6d682a422..6b25b8f9c 100644 --- a/passbook/oauth_client/views/dispatcher.py +++ b/passbook/sources/oauth/views/dispatcher.py @@ -3,8 +3,8 @@ from django.http import Http404 from django.shortcuts import get_object_or_404 from django.views import View -from passbook.oauth_client.models import OAuthSource -from passbook.oauth_client.source_types.manager import MANAGER, RequestKind +from passbook.sources.oauth.models import OAuthSource +from passbook.sources.oauth.types.manager import MANAGER, RequestKind class DispatcherView(View): diff --git a/passbook/oauth_client/views/user.py b/passbook/sources/oauth/views/user.py similarity index 85% rename from passbook/oauth_client/views/user.py rename to passbook/sources/oauth/views/user.py index 0fd3378e5..c47a670c3 100644 --- a/passbook/oauth_client/views/user.py +++ b/passbook/sources/oauth/views/user.py @@ -3,7 +3,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import get_object_or_404 from django.views.generic import TemplateView -from passbook.oauth_client.models import OAuthSource, UserOAuthSourceConnection +from passbook.sources.oauth.models import (OAuthSource, + UserOAuthSourceConnection) class UserSettingsView(LoginRequiredMixin, TemplateView): diff --git a/passbook/suspicious_policy/admin.py b/passbook/suspicious_policy/admin.py deleted file mode 100644 index 983385432..000000000 --- a/passbook/suspicious_policy/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Passbook suspicious_policy Admin""" - -from passbook.lib.admin import admin_autoregister - -admin_autoregister('passbook_suspicious_policy') diff --git a/passbook/suspicious_policy/apps.py b/passbook/suspicious_policy/apps.py deleted file mode 100644 index c0fb6f6bf..000000000 --- a/passbook/suspicious_policy/apps.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Passbook suspicious_policy app config""" -from importlib import import_module - -from django.apps import AppConfig - - -class PassbookSuspiciousPolicyConfig(AppConfig): - """Passbook suspicious_policy app config""" - - name = 'passbook.suspicious_policy' - label = 'passbook_suspicious_policy' - verbose_name = 'passbook Suspicious Request Detector' - - def ready(self): - import_module('passbook.suspicious_policy.signals') diff --git a/passbook/suspicious_policy/migrations/0002_auto_20190303_1820.py b/passbook/suspicious_policy/migrations/0002_auto_20190303_1820.py deleted file mode 100644 index 7db3e4d1c..000000000 --- a/passbook/suspicious_policy/migrations/0002_auto_20190303_1820.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-03 18:20 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('passbook_suspicious_policy', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='suspiciousrequestpolicy', - options={'verbose_name': 'Suspicious Request Policy', 'verbose_name_plural': 'Suspicious Request Policies'}, - ), - ] diff --git a/passbook/suspicious_policy/migrations/0003_auto_20190303_1833.py b/passbook/suspicious_policy/migrations/0003_auto_20190303_1833.py deleted file mode 100644 index 23d2fac40..000000000 --- a/passbook/suspicious_policy/migrations/0003_auto_20190303_1833.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-03 18:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('passbook_suspicious_policy', '0002_auto_20190303_1820'), - ] - - operations = [ - migrations.AlterField( - model_name='ipscore', - name='ip', - field=models.GenericIPAddressField(unique=True), - ), - migrations.AlterField( - model_name='userscore', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ]