sources/oauth: split up single large "core" views

This commit is contained in:
Jens Langhammer 2020-07-09 23:08:28 +02:00
parent 2d2b2d08f4
commit c70310730a
14 changed files with 168 additions and 119 deletions

View file

@ -1,10 +1,10 @@
"""AzureAD OAuth2 Views"""
import uuid
from typing import Any, Dict
from uuid import UUID
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
from passbook.sources.oauth.views.core import OAuthCallback
from passbook.sources.oauth.views.callback import OAuthCallback
@MANAGER.source(kind=RequestKind.callback, name="Azure AD")
@ -12,7 +12,7 @@ class AzureADOAuthCallback(OAuthCallback):
"""AzureAD OAuth2 Callback"""
def get_user_id(self, source: OAuthSource, info: Dict[str, Any]) -> str:
return str(uuid.UUID(info.get("objectId")).int)
return str(UUID(info.get("objectId")).int)
def get_user_enroll_context(
self,

View file

@ -3,7 +3,8 @@ from typing import Any, Dict
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
from passbook.sources.oauth.views.callback import OAuthCallback
from passbook.sources.oauth.views.redirect import OAuthRedirect
@MANAGER.source(kind=RequestKind.redirect, name="Discord")

View file

@ -6,7 +6,8 @@ from facebook import GraphAPI
from passbook.sources.oauth.clients import OAuth2Client
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
from passbook.sources.oauth.views.callback import OAuthCallback
from passbook.sources.oauth.views.redirect import OAuthRedirect
@MANAGER.source(kind=RequestKind.redirect, name="Facebook")

View file

@ -3,7 +3,7 @@ from typing import Any, Dict
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
from passbook.sources.oauth.views.core import OAuthCallback
from passbook.sources.oauth.views.callback import OAuthCallback
@MANAGER.source(kind=RequestKind.callback, name="GitHub")

View file

@ -3,7 +3,8 @@ from typing import Any, Dict
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
from passbook.sources.oauth.views.callback import OAuthCallback
from passbook.sources.oauth.views.redirect import OAuthRedirect
@MANAGER.source(kind=RequestKind.redirect, name="Google")

View file

@ -6,7 +6,8 @@ from django.utils.text import slugify
from structlog import get_logger
from passbook.sources.oauth.models import OAuthSource
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
from passbook.sources.oauth.views.callback import OAuthCallback
from passbook.sources.oauth.views.redirect import OAuthRedirect
LOGGER = get_logger()

View file

@ -3,7 +3,8 @@ from typing import Any, Dict
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
from passbook.sources.oauth.views.callback import OAuthCallback
from passbook.sources.oauth.views.redirect import OAuthRedirect
@MANAGER.source(kind=RequestKind.redirect, name="OpenID Connect")

View file

@ -6,7 +6,8 @@ from requests.auth import HTTPBasicAuth
from passbook.sources.oauth.clients import OAuth2Client
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.types.manager import MANAGER, RequestKind
from passbook.sources.oauth.views.core import OAuthCallback, OAuthRedirect
from passbook.sources.oauth.views.callback import OAuthCallback
from passbook.sources.oauth.views.redirect import OAuthRedirect
@MANAGER.source(kind=RequestKind.redirect, name="reddit")

View file

@ -2,9 +2,9 @@
from typing import Any, Dict
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.views.callback import OAuthCallback
# from passbook.sources.oauth.types.manager import MANAGER, RequestKind
from passbook.sources.oauth.views.core import OAuthCallback
# @MANAGER.source(kind=RequestKind.callback, name="Twitter")

View file

@ -1,29 +1,30 @@
"""passbook oauth_client urls"""
"""passbook OAuth source urls"""
from django.urls import path
from passbook.sources.oauth.types.manager import RequestKind
from passbook.sources.oauth.views import core, dispatcher, user
from passbook.sources.oauth.views.dispatcher import DispatcherView
from passbook.sources.oauth.views.user import DisconnectView, UserSettingsView
urlpatterns = [
path(
"login/<slug:source_slug>/",
dispatcher.DispatcherView.as_view(kind=RequestKind.redirect),
DispatcherView.as_view(kind=RequestKind.redirect),
name="oauth-client-login",
),
path(
"callback/<slug:source_slug>/",
dispatcher.DispatcherView.as_view(kind=RequestKind.callback),
DispatcherView.as_view(kind=RequestKind.callback),
name="oauth-client-callback",
),
path(
"disconnect/<slug:source_slug>/",
core.DisconnectView.as_view(),
name="oauth-client-disconnect",
),
path(
"user/<slug:source_slug>/",
user.UserSettingsView.as_view(),
UserSettingsView.as_view(),
name="oauth-client-user",
),
path(
"user/<slug:source_slug>/disconnect/",
DisconnectView.as_view(),
name="oauth-client-disconnect",
),
]

View file

@ -0,0 +1,19 @@
"""OAuth Base views"""
from typing import Callable, Optional
from passbook.sources.oauth.clients import BaseOAuthClient, get_client
from passbook.sources.oauth.models import OAuthSource
# pylint: disable=too-few-public-methods
class OAuthClientMixin:
"Mixin for getting OAuth client for a source."
client_class: Optional[Callable] = None
def get_client(self, source: OAuthSource) -> BaseOAuthClient:
"Get instance of the OAuth client for this source."
if self.client_class is not None:
# pylint: disable=not-callable
return self.client_class(source)
return get_client(source)

View file

@ -1,11 +1,10 @@
"""Core OAauth Views"""
"""OAuth Callback Views"""
from typing import Any, Callable, Dict, Optional
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.views.generic import RedirectView, View
@ -25,62 +24,13 @@ from passbook.policies.utils import delete_none_keys
from passbook.sources.oauth.auth import AuthorizedServiceBackend
from passbook.sources.oauth.clients import BaseOAuthClient, get_client
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.views.base import OAuthClientMixin
from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
# pylint: disable=too-few-public-methods
class OAuthClientMixin:
"Mixin for getting OAuth client for a source."
client_class: Optional[Callable] = None
def get_client(self, source: OAuthSource) -> BaseOAuthClient:
"Get instance of the OAuth client for this source."
if self.client_class is not None:
# pylint: disable=not-callable
return self.client_class(source)
return get_client(source)
class OAuthRedirect(OAuthClientMixin, RedirectView):
"Redirect user to OAuth source to enable access."
permanent = False
params = None
# pylint: disable=unused-argument
def get_additional_parameters(self, source: OAuthSource) -> Dict[str, Any]:
"Return additional redirect parameters for this source."
return self.params or {}
def get_callback_url(self, source: OAuthSource) -> str:
"Return the callback url for this source."
return reverse(
"passbook_sources_oauth:oauth-client-callback",
kwargs={"source_slug": source.slug},
)
def get_redirect_url(self, **kwargs) -> str:
"Build redirect url for a given source."
slug = kwargs.get("source_slug", "")
try:
source = OAuthSource.objects.get(slug=slug)
except OAuthSource.DoesNotExist:
raise Http404(f"Unknown OAuth source '{slug}'.")
else:
if not source.enabled:
raise Http404(f"source {slug} is not enabled.")
client = self.get_client(source)
callback = self.get_callback_url(source)
params = self.get_additional_parameters(source)
return client.get_redirect_url(
self.request, callback=callback, parameters=params
)
class OAuthCallback(OAuthClientMixin, View):
"Base OAuth callback view."
@ -258,46 +208,3 @@ class OAuthCallback(OAuthClientMixin, View):
)
}
return self.handle_login_flow(source.enrollment_flow, **context)
class DisconnectView(LoginRequiredMixin, View):
"""Delete connection with source"""
source = None
aas = None
def dispatch(self, request, source_slug):
self.source = get_object_or_404(OAuthSource, slug=source_slug)
self.aas = get_object_or_404(
UserOAuthSourceConnection, source=self.source, user=request.user
)
return super().dispatch(request, source_slug)
def post(self, request, source_slug):
"""Delete connection object"""
if "confirmdelete" in request.POST:
# User confirmed deletion
self.aas.delete()
messages.success(request, _("Connection successfully deleted"))
return redirect(
reverse(
"passbook_sources_oauth:oauth-client-user",
kwargs={"source_slug": self.source.slug},
)
)
return self.get(request, source_slug)
# pylint: disable=unused-argument
def get(self, request, source_slug):
"""Show delete form"""
return render(
request,
"generic/delete.html",
{
"object": self.source,
"delete_url": reverse(
"passbook_sources_oauth:oauth-client-disconnect",
kwargs={"source_slug": self.source.slug},
),
},
)

View file

@ -0,0 +1,67 @@
"""OAuth Redirect Views"""
from typing import Any, Callable, Dict, Optional
from django.conf import settings
from django.contrib import messages
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.views.generic import RedirectView, View
from structlog import get_logger
from passbook.audit.models import Event, EventAction
from passbook.core.models import User
from passbook.flows.models import Flow
from passbook.flows.planner import (
PLAN_CONTEXT_PENDING_USER,
PLAN_CONTEXT_SSO,
FlowPlanner,
)
from passbook.flows.views import SESSION_KEY_PLAN
from passbook.lib.utils.urls import redirect_with_qs
from passbook.policies.utils import delete_none_keys
from passbook.sources.oauth.auth import AuthorizedServiceBackend
from passbook.sources.oauth.clients import BaseOAuthClient, get_client
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from passbook.sources.oauth.views.base import OAuthClientMixin
from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
class OAuthRedirect(OAuthClientMixin, RedirectView):
"Redirect user to OAuth source to enable access."
permanent = False
params = None
# pylint: disable=unused-argument
def get_additional_parameters(self, source: OAuthSource) -> Dict[str, Any]:
"Return additional redirect parameters for this source."
return self.params or {}
def get_callback_url(self, source: OAuthSource) -> str:
"Return the callback url for this source."
return reverse(
"passbook_sources_oauth:oauth-client-callback",
kwargs={"source_slug": source.slug},
)
def get_redirect_url(self, **kwargs) -> str:
"Build redirect url for a given source."
slug = kwargs.get("source_slug", "")
try:
source = OAuthSource.objects.get(slug=slug)
except OAuthSource.DoesNotExist:
raise Http404(f"Unknown OAuth source '{slug}'.")
else:
if not source.enabled:
raise Http404(f"source {slug} is not enabled.")
client = self.get_client(source)
callback = self.get_callback_url(source)
params = self.get_additional_parameters(source)
return client.get_redirect_url(
self.request, callback=callback, parameters=params
)

View file

@ -1,7 +1,13 @@
"""passbook oauth_client user views"""
from typing import Optional
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404
from django.views.generic import TemplateView
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.views.generic import TemplateView, View
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
@ -19,3 +25,46 @@ class UserSettingsView(LoginRequiredMixin, TemplateView):
kwargs["source"] = source
kwargs["connections"] = connections
return super().get_context_data(**kwargs)
class DisconnectView(LoginRequiredMixin, View):
"""Delete connection with source"""
source: Optional[OAuthSource] = None
aas: Optional[UserOAuthSourceConnection] = None
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
self.source = get_object_or_404(OAuthSource, slug=source_slug)
self.aas = get_object_or_404(
UserOAuthSourceConnection, source=self.source, user=request.user
)
return super().dispatch(request, source_slug)
def post(self, request: HttpRequest, source_slug: str) -> HttpResponse:
"""Delete connection object"""
if "confirmdelete" in request.POST:
# User confirmed deletion
self.aas.delete()
messages.success(request, _("Connection successfully deleted"))
return redirect(
reverse(
"passbook_sources_oauth:oauth-client-user",
kwargs={"source_slug": self.source.slug},
)
)
return self.get(request, source_slug)
# pylint: disable=unused-argument
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse:
"""Show delete form"""
return render(
request,
"generic/delete.html",
{
"object": self.source,
"delete_url": reverse(
"passbook_sources_oauth:oauth-client-disconnect",
kwargs={"source_slug": self.source.slug},
),
},
)