nhance-enrollment/app/Controllers/SamlController.php
2026-04-03 15:17:00 +05:30

249 lines
7.0 KiB
PHP

<?php
namespace App\Controllers;
use App\Models\SamlClientModel;
use App\Models\UserModel;
use OneLogin\Saml2\Auth as SamlAuth;
use OneLogin\Saml2\Error as SamlError;
use OneLogin\Saml2\Settings as SamlSettings;
class SamlController extends BaseController
{
public function startLogin()
{
$email = $this->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<string, mixed> $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);
}
}