Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate SAMLRequst URL query parameter for unsigned SP-initiated login #67

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 55 additions & 7 deletions samlauthenticator/samlauthenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
'''

# Imports from python standard library
from base64 import b64decode
from base64 import b64decode, b64encode
from datetime import datetime, timezone
from urllib.request import urlopen
from urllib.parse import quote_plus

import asyncio
import pwd
import subprocess
import zlib

# Imports to work with JupyterHub
from jupyterhub.auth import Authenticator
Expand Down Expand Up @@ -705,7 +707,7 @@ def _authenticate(self, handler, data):
def authenticate(self, handler, data):
return self._authenticate(handler, data)

def _get_redirect_from_metadata_and_redirect(authenticator_self, element_name, handler_self):
def _get_redirect_from_metadata(authenticator_self, element_name, handler_self):
saml_metadata_etree = authenticator_self._get_saml_metadata_etree()

handler_self.log.debug('Got metadata etree')
Expand All @@ -724,9 +726,27 @@ def _get_redirect_from_metadata_and_redirect(authenticator_self, element_name, h

redirect_link_getter = xpath_with_namespaces(final_xpath)

return redirect_link_getter(saml_metadata_etree)[0]

def _get_redirect_from_metadata_and_redirect(authenticator_self, element_name, handler_self, add_authn_request=False):

redirect_url = authenticator_self._get_redirect_from_metadata(element_name, handler_self)

# Here permanent MUST BE False - otherwise the /hub/logout GET will not be fired
# by the user's browser.
handler_self.redirect(redirect_link_getter(saml_metadata_etree)[0], permanent=False)
if add_authn_request:
authn_requst = quote_plus(b64encode(zlib.compress(
authenticator_self._make_authn_request(element_name, handler_self).encode('utf8')
)[2:-4]))
handler_self.redirect(
f"{redirect_url}?SAMLRequest={authn_requst}",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if redurect url already has queryparametar, question mark will be appeared twice.

actually google workspace saml include idpid parameter in redirect URL. if that case redirection will fail.

permanent=False
)
else:
handler_self.redirect(
redirect_url,
permanent=False
)

def _make_org_metadata(self):
if self.organization_name or \
Expand Down Expand Up @@ -763,6 +783,28 @@ def _make_org_metadata(self):

return ''

def _make_authn_request(authenticator_self, element_name, handler_self):
authn_request_text = '''<?xml version="1.0" encoding="UTF-8"?>
<samlp:AuthnRequest
ID="0"
Version="2.0"
IssueInstant="{{ issue_time }}"
Destination="{{ sso_login_url }}"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
AssertionConsumerServiceURL="{{ acs_url }}"
ProviderName=""
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">{{ audience }}</saml:Issuer>
</samlp:AuthnRequest>'''

xml_template = Template(authn_request_text)
return xml_template.render(
issue_time = datetime.now().strftime(authenticator_self.time_format_string),
sso_login_url = authenticator_self._get_redirect_from_metadata(element_name, handler_self),
acs_url = authenticator_self.acs_endpoint_url,
audience = authenticator_self.audience
)

def _make_sp_metadata(authenticator_self, meta_handler_self):
metadata_text = '''<?xml version="1.0"?>
<EntityDescriptor
Expand Down Expand Up @@ -804,8 +846,11 @@ class SAMLLoginHandler(LoginHandler):

async def get(login_handler_self):
login_handler_self.log.info('Starting SP-initiated SAML Login')
authenticator_self._get_redirect_from_metadata_and_redirect('md:SingleSignOnService',
login_handler_self)
authenticator_self._get_redirect_from_metadata_and_redirect(
'md:SingleSignOnService',
login_handler_self,
add_authn_request=True
)

class SAMLLogoutHandler(LogoutHandler):
# TODO: When the time is right to force users onto JupyterHub 1.0.0,
Expand Down Expand Up @@ -848,8 +893,11 @@ async def get(logout_handler_self):
forward_on_logout = True if authenticator_self.slo_forward_on_logout else False
forwad_on_logout = True if authenticator_self.slo_forwad_on_logout else False
if forward_on_logout or forwad_on_logout:
authenticator_self._get_redirect_from_metadata_and_redirect('md:SingleLogoutService',
logout_handler_self)
authenticator_self._get_redirect_from_metadata_and_redirect(
'md:SingleLogoutService',
logout_handler_self,
add_authn_request=False
)
else:
html = logout_handler_self.render_template('logout.html')
logout_handler_self.finish(html)
Expand Down