-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(socialaccount): Added support for SAML 2.0
Add support for SAML 2.0
- Loading branch information
Showing
19 changed files
with
877 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import base64 | ||
from unittest.mock import patch | ||
|
||
from django.test.client import Client | ||
|
||
import pytest | ||
|
||
|
||
@pytest.fixture | ||
def client(): | ||
client = Client(HTTP_HOST="example.com") | ||
return client | ||
|
||
|
||
@pytest.fixture | ||
def saml_settings(settings): | ||
settings.SOCIALACCOUNT_PROVIDERS = { | ||
"saml": { | ||
"APPS": [ | ||
{ | ||
"client_id": "org", | ||
"provider_id": "urn:dev-123.us.auth0.com", | ||
"settings": { | ||
"attribute_mapping": { | ||
"uid": "http://schemas.auth0.com/clientID", | ||
"email_verified": "http://schemas.auth0.com/email_verified", | ||
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", | ||
}, | ||
"idp": { | ||
"name": "Test IdP", | ||
"entity_id": "urn:dev-123.us.auth0.com", | ||
"sso_url": "https://dev-123.us.auth0.com/samlp/456", | ||
"slo_url": "https://dev-123.us.auth0.com/samlp/456", | ||
"x509cert": "", | ||
}, | ||
"advanced": { | ||
"strict": False, | ||
}, | ||
}, | ||
} | ||
] | ||
} | ||
} | ||
|
||
|
||
@pytest.fixture | ||
def acs_saml_response(): | ||
xml = """<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="123" InResponseTo="ONELOGIN_456" Version="2.0" IssueInstant="2023-07-08T08:24:14.141Z" Destination="https://allauth.org/accounts/org/acs/"> | ||
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:dev-123.us.auth0.com | ||
</saml:Issuer> | ||
<samlp:Status> | ||
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/> | ||
</samlp:Status> | ||
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="123" IssueInstant="2023-07-08T08:24:14.094Z"> | ||
<saml:Issuer>urn:dev-123.us.auth0.com | ||
</saml:Issuer> | ||
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> | ||
<SignedInfo> | ||
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> | ||
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/> | ||
<Reference URI="#123"> | ||
<Transforms> | ||
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> | ||
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> | ||
</Transforms> | ||
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> | ||
<DigestValue>123 | ||
</DigestValue> | ||
</Reference> | ||
</SignedInfo> | ||
<SignatureValue>If7dFg... | ||
</SignatureValue> | ||
<KeyInfo> | ||
<X509Data> | ||
<X509Certificate>MIIDHTCC... | ||
</X509Certificate> | ||
</X509Data> | ||
</KeyInfo> | ||
</Signature> | ||
<saml:Subject> | ||
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">google-oauth2|108204123456789 | ||
</saml:NameID> | ||
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"> | ||
<saml:SubjectConfirmationData NotOnOrAfter="2023-07-08T09:24:14.094Z" Recipient="https://allauth.org/accounts/org/acs/" InResponseTo="ONELOGIN_f293b01d18bb0ac85a611b35e0c898af582bcfdd"/> | ||
</saml:SubjectConfirmation> | ||
</saml:Subject> | ||
<saml:Conditions NotBefore="2023-07-08T08:24:14.094Z" NotOnOrAfter="2023-07-08T09:24:14.094Z"> | ||
<saml:AudienceRestriction> | ||
<saml:Audience>https://allauth.org/accounts/org/metadata/ | ||
</saml:Audience> | ||
</saml:AudienceRestriction> | ||
</saml:Conditions> | ||
<saml:AuthnStatement AuthnInstant="2023-07-08T08:24:14.094Z" SessionIndex="_qPrYdL0O8w3vdb8eCEY5ZtHe76LA8-JU"> | ||
<saml:AuthnContext> | ||
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified | ||
</saml:AuthnContextClassRef> | ||
</saml:AuthnContext> | ||
</saml:AuthnStatement> | ||
<saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">google-oauth2|108204123456789 | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">[email protected] | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">John | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">John | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">[email protected] | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/identities/default/provider" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">google-oauth2 | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/identities/default/connection" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">google-oauth2 | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/identities/default/isSocial" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:boolean">true | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/clientID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">dummysamluid | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/created_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:anyType">Wed Jun 28 2023 17:53:49 GMT+0000 (Coordinated Universal Time) | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/email_verified" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:boolean">true | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/locale" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">en | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/nickname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">john.doe | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/picture" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:string">https://lh3.googleusercontent.com/a/AAcHTtfZ0fEyL3BKP1Hk2v1bNwpJd6ckIeo6jSExlkVjMXaIpsY=s96-c | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
<saml:Attribute Name="http://schemas.auth0.com/updated_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"> | ||
<saml:AttributeValue xsi:type="xs:anyType">Sat Jul 08 2023 06:13:07 GMT+0000 (Coordinated Universal Time) | ||
</saml:AttributeValue> | ||
</saml:Attribute> | ||
</saml:AttributeStatement> | ||
</saml:Assertion> | ||
</samlp:Response> | ||
""" | ||
return base64.b64encode(xml.encode("utf8")).decode("utf8") | ||
|
||
|
||
@pytest.fixture | ||
def sls_saml_request(): | ||
xml = "<dummy></dummy>" | ||
return base64.b64encode(xml.encode("utf8")).decode("utf8") | ||
|
||
|
||
@pytest.fixture | ||
def mocked_signature_validation(): | ||
with patch("onelogin.saml2.utils.OneLogin_Saml2_Utils.validate_sign") as mock: | ||
mock.return_value = True | ||
yield |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
from django.urls import reverse | ||
from django.utils.http import urlencode | ||
|
||
from allauth.socialaccount.providers.base import Provider, ProviderAccount | ||
|
||
|
||
class SAMLAccount(ProviderAccount): | ||
def to_str(self): | ||
return super().to_str() | ||
|
||
|
||
class SAMLProvider(Provider): | ||
id = "saml" | ||
account_class = SAMLAccount | ||
default_attribute_mapping = { | ||
"uid": [ | ||
"http://schemas.auth0.com/clientID", | ||
"urn:oasis:names:tc:SAML:attribute:subject-id", | ||
], | ||
"email": [ | ||
"urn:oid:0.9.2342.19200300.100.1.3", | ||
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", | ||
], | ||
"email_verified": [ | ||
"http://schemas.auth0.com/email_verified", | ||
], | ||
"first_name": [ | ||
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", | ||
"urn:oid:2.5.4.42", | ||
], | ||
"last_name": [ | ||
"urn:oid:2.5.4.4", | ||
], | ||
"username": [ | ||
"http://schemas.auth0.com/nickname", | ||
], | ||
} | ||
|
||
@property | ||
def name(self): | ||
return self.app.name or self.app.client_id or self.id | ||
|
||
def get_login_url(self, request, **kwargs): | ||
url = reverse("saml_login", kwargs={"organization_slug": self.app.client_id}) | ||
if kwargs: | ||
url = url + "?" + urlencode(kwargs) | ||
return url | ||
|
||
def extract_extra_data(self, data): | ||
return data.get_attributes() | ||
|
||
def extract_uid(self, data): | ||
""" | ||
The `uid` is not unique across different SAML IdP's. Therefore, | ||
we're using a fully qualified ID: <uid>@<entity_id>. | ||
""" | ||
return self._extract(data)["uid"] | ||
|
||
def extract_common_fields(self, data): | ||
ret = self._extract(data) | ||
ret.pop("uid", None) | ||
return ret | ||
|
||
def _extract(self, data): | ||
provider_config = self.app.settings | ||
raw_attributes = data.get_attributes() | ||
attributes = {} | ||
attribute_mapping = provider_config.get( | ||
"attribute_mapping", self.default_attribute_mapping | ||
) | ||
# map configured provider attributes | ||
for key, provider_keys in attribute_mapping.items(): | ||
if isinstance(provider_keys, str): | ||
provider_keys = [provider_keys] | ||
for provider_key in provider_keys: | ||
attribute_list = raw_attributes.get(provider_key, [""]) | ||
if len(attribute_list) > 0: | ||
attributes[key] = attribute_list[0] | ||
break | ||
email_verified = attributes.get("email_verified") | ||
if email_verified: | ||
email_verified = email_verified.lower() in ["true", "1", "t", "y", "yes"] | ||
attributes["email_verified"] = email_verified | ||
return attributes | ||
|
||
|
||
provider_classes = [SAMLProvider] |
Oops, something went wrong.