all: general maintenance, prepare for pyright
This commit is contained in:
parent
865abc005a
commit
9267d0c1dd
|
@ -304,7 +304,9 @@ class PropertyMapping(UUIDModel):
|
||||||
form = ""
|
form = ""
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
def evaluate(self, user: User, request: HttpRequest, **kwargs) -> Any:
|
def evaluate(
|
||||||
|
self, user: Optional[User], request: Optional[HttpRequest], **kwargs
|
||||||
|
) -> Any:
|
||||||
"""Evaluate `self.expression` using `**kwargs` as Context."""
|
"""Evaluate `self.expression` using `**kwargs` as Context."""
|
||||||
try:
|
try:
|
||||||
expression = NATIVE_ENVIRONMENT.from_string(self.expression)
|
expression = NATIVE_ENVIRONMENT.from_string(self.expression)
|
||||||
|
|
|
@ -19,7 +19,7 @@ from structlog import get_logger
|
||||||
from passbook.audit.models import Event, EventAction
|
from passbook.audit.models import Event, EventAction
|
||||||
from passbook.factors.otp.forms import OTPSetupForm
|
from passbook.factors.otp.forms import OTPSetupForm
|
||||||
from passbook.factors.otp.utils import otpauth_url
|
from passbook.factors.otp.utils import otpauth_url
|
||||||
from passbook.lib.boilerplate import NeverCacheMixin
|
from passbook.lib.mixins import NeverCacheMixin
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
OTP_SESSION_KEY = "passbook_factors_otp_key"
|
OTP_SESSION_KEY = "passbook_factors_otp_key"
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
"""passbook django boilerplate code"""
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views.decorators.cache import never_cache
|
|
||||||
|
|
||||||
|
|
||||||
class NeverCacheMixin:
|
|
||||||
"""Use never_cache as mixin for CBV"""
|
|
||||||
|
|
||||||
@method_decorator(never_cache)
|
|
||||||
def dispatch(self, *args, **kwargs):
|
|
||||||
"""Use never_cache as mixin for CBV"""
|
|
||||||
return super().dispatch(*args, **kwargs)
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""passbook util mixins"""
|
"""passbook util mixins"""
|
||||||
|
from django.views.decorators.cache import never_cache
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
@ -10,3 +11,12 @@ class CSRFExemptMixin:
|
||||||
def dispatch(self, *args, **kwargs):
|
def dispatch(self, *args, **kwargs):
|
||||||
"""wrapper to apply @csrf_exempt to CBV"""
|
"""wrapper to apply @csrf_exempt to CBV"""
|
||||||
return super().dispatch(*args, **kwargs)
|
return super().dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class NeverCacheMixin:
|
||||||
|
"""Use never_cache as mixin for CBV"""
|
||||||
|
|
||||||
|
@method_decorator(never_cache)
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
"""Use never_cache as mixin for CBV"""
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""policy structures"""
|
"""policy structures"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
@ -27,8 +27,8 @@ class PolicyRequest:
|
||||||
class PolicyResult:
|
class PolicyResult:
|
||||||
"""Small data-class to hold policy results"""
|
"""Small data-class to hold policy results"""
|
||||||
|
|
||||||
passing: bool = False
|
passing: bool
|
||||||
messages: List[str] = []
|
messages: Tuple[str]
|
||||||
|
|
||||||
def __init__(self, passing: bool, *messages: str):
|
def __init__(self, passing: bool, *messages: str):
|
||||||
self.passing = passing
|
self.passing = passing
|
||||||
|
|
|
@ -15,24 +15,32 @@ from passbook.policies.engine import PolicyEngine
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
def client_related_provider(client: Client) -> Optional[Provider]:
|
||||||
|
"""Lookup related Application from Client"""
|
||||||
|
# because oidc_provider is also used by app_gw, we can't be
|
||||||
|
# sure an OpenIDPRovider instance exists. hence we look through all related models
|
||||||
|
# and choose the one that inherits from Provider, which is guaranteed to
|
||||||
|
# have the application property
|
||||||
|
collector = Collector(using="default")
|
||||||
|
collector.collect([client])
|
||||||
|
for _, related in collector.data.items():
|
||||||
|
related_object = next(iter(related))
|
||||||
|
if isinstance(related_object, Provider):
|
||||||
|
return related_object
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def check_permissions(
|
def check_permissions(
|
||||||
request: HttpRequest, user: User, client: Client
|
request: HttpRequest, user: User, client: Client
|
||||||
) -> Optional[HttpResponse]:
|
) -> Optional[HttpResponse]:
|
||||||
"""Check permissions, used for
|
"""Check permissions, used for
|
||||||
https://django-oidc-provider.readthedocs.io/en/latest/
|
https://django-oidc-provider.readthedocs.io/en/latest/
|
||||||
sections/settings.html#oidc-after-userlogin-hook"""
|
sections/settings.html#oidc-after-userlogin-hook"""
|
||||||
|
provider = client_related_provider(client)
|
||||||
|
if not provider:
|
||||||
|
return redirect("passbook_providers_oauth:oauth2-permission-denied")
|
||||||
try:
|
try:
|
||||||
# because oidc_provider is also used by app_gw, we can't be
|
application = provider.application
|
||||||
# sure an OpenIDPRovider instance exists. hence we look through all related models
|
|
||||||
# and choose the one that inherits from Provider, which is guaranteed to
|
|
||||||
# have the application property
|
|
||||||
collector = Collector(using="default")
|
|
||||||
collector.collect([client])
|
|
||||||
for _, related in collector.data.items():
|
|
||||||
related_object = next(iter(related))
|
|
||||||
if isinstance(related_object, Provider):
|
|
||||||
application = related_object.application
|
|
||||||
break
|
|
||||||
except Application.DoesNotExist:
|
except Application.DoesNotExist:
|
||||||
return redirect("passbook_providers_oauth:oauth2-permission-denied")
|
return redirect("passbook_providers_oauth:oauth2-permission-denied")
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
|
|
|
@ -42,7 +42,11 @@ class AccessRequiredView(AccessMixin, View):
|
||||||
application = get_object_or_404(
|
application = get_object_or_404(
|
||||||
Application, slug=self.kwargs["application"]
|
Application, slug=self.kwargs["application"]
|
||||||
)
|
)
|
||||||
self._provider = get_object_or_404(SAMLProvider, pk=application.provider_id)
|
provider: SAMLProvider = get_object_or_404(
|
||||||
|
SAMLProvider, pk=application.provider_id
|
||||||
|
)
|
||||||
|
self._provider = provider
|
||||||
|
return self._provider
|
||||||
return self._provider
|
return self._provider
|
||||||
|
|
||||||
def _has_access(self) -> bool:
|
def _has_access(self) -> bool:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""Core OAauth Views"""
|
"""Core OAauth Views"""
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -23,7 +24,7 @@ LOGGER = get_logger()
|
||||||
class OAuthClientMixin:
|
class OAuthClientMixin:
|
||||||
"Mixin for getting OAuth client for a source."
|
"Mixin for getting OAuth client for a source."
|
||||||
|
|
||||||
client_class = None
|
client_class: Optional[Callable] = None
|
||||||
|
|
||||||
def get_client(self, source):
|
def get_client(self, source):
|
||||||
"Get instance of the OAuth client for this source."
|
"Get instance of the OAuth client for this source."
|
||||||
|
|
|
@ -21,13 +21,15 @@ class SAMLSource(Source):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def login_button(self):
|
def login_button(self):
|
||||||
url = reverse_lazy("passbook_sources_saml:login", kwargs={"source": self.slug})
|
url = reverse_lazy(
|
||||||
|
"passbook_sources_saml:login", kwargs={"source_slug": self.slug}
|
||||||
|
)
|
||||||
return url, "", self.name
|
return url, "", self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_info(self):
|
def additional_info(self):
|
||||||
metadata_url = reverse_lazy(
|
metadata_url = reverse_lazy(
|
||||||
"passbook_sources_saml:metadata", kwargs={"source": self}
|
"passbook_sources_saml:metadata", kwargs={"source_slug": self}
|
||||||
)
|
)
|
||||||
return f'<a href="{metadata_url}" class="btn btn-default btn-sm">Metadata Download</a>'
|
return f'<a href="{metadata_url}" class="btn btn-default btn-sm">Metadata Download</a>'
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ from django.urls import path
|
||||||
from passbook.sources.saml.views import ACSView, InitiateView, MetadataView, SLOView
|
from passbook.sources.saml.views import ACSView, InitiateView, MetadataView, SLOView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("<slug:source>/", InitiateView.as_view(), name="login"),
|
path("<slug:source_slug>/", InitiateView.as_view(), name="login"),
|
||||||
path("<slug:source>/acs/", ACSView.as_view(), name="acs"),
|
path("<slug:source_slug>/acs/", ACSView.as_view(), name="acs"),
|
||||||
path("<slug:source>/slo/", SLOView.as_view(), name="slo"),
|
path("<slug:source_slug>/slo/", SLOView.as_view(), name="slo"),
|
||||||
path("<slug:source>/metadata/", MetadataView.as_view(), name="metadata"),
|
path("<slug:source_slug>/metadata/", MetadataView.as_view(), name="metadata"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -24,9 +24,9 @@ from passbook.sources.saml.xml_render import get_authnrequest_xml
|
||||||
class InitiateView(View):
|
class InitiateView(View):
|
||||||
"""Get the Form with SAML Request, which sends us to the IDP"""
|
"""Get the Form with SAML Request, which sends us to the IDP"""
|
||||||
|
|
||||||
def get(self, request: HttpRequest, source: str) -> HttpResponse:
|
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||||
"""Replies with an XHTML SSO Request."""
|
"""Replies with an XHTML SSO Request."""
|
||||||
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
||||||
if not source.enabled:
|
if not source.enabled:
|
||||||
raise Http404
|
raise Http404
|
||||||
sso_destination = request.GET.get("next", None)
|
sso_destination = request.GET.get("next", None)
|
||||||
|
@ -56,9 +56,9 @@ class InitiateView(View):
|
||||||
class ACSView(View):
|
class ACSView(View):
|
||||||
"""AssertionConsumerService, consume assertion and log user in"""
|
"""AssertionConsumerService, consume assertion and log user in"""
|
||||||
|
|
||||||
def post(self, request: HttpRequest, source: str) -> HttpResponse:
|
def post(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||||
"""Handles a POSTed SSO Assertion and logs the user in."""
|
"""Handles a POSTed SSO Assertion and logs the user in."""
|
||||||
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
||||||
if not source.enabled:
|
if not source.enabled:
|
||||||
raise Http404
|
raise Http404
|
||||||
# sso_session = request.POST.get('RelayState', None)
|
# sso_session = request.POST.get('RelayState', None)
|
||||||
|
@ -74,9 +74,9 @@ class ACSView(View):
|
||||||
class SLOView(View):
|
class SLOView(View):
|
||||||
"""Single-Logout-View"""
|
"""Single-Logout-View"""
|
||||||
|
|
||||||
def dispatch(self, request: HttpRequest, source: str) -> HttpResponse:
|
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||||
"""Replies with an XHTML SSO Request."""
|
"""Replies with an XHTML SSO Request."""
|
||||||
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
||||||
if not source.enabled:
|
if not source.enabled:
|
||||||
raise Http404
|
raise Http404
|
||||||
logout(request)
|
logout(request)
|
||||||
|
@ -93,9 +93,9 @@ class SLOView(View):
|
||||||
class MetadataView(View):
|
class MetadataView(View):
|
||||||
"""Return XML Metadata for IDP"""
|
"""Return XML Metadata for IDP"""
|
||||||
|
|
||||||
def dispatch(self, request: HttpRequest, source: str) -> HttpResponse:
|
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||||
"""Replies with the XML Metadata SPSSODescriptor."""
|
"""Replies with the XML Metadata SPSSODescriptor."""
|
||||||
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
||||||
entity_id = get_entity_id(request, source)
|
entity_id = get_entity_id(request, source)
|
||||||
return render_xml(
|
return render_xml(
|
||||||
request,
|
request,
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"reportMissingTypeStubs": false
|
||||||
|
}
|
Reference in New Issue