request->getPost('email'); if (! $email) { return $this->response->setStatusCode(400)->setBody('Email required'); } $email = trim((string) $email); $at = strrchr($email, '@'); if ($at === false) { return $this->response->setStatusCode(400)->setBody('Invalid email'); } $domain = strtolower(substr($at, 1)); $model = new SamlClientModel(); $samlClient = $model->getByDomain($domain); if (! $samlClient) { return $this->response->setStatusCode(404)->setBody('SAML not configured for this domain'); } session()->set('saml_client_id', $samlClient['id']); return redirect()->to(site_url('saml/login')); } private function loadMergedSettings(): array { $settings = require APPPATH . 'Libraries/Saml/settings.php'; $advanced = require APPPATH . 'Libraries/Saml/advanced_settings.php'; return array_merge($settings, $advanced); } private function getSamlAuth(): SamlAuth { $samlClientId = session()->get('saml_client_id'); if (! $samlClientId) { throw new \RuntimeException('SAML client not found in session'); } $model = new SamlClientModel(); $samlClient = $model->find($samlClientId); if (! $samlClient) { throw new \RuntimeException('Invalid SAML client'); } $settings = $this->loadMergedSettings(); $settings['idp'] = [ 'entityId' => $samlClient['saml_entity_id'], 'singleSignOnService' => [ 'url' => $samlClient['saml_sso_url'], ], 'singleLogoutService' => [ 'url' => $samlClient['saml_slo_url'] ?? '', ], 'x509cert' => $samlClient['saml_x509_cert'], ]; return new SamlAuth($settings); } public function login() { try { $auth = $this->getSamlAuth(); } catch (\Throwable $e) { return redirect()->to(site_url('login'))->with('error', $e->getMessage()); } $url = $auth->login(null, [], false, false, true); return redirect()->to($url); } public function acs() { try { $auth = $this->getSamlAuth(); } catch (\Throwable $e) { return redirect()->to(site_url('login'))->with('error', $e->getMessage()); } $auth->processResponse(); if (! $auth->isAuthenticated()) { return $this->response->setJSON($auth->getErrors()); } $attributes = $auth->getAttributes(); $nameId = $auth->getNameId(); $email = $this->resolveEmailFromSaml($nameId, $attributes); $UserModel = new UserModel(); $user = $UserModel->getUserByEmail($email); if (! $user || $user->is_active === '0') { session()->remove('saml_client_id'); return redirect()->to(site_url('login'))->with('error', 'User not registered or inactive'); } $user_team = $UserModel->getUserTeamsByUserID($user->id); session()->regenerate(true); $session_data = [ 'isLoggedIn' => true, 'userid' => $user->id, 'userData' => $user, 'userProfile' => null, 'user_team' => $user_team, 'saml_name_id' => $nameId, 'saml_attrs' => $attributes, ]; set_session_data($session_data); set_session_data(['fingerprint' => generateFingerprint()]); $this->getUserDeviceInfo($user->id, 'NhanceUser'); return redirect()->to(site_url('dashboard/view')); } protected function getUserDeviceInfo(int $userId, string $type_of_user): void { $userAgent = $this->request->getUserAgent(); $datd = [ 'user_id' => $userId, 'user_type' => $type_of_user, 'ip' => $this->request->getIPAddress(), 'platform' => $userAgent->getPlatform(), 'broswer' => $userAgent->getBrowser(), ]; $AuthHistoryModel = new \App\Models\AuthHistoryModel(); $AuthHistoryModel->insert($datd); } /** * @param array $attributes */ private function resolveEmailFromSaml(?string $nameId, array $attributes): string { if ($nameId && filter_var($nameId, FILTER_VALIDATE_EMAIL)) { return $nameId; } $keys = [ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', 'http://schemas.microsoft.com/identity/claims/emailaddress', 'email', 'Email', 'mail', ]; foreach ($keys as $k) { if (! empty($attributes[$k][0])) { return (string) $attributes[$k][0]; } } foreach ($attributes as $vals) { if (is_array($vals) && ! empty($vals[0]) && filter_var($vals[0], FILTER_VALIDATE_EMAIL)) { return (string) $vals[0]; } } return (string) $nameId; } public function logout() { try { $auth = $this->getSamlAuth(); $url = $auth->logout(null, [], null, null, true); } catch (SamlError $e) { session()->destroy(); return redirect()->to(site_url('login')); } catch (\Throwable $e) { session()->destroy(); return redirect()->to(site_url('login')); } session()->destroy(); return redirect()->to($url); } public function slo() { try { $auth = $this->getSamlAuth(); } catch (\Throwable $e) { return redirect()->to(site_url('login')); } try { $redirectUrl = $auth->processSLO(false, null, false, null, true); } catch (SamlError $e) { return redirect()->to(site_url('login')); } if (! empty($auth->getErrors())) { return $this->response->setJSON($auth->getErrors()); } if ($redirectUrl) { return redirect()->to($redirectUrl); } return redirect()->to(site_url('login')); } public function metadata() { $settings = $this->loadMergedSettings(); try { $samlSettings = new SamlSettings($settings, true); $metadata = $samlSettings->getSPMetadata(); } catch (\Throwable $e) { return $this->response->setStatusCode(500)->setBody($e->getMessage()); } return $this->response ->setHeader('Content-Type', 'application/xml; charset=utf-8') ->setBody($metadata); } }