nhance-enrollment/app/Controllers/SamlController.php
2026-05-22 18:38:57 +05:30

500 lines
16 KiB
PHP

<?php
namespace App\Controllers;
use App\Models\SamlClientModel;
use App\Helpers\JWTToken;
use App\Helpers\RestAuthHelper;
use App\Controllers\RestAuthenticationController;
use OneLogin\Saml2\Auth as SamlAuth;
use OneLogin\Saml2\Error as SamlError;
use OneLogin\Saml2\Settings as SamlSettings;
class SamlController extends BaseController
{
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($clientId): SamlAuth
{
$model = new SamlClientModel();
$samlClient = $model->where('id', $clientId)->first();
if (! $samlClient) {
throw new \RuntimeException('Invalid SAML client with ID: ' . $clientId);
}
$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 {
$email = $this->request->getGet('email');
if (! $email) {
return $this->response->setStatusCode(400)->setJSON([
'status' => 'failed',
'code' => 400,
'data' => '',
'message' => 'Email required',
]);
}
$email = trim((string) $email);
$at = strrchr($email, '@');
if ($at === false) {
return $this->response->setStatusCode(400)->setJSON([
'status' => 'failed',
'code' => 400,
'data' => '',
'message' => 'Invalid email',
]);
}
$domain = strtolower(substr($at, 1));
// dd($domain);
$model = new SamlClientModel();
$samlClient = $model->getByDomain($domain);
// dd($samlClient);
if (! $samlClient) {
return $this->response->setStatusCode(404)->setJSON([
'status' => 'failed',
'code' => 404,
'data' => '',
'message' => 'This email is not configured with Microsoft. Please try other login options.',
]);
}
$auth = $this->getSamlAuth($samlClient['id']);
} catch (\Throwable $e) {
return $this->response->setStatusCode(500)->setJSON([
'status' => 'failed',
'code' => 500,
'data' => '',
'message' => $e->getMessage(),
]);
}
$url = $auth->login(null, [], false, false, true);
return redirect()->to($url);
}
public function acs()
{
try {
$postedEmail = $this->extractEmailFromPostedSamlResponse();
if (! $postedEmail) {
return $this->redirectToFrontendSso([
'status' => 'failed',
'code' => 400,
'data' => '',
'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''],
'message' => 'Unable to resolve email from SAML response',
]);
}
$at = strrchr($postedEmail, '@');
if ($at === false) {
return $this->redirectToFrontendSso([
'status' => 'failed',
'code' => 400,
'data' => '',
'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''],
'message' => 'Invalid email in SAML response',
]);
}
$domain = strtolower(substr($at, 1));
$model = new SamlClientModel();
$client = $model->getByDomain($domain);
if (! $client) {
return $this->redirectToFrontendSso([
'status' => 'failed',
'code' => 404,
'data' => '',
'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''],
'message' => 'This email is not configured with Microsoft. Please try other login options.',
]);
}
$clientId = $client['id'];
$auth = $this->getSamlAuth($clientId);
$auth->processResponse();
} catch (\Throwable $e) {
return $this->redirectToFrontendSso([
'status' => 'failed',
'code' => 500,
'data' => '',
'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''],
'message' => $e->getMessage(),
]);
}
if (! $auth->isAuthenticated()) {
return $this->redirectToFrontendSso([
'status' => 'failed',
'code' => 401,
'data' => '',
'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''],
'message' => implode(', ', $auth->getErrors()),
]);
}
$attributes = $auth->getAttributes();
$nameId = $auth->getNameId();
$email = $this->resolveEmailFromSaml($nameId, $attributes);
$email = trim((string) $email);
// Same email lookup path used in RestAuthenticationController::getVerifiedUserData()
$empdata = RestAuthHelper::getPreAndPostDataByEmailOrMobile(['email_id' => $email]);
if (! empty($empdata['pre']['email_corporate'])) {
$email = (string) $empdata['pre']['email_corporate'];
} elseif (! empty($empdata['post']['email_id'])) {
$email = (string) $empdata['post']['email_id'];
}
$postEnrollment = ['status' => 'failed', 'code' => 404, 'data' => ''];
if (! empty($empdata['post'])) {
$postId = $empdata['post']['id'] ?? $empdata['post']['employee_id'] ?? null;
if ($postId !== null && $postId !== '') {
$restAuth = new RestAuthenticationController();
$rawBody = $restAuth->callThirdPartyGETAPI(['id' => $postId],'getTokenforSamulssOAuthLogin');
$api = is_string($rawBody) ? json_decode($rawBody, true) : null;
if (is_array($api)
&& (($api['status'] ?? '') === 'success' || (int) ($api['code'] ?? 0) === 200)
&& isset($api['data'])
&& $api['data'] !== ''
) {
$postEnrollment = [
'status' => 'success',
'code' => 200,
'data' => is_string($api['data']) ? $api['data'] : (string) $api['data'],
];
} else {
$postEnrollment = [
'status' => 'failed',
'code' => (int) ($api['code'] ?? 404),
'data' => '',
];
}
}
}
if (empty($empdata)) {
return $this->redirectToFrontendSso([
'status' => 'failed',
'code' => 404,
'data' => '',
'post_enrollment' => $postEnrollment,
'message' => 'User not found',
]);
}
if (! empty($empdata['pre'])) {
$employeeData = $empdata['pre'];
unset($employeeData['employee_id']);
$employeeData['token_type'] = 'pre';
$result = JWTToken::encode($employeeData);
return $this->redirectToFrontendSso([
'status' => 'success',
'code' => 200,
'data' => $result,
'post_enrollment' => $postEnrollment,
]);
}
return $this->redirectToFrontendSso([
'status' => 'failed',
'code' => 404,
'data' => '',
'post_enrollment' => $postEnrollment,
'message' => 'Employee not found in pre-enrollment',
]);
}
private function redirectToFrontendSso(array $payload)
{
$feUrl = rtrim((string) env('fe_url'), '/');
if ($feUrl === '') {
$feUrl = rtrim((string) env('FE_URL'), '/');
}
$responsePayload = [
'status' => $payload['status'] ?? 'failed',
'code' => $payload['code'] ?? 500,
'data' => $payload['data'] ?? '',
'post_enrollment' => $payload['post_enrollment'] ?? ['status' => 'failed', 'code' => 404, 'data' => ''],
];
// dd($responsePayload);
if (! empty($payload['message'])) {
$responsePayload['message'] = $payload['message'];
}
$encodedPayload = base64_encode((string) json_encode($responsePayload));
$params = http_build_query([
'payload' => $encodedPayload,
]);
// dd($feUrl . '/sso-login?' . $params);
return redirect()->to($feUrl . '/sso-login?' . $params);
}
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;
}
private function extractEmailFromPostedSamlResponse(): ?string
{
$raw = $this->request->getPost('SAMLResponse');
if (! is_string($raw) || $raw === '') {
return null;
}
$decoded = base64_decode($raw, true);
if ($decoded === false || trim($decoded) === '') {
return null;
}
libxml_use_internal_errors(true);
$xml = simplexml_load_string($decoded);
if ($xml === false) {
return null;
}
$emailNodes = $xml->xpath("//*[local-name()='Attribute' and (@Name='email' or @Name='Email' or @Name='mail' or @Name='http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' or @Name='http://schemas.microsoft.com/identity/claims/emailaddress')]/*[local-name()='AttributeValue']");
if (is_array($emailNodes)) {
foreach ($emailNodes as $node) {
$val = trim((string) $node);
if ($val !== '' && filter_var($val, FILTER_VALIDATE_EMAIL)) {
return $val;
}
}
}
$nameIdNodes = $xml->xpath("//*[local-name()='NameID']");
if (is_array($nameIdNodes)) {
foreach ($nameIdNodes as $node) {
$val = trim((string) $node);
if ($val !== '' && filter_var($val, FILTER_VALIDATE_EMAIL)) {
return $val;
}
}
}
return null;
}
public function logout()
{
try {
$clientId = $this->request->getGet('client_id');
if (! $clientId) {
return $this->response->setStatusCode(400)->setBody('Client ID required');
}
$auth = $this->getSamlAuth($clientId);
$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 {
$clientId = $this->request->getGet('client_id');
if (! $clientId) {
return $this->response->setStatusCode(400)->setBody('Client ID required');
}
$auth = $this->getSamlAuth($clientId);
} 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);
}
// public function verifyMSAuthUser()
// {
// $code = $this->request->getVar('code');
// // echo $code;
// try {
// $token = $this->provider->getAccessToken('authorization_code', [
// 'code' => $code
// ]);
// $ownerDetails = $this->provider->getResourceOwner($token);
// $data = $ownerDetails->toArray();
// $name = $data['name'] ?? null;
// $email = $data['upn'] ?? $data['unique_name'] ?? null;
// $res = checkUserExist($name, $email);
// if($res['status'] == true)
// {
// $user = $res['data'];
// //get service for the organization
// $orgService = $this->organizationModel->find($user['org_id']);
// $user['service'] = json_decode($orgService['services_ids'],true);
// foreach ($user['service'] as $key => $value)
// {
// $serviceData = $this->serviceModel->find($value['service_id']);
// $user['service'][$key]['name'] = $serviceData['name'];
// $user['service'][$key]['icon'] = $serviceData['icon'];
// }
// //get role
// $user['role'] = $this->userModel->getRole($user['role_id']);
// //find user has the all access
// $user['plan_action'] = getUserPlanCreationRestrictionStatus($user);
// $token = generateJWT($user);
// return $this->respond([ 'status' => 200,'message' => 'Login successful','token' => $token ]);
// }else{
// return $this->respond([ 'status' => 401,'message' => 'Login Failed','data' => [] ]);
// }
// } catch (IdentityProviderException $e) {
// exit($e->getMessage());
// }
// }
//end of microsoft oAuth
}