You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

368 lines
12 KiB

<?php
namespace app\core\auth;
require_once APPPATH . 'core/data/Constants.php';
use app\core\auth\User as UserAuth;
use app\core\auth\Unregister;
use app\core\traits\Rememberme;
use app\core\contract\Sso as SsoContract;
class Sso implements SsoContract {
/**
* Initialize sso connection
* If connection has been established already, re-use it instead
*
* @return bool|array
*/
public static function connect() {
if(strpos(current_url(), 'logout') !== false)
self::unsetUserCookies();
if (self::isLoggedIn())
return true;
/**
* This is just to make sure that, querying SSO from lemonde.fr
* happens only on fresh login.
* If user has already an SSO Session, requesting to lemonde.fr
* happens only when the SSO has expired
* (print_r($_SESSION) to check for the SSO session info)
*/
if (isset($_SESSION['user_token'], $_SESSION['user_iban'])) {
$response = self::establishSSOConnection();
if (!$response)
return false;
if (isset($_COOKIE['lmd_stay_connected'])) {
$response['stay_connected'] = (bool)($_COOKIE['lmd_stay_connected'] ?? false);
$response = self::establishSSOConnection();
}
return $response;
}
return false;
}
/**
* This is generally useful when user logs out
* Resetting his cookies will also logout his session in the parent site
*
* @return void
*/
public static function unsetUserCookies() {
$past = time() - (86400 * 30);
setcookie(self::TEST_SSO_KEY, null, $past);
unset($_SESSION['user_token']);
unset($_SESSION['user_iban']);
}
/**
* Request for user details from lemonde.fr using the cookies
*
* @param string $email
*
* @return bool|object
*/
private static function establishSSOConnection($email = '') {
try {
// Get CI instance
$CI = &get_instance();
// Check if the request is from a bot
if ($CI->agent->is_robot()) {
return false;
}
// Create API data array
$apiData = json_encode([
'Autorization' => $_SESSION['user_token'], // Corrected the header name
'token' => $_SESSION['user_token'],
'app' => "cpay",
'iban' => $_SESSION['user_iban'],
'action' => "sso_info"
]);
// Prepare HTTP headers
$httpHeaders = [
"Content-Type: application/json", // Ensure the API interprets the request correctly
"Content-Length: " . strlen($apiData), // Set Content-Length for php://input
"authority: secure.c4m.mg",
"cache-control: max-age=0",
"upgrade-insecure-requests: 1",
"user-agent: " . ($_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'),
"sec-fetch-user: ?1",
"accept-language: en-US,en;q=0.9"
];
// Initialize cURL
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => self::CPAYMG,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $apiData, // Send data via php://input
CURLOPT_HTTPHEADER => $httpHeaders,
CURLOPT_SSL_VERIFYPEER => false, // Disable SSL peer verification
CURLOPT_SSL_VERIFYHOST => 0 // Disable SSL host verification
]);
// Execute cURL request
$response1 = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
// Decode response
$response = json_decode($response1, true);
// Check for errors and return response
if (!$err && !isset($response['error']) && empty($response['error'])) {
return $response;
}
// Log error or handle unexpected cases
return false;
} catch (\Throwable $e) {
// Handle exception gracefully
log_message('error', 'Error in cURL request: ' . $e->getMessage());
return false;
}
return false;
}
/**
* For Frontoffice Users Only!
* Reset SSO data in session when sso expires
* This will force the request to lemonde.fr to get the updated
* SSO data. This is necessary especially when the user change something
* on his account like a change in the services __ from premium to not premium.
* The current reset_time every 10mins
*
* @return @void
*/
public static function resetSSOInSession() {
if (!self::isLoggedIn())
return;
// Get CI instance
$CI =& get_instance();
$logged_in_user = UserAuth::auth();
// Request new SSO data
$sso = self::establishSSOConnection();
if (!$sso)
return false;
if (isset($logged_in_user['sso']['sso_expiration'])) {
// Check if reset time is reached
if (strtotime('now') <= $logged_in_user['sso']['sso_expiration']) {
return false;
}
$session_key = 'logged_in';
$logged_in_user['sso']['sso_expiration'] = strtotime('+10 minutes',strtotime('now'));
$logged_in_user['sso']['product_code'] = $sso['product_code'];
$logged_in_user['premium'] = !!$sso['premium'];
$new_session_data[$session_key] = $logged_in_user;
// Finally reset login session
$CI->session->unset_userdata('logged_in');
// set new session
$CI->session->set_userdata($new_session_data);
}
}
public static function isLoggedIn() {
return UserAuth::isAuth() && UserAuth::isFOUser();
}
/**
* Forced logout in evenements when user logs out from LeMonde.fr
* Check whether cookies are present, when not then logout the user
* $_COOKIE['lmd_stay_connected'], $_COOKIE['lmd_a_s'], $_COOKIE['lmd_a_m']
*
* @return void
*/
public static function signOutUserWhenLoggedOutFromParentSite() {
if (self::isLoggedIn() && !isset($_SESSION['user_token'], $_SESSION['user_iban'])) {
redirect('logout', 'refresh');
}
}
/**
* Authenticate user when cookie is present but no login session is added yet.
* This is useful when user visits directly the hompage or any page in
* the FO with just the cookie from parent site
*
* @param array $sso data from LeMonde.fr
* @return void
*/
public static function authenticateBySSO($sso) {
if (!self::isLoggedIn() && countVal($sso)) {
redirect('authenticate');
}
}
/**
* Refresh the sso data every 10mins by default
* This is to avoid checking if user is logged in from the lemonde.fr every server request.
* That will cost more time for each process,
* So, we cache that in evenements and only after 10mins the checking of sso in lemonde.fr will happen
* @param array $sso provided by lemonde.fr
* @param array $session_data user session
* @return array updated user session
*/
public static function setSSORefreshTime($sso) {
if (!countVal($sso))
return [];
/**
* Make sure were not adding sso expiration for local testing
* SSO Expiration should be for real login only,
* which means the user logs in from Lemonde.fr
*/
if (!isset($_COOKIE[self::TEST_SSO_KEY])) {
return [
'sso_expiration' => strtotime('+10 minutes',strtotime('now')),
'product_code' => $sso['product_code']
];
}
// For test sso only
if (countVal($sso))
return [
'product_code' => $sso['sso']['product_code']
];
}
/**
* For local / preprod testing
* @param string $email test email
*/
public static function set_mock_cookies($sso) {
$testCookies = MOCK_TEST_EMAILS[$sso['mail']] ?? MOCK_TEST_EMAILS['guillaume.b@wylog.com'];
$expiration = time() + (86400 * 30);
if ($testCookies) {
foreach($testCookies as $c) {
$tmpCookies = explode("=", explode(" ", $c)[1]);
setcookie($tmpCookies[0], $tmpCookies[1], $expiration, '/');
}
setcookie(self::TEST_SSO_KEY, json_encode($sso) , $expiration, '/');
}
}
/**
* Get user email
*/
public static function getEmail() {
if (self::isLoggedIn())
return UserAuth::auth()['email_address'];
if (isset($_COOKIE[self::TEST_SSO_KEY])) {
$testSSo = json_decode($_COOKIE[self::TEST_SSO_KEY], true);
if(isset($testSSo['mail']))
return $testSSo['mail'];
}
return '';
}
/**
* Check if [FO-User] Regular session or Temp session is active
*
* Regular Session is when the user is loggedin
* Temp Session is when unknown / not yet registered user is loggedin from parent site (via auth cookies)
*/
public static function withSession() {
return self::isLoggedIn();
}
/**
* Get test sso
*/
public static function getTestSSO() {
if (isset($_COOKIE[self::TEST_SSO_KEY]) && !empty($_COOKIE[self::TEST_SSO_KEY]))
return json_decode($_COOKIE[self::TEST_SSO_KEY], true);
return [];
}
/**
* This should not be used in production!!!
* This is only for preprod testing to bypass SSO authentication
*
* If you want to check if account is premium or not
* Refer to app\core\auth\User::isPremium() instead!
*/
public static function isPremium($sso = []): bool {
if (countVal($sso))
return !!$sso['premium'];
$testSSo = self::getTestSSO();
if ($testSSo && countVal($testSSo))
return $testSSo['premium'];
if (self::isLoggedIn())
return UserAuth::isPremium();
return false;
}
/**
* Redirect to mon-compte when user with sso is not yet registered in Masterclass
*/
public static function persistMyAccountPage(): void {
// Get CI instance
$CI =& get_instance();
$currentURL = current_url();
$whitelistedRoute = 'mon-compte';
if( strpos($currentURL, $whitelistedRoute) === false )
redirect( $whitelistedRoute );
}
public static function ssoHasUserData($sso = []) {
$required = ['firstname', 'name', 'mail', 'premium'];
if (countVal($sso)) {
if (!isset($sso['mail'], $sso['premium']))
redirect('home');
}
}
/**
* Get subscription type
*
* This can be found as "product_code" in sso
*
* Essential: ESS
* Integral: INT
* Family: FAM
* Former Premium offer: PRE
* Paper + digital offer: MQ
*/
public static function getSubsriptionType() {
if (!self::isLoggedIn() || !UserAuth::auth()['sso'])
return null;
return UserAuth::auth()['sso']['product_code'];
}
}