249 lines
7.0 KiB
PHP
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);
|
|
}
|
|
}
|