diff --git a/README.md b/README.md index 01b588d..d947927 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,19 @@ Within the saml.php config file the SAML Service Provider array needs to be fill // The destination is the consuming SAML URL. This might be a SamlAuthController receiving the SAML response. 'destination' => 'https://localhost/samlsp/module.php/saml/sp/saml2-acs.php/default-sp', + // Issuer could be anything, mostly it makes sense to pass the metadata URL + // This is the IDP Entity ID that will be sent to the SP. 'issuer' => 'https://localhost', // OPTIONAL: Use a specific audience restriction value when creating the SAMLRequest object. // Default value is the assertion consumer service URL (the base64 encoded SP url). // This is a bugfix for Nextcloud as SP and can be removed for normal SPs. 'audience_restriction' => 'http://localhost', + + // OPTIONAL: Use to override the default email attribute to set a custom NameID value for a SP + // The NameID attribute must be a property on the User model. + 'nameID' => 'username', ], ], diff --git a/src/Http/Traits/SamlAuth.php b/src/Http/Traits/SamlAuth.php index f09f9ac..43314eb 100644 --- a/src/Http/Traits/SamlAuth.php +++ b/src/Http/Traits/SamlAuth.php @@ -21,21 +21,24 @@ trait SamlAuth /** * Get either the url or the content of a given file. - */ - protected function getSamlFile($configPath, $url) { - if ($url) + */ + protected function getSamlFile($configPath, $url) + { + if ($url) { return Storage::disk('saml')->url($configPath); + } return Storage::disk('saml')->get($configPath); - } + } /** * Get either the url or the content of the saml metadata file. * * @param boolean url Set to true to get the metadata url, otherwise the - * file content will be returned. Defaults to false. + * file content will be returned. Defaults to false. * @return String with either the url or the content */ - protected function metadata($url = false) { + protected function metadata($url = false) + { return $this->getSamlFile(config('saml.idp.metadata'), $url); } @@ -43,10 +46,11 @@ protected function metadata($url = false) { * Get either the url or the content of the certificate file. * * @param boolean url Set to true to get the certificate url, otherwise the - * file content will be returned. Defaults to false. + * file content will be returned. Defaults to false. * @return String with either the url or the content */ - protected function certfile($url = false) { + protected function certfile($url = false) + { return $this->getSamlFile(config('saml.idp.cert'), $url); } @@ -54,10 +58,11 @@ protected function certfile($url = false) { * Get either the url or the content of the certificate keyfile. * * @param boolean url Set to true to get the certificate key url, otherwise - * the file content will be returned. Defaults to false. + * the file content will be returned. Defaults to false. * @return String with either the url or the content */ - protected function keyfile($url = false) { + protected function keyfile($url = false) + { return $this->getSamlFile(config('saml.idp.key'), $url); } @@ -65,18 +70,19 @@ protected function keyfile($url = false) { |-------------------------------------------------------------------------- | Saml authentication |-------------------------------------------------------------------------- - */ + */ /** * Handle an http request as saml authentication request. Note that the method - * should only be called in case a saml request is also included. + * should only be called in case a saml request is also included. * * @param \Illuminate\Http\Request $request * @return void - */ - public function handleSamlLoginRequest($request) { + */ + public function handleSamlLoginRequest($request) + { // Store RelayState to session if provided - if(!empty($request->input('RelayState'))){ + if (!empty($request->input('RelayState'))) { session()->put('RelayState', $request->input('RelayState')); } // Handle SamlRequest if provided, otherwise just exit @@ -112,7 +118,7 @@ protected function buildSamlResponse($authnRequest, $request) // Load in both certificate and keyfile // The files are stored within a private storage path, this prevents from - // making them accessible from outside + // making them accessible from outside $x509 = new X509Certificate(); $certificate = $x509->loadPem($this->certfile()); // Load in keyfile content (last parameter determines of the first one is a file or its content) @@ -144,16 +150,18 @@ protected function buildSamlResponse($authnRequest, $request) // We are responding with both the email and the username as attributes // TODO: Add here other attributes, e.g. groups / roles / permissions $roles = array(); - if(\Auth::check()){ - $user = \Auth::user(); - $email = $user->email; - $name = $user->name; - if (config('saml.forward_roles')) + if (\Auth::check()) { + $user = \Auth::user(); + $nameID = $this->getNameId($user, $authnRequest); + $email = $user->email; + $name = $user->name; + if (config('saml.forward_roles')) { $roles = $user->roles->pluck('name')->all(); - }else { + } + } else { $email = $request['email']; $name = 'Place Holder'; - } + } // Generate the SAML assertion for the response xml object $assertion @@ -162,60 +170,61 @@ protected function buildSamlResponse($authnRequest, $request) ->setIssuer(new \LightSaml\Model\Assertion\Issuer($issuer)) ->setSubject( - (new \LightSaml\Model\Assertion\Subject()) + (new \LightSaml\Model\Assertion\Subject()) ->setNameID(new \LightSaml\Model\Assertion\NameID( - $email, + $nameID, \LightSaml\SamlConstants::NAME_ID_FORMAT_EMAIL )) ->addSubjectConfirmation( - (new \LightSaml\Model\Assertion\SubjectConfirmation()) + (new \LightSaml\Model\Assertion\SubjectConfirmation()) ->setMethod(\LightSaml\SamlConstants::CONFIRMATION_METHOD_BEARER) ->setSubjectConfirmationData( - (new \LightSaml\Model\Assertion\SubjectConfirmationData()) + (new \LightSaml\Model\Assertion\SubjectConfirmationData()) ->setInResponseTo($authnRequest->getId()) ->setNotOnOrAfter(new \DateTime('+1 MINUTE')) ->setRecipient($authnRequest->getAssertionConsumerServiceURL()) - ) - ) - ) + ) + ) + ) ->setConditions( (new \LightSaml\Model\Assertion\Conditions()) ->setNotBefore(new \DateTime()) ->setNotOnOrAfter(new \DateTime('+1 MINUTE')) ->addItem( new \LightSaml\Model\Assertion\AudienceRestriction([ - config('saml.sp.'.base64_encode($authnRequest->getAssertionConsumerServiceURL()).'.audience_restriction', - $authnRequest->getAssertionConsumerServiceURL())]) + config( + 'saml.sp.'.base64_encode($authnRequest->getAssertionConsumerServiceURL()).'.audience_restriction', + $authnRequest->getAssertionConsumerServiceURL() + )]) ) ) ->addItem( - (new \LightSaml\Model\Assertion\AttributeStatement()) + (new \LightSaml\Model\Assertion\AttributeStatement()) ->addAttribute(new \LightSaml\Model\Assertion\Attribute( - \LightSaml\ClaimTypes::EMAIL_ADDRESS, - $email - )) + \LightSaml\ClaimTypes::EMAIL_ADDRESS, + $email + )) ->addAttribute(new \LightSaml\Model\Assertion\Attribute( - \LightSaml\ClaimTypes::COMMON_NAME, - $name - )) + \LightSaml\ClaimTypes::COMMON_NAME, + $name + )) ->addAttribute(new \LightSaml\Model\Assertion\Attribute( - \LightSaml\ClaimTypes::ROLE, - $roles - )) - ) + \LightSaml\ClaimTypes::ROLE, + $roles + )) + ) ->addItem( - (new \LightSaml\Model\Assertion\AuthnStatement()) - ->setAuthnInstant(new \DateTime('-10 MINUTE')) - ->setSessionIndex('_some_session_index') - ->setAuthnContext( - (new \LightSaml\Model\Assertion\AuthnContext()) - ->setAuthnContextClassRef(\LightSaml\SamlConstants::AUTHN_CONTEXT_PASSWORD_PROTECTED_TRANSPORT) - ) + (new \LightSaml\Model\Assertion\AuthnStatement()) + ->setAuthnInstant(new \DateTime('-10 MINUTE')) + ->setSessionIndex('_some_session_index') + ->setAuthnContext( + (new \LightSaml\Model\Assertion\AuthnContext()) + ->setAuthnContextClassRef(\LightSaml\SamlConstants::AUTHN_CONTEXT_PASSWORD_PROTECTED_TRANSPORT) ) - ; + ); - // Send out the saml response - $this->sendSamlResponse($response); + // Send out the saml response + $this->sendSamlResponse($response); } /** @@ -245,4 +254,16 @@ protected function addRelayStateToResponse($response) session()->remove('RelayState'); } } + + /** + * Get the NameID attribute to be used. + * @param User $user + * @param \Illuminate\Http\Request $request + * @return string + */ + protected function getNameId($user, $authnRequest) + { + $nameIdAttribute = config('saml.sp.'.base64_encode($authnRequest->getAssertionConsumerServiceURL()).'.nameID', 'email'); + return $user->{$nameIdAttribute}; + } }