input('SAMLRequest') ?? session('saml_pending_request'); $relayState = $request->input('RelayState') ?? session('saml_pending_relay_state'); Log::info('SAML SSO Request received.', [ 'ip' => $request->ip(), 'method' => $request->method(), 'has_saml_request' => ! empty($samlRequest), 'has_relay_state' => ! empty($relayState), 'session_has_pending' => session()->has('saml_pending_request'), ]); if (empty($samlRequest)) { Log::warning('SAML SSO access rejected: Missing SAMLRequest parameter.', [ 'ip' => $request->ip(), ]); abort(400, 'Missing SAMLRequest parameter.'); } try { Log::info('Decoding and parsing SAMLRequest XML payload.'); $parsed = $this->samlService->parseRequest($samlRequest); Log::info('SAMLRequest successfully parsed.', [ 'request_id' => $parsed['requestId'], 'issuer' => $parsed['issuer'], 'acs_url' => $parsed['acsUrl'], ]); } catch (Throwable $e) { Log::error('SAMLRequest parsing failed.', [ 'ip' => $request->ip(), 'error' => $e->getMessage(), ]); abort(400, 'Invalid SAMLRequest: '.$e->getMessage()); } // Find corresponding Connected App by issuer (SAML Entity ID) $connectedApp = $this->samlService->findConnectedApp($parsed['issuer']); if (! $connectedApp) { Log::warning('SAML SSO access rejected: Unregistered Service Provider (EntityID / Issuer).', [ 'issuer' => $parsed['issuer'], 'request_id' => $parsed['requestId'], 'ip' => $request->ip(), ]); abort( 403, "The Service Provider [{$parsed['issuer']}] is not authorized in this system.", ); } Log::info('SAML ConnectedApp matched successfully.', [ 'app_id' => $connectedApp->id, 'app_name' => $connectedApp->name, 'status' => $connectedApp->status?->name, ]); if ('disconnected' === $connectedApp->status?->name) { Log::warning('SAML SSO access rejected: Connected App is deactivated.', [ 'app_id' => $connectedApp->id, 'app_name' => $connectedApp->name, 'request_id' => $parsed['requestId'], ]); abort( 403, "The Service Provider [{$connectedApp->name}] is currently deactivated.", ); } /** * @var array{ * saml?: array{ * entity_id?: string, * acs_url?: string * } * } $settings */ $settings = $connectedApp->settings; $acsUrl = $parsed['acsUrl'] ?: $settings['saml']['acs_url'] ?? null; if (empty($acsUrl)) { Log::error('SAML SSO failed: Assertion Consumer Service (ACS) URL is undefined.', [ 'app_id' => $connectedApp->id, 'app_name' => $connectedApp->name, 'request_id' => $parsed['requestId'], ]); abort(400, 'Assertion Consumer Service (ACS) URL is not defined.'); } // Authenticate the User if (! Auth::check()) { Log::info('SAML SSO guest session intercepted. Persisting request parameters to session and redirecting to login.', [ 'request_id' => $parsed['requestId'], 'relay_state' => $relayState, 'ip' => $request->ip(), ]); session([ 'saml_pending_request' => $samlRequest, 'saml_pending_relay_state' => $relayState, ]); return redirect()->route('login'); } /** @var User $user */ $user = Auth::user(); Log::info('SAML SSO user authenticated session detected. Generating signed SAML Response assertion.', [ 'user_id' => $user->id, 'user_email' => $user->email, 'app_id' => $connectedApp->id, 'app_name' => $connectedApp->name, 'acs_url' => $acsUrl, 'request_id' => $parsed['requestId'], ]); try { $samlResponse = $this->samlService->generateResponse( $user, $connectedApp, $parsed['requestId'], $acsUrl, ); Log::info('Signed SAML Response assertion successfully generated.'); } catch (Throwable $e) { Log::error('SAML Response generation failed.', [ 'user_id' => $user->id, 'app_id' => $connectedApp->id, 'error' => $e->getMessage(), ]); abort(500, 'Cryptographic signing of SAML Response failed.'); } // Clear pending SAML request from session session()->forget(['saml_pending_request', 'saml_pending_relay_state']); Log::info('Pending SAML session parameters cleared.'); Log::info('Rendering SAML HTTP-POST auto-submission redirect form.', [ 'user_id' => $user->id, 'app_name' => $connectedApp->name, 'acs_url' => $acsUrl, 'relay_state' => $relayState, ]); // Return HTTP-POST Auto-Submission Page return view('saml.post_response', [ 'appName' => $connectedApp->name, 'acsUrl' => $acsUrl, 'samlResponse' => $samlResponse, 'relayState' => $relayState, ]); } /** * SAML 2.0 Identity Provider (IdP) Metadata Endpoint. * Exposes public keys and bindings to Service Providers like Microsoft Entra ID. */ public function metadata(): Response { $idpEntityId = route('saml.metadata'); $ssoUrl = route('saml.sso'); Log::info('SAML IdP Metadata requested.', [ 'ip' => request()->ip(), 'user_agent' => request()->userAgent(), 'entity_id' => $idpEntityId, 'sso_url' => $ssoUrl, ]); try { $metadataXml = $this->samlService->generateMetadata( $idpEntityId, $ssoUrl, ); Log::info('SAML IdP Metadata XML descriptor generated successfully.'); } catch (Throwable $e) { Log::error('SAML IdP Metadata XML generation failed.', [ 'error' => $e->getMessage(), ]); abort( 500, 'Failed to retrieve Identity Provider cryptographic identity.', ); } return response($metadataXml, 200, [ 'Content-Type' => 'application/xml; charset=utf-8', ]); } }