*(minor): small refactor
This commit is contained in:
		| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								passbook/app_gw/.DS_Store
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								passbook/app_gw/.DS_Store
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -1,11 +0,0 @@ | ||||
| """passbook Application Security Gateway app""" | ||||
| from django.apps import AppConfig | ||||
|  | ||||
|  | ||||
| class PassbookApplicationApplicationGatewayConfig(AppConfig): | ||||
|     """passbook app_gw app""" | ||||
|  | ||||
|     name = 'passbook.app_gw' | ||||
|     label = 'passbook_app_gw' | ||||
|     verbose_name = 'passbook Application Security Gateway' | ||||
|     # mountpoint = 'app_gw/' | ||||
							
								
								
									
										
											BIN
										
									
								
								passbook/app_gw/migrations/.DS_Store
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								passbook/app_gw/migrations/.DS_Store
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -1,18 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-03-21 15:21 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_app_gw', '0001_initial'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='rewriterule', | ||||
|             name='conditions', | ||||
|             field=models.ManyToManyField(blank=True, to='passbook_core.Policy'), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,18 +0,0 @@ | ||||
| # Generated by Django 2.2 on 2019-04-11 13:14 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_app_gw', '0002_auto_20190321_1521'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='applicationgatewayprovider', | ||||
|             name='authentication_header', | ||||
|             field=models.TextField(blank=True, default='X-Remote-User'), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,7 +1,8 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-16 09:13 | ||||
| # Generated by Django 2.2.6 on 2019-10-07 14:07 | ||||
|  | ||||
| import uuid | ||||
|  | ||||
| import 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')}, | ||||
|         ), | ||||
|     ] | ||||
|  | ||||
| @ -1,18 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-21 12:01 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_audit', '0001_initial'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='loginattempt', | ||||
|             name='created', | ||||
|             field=models.DateTimeField(auto_now_add=True), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,23 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-21 12:40 | ||||
|  | ||||
| import django.contrib.postgres.fields.jsonb | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_audit', '0002_auto_20190221_1201'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.RemoveField( | ||||
|             model_name='auditentry', | ||||
|             name='_context', | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='auditentry', | ||||
|             name='context', | ||||
|             field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,16 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-03-08 14:53 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_audit', '0003_auto_20190221_1240'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.DeleteModel( | ||||
|             name='LoginAttempt', | ||||
|         ), | ||||
|     ] | ||||
| @ -1,10 +0,0 @@ | ||||
| """passbook captcha app""" | ||||
| from django.apps import AppConfig | ||||
|  | ||||
|  | ||||
| class PassbookCaptchaFactorConfig(AppConfig): | ||||
|     """passbook captcha app""" | ||||
|  | ||||
|     name = 'passbook.captcha_factor' | ||||
|     label = 'passbook_captcha_factor' | ||||
|     verbose_name = 'passbook Captcha' | ||||
| @ -1,2 +0,0 @@ | ||||
| """passbook core""" | ||||
| __version__ = '0.2.6-beta' | ||||
|  | ||||
| @ -2,12 +2,12 @@ | ||||
| from importlib import import_module | ||||
|  | ||||
| from 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) | ||||
|  | ||||
| @ -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(), | ||||
|  | ||||
| @ -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'), | ||||
|         } | ||||
|  | ||||
| @ -1,45 +0,0 @@ | ||||
| """passbook import_users management command""" | ||||
| from csv import DictReader | ||||
|  | ||||
| from django.core.management.base import BaseCommand | ||||
| from django.core.validators import EmailValidator, ValidationError | ||||
| from structlog import get_logger | ||||
|  | ||||
| from passbook.core.models import User | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| class Command(BaseCommand): | ||||
|     """Import users from CSV file""" | ||||
|  | ||||
|     def add_arguments(self, parser): | ||||
|         # Positional arguments | ||||
|         parser.add_argument('file', nargs='+', type=str) | ||||
|  | ||||
|     def handle(self, *args, **options): | ||||
|         """Create Users from CSV file""" | ||||
|         for file in options.get('file'): | ||||
|             with open(file, 'r') as _file: | ||||
|                 reader = DictReader(_file) | ||||
|                 for user in reader: | ||||
|                     LOGGER.debug('User %s', user.get('username')) | ||||
|                     try: | ||||
|                         # only import users with valid email addresses | ||||
|                         if user.get('email'): | ||||
|                             validator = EmailValidator() | ||||
|                             validator(user.get('email')) | ||||
|                         # use combination of username and email to check for existing user | ||||
|                         if User.objects.filter( | ||||
|                                 username=user.get('username'), | ||||
|                                 email=user.get('email')).exists(): | ||||
|                             LOGGER.debug('User %s exists already, skipping', user.get('username')) | ||||
|                         # Create user | ||||
|                         User.objects.create( | ||||
|                             username=user.get('username'), | ||||
|                             email=user.get('email'), | ||||
|                             name=user.get('name'), | ||||
|                             password=user.get('password')) | ||||
|                         LOGGER.debug('Created User %s', user.get('username')) | ||||
|                     except ValidationError as exc: | ||||
|                         LOGGER.warning('User %s caused %r, skipping', user.get('username'), exc) | ||||
|                         continue | ||||
| @ -1,35 +0,0 @@ | ||||
| """passbook Webserver management command""" | ||||
|  | ||||
| import cherrypy | ||||
| from django.conf import settings | ||||
| from django.core.management.base import BaseCommand | ||||
| from structlog import get_logger | ||||
|  | ||||
| from passbook.lib.config import CONFIG | ||||
| from passbook.root.wsgi import application | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class Command(BaseCommand): | ||||
|     """Run CherryPy webserver""" | ||||
|  | ||||
|     def handle(self, *args, **options): | ||||
|         """passbook cherrypy server""" | ||||
|         cherrypy.config.update(CONFIG.y('web')) | ||||
|         cherrypy.tree.graft(application, '/') | ||||
|         # Mount NullObject to serve static files | ||||
|         cherrypy.tree.mount(None, settings.STATIC_URL, config={ | ||||
|             '/': { | ||||
|                 'tools.staticdir.on': True, | ||||
|                 'tools.staticdir.dir': settings.STATIC_ROOT, | ||||
|                 'tools.expires.on': True, | ||||
|                 'tools.expires.secs': 86400, | ||||
|                 'tools.gzip.on': True, | ||||
|             } | ||||
|         }) | ||||
|         cherrypy.engine.start() | ||||
|         for file in CONFIG.loaded_file: | ||||
|             cherrypy.engine.autoreload.files.add(file) | ||||
|             LOGGER.info("Added '%s' to autoreload triggers", file) | ||||
|         cherrypy.engine.block() | ||||
| @ -1,22 +0,0 @@ | ||||
| """passbook Worker management command""" | ||||
|  | ||||
| from django.core.management.base import BaseCommand | ||||
| from django.utils import autoreload | ||||
| from structlog import get_logger | ||||
|  | ||||
| from passbook.root.celery import CELERY_APP | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class Command(BaseCommand): | ||||
|     """Run Celery Worker""" | ||||
|  | ||||
|     def handle(self, *args, **options): | ||||
|         """celery worker""" | ||||
|         autoreload.run_with_reloader(self.celery_worker) | ||||
|  | ||||
|     def celery_worker(self): | ||||
|         """Run celery worker within autoreload""" | ||||
|         autoreload.raise_last_exception() | ||||
|         CELERY_APP.worker_main(['worker', '--autoscale=10,3', '-E', '-B']) | ||||
| @ -1,21 +1,24 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-16 09:10 | ||||
| # Generated by Django 2.2.6 on 2019-10-07 14:06 | ||||
|  | ||||
| import uuid | ||||
|  | ||||
| import 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')}, | ||||
|         ), | ||||
|     ] | ||||
|  | ||||
| @ -1,29 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-16 10:02 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0001_initial'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterModelOptions( | ||||
|             name='debugpolicy', | ||||
|             options={'verbose_name': 'Debug Policy', 'verbose_name_plural': 'Debug Policies'}, | ||||
|         ), | ||||
|         migrations.AlterModelOptions( | ||||
|             name='fieldmatcherpolicy', | ||||
|             options={'verbose_name': 'Field matcher Policy', 'verbose_name_plural': 'Field matcher Policies'}, | ||||
|         ), | ||||
|         migrations.AlterModelOptions( | ||||
|             name='passwordpolicypolicy', | ||||
|             options={'verbose_name': 'Password Policy Policy', 'verbose_name_plural': 'Password Policy Policies'}, | ||||
|         ), | ||||
|         migrations.AlterModelOptions( | ||||
|             name='webhookpolicy', | ||||
|             options={'verbose_name': 'Webhook Policy', 'verbose_name_plural': 'Webhook Policies'}, | ||||
|         ), | ||||
|     ] | ||||
| @ -1,17 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-16 10:04 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0002_auto_20190216_1002'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.RenameModel( | ||||
|             old_name='PasswordPolicyPolicy', | ||||
|             new_name='PasswordPolicy', | ||||
|         ), | ||||
|     ] | ||||
| @ -1,17 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-16 10:13 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0003_auto_20190216_1004'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterModelOptions( | ||||
|             name='passwordpolicy', | ||||
|             options={'verbose_name': 'Password Policy', 'verbose_name_plural': 'Password Policies'}, | ||||
|         ), | ||||
|     ] | ||||
| @ -1,28 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-21 12:01 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0004_auto_20190216_1013'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='policy', | ||||
|             name='created', | ||||
|             field=models.DateTimeField(auto_now_add=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='policymodel', | ||||
|             name='created', | ||||
|             field=models.DateTimeField(auto_now_add=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='usersourceconnection', | ||||
|             name='created', | ||||
|             field=models.DateTimeField(auto_now_add=True), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,19 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-21 12:32 | ||||
|  | ||||
| import django.contrib.postgres.fields.jsonb | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0005_auto_20190221_1201'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='factor', | ||||
|             name='arguments', | ||||
|             field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,19 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-21 12:33 | ||||
|  | ||||
| import django.contrib.postgres.fields.jsonb | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0006_factor_arguments'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='factor', | ||||
|             name='arguments', | ||||
|             field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,18 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-21 15:16 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0007_auto_20190221_1233'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='fieldmatcherpolicy', | ||||
|             name='match_action', | ||||
|             field=models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('contains', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,44 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-24 09:50 | ||||
|  | ||||
| import django.contrib.postgres.fields | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0008_auto_20190221_1516'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='DummyFactor', | ||||
|             fields=[ | ||||
|                 ('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'abstract': False, | ||||
|             }, | ||||
|             bases=('passbook_core.factor',), | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='PasswordFactor', | ||||
|             fields=[ | ||||
|                 ('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')), | ||||
|                 ('backends', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)), | ||||
|             ], | ||||
|             options={ | ||||
|                 'abstract': False, | ||||
|             }, | ||||
|             bases=('passbook_core.factor',), | ||||
|         ), | ||||
|         migrations.RemoveField( | ||||
|             model_name='factor', | ||||
|             name='arguments', | ||||
|         ), | ||||
|         migrations.RemoveField( | ||||
|             model_name='factor', | ||||
|             name='type', | ||||
|         ), | ||||
|     ] | ||||
| @ -1,21 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-24 10:16 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0009_auto_20190224_0950'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterModelOptions( | ||||
|             name='dummyfactor', | ||||
|             options={'verbose_name': 'Dummy Factor', 'verbose_name_plural': 'Dummy Factors'}, | ||||
|         ), | ||||
|         migrations.AlterModelOptions( | ||||
|             name='passwordfactor', | ||||
|             options={'verbose_name': 'Password Factor', 'verbose_name_plural': 'Password Factors'}, | ||||
|         ), | ||||
|     ] | ||||
| @ -1,25 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-25 14:38 | ||||
|  | ||||
| import django.utils.timezone | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0010_auto_20190224_1016'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='passwordfactor', | ||||
|             name='password_policies', | ||||
|             field=models.ManyToManyField(blank=True, to='passbook_core.Policy'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='password_change_date', | ||||
|             field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), | ||||
|             preserve_default=False, | ||||
|         ), | ||||
|     ] | ||||
| @ -1,31 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-25 19:12 | ||||
|  | ||||
| import uuid | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.conf import settings | ||||
| from django.db import migrations, models | ||||
|  | ||||
| import passbook.core.models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0011_auto_20190225_1438'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='Nonce', | ||||
|             fields=[ | ||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||
|                 ('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)), | ||||
|                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'Nonce', | ||||
|                 'verbose_name_plural': 'Nonces', | ||||
|             }, | ||||
|         ), | ||||
|     ] | ||||
| @ -1,18 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-25 19:57 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0012_nonce'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='invitation', | ||||
|             name='needs_confirmation', | ||||
|             field=models.BooleanField(default=True), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,19 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-26 14:28 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0014_auto_20190226_0850'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='passwordpolicy', | ||||
|             name='error_message', | ||||
|             field=models.TextField(default=''), | ||||
|             preserve_default=False, | ||||
|         ), | ||||
|     ] | ||||
| @ -1,38 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-27 13:55 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| def migrate_names(apps, schema_editor): | ||||
|     """migrate first_name and last_name to name""" | ||||
|     User = apps.get_model("passbook_core", "User") | ||||
|     for user in User.objects.all(): | ||||
|         user.name = '%s %s' % (user.first_name, user.last_name) | ||||
|         user.save() | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0015_passwordpolicy_error_message'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='name', | ||||
|             field=models.TextField(default=''), | ||||
|             preserve_default=False, | ||||
|         ), | ||||
|         migrations.RunPython(migrate_names), | ||||
|         migrations.AlterField( | ||||
|             model_name='user', | ||||
|             name='name', | ||||
|             field=models.TextField(), | ||||
|             preserve_default=False, | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='fieldmatcherpolicy', | ||||
|             name='user_field', | ||||
|             field=models.TextField(choices=[('username', 'Username'), ('name', 'Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')]), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,26 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-03-08 10:40 | ||||
|  | ||||
| import uuid | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0016_auto_20190227_1355'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='PropertyMapping', | ||||
|             fields=[ | ||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||
|                 ('name', models.TextField()), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'Property Mapping', | ||||
|                 'verbose_name_plural': 'Property Mappings', | ||||
|             }, | ||||
|         ), | ||||
|     ] | ||||
| @ -1,18 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-03-08 10:50 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0017_propertymapping'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='provider', | ||||
|             name='property_mappings', | ||||
|             field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,25 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-03-10 16:15 | ||||
|  | ||||
| import django.contrib.postgres.fields.hstore | ||||
| from django.contrib.postgres.operations import HStoreExtension | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0018_provider_property_mappings'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.RemoveField( | ||||
|             model_name='group', | ||||
|             name='extra_data', | ||||
|         ), | ||||
|         HStoreExtension(), | ||||
|         migrations.AddField( | ||||
|             model_name='group', | ||||
|             name='tags', | ||||
|             field=django.contrib.postgres.fields.hstore.HStoreField(default=dict), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,18 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-03-21 12:03 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0020_groupmembershippolicy'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='policy', | ||||
|             name='timeout', | ||||
|             field=models.IntegerField(default=30), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,18 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-04-04 19:42 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0021_policy_timeout'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='nonce', | ||||
|             name='expiring', | ||||
|             field=models.BooleanField(default=True), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,17 +0,0 @@ | ||||
| # Generated by Django 2.2 on 2019-04-13 15:51 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0022_nonce_expiring'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.RemoveField( | ||||
|             model_name='user', | ||||
|             name='applications', | ||||
|         ), | ||||
|     ] | ||||
| @ -1,12 +1,11 @@ | ||||
| """passbook core models""" | ||||
| 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: | ||||
|  | ||||
|  | ||||
							
								
								
									
										5
									
								
								passbook/core/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								passbook/core/settings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| """core settings""" | ||||
|  | ||||
| PASSBOOK_CORE_FACTORS = [ | ||||
|  | ||||
| ] | ||||
| @ -5,8 +5,6 @@ from django.db.models.signals import post_save | ||||
| from django.dispatch import receiver | ||||
| from 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): | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -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): | ||||
							
								
								
									
										10
									
								
								passbook/factors/captcha/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								passbook/factors/captcha/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| """passbook captcha app""" | ||||
| from django.apps import AppConfig | ||||
|  | ||||
|  | ||||
| class PassbookFactorCaptchaConfig(AppConfig): | ||||
|     """passbook captcha app""" | ||||
|  | ||||
|     name = 'passbook.factors.captcha' | ||||
|     label = 'passbook_factors_captcha' | ||||
|     verbose_name = 'passbook Factors.Captcha' | ||||
| @ -2,8 +2,8 @@ | ||||
| 
 | ||||
| from django.views.generic import FormView | ||||
| 
 | ||||
| from 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 | ||||
| @ -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): | ||||
| @ -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 = [ | ||||
| @ -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: | ||||
| 
 | ||||
| @ -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' | ||||
							
								
								
									
										5
									
								
								passbook/factors/dummy/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								passbook/factors/dummy/admin.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| """dummy factor admin""" | ||||
|  | ||||
| from passbook.lib.admin import admin_autoregister | ||||
|  | ||||
| admin_autoregister('passbook_factors_dummy') | ||||
							
								
								
									
										11
									
								
								passbook/factors/dummy/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								passbook/factors/dummy/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| """passbook dummy factor config""" | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|  | ||||
|  | ||||
| class PassbookFactorDummyConfig(AppConfig): | ||||
|     """passbook dummy factor config""" | ||||
|  | ||||
|     name = 'passbook.factors.dummy' | ||||
|     label = 'passbook_factors_dummy' | ||||
|     verbose_name = 'passbook Factors.Dummy' | ||||
| @ -1,14 +1,12 @@ | ||||
| """passbook multi-factor authentication engine""" | ||||
| 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() | ||||
							
								
								
									
										21
									
								
								passbook/factors/dummy/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								passbook/factors/dummy/forms.py
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
|         } | ||||
							
								
								
									
										27
									
								
								passbook/factors/dummy/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								passbook/factors/dummy/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							| @ -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',), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										19
									
								
								passbook/factors/dummy/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								passbook/factors/dummy/models.py
									
									
									
									
									
										Normal file
									
								
							| @ -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') | ||||
							
								
								
									
										3
									
								
								passbook/factors/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								passbook/factors/forms.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| """factor forms""" | ||||
|  | ||||
| GENERAL_FIELDS = ['name', 'slug', 'order', 'policies', 'enabled'] | ||||
							
								
								
									
										12
									
								
								passbook/factors/otp/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								passbook/factors/otp/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| """passbook OTP AppConfig""" | ||||
|  | ||||
| from django.apps.config import AppConfig | ||||
|  | ||||
|  | ||||
| class PassbookFactorOTPConfig(AppConfig): | ||||
|     """passbook OTP AppConfig""" | ||||
|  | ||||
|     name = 'passbook.factors.otp' | ||||
|     label = 'passbook_factors_otp' | ||||
|     verbose_name = 'passbook Factors.OTP' | ||||
|     mountpoint = 'user/otp/' | ||||
| @ -5,9 +5,9 @@ from django.views.generic import FormView | ||||
| from django_otp import match_token, user_has_device | ||||
| from 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() | ||||
| 
 | ||||
| @ -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("<img src=\"%s\" />" % value) # nosec | ||||
|         return mark_safe(f'<img src="{value}" />') # 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], | ||||
| @ -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 = [ | ||||
| @ -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: | ||||
| 
 | ||||
| @ -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'), | ||||
| @ -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' | ||||
							
								
								
									
										5
									
								
								passbook/factors/password/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								passbook/factors/password/admin.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| """password factor admin""" | ||||
|  | ||||
| from passbook.lib.admin import admin_autoregister | ||||
|  | ||||
| admin_autoregister('passbook_factors_password') | ||||
							
								
								
									
										15
									
								
								passbook/factors/password/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								passbook/factors/password/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| """passbook core app config""" | ||||
| from importlib import import_module | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|  | ||||
|  | ||||
| class PassbookFactorPasswordConfig(AppConfig): | ||||
|     """passbook password factor config""" | ||||
|  | ||||
|     name = 'passbook.factors.password' | ||||
|     label = 'passbook_factors_password' | ||||
|     verbose_name = 'passbook Factors.Password' | ||||
|  | ||||
|     def ready(self): | ||||
|         import_module('passbook.factors.password.signals') | ||||
| @ -1,4 +1,4 @@ | ||||
| """passbook core exceptions""" | ||||
| """passbook password policy exceptions""" | ||||
| 
 | ||||
| class PasswordPolicyInvalid(Exception): | ||||
|     """Exception raised when a Password Policy fails""" | ||||
| @ -11,11 +11,11 @@ from django.utils.translation import gettext as _ | ||||
| from django.views.generic import FormView | ||||
| from 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)() | ||||
| @ -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) | ||||
|         } | ||||
							
								
								
									
										30
									
								
								passbook/factors/password/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								passbook/factors/password/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| # Generated by Django 2.2.6 on 2019-10-07 14:07 | ||||
|  | ||||
| import django.contrib.postgres.fields | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0001_initial'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='PasswordFactor', | ||||
|             fields=[ | ||||
|                 ('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')), | ||||
|                 ('backends', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)), | ||||
|                 ('password_policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'Password Factor', | ||||
|                 'verbose_name_plural': 'Password Factors', | ||||
|             }, | ||||
|             bases=('passbook_core.factor',), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,11 +1,11 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-26 08:50 | ||||
| # Generated by Django 2.2.6 on 2019-10-07 14:11 | ||||
| 
 | ||||
| from django.db import migrations | ||||
| 
 | ||||
| 
 | ||||
| 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 = [ | ||||
							
								
								
									
										34
									
								
								passbook/factors/password/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								passbook/factors/password/models.py
									
									
									
									
									
										Normal file
									
								
							| @ -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') | ||||
							
								
								
									
										20
									
								
								passbook/factors/password/signals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								passbook/factors/password/signals.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| """passbook password factor signals""" | ||||
| from django.dispatch import receiver | ||||
|  | ||||
| from passbook.core.signals import password_changed | ||||
| from passbook.factors.password.exceptions import PasswordPolicyInvalid | ||||
|  | ||||
|  | ||||
| @receiver(password_changed) | ||||
| def password_policy_checker(sender, password, **_): | ||||
|     """Run password through all password policies which are applied to the user""" | ||||
|     from passbook.factors.password.models import PasswordFactor | ||||
|     from passbook.policies.engine import PolicyEngine | ||||
|     setattr(sender, '__password__', password) | ||||
|     _all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order') | ||||
|     for factor in _all_factors: | ||||
|         policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses()) | ||||
|         policy_engine.for_user(sender).build() | ||||
|         passing, messages = policy_engine.result | ||||
|         if not passing: | ||||
|             raise PasswordPolicyInvalid(*messages) | ||||
| @ -7,8 +7,10 @@ from django.contrib.sessions.middleware import SessionMiddleware | ||||
| from django.test import RequestFactory, TestCase | ||||
| from django.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): | ||||
| @ -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') | ||||
| @ -1,11 +0,0 @@ | ||||
| """Passbook hibp app config""" | ||||
|  | ||||
| from django.apps import AppConfig | ||||
|  | ||||
|  | ||||
| class PassbookHIBPConfig(AppConfig): | ||||
|     """Passbook hibp app config""" | ||||
|  | ||||
|     name = 'passbook.hibp_policy' | ||||
|     label = 'passbook_hibp_policy' | ||||
|     verbose_name = 'passbook HaveIBeenPwned Policy' | ||||
| @ -1,17 +0,0 @@ | ||||
| # Generated by Django 2.1.7 on 2019-02-25 19:12 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_hibp_policy', '0001_initial'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterModelOptions( | ||||
|             name='haveibeenpwendpolicy', | ||||
|             options={'verbose_name': 'have i been pwned Policy', 'verbose_name_plural': 'have i been pwned Policies'}, | ||||
|         ), | ||||
|     ] | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Langhammer, Jens
					Langhammer, Jens