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