cleanup SAML urls

This commit is contained in:
Jens Langhammer 2019-03-03 00:07:40 +01:00
parent 9b131b619f
commit fcb5d36e07
4 changed files with 27 additions and 23 deletions

View File

@ -42,7 +42,7 @@ class SAMLProvider(Provider):
"""Get link to download XML metadata for admin interface""" """Get link to download XML metadata for admin interface"""
try: try:
# pylint: disable=no-member # pylint: disable=no-member
return reverse('passbook_saml_idp:metadata_xml', return reverse('passbook_saml_idp:saml-metadata',
kwargs={'application': self.application.slug}) kwargs={'application': self.application.slug})
except Provider.application.RelatedObjectDoesNotExist: except Provider.application.RelatedObjectDoesNotExist:
return None return None

View File

@ -39,7 +39,7 @@
</section> </section>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<a href="{% url 'passbook_saml_idp:metadata_xml' %}" class="btn btn-primary"><clr-icon shape="download"></clr-icon>{% trans 'Download Metadata' %}</a> <a href="{% url 'passbook_saml_idp:saml-metadata' %}" class="btn btn-primary"><clr-icon shape="download"></clr-icon>{% trans 'Download Metadata' %}</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,13 +4,14 @@ from django.urls import path
from passbook.saml_idp import views from passbook.saml_idp import views
urlpatterns = [ urlpatterns = [
path('login/<slug:application>/', path('<slug:application>/login/',
views.LoginBeginView.as_view(), name="saml_login_begin"), views.LoginBeginView.as_view(), name="saml-login"),
path('login/<slug:application>/initiate/', path('<slug:application>/login/initiate/',
views.InitiateLoginView.as_view(), name="saml_login_init"), views.InitiateLoginView.as_view(), name="saml-login-initiate"),
path('login/<slug:application>/process/', path('<slug:application>/login/process/',
views.LoginProcessView.as_view(), name='saml_login_process'), views.LoginProcessView.as_view(), name='saml-login-process'),
path('logout/', views.LogoutView.as_view(), name="saml_logout"), path('<slug:application>/logout/', views.LogoutView.as_view(), name="saml-logout"),
path('metadata/<slug:application>/', path('<slug:application>/logout/slo/', views.SLOLogout.as_view(), name="saml-logout-slo"),
views.DescriptorDownloadView.as_view(), name='metadata_xml'), path('<slug:application>/metadata/',
views.DescriptorDownloadView.as_view(), name='saml-metadata'),
] ]

View File

@ -14,6 +14,7 @@ from django.views.decorators.csrf import csrf_exempt
from signxml.util import strip_pem_header from signxml.util import strip_pem_header
from passbook.core.models import Application from passbook.core.models import Application
from passbook.core.policies import PolicyEngine
from passbook.lib.config import CONFIG from passbook.lib.config import CONFIG
from passbook.lib.mixins import CSRFExemptMixin from passbook.lib.mixins import CSRFExemptMixin
from passbook.lib.utils.template import render_to_string from passbook.lib.utils.template import render_to_string
@ -75,7 +76,7 @@ class LoginBeginView(LoginRequiredMixin, View):
return HttpResponseBadRequest('the SAML request payload is missing') return HttpResponseBadRequest('the SAML request payload is missing')
request.session['RelayState'] = source.get('RelayState', '') request.session['RelayState'] = source.get('RelayState', '')
return redirect(reverse('passbook_saml_idp:saml_login_process', kwargs={ return redirect(reverse('passbook_saml_idp:saml-login-process', kwargs={
'application': application 'application': application
})) }))
@ -94,17 +95,22 @@ class RedirectToSPView(LoginRequiredMixin, View):
}) })
class LoginProcessView(ProviderMixin, LoginRequiredMixin, View): class LoginProcessView(ProviderMixin, LoginRequiredMixin, View):
"""Processor-based login continuation. """Processor-based login continuation.
Presents a SAML 2.0 Assertion for POSTing back to the Service Provider.""" Presents a SAML 2.0 Assertion for POSTing back to the Service Provider."""
def _has_access(self):
"""Check if user has access to application"""
policy_engine = PolicyEngine(self.provider.application.policies.all())
policy_engine.for_user(self.request.user)
return policy_engine.result
def get(self, request, application): def get(self, request, application):
"""Handle get request, i.e. render form""" """Handle get request, i.e. render form"""
LOGGER.debug("Request: %s", request) LOGGER.debug("Request: %s", request)
# Check if user has access # Check if user has access
access = True if self.provider.application.skip_authorization and self._has_access():
# TODO: Check access here
if self.provider.application.skip_authorization and access:
ctx = self.provider.processor.generate_response() ctx = self.provider.processor.generate_response()
# TODO: AuditLog Skipped Authz # TODO: AuditLog Skipped Authz
return RedirectToSPView.as_view()( return RedirectToSPView.as_view()(
@ -122,9 +128,7 @@ class LoginProcessView(ProviderMixin, LoginRequiredMixin, View):
"""Handle post request, return back to ACS""" """Handle post request, return back to ACS"""
LOGGER.debug("Request: %s", request) LOGGER.debug("Request: %s", request)
# Check if user has access # Check if user has access
access = True if request.POST.get('ACSUrl', None) and self._has_access():
# TODO: Check access here
if request.POST.get('ACSUrl', None) and access:
# User accepted request # User accepted request
# TODO: AuditLog accepted # TODO: AuditLog accepted
return RedirectToSPView.as_view()( return RedirectToSPView.as_view()(
@ -144,7 +148,7 @@ class LogoutView(CSRFExemptMixin, LoginRequiredMixin, View):
returns a standard logged-out page. (SalesForce and others use this method, returns a standard logged-out page. (SalesForce and others use this method,
though it's technically not SAML 2.0).""" though it's technically not SAML 2.0)."""
def get(self, request): def get(self, request, application):
"""Perform logout""" """Perform logout"""
logout(request) logout(request)
@ -164,11 +168,10 @@ class SLOLogout(CSRFExemptMixin, LoginRequiredMixin, View):
"""Receives a SAML 2.0 LogoutRequest from a Service Provider, """Receives a SAML 2.0 LogoutRequest from a Service Provider,
logs out the user and returns a standard logged-out page.""" logs out the user and returns a standard logged-out page."""
def post(self, request): def post(self, request, application):
"""Perform logout""" """Perform logout"""
request.session['SAMLRequest'] = request.POST['SAMLRequest'] request.session['SAMLRequest'] = request.POST['SAMLRequest']
# TODO: Parse SAML LogoutRequest from POST data, similar to login_process(). # TODO: Parse SAML LogoutRequest from POST data, similar to login_process().
# TODO: Add a URL dispatch for this view.
# TODO: Modify the base processor to handle logouts? # TODO: Modify the base processor to handle logouts?
# TODO: Combine this with login_process(), since they are so very similar? # TODO: Combine this with login_process(), since they are so very similar?
# TODO: Format a LogoutResponse and return it to the browser. # TODO: Format a LogoutResponse and return it to the browser.
@ -183,8 +186,8 @@ class DescriptorDownloadView(ProviderMixin, View):
def get(self, request, application): def get(self, request, application):
"""Replies with the XML Metadata IDSSODescriptor.""" """Replies with the XML Metadata IDSSODescriptor."""
entity_id = CONFIG.y('saml_idp.issuer') entity_id = CONFIG.y('saml_idp.issuer')
slo_url = request.build_absolute_uri(reverse('passbook_saml_idp:saml_logout')) slo_url = request.build_absolute_uri(reverse('passbook_saml_idp:saml-logout'))
sso_url = request.build_absolute_uri(reverse('passbook_saml_idp:saml_login_begin', kwargs={ sso_url = request.build_absolute_uri(reverse('passbook_saml_idp:saml-login', kwargs={
'application': application 'application': application
})) }))
pubkey = strip_pem_header(self.provider.signing_cert.replace('\r', '')).replace('\n', '') pubkey = strip_pem_header(self.provider.signing_cert.replace('\r', '')).replace('\n', '')