Skip to content

Commit d6921bd

Browse files
author
Boy Baukema
committed
Fixed casing
1 parent 3eee672 commit d6921bd

File tree

4 files changed

+329
-0
lines changed

4 files changed

+329
-0
lines changed

src/OneLogin/Saml/AuthRequest.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/**
4+
* Create a SAML authorization request.
5+
*/
6+
class OneLogin_Saml_AuthRequest
7+
{
8+
const ID_PREFIX = 'ONELOGIN';
9+
10+
/**
11+
* A SamlResponse class provided to the constructor.
12+
* @var OneLogin_Saml_Settings
13+
*/
14+
private $_settings;
15+
16+
/**
17+
* Construct the response object.
18+
*
19+
* @param OneLogin_Saml_Settings $settings
20+
* A SamlResponse settings object containing the necessary
21+
* x509 certicate to decode the XML.
22+
*/
23+
public function __construct(OneLogin_Saml_Settings $settings)
24+
{
25+
$this->_settings = $settings;
26+
}
27+
28+
/**
29+
* Generate the request.
30+
*
31+
* @return string A fully qualified URL that can be redirected to in order to process the authorization request.
32+
*/
33+
public function getRedirectUrl()
34+
{
35+
$id = $this->_generateUniqueID();
36+
$issueInstant = $this->_getTimestamp();
37+
38+
$request = <<<AUTHNREQUEST
39+
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
40+
ID="$id"
41+
Version="2.0"
42+
IssueInstant="$issueInstant"
43+
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
44+
AssertionConsumerServiceURL="{$this->_settings->spReturnUrl}">
45+
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">{$this->_settings->spIssuer}</saml:Issuer>
46+
<samlp:NameIDPolicy xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
47+
Format="{$this->_settings->requestedNameIdFormat}"
48+
AllowCreate="true"></samlp:NameIDPolicy>
49+
<samlp:RequestedAuthnContext xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Comparison="exact">
50+
<saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
51+
>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
52+
</samlp:RequestedAuthnContext>
53+
</samlp:AuthnRequest>";
54+
AUTHNREQUEST;
55+
56+
$deflatedRequest = gzdeflate($request);
57+
$base64Request = base64_encode($deflatedRequest);
58+
$encodedRequest = urlencode($base64Request);
59+
60+
return $this->_settings->idpSingleSignOnUrl . "?SAMLRequest=" . $encodedRequest;
61+
}
62+
63+
private function _generateUniqueID()
64+
{
65+
return self::ID_PREFIX . sha1(uniqid(mt_rand(), TRUE));
66+
}
67+
68+
private function _getTimestamp()
69+
{
70+
$defaultTimezone = date_default_timezone_get();
71+
date_default_timezone_set('UTC');
72+
$timestamp = strftime("%Y-%m-%dT%H:%M:%SZ");
73+
date_default_timezone_set($defaultTimezone);
74+
return $timestamp;
75+
}
76+
}

src/OneLogin/Saml/Response.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/**
4+
* Parse the SAML response and maintain the XML for it.
5+
*/
6+
class OneLogin_Saml_Response
7+
{
8+
/**
9+
* @var OneLogin_Saml_Settings
10+
*/
11+
private $_settings;
12+
13+
/**
14+
* The decoded, unprocessed XML assertion provided to the constructor.
15+
* @var string
16+
*/
17+
public $assertion;
18+
19+
/**
20+
* A DOMDocument class loaded from the $assertion.
21+
* @var DomDocument
22+
*/
23+
public $document;
24+
25+
/**
26+
* Construct the response object.
27+
*
28+
* @param OneLogin_Saml_Settings $settings Settings containing the necessary X.509 certificate to decode the XML.
29+
* @param string $assertion A UUEncoded SAML assertion from the IdP.
30+
*/
31+
public function __construct(OneLogin_Saml_Settings $settings, $assertion)
32+
{
33+
$this->_settings = $settings;
34+
$this->assertion = base64_decode($assertion);
35+
$this->document = new DOMDocument();
36+
$this->document->loadXML($this->assertion);
37+
}
38+
39+
/**
40+
* Determine if the SAML Response is valid using the certificate.
41+
*
42+
* @throws Exception
43+
* @return bool Validate the document
44+
*/
45+
public function isValid()
46+
{
47+
$xmlSec = new OneLogin_Saml_XmlSec($this->_settings, $this);
48+
return $xmlSec->isValid();
49+
}
50+
51+
/**
52+
* Get the NameID provided by the SAML response from the IdP.
53+
*/
54+
public function getNameId()
55+
{
56+
$entries = $this->_queryAssertion('/saml:Subject/saml:NameID');
57+
return $entries->item(0)->nodeValue;
58+
}
59+
60+
public function getAttributes()
61+
{
62+
$entries = $this->_queryAssertion('/saml:AttributeStatement/saml:Attribute');
63+
64+
$attributes = array();
65+
/** @var $entry DOMNode */
66+
foreach ($entries as $entry) {
67+
$attributeName = $entry->attributes->getNamedItem('Name')->nodeValue;
68+
69+
$attributeValues = array();
70+
foreach ($entry->childNodes as $childNode) {
71+
if ($childNode->tagName === 'saml:AttributeValue'){
72+
$attributeValues[] = $childNode->nodeValue;
73+
}
74+
}
75+
76+
$attributes[$attributeName] = $attributeValues;
77+
}
78+
return $attributes;
79+
}
80+
81+
/**
82+
* @param string $assertionXpath
83+
* @return DOMNodeList
84+
*/
85+
private function _queryAssertion($assertionXpath)
86+
{
87+
$xpath = new DOMXPath($this->document);
88+
$xpath->registerNamespace('samlp' , 'urn:oasis:names:tc:SAML:2.0:protocol');
89+
$xpath->registerNamespace('saml' , 'urn:oasis:names:tc:SAML:2.0:assertion');
90+
$xpath->registerNamespace('ds' , 'http://www.w3.org/2000/09/xmldsig#');
91+
92+
$signatureQuery = '//ds:Reference[@URI]';
93+
$id = substr($xpath->query($signatureQuery)->item(0)->getAttribute('URI'), 1);
94+
95+
$nameQuery = "/samlp:Response/saml:Assertion[@ID='$id']" . $assertionXpath;
96+
return $xpath->query($nameQuery);
97+
}
98+
}

src/OneLogin/Saml/Settings.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/**
4+
* Holds SAML settings for the SamlResponse and SamlAuthRequest classes.
5+
*
6+
* These settings need to be filled in by the user prior to being used.
7+
*/
8+
class OneLogin_Saml_Settings
9+
{
10+
const NAMEID_EMAIL_ADDRESS = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress';
11+
const NAMEID_X509_SUBJECT_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName';
12+
const NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName';
13+
const NAMEID_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos';
14+
const NAMEID_ENTITY = 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity';
15+
const NAMEID_TRANSIENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient';
16+
const NAMEID_PERSISTENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent';
17+
18+
/**
19+
* The URL to submit SAML authentication requests to.
20+
* @var string
21+
*/
22+
public $idpSingleSignOnUrl = '';
23+
24+
/**
25+
* The x509 certificate used to authenticate the request.
26+
* @var string
27+
*/
28+
public $idpPublicCertificate = '';
29+
30+
/**
31+
* The URL where to the SAML Response/SAML Assertion will be posted.
32+
* @var string
33+
*/
34+
public $spReturnUrl = '';
35+
36+
/**
37+
* The name of the application.
38+
* @var string
39+
*/
40+
public $spIssuer = 'php-saml';
41+
42+
/**
43+
* Specifies what format to return the authentication token, i.e, the email address.
44+
* @var string
45+
*/
46+
public $requestedNameIdFormat = self::NAMEID_EMAIL_ADDRESS;
47+
}

src/OneLogin/Saml/XmlSec.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
/**
4+
* Determine if the SAML response is valid using a provided x509 certificate.
5+
*/
6+
class OneLogin_Saml_XmlSec
7+
{
8+
/**
9+
* A SamlResponse class provided to the constructor.
10+
* @var OneLogin_Saml_Settings
11+
*/
12+
private $_settings;
13+
14+
/**
15+
* The document to be tested.
16+
* @var DomDocument
17+
*/
18+
private $_document;
19+
20+
/**
21+
* Construct the SamlXmlSec object.
22+
*
23+
* @param OneLogin_Saml_Settings $settings A SamlResponse settings object containing the necessary
24+
* x509 certicate to test the document.
25+
* @param OneLogin_Saml_Response $response The document to test.
26+
*/
27+
public function __construct(OneLogin_Saml_Settings $settings, OneLogin_Saml_Response $response)
28+
{
29+
$this->_settings = $settings;
30+
$this->_document = $response->document;
31+
}
32+
33+
/**
34+
* Verify that the document only contains a single Assertion
35+
*
36+
* @return bool TRUE if the document passes.
37+
*/
38+
public function validateNumAssertions()
39+
{
40+
$rootNode = $this->_document;
41+
$assertionNodes = $rootNode->getElementsByTagName('Assertion');
42+
return ($assertionNodes->length == 1);
43+
}
44+
45+
/**
46+
* Verify that the document is still valid according
47+
*
48+
* @return bool
49+
*/
50+
public function validateTimestamps()
51+
{
52+
$rootNode = $this->_document;
53+
$timestampNodes = $rootNode->getElementsByTagName('Conditions');
54+
for ($i = 0; $i < $timestampNodes->length; $i++) {
55+
$nbAttribute = $timestampNodes->item($i)->attributes->getNamedItem("NotBefore");
56+
$naAttribute = $timestampNodes->item($i)->attributes->getNamedItem("NotOnOrAfter");
57+
if ($nbAttribute && strtotime($nbAttribute->textContent) > time()) {
58+
return false;
59+
}
60+
if ($naAttribute && strtotime($naAttribute->textContent) <= time()) {
61+
return false;
62+
}
63+
}
64+
return true;
65+
}
66+
67+
/**
68+
* @return bool
69+
* @throws Exception
70+
*/
71+
public function isValid()
72+
{
73+
$objXMLSecDSig = new XMLSecurityDSig();
74+
75+
$objDSig = $objXMLSecDSig->locateSignature($this->_document);
76+
if (!$objDSig) {
77+
throw new Exception("Cannot locate Signature Node");
78+
}
79+
$objXMLSecDSig->canonicalizeSignedInfo();
80+
$objXMLSecDSig->idKeys = array('ID');
81+
82+
$retVal = $objXMLSecDSig->validateReference();
83+
if (!$retVal) {
84+
throw new Exception("Reference Validation Failed");
85+
}
86+
87+
$singleAssertion = $this->validateNumAssertions();
88+
if (!$singleAssertion) {
89+
throw new Exception("Only one SAMLAssertion allowed");
90+
}
91+
92+
$validTimestamps = $this->validateTimestamps();
93+
if (!$validTimestamps) {
94+
throw new Exception("SAMLAssertion conditions not met");
95+
}
96+
97+
$objKey = $objXMLSecDSig->locateKey();
98+
if (!$objKey) {
99+
throw new Exception("We have no idea about the key");
100+
}
101+
102+
XMLSecEnc::staticLocateKeyInfo($objKey, $objDSig);
103+
104+
$objKey->loadKey($this->_settings->idpPublicCertificate, FALSE, TRUE);
105+
106+
return ($objXMLSecDSig->verify($objKey) === 1);
107+
}
108+
}

0 commit comments

Comments
 (0)