all: general maintenance, prepare for pyright

This commit is contained in:
Jens Langhammer 2020-02-18 22:12:51 +01:00
parent 865abc005a
commit 9267d0c1dd
12 changed files with 62 additions and 44 deletions

View File

@ -304,7 +304,9 @@ class PropertyMapping(UUIDModel):
form = ""
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."""
try:
expression = NATIVE_ENVIRONMENT.from_string(self.expression)

View File

@ -19,7 +19,7 @@ from structlog import get_logger
from passbook.audit.models import Event, EventAction
from passbook.factors.otp.forms import OTPSetupForm
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
OTP_SESSION_KEY = "passbook_factors_otp_key"

View File

@ -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)

View File

@ -1,4 +1,5 @@
"""passbook util mixins"""
from django.views.decorators.cache import never_cache
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
@ -10,3 +11,12 @@ class CSRFExemptMixin:
def dispatch(self, *args, **kwargs):
"""wrapper to apply @csrf_exempt to CBV"""
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)

View File

@ -1,7 +1,7 @@
"""policy structures"""
from __future__ import annotations
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING, Tuple
from django.db.models import Model
from django.http import HttpRequest
@ -27,8 +27,8 @@ class PolicyRequest:
class PolicyResult:
"""Small data-class to hold policy results"""
passing: bool = False
messages: List[str] = []
passing: bool
messages: Tuple[str]
def __init__(self, passing: bool, *messages: str):
self.passing = passing

View File

@ -15,24 +15,32 @@ from passbook.policies.engine import PolicyEngine
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(
request: HttpRequest, user: User, client: Client
) -> Optional[HttpResponse]:
"""Check permissions, used for
https://django-oidc-provider.readthedocs.io/en/latest/
sections/settings.html#oidc-after-userlogin-hook"""
provider = client_related_provider(client)
if not provider:
return redirect("passbook_providers_oauth:oauth2-permission-denied")
try:
# 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):
application = related_object.application
break
application = provider.application
except Application.DoesNotExist:
return redirect("passbook_providers_oauth:oauth2-permission-denied")
LOGGER.debug(

View File

@ -42,7 +42,11 @@ class AccessRequiredView(AccessMixin, View):
application = get_object_or_404(
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
def _has_access(self) -> bool:

View File

@ -1,4 +1,5 @@
"""Core OAauth Views"""
from typing import Callable, Optional
from django.conf import settings
from django.contrib import messages
@ -23,7 +24,7 @@ LOGGER = get_logger()
class OAuthClientMixin:
"Mixin for getting OAuth client for a source."
client_class = None
client_class: Optional[Callable] = None
def get_client(self, source):
"Get instance of the OAuth client for this source."

View File

@ -21,13 +21,15 @@ class SAMLSource(Source):
@property
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
@property
def additional_info(self):
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>'

View File

@ -4,8 +4,8 @@ from django.urls import path
from passbook.sources.saml.views import ACSView, InitiateView, MetadataView, SLOView
urlpatterns = [
path("<slug:source>/", InitiateView.as_view(), name="login"),
path("<slug:source>/acs/", ACSView.as_view(), name="acs"),
path("<slug:source>/slo/", SLOView.as_view(), name="slo"),
path("<slug:source>/metadata/", MetadataView.as_view(), name="metadata"),
path("<slug:source_slug>/", InitiateView.as_view(), name="login"),
path("<slug:source_slug>/acs/", ACSView.as_view(), name="acs"),
path("<slug:source_slug>/slo/", SLOView.as_view(), name="slo"),
path("<slug:source_slug>/metadata/", MetadataView.as_view(), name="metadata"),
]

View File

@ -24,9 +24,9 @@ from passbook.sources.saml.xml_render import get_authnrequest_xml
class InitiateView(View):
"""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."""
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
if not source.enabled:
raise Http404
sso_destination = request.GET.get("next", None)
@ -56,9 +56,9 @@ class InitiateView(View):
class ACSView(View):
"""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."""
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
if not source.enabled:
raise Http404
# sso_session = request.POST.get('RelayState', None)
@ -74,9 +74,9 @@ class ACSView(View):
class SLOView(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."""
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
if not source.enabled:
raise Http404
logout(request)
@ -93,9 +93,9 @@ class SLOView(View):
class MetadataView(View):
"""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."""
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)
return render_xml(
request,

3
pyrightconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"reportMissingTypeStubs": false
}