diff --git a/config/samlidp.php b/config/samlidp.php index f8618ff..7fb173a 100644 --- a/config/samlidp.php +++ b/config/samlidp.php @@ -15,6 +15,10 @@ 'email_field' => 'email', // Define the name field in the users table 'name_field' => 'name', + // Define whether or not to use NameID + 'use_name_id' => true, + // Defiine the NameID (optional) + // 'name_id_format' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', // The URI to your login page 'login_uri' => 'login', // Log out of the IdP after SLO diff --git a/resources/views/metadata.blade.php b/resources/views/metadata.blade.php index 7886703..e8e4852 100644 --- a/resources/views/metadata.blade.php +++ b/resources/views/metadata.blade.php @@ -1,6 +1,8 @@ - - + + @@ -15,7 +17,8 @@ - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - + {{ $nameIdFormat }} + - \ No newline at end of file + diff --git a/src/Events/Assertion.php b/src/Events/Assertion.php index cb1d43f..8a457a3 100644 --- a/src/Events/Assertion.php +++ b/src/Events/Assertion.php @@ -29,7 +29,21 @@ public function __construct(\LightSaml\Model\Assertion\AttributeStatement &$attr { $this->attribute_statement = &$attribute_statement; $this->attribute_statement - ->addAttribute(new Attribute(ClaimTypes::EMAIL_ADDRESS, auth($guard)->user()->__get(config('samlidp.email_field', 'email')))) - ->addAttribute(new Attribute(ClaimTypes::COMMON_NAME, auth($guard)->user()->__get(config('samlidp.name_field', 'name')))); + ->addAttribute( + new Attribute( + ClaimTypes::EMAIL_ADDRESS, + auth($guard) + ->user() + ->__get(config('samlidp.email_field', 'email')) + ) + ) + ->addAttribute( + new Attribute( + ClaimTypes::COMMON_NAME, + auth($guard) + ->user() + ->__get(config('samlidp.name_field', 'name')) + ) + ); } } diff --git a/src/Http/Controllers/MetadataController.php b/src/Http/Controllers/MetadataController.php index ab934d5..09184c9 100644 --- a/src/Http/Controllers/MetadataController.php +++ b/src/Http/Controllers/MetadataController.php @@ -2,6 +2,7 @@ namespace CodeGreenCreative\SamlIdp\Http\Controllers; +use LightSaml\SamlConstants; use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Storage; @@ -29,9 +30,11 @@ public function index() } $cert = preg_replace('/^\W+\w+\s+\w+\W+\s(.*)\s+\W+.*$/s', '$1', trim($cert)); - $cert = str_replace(PHP_EOL, "", $cert); + $cert = str_replace(PHP_EOL, '', $cert); - return response(view('samlidp::metadata', compact('cert')), 200, [ + $nameIdFormat = config('samlidp.name_id_format', SamlConstants::NAME_ID_FORMAT_EMAIL); + + return response(view('samlidp::metadata', compact('cert', 'nameIdFormat')), 200, [ 'Content-Type' => 'application/xml', ]); } diff --git a/src/Jobs/SamlSlo.php b/src/Jobs/SamlSlo.php index 015018f..619aa55 100644 --- a/src/Jobs/SamlSlo.php +++ b/src/Jobs/SamlSlo.php @@ -2,26 +2,29 @@ namespace CodeGreenCreative\SamlIdp\Jobs; -use CodeGreenCreative\SamlIdp\Traits\PerformsSingleSignOn; -use Illuminate\Foundation\Bus\Dispatchable; use LightSaml\Helper; +use Illuminate\Support\Arr; +use Illuminate\Support\Str; +use LightSaml\SamlConstants; +use LightSaml\Model\Protocol\Status; use LightSaml\Model\Assertion\Issuer; use LightSaml\Model\Assertion\NameID; -use LightSaml\Model\Context\DeserializationContext; +use LightSaml\Model\Protocol\StatusCode; +use Illuminate\Foundation\Bus\Dispatchable; use LightSaml\Model\Protocol\LogoutRequest; use LightSaml\Model\Protocol\LogoutResponse; -use LightSaml\Model\Protocol\Status; -use LightSaml\Model\Protocol\StatusCode; use LightSaml\Model\XmlDSig\SignatureWriter; -use LightSaml\SamlConstants; -use Illuminate\Support\Arr; -use Illuminate\Support\Str; +use LightSaml\Model\Context\DeserializationContext; +use CodeGreenCreative\SamlIdp\Traits\PerformsSingleSignOn; class SamlSlo { - use Dispatchable, PerformsSingleSignOn; + use Dispatchable; + use PerformsSingleSignOn; private $sp; + private $destination; + private $logout_request; /** * [__construct description] @@ -44,13 +47,13 @@ public function handle() // We are receiving a Logout Request if (request()->filled('SAMLRequest')) { $xml = gzinflate(base64_decode(request('SAMLRequest'))); - $deserializationContext = new DeserializationContext; + $deserializationContext = new DeserializationContext(); $deserializationContext->getDocument()->loadXML($xml); // Get the final destination session()->put('RelayState', request('RelayState')); } elseif (request()->filled('SAMLResponse')) { $xml = gzinflate(base64_decode(request('SAMLResponse'))); - $deserializationContext = new DeserializationContext; + $deserializationContext = new DeserializationContext(); $deserializationContext->getDocument()->loadXML($xml); } // Send the request to log out @@ -63,15 +66,18 @@ public function handle() */ public function response() { - $this->response = (new LogoutResponse)->setIssuer(new Issuer($this->issuer)) + $this->response = (new LogoutResponse()) + ->setIssuer(new Issuer($this->issuer)) ->setID(Helper::generateID()) - ->setIssueInstant(new \DateTime) + ->setIssueInstant(new \DateTime()) ->setDestination($this->destination) ->setInResponseTo($this->logout_request->getId()) ->setStatus(new Status(new StatusCode('urn:oasis:names:tc:SAML:2.0:status:Success'))); if (config('samlidp.messages_signed')) { - $this->response->setSignature(new SignatureWriter($this->certificate, $this->private_key, $this->digest_algorithm)); + $this->response->setSignature( + new SignatureWriter($this->certificate, $this->private_key, $this->digest_algorithm) + ); } return $this->send(SamlConstants::BINDING_SAML2_HTTP_REDIRECT); @@ -83,15 +89,17 @@ public function response() */ public function request() { - $this->response = (new LogoutRequest) + $this->response = (new LogoutRequest()) ->setIssuer(new Issuer($this->issuer)) - ->setNameID((new NameID(Helper::generateID(), SamlConstants::NAME_ID_FORMAT_TRANSIENT))) + ->setNameID(new NameID(Helper::generateID(), SamlConstants::NAME_ID_FORMAT_TRANSIENT)) ->setID(Helper::generateID()) - ->setIssueInstant(new \DateTime) + ->setIssueInstant(new \DateTime()) ->setDestination($this->destination); if (config('samlidp.messages_signed')) { - $this->response->setSignature(new SignatureWriter($this->certificate, $this->private_key, $this->digest_algorithm)); + $this->response->setSignature( + new SignatureWriter($this->certificate, $this->private_key, $this->digest_algorithm) + ); } return $this->send(SamlConstants::BINDING_SAML2_HTTP_REDIRECT); @@ -102,27 +110,26 @@ private function setDestination() $destination = $this->sp['logout']; $queryParams = $this->getQueryParams(); if (!empty($queryParams)) { - if (!parse_url($destination, PHP_URL_QUERY)){ + if (!parse_url($destination, PHP_URL_QUERY)) { $destination = Str::finish(url($destination), '?') . Arr::query($queryParams); - } - else{ - $destination .= '&'.Arr::query($queryParams); + } else { + $destination .= '&' . Arr::query($queryParams); } } $this->destination = $destination; } - private function getQueryParams() - { - $queryParams = (isset($this->sp['query_params']) ? $this->sp['query_params'] : null); + private function getQueryParams() + { + $queryParams = isset($this->sp['query_params']) ? $this->sp['query_params'] : null; if (is_null($queryParams)) { $queryParams = [ - 'idp' => config('app.url') + 'idp' => config('app.url'), ]; } return $queryParams; - } + } } diff --git a/src/Jobs/SamlSso.php b/src/Jobs/SamlSso.php index b6ea104..72c1b60 100644 --- a/src/Jobs/SamlSso.php +++ b/src/Jobs/SamlSso.php @@ -39,6 +39,10 @@ class SamlSso implements SamlContract use Dispatchable; use PerformsSingleSignOn; + private $guard; + private $authn_request; + private $destination; + /** * [__construct description] */ @@ -82,14 +86,30 @@ public function response() ->setIssueInstant(new \DateTime()) ->setIssuer(new Issuer($this->issuer)) ->setSignature(new SignatureWriter($this->certificate, $this->private_key, $this->digest_algorithm)) - ->setSubject( + ->setConditions( + (new Conditions()) + ->setNotBefore(new \DateTime()) + ->setNotOnOrAfter(new \DateTime('+1 MINUTE')) + ->addItem(new AudienceRestriction([$this->authn_request->getIssuer()->getValue()])) + ) + ->addItem( + (new AuthnStatement()) + ->setAuthnInstant(new \DateTime('-10 MINUTE')) + ->setSessionIndex(Helper::generateID()) + ->setAuthnContext( + (new AuthnContext())->setAuthnContextClassRef(SamlConstants::NAME_ID_FORMAT_UNSPECIFIED) + ) + ); + + if (config('samlidp.use_name_id', true)) { + $assertion->setSubject( (new Subject()) ->setNameID( new NameID( auth($this->guard) ->user() ->__get(config('samlidp.email_field', 'email')), - SamlConstants::NAME_ID_FORMAT_EMAIL + config('samlidp.name_id_format', SamlConstants::NAME_ID_FORMAT_EMAIL) ) ) ->addSubjectConfirmation( @@ -102,21 +122,8 @@ public function response() ->setRecipient($this->authn_request->getAssertionConsumerServiceURL()) ) ) - ) - ->setConditions( - (new Conditions()) - ->setNotBefore(new \DateTime()) - ->setNotOnOrAfter(new \DateTime('+1 MINUTE')) - ->addItem(new AudienceRestriction([$this->authn_request->getIssuer()->getValue()])) - ) - ->addItem( - (new AuthnStatement()) - ->setAuthnInstant(new \DateTime('-10 MINUTE')) - ->setSessionIndex(Helper::generateID()) - ->setAuthnContext( - (new AuthnContext())->setAuthnContextClassRef(SamlConstants::NAME_ID_FORMAT_UNSPECIFIED) - ) ); + } $attribute_statement = new AttributeStatement(); event(new AssertionEvent($attribute_statement, $this->guard)); @@ -124,12 +131,9 @@ public function response() $assertion->addItem($attribute_statement); // Encrypt the assertion - if ($this->encryptAssertion()) { $encryptedAssertion = new EncryptedAssertionWriter(); - $encryptedAssertion->encrypt($assertion, KeyHelper::createPublicKey( - $this->getSpCertificate() - )); + $encryptedAssertion->encrypt($assertion, KeyHelper::createPublicKey($this->getSpCertificate())); $this->response->addEncryptedAssertion($encryptedAssertion); } else { $this->response->addAssertion($assertion); @@ -204,14 +208,11 @@ private function getQueryParams() private function getSpCertificate() { - $spCertificate = config(sprintf( - 'samlidp.sp.%s.certificate', - $this->getServiceProvider($this->authn_request) - )); + $spCertificate = config(sprintf('samlidp.sp.%s.certificate', $this->getServiceProvider($this->authn_request))); - return (strpos($spCertificate, 'file://') === 0) + return strpos($spCertificate, 'file://') === 0 ? X509Certificate::fromFile($spCertificate) - : (new X509Certificate)->loadPem($spCertificate); + : (new X509Certificate())->loadPem($spCertificate); } /**