Revert "*: providers and sources -> channels, PolicyModel to PolicyBindingModel that uses custom M2M through"

This reverts commit 7ed3ceb960.
This commit is contained in:
Jens Langhammer
2020-05-16 16:02:42 +02:00
parent 7ed3ceb960
commit 406f69080b
293 changed files with 4692 additions and 3244 deletions

View File

@ -16,7 +16,7 @@ class ApplicationSerializer(ModelSerializer):
"name",
"slug",
"skip_authorization",
"outlet",
"provider",
"meta_launch_url",
"meta_icon_url",
"meta_description",

View File

@ -1,31 +0,0 @@
"""Inlet API Views"""
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from passbook.admin.forms.inlet import INLET_SERIALIZER_FIELDS
from passbook.core.models import Inlet
class InletSerializer(ModelSerializer):
"""Inlet Serializer"""
__type__ = SerializerMethodField(method_name="get_type")
def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("inlet", "")
class Meta:
model = Inlet
fields = INLET_SERIALIZER_FIELDS + ["__type__"]
class InletViewSet(ReadOnlyModelViewSet):
"""Inlet Viewset"""
queryset = Inlet.objects.all()
serializer_class = InletSerializer
def get_queryset(self):
return Inlet.objects.select_subclasses()

View File

@ -1,30 +0,0 @@
"""Outlet API Views"""
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from passbook.core.models import Outlet
class OutletSerializer(ModelSerializer):
"""Outlet Serializer"""
__type__ = SerializerMethodField(method_name="get_type")
def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("outlet", "")
class Meta:
model = Outlet
fields = ["pk", "property_mappings", "__type__"]
class OutletViewSet(ReadOnlyModelViewSet):
"""Outlet Viewset"""
queryset = Outlet.objects.all()
serializer_class = OutletSerializer
def get_queryset(self):
return Outlet.objects.select_subclasses()

View File

@ -2,8 +2,8 @@
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from passbook.core.models import Policy
from passbook.policies.forms import GENERAL_FIELDS
from passbook.policies.models import Policy
class PolicySerializer(ModelSerializer):

View File

@ -0,0 +1,30 @@
"""Provider API Views"""
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from passbook.core.models import Provider
class ProviderSerializer(ModelSerializer):
"""Provider Serializer"""
__type__ = SerializerMethodField(method_name="get_type")
def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("provider", "")
class Meta:
model = Provider
fields = ["pk", "property_mappings", "__type__"]
class ProviderViewSet(ReadOnlyModelViewSet):
"""Provider Viewset"""
queryset = Provider.objects.all()
serializer_class = ProviderSerializer
def get_queryset(self):
return Provider.objects.select_subclasses()

View File

@ -0,0 +1,31 @@
"""Source API Views"""
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from passbook.admin.forms.source import SOURCE_SERIALIZER_FIELDS
from passbook.core.models import Source
class SourceSerializer(ModelSerializer):
"""Source Serializer"""
__type__ = SerializerMethodField(method_name="get_type")
def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("source", "")
class Meta:
model = Source
fields = SOURCE_SERIALIZER_FIELDS + ["__type__"]
class SourceViewSet(ReadOnlyModelViewSet):
"""Source Viewset"""
queryset = Source.objects.all()
serializer_class = SourceSerializer
def get_queryset(self):
return Source.objects.select_subclasses()

View File

@ -3,14 +3,14 @@ from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext_lazy as _
from passbook.core.models import Application, Outlet
from passbook.core.models import Application, Provider
class ApplicationForm(forms.ModelForm):
"""Application Form"""
outlet = forms.ModelChoiceField(
queryset=Outlet.objects.all().order_by("pk").select_subclasses(),
provider = forms.ModelChoiceField(
queryset=Provider.objects.all().order_by("pk").select_subclasses(),
required=False,
)
@ -21,7 +21,7 @@ class ApplicationForm(forms.ModelForm):
"name",
"slug",
"skip_authorization",
"outlet",
"provider",
"meta_launch_url",
"meta_icon_url",
"meta_description",

View File

@ -1,4 +1,4 @@
# Generated by Django 3.0.5 on 2020-05-15 19:59
# Generated by Django 2.2.6 on 2019-10-07 14:06
import uuid
@ -7,7 +7,6 @@ import django.contrib.auth.validators
import django.contrib.postgres.fields.jsonb
import django.db.models.deletion
import django.utils.timezone
import guardian.mixins
from django.conf import settings
from django.db import migrations, models
@ -20,7 +19,6 @@ class Migration(migrations.Migration):
dependencies = [
("auth", "0011_update_proxy_permissions"),
("passbook_policies", "0001_initial"),
]
operations = [
@ -107,41 +105,63 @@ class Migration(migrations.Migration):
),
),
("uuid", models.UUIDField(default=uuid.uuid4, editable=False)),
("name", models.TextField(help_text="User's display name.")),
("name", models.TextField()),
("password_change_date", models.DateTimeField(auto_now_add=True)),
(
"attributes",
django.contrib.postgres.fields.jsonb.JSONField(
blank=True, default=dict
),
),
],
options={"permissions": (("reset_user_password", "Reset Password"),),},
bases=(guardian.mixins.GuardianUserMixin, models.Model),
options={
"verbose_name": "user",
"verbose_name_plural": "users",
"abstract": False,
},
managers=[("objects", django.contrib.auth.models.UserManager()),],
),
migrations.CreateModel(
name="Inlet",
name="Policy",
fields=[
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"policybindingmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
to="passbook_policies.PolicyBindingModel",
),
),
("name", models.TextField(help_text="Inlet's display Name.")),
("name", models.TextField(blank=True, null=True)),
(
"slug",
models.SlugField(help_text="Internal source name, used in URLs."),
"action",
models.CharField(
choices=[("allow", "allow"), ("deny", "deny")], max_length=20
),
),
("enabled", models.BooleanField(default=True)),
("negate", models.BooleanField(default=False)),
("order", models.IntegerField(default=0)),
("timeout", models.IntegerField(default=30)),
],
bases=("passbook_policies.policybindingmodel",),
options={"abstract": False,},
),
migrations.CreateModel(
name="PolicyModel",
fields=[
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"policies",
models.ManyToManyField(blank=True, to="passbook_core.Policy"),
),
],
options={"abstract": False,},
),
migrations.CreateModel(
name="PropertyMapping",
@ -156,7 +176,6 @@ class Migration(migrations.Migration):
),
),
("name", models.TextField()),
("expression", models.TextField()),
],
options={
"verbose_name": "Property Mapping",
@ -164,38 +183,74 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
name="UserInletConnection",
name="DebugPolicy",
fields=[
(
"id",
models.AutoField(
"policy_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"inlet",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="passbook_core.Inlet",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
to="passbook_core.Policy",
),
),
("result", models.BooleanField(default=False)),
("wait_min", models.IntegerField(default=5)),
("wait_max", models.IntegerField(default=30)),
],
options={"unique_together": {("user", "inlet")},},
options={
"verbose_name": "Debug Policy",
"verbose_name_plural": "Debug Policies",
},
bases=("passbook_core.policy",),
),
migrations.CreateModel(
name="Outlet",
name="Factor",
fields=[
(
"policymodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.PolicyModel",
),
),
("name", models.TextField()),
("slug", models.SlugField(unique=True)),
("order", models.IntegerField()),
("enabled", models.BooleanField(default=True)),
],
options={"abstract": False,},
bases=("passbook_core.policymodel",),
),
migrations.CreateModel(
name="Source",
fields=[
(
"policymodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.PolicyModel",
),
),
("name", models.TextField()),
("slug", models.SlugField()),
("enabled", models.BooleanField(default=True)),
],
options={"abstract": False,},
bases=("passbook_core.policymodel",),
),
migrations.CreateModel(
name="Provider",
fields=[
(
"id",
@ -215,7 +270,7 @@ class Migration(migrations.Migration):
],
),
migrations.CreateModel(
name="Token",
name="Nonce",
fields=[
(
"uuid",
@ -229,11 +284,10 @@ class Migration(migrations.Migration):
(
"expires",
models.DateTimeField(
default=passbook.core.models.default_token_duration
default=passbook.core.models.default_nonce_duration
),
),
("expiring", models.BooleanField(default=True)),
("description", models.TextField(blank=True, default="")),
(
"user",
models.ForeignKey(
@ -242,14 +296,36 @@ class Migration(migrations.Migration):
),
),
],
options={"verbose_name": "Token", "verbose_name_plural": "Tokens",},
options={"verbose_name": "Nonce", "verbose_name_plural": "Nonces",},
),
migrations.AddField(
model_name="inlet",
name="property_mappings",
field=models.ManyToManyField(
blank=True, default=None, to="passbook_core.PropertyMapping"
),
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",
@ -265,7 +341,7 @@ class Migration(migrations.Migration):
),
("name", models.CharField(max_length=80, verbose_name="name")),
(
"attributes",
"tags",
django.contrib.postgres.fields.jsonb.JSONField(
blank=True, default=dict
),
@ -283,57 +359,11 @@ class Migration(migrations.Migration):
],
options={"unique_together": {("name", "parent")},},
),
migrations.CreateModel(
name="Application",
fields=[
(
"policybindingmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_policies.PolicyBindingModel",
),
),
("name", models.TextField(help_text="Application's display Name.")),
(
"slug",
models.SlugField(
help_text="Internal application name, used in URLs."
),
),
("skip_authorization", models.BooleanField(default=False)),
("meta_launch_url", models.URLField(blank=True, default="")),
("meta_icon_url", models.TextField(blank=True, default="")),
("meta_description", models.TextField(blank=True, default="")),
("meta_publisher", models.TextField(blank=True, default="")),
(
"outlet",
models.OneToOneField(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="passbook_core.Outlet",
),
),
],
bases=("passbook_policies.policybindingmodel",),
),
migrations.AddField(
model_name="user",
name="groups",
field=models.ManyToManyField(to="passbook_core.Group"),
),
migrations.AddField(
model_name="user",
name="inlets",
field=models.ManyToManyField(
through="passbook_core.UserInletConnection", to="passbook_core.Inlet"
),
),
migrations.AddField(
model_name="user",
name="user_permissions",
@ -347,33 +377,74 @@ class Migration(migrations.Migration):
),
),
migrations.CreateModel(
name="PropertyMappingBinding",
name="UserSourceConnection",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("order", models.IntegerField(default=0)),
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"outlet",
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="passbook_core.Outlet",
to=settings.AUTH_USER_MODEL,
),
),
(
"property_mapping",
"source",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="passbook_core.PropertyMapping",
to="passbook_core.Source",
),
),
],
options={"unique_together": {("property_mapping", "outlet", "order")},},
options={"unique_together": {("user", "source")},},
),
migrations.CreateModel(
name="Application",
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()),
("launch_url", models.URLField(blank=True, null=True)),
("icon_url", models.TextField(blank=True, null=True)),
("skip_authorization", models.BooleanField(default=False)),
(
"provider",
models.OneToOneField(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="passbook_core.Provider",
),
),
],
options={"abstract": False,},
bases=("passbook_core.policymodel",),
),
migrations.AddField(
model_name="user",
name="sources",
field=models.ManyToManyField(
through="passbook_core.UserSourceConnection", to="passbook_core.Source"
),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.6 on 2019-10-10 10:58
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0001_initial"),
]
operations = [
migrations.AlterModelOptions(
name="user",
options={"permissions": (("reset_user_password", "Reset Password"),)},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.6 on 2019-10-10 11:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="nonce",
name="description",
field=models.TextField(blank=True, default=""),
),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 2.2.6 on 2019-10-11 09:14
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0002_nonce_description"),
]
operations = [
migrations.RenameField(
model_name="group", old_name="tags", new_name="attributes",
),
migrations.AddField(
model_name="source",
name="property_mappings",
field=models.ManyToManyField(
blank=True, default=None, to="passbook_core.PropertyMapping"
),
),
migrations.AddField(
model_name="user",
name="attributes",
field=django.contrib.postgres.fields.jsonb.JSONField(
blank=True, default=dict
),
),
]

View File

@ -0,0 +1,13 @@
# Generated by Django 2.2.6 on 2019-10-10 15:41
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0002_auto_20191010_1058"),
("passbook_core", "0002_nonce_description"),
]
operations = []

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.6 on 2019-10-14 11:56
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0003_auto_20191011_0914"),
]
operations = [
migrations.RemoveField(model_name="policy", name="action",),
]

View File

@ -0,0 +1,13 @@
# Generated by Django 2.2.6 on 2019-10-25 20:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0004_remove_policy_action"),
("passbook_core", "0003_merge_20191010_1541"),
]
operations = []

View File

@ -0,0 +1,19 @@
# Generated by Django 3.0.3 on 2020-02-17 16:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0005_merge_20191025_2022"),
]
operations = [
migrations.AddField(
model_name="propertymapping",
name="template",
field=models.TextField(default=""),
preserve_default=False,
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 3.0.3 on 2020-02-17 19:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0006_propertymapping_template"),
]
operations = [
migrations.RenameField(
model_name="propertymapping", old_name="template", new_name="expression",
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.0.3 on 2020-02-20 12:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0007_auto_20200217_1934"),
]
operations = [
migrations.RenameField(
model_name="application", old_name="icon_url", new_name="meta_icon_url",
),
migrations.RenameField(
model_name="application", old_name="launch_url", new_name="meta_launch_url",
),
migrations.AddField(
model_name="application",
name="meta_description",
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name="application",
name="meta_publisher",
field=models.TextField(blank=True, null=True),
),
]

View File

@ -0,0 +1,52 @@
# Generated by Django 3.0.3 on 2020-02-21 14:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0008_auto_20200220_1242"),
]
operations = [
migrations.AlterField(
model_name="application",
name="name",
field=models.TextField(help_text="Application's display Name."),
),
migrations.AlterField(
model_name="application",
name="slug",
field=models.SlugField(
help_text="Internal application name, used in URLs."
),
),
migrations.AlterField(
model_name="factor",
name="name",
field=models.TextField(help_text="Factor's display Name."),
),
migrations.AlterField(
model_name="factor",
name="slug",
field=models.SlugField(
help_text="Internal factor name, used in URLs.", unique=True
),
),
migrations.AlterField(
model_name="source",
name="name",
field=models.TextField(help_text="Source's display Name."),
),
migrations.AlterField(
model_name="source",
name="slug",
field=models.SlugField(help_text="Internal source name, used in URLs."),
),
migrations.AlterField(
model_name="user",
name="name",
field=models.TextField(help_text="User's display name."),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 3.0.3 on 2020-02-21 22:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0009_auto_20200221_1410"),
]
operations = [
migrations.AlterField(
model_name="application",
name="meta_description",
field=models.TextField(blank=True, default=""),
),
migrations.AlterField(
model_name="application",
name="meta_icon_url",
field=models.TextField(blank=True, default=""),
),
migrations.AlterField(
model_name="application",
name="meta_launch_url",
field=models.URLField(blank=True, default=""),
),
migrations.AlterField(
model_name="application",
name="meta_publisher",
field=models.TextField(blank=True, default=""),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 3.0.3 on 2020-02-22 18:22
from django.db import migrations
def fix_application_null(apps, schema_editor):
"""Fix Application meta_fields being null"""
Application = apps.get_model("passbook_core", "Application")
for app in Application.objects.all():
if app.meta_launch_url is None:
app.meta_launch_url = ""
if app.meta_icon_url is None:
app.meta_icon_url = ""
if app.meta_description is None:
app.meta_description = ""
if app.meta_publisher is None:
app.meta_publisher = ""
app.save()
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0010_auto_20200221_2208"),
]
operations = [migrations.RunPython(fix_application_null)]

View File

@ -0,0 +1,14 @@
# Generated by Django 3.0.3 on 2020-05-08 17:58
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0011_auto_20200222_1822"),
]
operations = [
migrations.DeleteModel(name="Factor",),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 3.0.5 on 2020-05-10 10:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_policies", "0003_auto_20200508_1642"),
("passbook_stages_password", "0001_initial"),
("passbook_core", "0012_delete_factor"),
]
operations = [
migrations.DeleteModel(name="DebugPolicy",),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 3.0.5 on 2020-05-11 19:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0013_delete_debugpolicy"),
]
operations = [
migrations.DeleteModel(name="Invitation",),
]

View File

@ -10,6 +10,7 @@ from django.db import models
from django.http import HttpRequest
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_prometheus.models import ExportModelOperationsMixin
from guardian.mixins import GuardianUserMixin
from jinja2 import Undefined
from jinja2.exceptions import TemplateSyntaxError, UndefinedError
@ -21,18 +22,19 @@ from passbook.core.exceptions import PropertyMappingExpressionException
from passbook.core.signals import password_changed
from passbook.core.types import UILoginButton, UIUserSettings
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
from passbook.policies.models import PolicyBindingModel
from passbook.policies.exceptions import PolicyException
from passbook.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger()
NATIVE_ENVIRONMENT = NativeEnvironment()
def default_token_duration():
"""Default duration a Token is valid"""
def default_nonce_duration():
"""Default duration a Nonce is valid"""
return now() + timedelta(minutes=30)
class Group(UUIDModel):
class Group(ExportModelOperationsMixin("group"), UUIDModel):
"""Custom Group model which supports a basic hierarchy"""
name = models.CharField(_("name"), max_length=80)
@ -53,13 +55,13 @@ class Group(UUIDModel):
unique_together = (("name", "parent",),)
class User(GuardianUserMixin, AbstractUser):
class User(ExportModelOperationsMixin("user"), GuardianUserMixin, AbstractUser):
"""Custom User model to allow easier adding o f user-based settings"""
uuid = models.UUIDField(default=uuid4, editable=False)
name = models.TextField(help_text=_("User's display name."))
inlets = models.ManyToManyField("Inlet", through="UserInletConnection")
sources = models.ManyToManyField("Source", through="UserSourceConnection")
groups = models.ManyToManyField("Group")
password_change_date = models.DateTimeField(auto_now_add=True)
@ -76,7 +78,29 @@ class User(GuardianUserMixin, AbstractUser):
permissions = (("reset_user_password", "Reset Password"),)
class Application(PolicyBindingModel):
class Provider(ExportModelOperationsMixin("provider"), models.Model):
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
property_mappings = models.ManyToManyField(
"PropertyMapping", default=None, blank=True
)
objects = InheritanceManager()
# This class defines no field for easier inheritance
def __str__(self):
if hasattr(self, "name"):
return getattr(self, "name")
return super().__str__()
class PolicyModel(UUIDModel, CreatedUpdatedModel):
"""Base model which can have policies applied to it"""
policies = models.ManyToManyField("Policy", blank=True)
class Application(ExportModelOperationsMixin("application"), PolicyModel):
"""Every Application which uses passbook for authentication/identification/authorization
needs an Application record. Other authentication types can subclass this Model to
add custom fields and other properties"""
@ -84,8 +108,8 @@ class Application(PolicyBindingModel):
name = models.TextField(help_text=_("Application's display Name."))
slug = models.SlugField(help_text=_("Internal application name, used in URLs."))
skip_authorization = models.BooleanField(default=False)
outlet = models.OneToOneField(
"Outlet", null=True, blank=True, default=None, on_delete=models.SET_DEFAULT
provider = models.OneToOneField(
"Provider", null=True, blank=True, default=None, on_delete=models.SET_DEFAULT
)
meta_launch_url = models.URLField(default="", blank=True)
@ -95,20 +119,20 @@ class Application(PolicyBindingModel):
objects = InheritanceManager()
def get_outlet(self) -> Optional["Outlet"]:
"""Get casted outlet instance"""
if not self.outlet:
def get_provider(self) -> Optional[Provider]:
"""Get casted provider instance"""
if not self.provider:
return None
return Outlet.objects.get_subclass(pk=self.outlet.pk)
return Provider.objects.get_subclass(pk=self.provider.pk)
def __str__(self):
return self.name
class Inlet(PolicyBindingModel):
class Source(ExportModelOperationsMixin("source"), PolicyModel):
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
name = models.TextField(help_text=_("Inlet's display Name."))
name = models.TextField(help_text=_("Source's display Name."))
slug = models.SlugField(help_text=_("Internal source name, used in URLs."))
enabled = models.BooleanField(default=True)
@ -141,69 +165,56 @@ class Inlet(PolicyBindingModel):
return self.name
class UserInletConnection(CreatedUpdatedModel):
"""Connection between User and Inlet."""
class UserSourceConnection(CreatedUpdatedModel):
"""Connection between User and Source."""
user = models.ForeignKey(User, on_delete=models.CASCADE)
inlet = models.ForeignKey(Inlet, on_delete=models.CASCADE)
source = models.ForeignKey(Source, on_delete=models.CASCADE)
class Meta:
unique_together = (("user", "inlet"),)
unique_together = (("user", "source"),)
class Token(UUIDModel):
class Policy(ExportModelOperationsMixin("policy"), UUIDModel, CreatedUpdatedModel):
"""Policies which specify if a user is authorized to use an Application. Can be overridden by
other types to add other fields, more logic, etc."""
name = models.TextField(blank=True, null=True)
negate = models.BooleanField(default=False)
order = models.IntegerField(default=0)
timeout = models.IntegerField(default=30)
objects = InheritanceManager()
def __str__(self):
return f"Policy {self.name}"
def passes(self, request: PolicyRequest) -> PolicyResult:
"""Check if user instance passes this policy"""
raise PolicyException()
class Nonce(ExportModelOperationsMixin("nonce"), UUIDModel):
"""One-time link for password resets/sign-up-confirmations"""
expires = models.DateTimeField(default=default_token_duration)
user = models.ForeignKey("User", on_delete=models.CASCADE, related_name="+")
expires = models.DateTimeField(default=default_nonce_duration)
user = models.ForeignKey("User", on_delete=models.CASCADE)
expiring = models.BooleanField(default=True)
description = models.TextField(default="", blank=True)
@property
def is_expired(self) -> bool:
"""Check if token is expired yet."""
"""Check if nonce is expired yet."""
return now() > self.expires
def __str__(self):
return f"Token f{self.uuid.hex} {self.description} (expires={self.expires})"
return f"Nonce f{self.uuid.hex} {self.description} (expires={self.expires})"
class Meta:
verbose_name = _("Token")
verbose_name_plural = _("Tokens")
class Outlet(models.Model):
"""Application-independent Outlet instance. For example SAML2 Remote, OAuth2 Application"""
property_mappings = models.ManyToManyField(
"PropertyMapping", default=None, blank=True
)
objects = InheritanceManager()
# This class defines no field for easier inheritance
def __str__(self):
if hasattr(self, "name"):
return getattr(self, "name")
return super().__str__()
class PropertyMappingBinding(UUIDModel):
"""Binds a PropertyMapping instance to an outlet"""
property_mapping = models.ForeignKey("PropertyMapping", on_delete=models.CASCADE)
outlet = models.ForeignKey("Outlet", on_delete=models.CASCADE)
order = models.IntegerField(default=0)
def __str__(self):
return f"PropertyMapping Binding p={self.property_mapping} outlet={self.outlet}"
class Meta:
unique_together = (("property_mapping", "outlet", "order"),)
verbose_name = _("Nonce")
verbose_name_plural = _("Nonces")
class PropertyMapping(UUIDModel):

View File

@ -17,7 +17,7 @@ password_changed = Signal(providing_args=["user", "password"])
# pylint: disable=unused-argument
def invalidate_policy_cache(sender, instance, **_):
"""Invalidate Policy cache when policy is updated"""
from passbook.policies.models import Policy
from passbook.core.models import Policy
from passbook.policies.process import cache_key
if isinstance(instance, Policy):

View File

@ -2,14 +2,14 @@
from django.utils.timezone import now
from structlog import get_logger
from passbook.core.models import Token
from passbook.core.models import Nonce
from passbook.root.celery import CELERY_APP
LOGGER = get_logger()
@CELERY_APP.task()
def clean_tokens():
"""Remove expired tokens"""
amount, _ = Token.objects.filter(expires__lt=now(), expiring=True).delete()
LOGGER.debug("Deleted expired tokens", amount=amount)
def clean_nonces():
"""Remove expired nonces"""
amount, _ = Nonce.objects.filter(expires__lt=now(), expiring=True).delete()
LOGGER.debug("Deleted expired nonces", amount=amount)

View File

@ -34,17 +34,17 @@
</ul>
</section>
{% endif %}
{% user_inlets as user_inlets_loc %}
{% if user_inlets_loc %}
{% user_sources as user_sources_loc %}
{% if user_sources_loc %}
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'Sources' %}</h2>
<ul class="pf-c-nav__list">
{% for inlet in user_inlets_loc %}
{% for source in user_sources_loc %}
<li class="pf-c-nav__item">
<a href="{{ inlet.view_name }}"
<a href="{{ source.view_name }}"
class="pf-c-nav__link {% if user_settings.view_name == request.get_full_path %} pf-m-current {% endif %}">
<i class="{{ inlet.icon }}"></i>
{{ inlet.name }}
<i class="{{ source.icon }}"></i>
{{ source.name }}
</a>
</li>
{% endfor %}

View File

@ -4,7 +4,7 @@ from typing import Iterable, List
from django import template
from django.template.context import RequestContext
from passbook.core.models import Inlet
from passbook.core.models import Source
from passbook.core.types import UIUserSettings
from passbook.flows.models import Stage
from passbook.policies.engine import PolicyEngine
@ -27,14 +27,14 @@ def user_stages(context: RequestContext) -> List[UIUserSettings]:
@register.simple_tag(takes_context=True)
def user_inlets(context: RequestContext) -> List[UIUserSettings]:
"""Return a list of all inlets which are enabled for the user"""
def user_sources(context: RequestContext) -> List[UIUserSettings]:
"""Return a list of all sources which are enabled for the user"""
user = context.get("request").user
_all_inlets: Iterable[(Inlet)] = (
(Inlet).objects.filter(enabled=True).select_subclasses()
_all_sources: Iterable[Source] = (
Source.objects.filter(enabled=True).select_subclasses()
)
matching_inlets: List[UIUserSettings] = []
for source in _all_inlets:
matching_sources: List[UIUserSettings] = []
for source in _all_sources:
user_settings = source.ui_user_settings
if not user_settings:
continue
@ -43,5 +43,5 @@ def user_inlets(context: RequestContext) -> List[UIUserSettings]:
)
policy_engine.build()
if policy_engine.passing:
matching_inlets.append(user_settings)
return matching_inlets
matching_sources.append(user_settings)
return matching_sources

View File

@ -6,7 +6,7 @@ from django.http import HttpRequest
from django.utils.translation import gettext as _
from structlog import get_logger
from passbook.core.models import Application, Outlet, User
from passbook.core.models import Application, Provider, User
from passbook.policies.engine import PolicyEngine
LOGGER = get_logger()
@ -14,19 +14,22 @@ LOGGER = get_logger()
class AccessMixin:
"""Mixin class for usage in Authorization views.
Outlet functions to check application access, etc"""
Provider functions to check application access, etc"""
# request is set by view but since this Mixin has no base class
request: HttpRequest = None
def outlet_to_application(self, outlet: Outlet) -> Application:
"""Lookup application assigned to outlet, throw error if no application assigned"""
def provider_to_application(self, provider: Provider) -> Application:
"""Lookup application assigned to provider, throw error if no application assigned"""
try:
return outlet.application
return provider.application
except Application.DoesNotExist as exc:
messages.error(
self.request,
_('Outlet "%(name)s" has no application assigned' % {"name": outlet}),
_(
'Provider "%(name)s" has no application assigned'
% {"name": provider}
),
)
raise exc