Skip to content
Open
Show file tree
Hide file tree
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
17 changes: 16 additions & 1 deletion examples/idp.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
#!/usr/bin/env python3
import logging
import pathlib

from flask import Flask, abort, redirect, request, session, url_for
from flask.views import MethodView

from flask_saml2.idp import IdentityProvider
from flask_saml2.utils import certificate_from_file
from tests.idp.base import CERTIFICATE, PRIVATE_KEY, User
from tests.sp.base import CERTIFICATE as SP_CERTIFICATE

logger = logging.getLogger(__name__)
keys = pathlib.Path(__file__).parent / 'keys'


class ExampleIdentityProvider(IdentityProvider):
Expand All @@ -27,6 +30,9 @@ def logout(self):
def get_current_user(self):
return users[session['user']]

def get_slo_url(self):
return None


users = {user.username: user for user in [
User('alex', 'alex@example.com'),
Expand Down Expand Up @@ -82,7 +88,16 @@ def post(self):
'acs_url': 'http://localhost:9000/saml/acs/',
'certificate': SP_CERTIFICATE,
},
}
},
{
'CLASS': 'flask_saml2.idp.SPHandler',
'OPTIONS': {
'display_name': 'samltest.id',
'entity_id': 'https://samltest.id/saml/sp',
'acs_url': 'https://samltest.id/Shibboleth.sso/SAML2/POST',
'certificate': certificate_from_file(keys / 'samltest-sp.key'),
}
},
]

app.add_url_rule('/login/', view_func=Login.as_view('login'))
Expand Down
25 changes: 25 additions & 0 deletions examples/keys/samltest-sp.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIERTCCAq2gAwIBAgIJAKmtzjCD1+tqMA0GCSqGSIb3DQEBCwUAMDUxMzAxBgNV
BAMTKmlwLTE3Mi0zMS0yOC02NC51cy13ZXN0LTIuY29tcHV0ZS5pbnRlcm5hbDAe
Fw0xODA4MTgyMzI0MjNaFw0yODA4MTUyMzI0MjNaMDUxMzAxBgNVBAMTKmlwLTE3
Mi0zMS0yOC02NC51cy13ZXN0LTIuY29tcHV0ZS5pbnRlcm5hbDCCAaIwDQYJKoZI
hvcNAQEBBQADggGPADCCAYoCggGBALhUlY3SkIOze+l8y6dBzM6p7B8OykJWlwiz
szU16Lih8D7KLhNJfahoVxbPxB3YFM/81PJLOeK2krvJ5zY6CJyQY3sPQAkZKI7I
8qq9lmZ2g4QPqybNstXS6YUXJNUt/ixbbK/N97+LKTiSutbD1J7AoFnouMuLjlhN
5VRZ43jez4xLSHVZaYuUFKn01Y9oLKbj46LQnZnJCAGpTgPqEQJr6GpVGw43bKyU
pGoaPrdDRgRgtPMUWgFDkgcI3QiV1lsKfBs1t1E2UA7ACFnlJZpEuBtwgivzo3Ve
itiSaF3Jxh25EY5/vABpcgQQRz3RH2l8MMKdRsxb8VT3yh2S+CX55s+cN67LiCPr
6f2u+KS1iKfB9mWN6o2S4lcmo82HIBbsuXJV0oA1HrGMyyc4Y9nng/I8iuAp8or1
JrWRHQ+8NzO85DWK0rtvtLPxkvw0HK32glyuOP/9F05Z7+tiVIgn67buC0EdoUm1
RSpibqmB1ST2PikslOlVbJuy4Ah93wIDAQABo1gwVjA1BgNVHREELjAsgippcC0x
NzItMzEtMjgtNjQudXMtd2VzdC0yLmNvbXB1dGUuaW50ZXJuYWwwHQYDVR0OBBYE
FAdsTxYfulJ5yunYtgYJHC9IcevzMA0GCSqGSIb3DQEBCwUAA4IBgQB3J6i7Krei
HL8NPMglfWLHk1PZOgvIEEpKL+GRebvcbyqgcuc3VVPylq70VvGqhJxp1q/mzLfr
aUiypzfWFGm9zfwIg0H5TqRZYEPTvgIhIICjaDWRwZBDJG8D5G/KoV60DlUG0crP
BlIuCCr/SRa5ZoDQqvucTfr3Rx4Ha6koXFSjoSXllR+jn4GnInhm/WH137a+v35P
UcffNxfuehoGn6i4YeXF3cwJK4e35cOFW+dLbnaLk+Ty7HOGvpw86h979C6mJ9qE
HYgq9rQyzlSPbLZGZSgVcIezunOaOsWm81BsXRNNJjzHGCqKf8RMhd8oZP55+2/S
VRBwnkGyUNCuDPrJcymC95ZT2NW/KeWkz28HF2i31xQmecT2r3lQRSM8acvOXQsN
EDCDvJvCzJT9c2AnsnO24r6arPXs/UWAxOI+MjclXPLkLD6uTHV+Oo8XZ7bOjegD
5hL6/bKUWnNMurQNGrmi/jvqsCFLDKftl7ajuxKjtodnSuwhoY7NQy8=
-----END CERTIFICATE-----
5 changes: 5 additions & 0 deletions flask_saml2/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
NAMEID_SAML1_1_EMAIL = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
NAMEID_SAML2_0_EMAIL = 'urn:oasis:names:tc:SAML:2.0:attrname-­format:basic'
NAMEID_EMAIL = {NAMEID_SAML1_1_EMAIL, NAMEID_SAML2_0_EMAIL}

ATTR_SAML2_0_BASIC = 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic'
11 changes: 9 additions & 2 deletions flask_saml2/idp/idp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from flask_saml2.types import X509, PKey
from flask_saml2.utils import certificate_to_string, import_string

from ..constants import NAMEID_EMAIL, NAMEID_SAML1_1_EMAIL
from .sphandler import SPHandler
from .views import (
CannotHandleAssertionView, LoginBegin, LoginProcess, Logout, Metadata,
Expand All @@ -29,6 +30,7 @@ class IdentityProvider(Generic[U]):

blueprint_name = 'flask_saml2_idp'

nameid_format = NAMEID_SAML1_1_EMAIL
#: The specific :class:`digest <~flask_saml2.signing.Digester>` method to
#: use in this IdP when creating responses.
#:
Expand Down Expand Up @@ -110,6 +112,9 @@ def get_idp_digester(self) -> Digester:
"""Get the method used to compute digests for the IdP."""
return self.idp_digester_class()

def get_idp_nameid_format(self) -> str:
return self.nameid_format

def get_service_providers(self) -> Iterable[Tuple[str, dict]]:
"""
Get an iterable of service provider ``config`` dicts. ``config`` should
Expand Down Expand Up @@ -177,7 +182,7 @@ def get_user_nameid(self, user: U, attribute: str):
Subclasses can override this to allow more attributes to be extracted.
By default, only email addresses are extracted using :meth:`get_user_email`.
"""
if attribute == 'urn:oasis:names:tc:SAML:2.0:nameid-format:email':
if attribute in NAMEID_EMAIL:
return self.get_user_email(user)

raise NotImplementedError("Can't fetch attribute {} from user".format(attribute))
Expand Down Expand Up @@ -210,10 +215,12 @@ def get_metadata_context(self) -> dict:
Suggested extra context variables include 'org' and 'contacts'.
"""
return {
'idp': self,
'nameid_format': self.get_idp_nameid_format(),
'entity_id': self.get_idp_entity_id(),
'certificate': certificate_to_string(self.get_idp_certificate()),
'slo_url': self.get_slo_url(),
'sso_url': self.get_sso_url(),
'slo_url': self.get_slo_url(),
'org': None,
'contacts': [],
}
Expand Down
3 changes: 2 additions & 1 deletion flask_saml2/idp/sphandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from flask_saml2.utils import get_random_id, utcnow
from flask_saml2.xml_templates import XmlTemplate

from ..constants import NAMEID_SAML1_1_EMAIL
from .parser import AuthnRequestParser, LogoutRequestParser
from .xml_templates import AssertionTemplate, ResponseTemplate

Expand All @@ -27,7 +28,7 @@ class SPHandler(object):
certificate: Optional[X509] = None
display_name: str = None

subject_format = 'urn:oasis:names:tc:SAML:2.0:nameid-format:email'
subject_format = NAMEID_SAML1_1_EMAIL
assertion_template = AssertionTemplate
response_template = ResponseTemplate

Expand Down
4 changes: 3 additions & 1 deletion flask_saml2/idp/templates/flask_saml2_idp/metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:email</md:NameIDFormat>
<md:NameIDFormat>{{ nameid_format }}</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="{{ sso_url }}"/>
{% if slo_url %}
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="{{ slo_url }}"/>
{% endif %}
</md:IDPSSODescriptor>
{% if org %}
<md:Organization>
Expand Down
5 changes: 4 additions & 1 deletion flask_saml2/idp/xml_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from flask_saml2.types import XmlNode
from flask_saml2.xml_templates import NameIDTemplate, XmlTemplate

from ..constants import ATTR_SAML2_0_BASIC


class AttributeTemplate(XmlTemplate):
"""
Expand All @@ -17,11 +19,12 @@ class AttributeTemplate(XmlTemplate):
</saml:Attribute>
"""
namespace = 'saml'
default_attribute_format = ATTR_SAML2_0_BASIC

def generate_xml(self):
return self.element('Attribute', attrs={
'Name': self.params['ATTRIBUTE_NAME'],
'NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'NameFormat': self.params.get('ATTRIBUTE_FORMAT', self.default_attribute_format),
}, children=[
self.element('AttributeValue', text=self.params['ATTRIBUTE_VALUE']),
])
Expand Down
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
universal = 1

[metadata]
description-file = README.rst
description_file = README.rst

[flake8]
max-line-length = 100
max_line_length = 100
ignore = E501, E731

[isort]
Expand Down