| Server IP : 13.126.101.145 / Your IP : 216.73.216.63 Web Server : Apache/2.4.52 (Ubuntu) System : Linux ip-11-115-0-196 6.8.0-1039-aws #41~22.04.1-Ubuntu SMP Thu Sep 11 10:54:48 UTC 2025 x86_64 User : www-data ( 33) PHP Version : 8.3.17 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/html/rentals_updated/wp-content/plugins/wpo365-login/Services/ |
Upload File : |
<?php
namespace Wpo\Services;
use WP_Error;
use \Wpo\Core\Wpmu_Helpers;
use \Wpo\Services\Authentication_Service;
use \Wpo\Services\Error_Service;
use \Wpo\Services\Log_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Services\Saml2_Service')) {
class Saml2_Service
{
/**
* Iniates a SAML 2.0 request and redirects the user to the IdP.
*
* @since 11.0
*
* @return void
*/
public static function initiate_request($redirect_to, $params = array())
{
Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
/**
* @since 32.0 Replace a possible "#" because add_query_arg and parse_url cannot handle it
*/
$redirect_to = str_replace('#', '_____', $redirect_to);
/**
* @since 16.0 Filters the redirect_to url
*/
$redirect_to = apply_filters('wpo365/cookie/remove/url', $redirect_to);
/**
* @since 27.0 Adds the IDP ID to the relay state URL
*/
$request_service = Request_Service::get_instance();
$request = $request_service->get_request($GLOBALS['WPO_CONFIG']['request_id']);
if (!empty($idp_id = $request->get_item('idp_id'))) {
$redirect_to = add_query_arg(
array('idp_id' => $idp_id),
$redirect_to
);
}
$redirect_to = urlencode($redirect_to);
require_once($GLOBALS['WPO_CONFIG']['plugin_dir'] . '/OneLogin/_toolkit_loader.php');
$forceAuthn = Options_Service::get_global_boolean_var('saml_force_authn');
$saml_settings = self::saml_settings();
$auth = new \OneLogin_Saml2_Auth($saml_settings);
$auth->login($redirect_to, $params, $forceAuthn);
}
/**
* Gets an attribute / claim from the SAML 2.0 response.
*
* @since 11.0
*
* @param $name string WPO365 User field name (looked up in the claim mappings setting)
* @param $saml_attributes array Attributes received as part of the SAML response
* @param $to_lower boolean True if the attribute value returned should be converted to lower case
* @param $type string Enumeration: string, array
*
* @return string Attribute's value as string or null if the claim was not found
*/
public static function get_attribute($claim, $saml_attributes, $to_lower = false, $type = 'string', $return_null = false)
{
$claim_mappings = array(
'preferred_username' => 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name',
'email' => 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
'first_name' => 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname',
'last_name' => 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname',
'full_name' => 'http://schemas.microsoft.com/identity/claims/displayname',
'tid' => 'http://schemas.microsoft.com/identity/claims/tenantid',
'objectidentifier' => 'http://schemas.microsoft.com/identity/claims/objectidentifier',
);
$claim_value = '';
if (isset($claim_mappings[$claim]) && isset($saml_attributes[$claim_mappings[$claim]])) {
$claim_value = $saml_attributes[$claim_mappings[$claim]];
} elseif (isset($saml_attributes[$claim])) {
$claim_value = $saml_attributes[$claim];
} elseif ($return_null) {
return null;
}
if ($type == 'string') {
if (is_array($claim_value) && sizeof($claim_value) > 0) {
$claim_value = $claim_value[0];
}
if (is_string($claim_value)) {
return $to_lower ? \strtolower($claim_value) : $claim_value;
}
} elseif ($type == 'array') {
if (is_array($claim_value)) {
return $claim_value;
}
}
if ($type == 'string') {
return '';
}
if ($type == 'array') {
return array();
}
return null;
}
/**
* Creates a OneLogin settings object with the settings configured through the WPO365 wizard.
*
* @since 11.0
*
* @return mixed(array|boolean) Array with OneLogin (non-advanced) settings or true / false when validating.
*/
public static function saml_settings($validate = false)
{
$base_url = Options_Service::get_aad_option('saml_base_url');
$sp_entity_id = Options_Service::get_aad_option('saml_sp_entity_id');
$sp_sls_url = Options_Service::get_aad_option('saml_sp_sls_url');
$idp_entity_id = Options_Service::get_aad_option('saml_idp_entity_id');
$idp_ssos_url = Options_Service::get_aad_option('saml_idp_ssos_url');
$idp_sls_url = Options_Service::get_aad_option('saml_idp_sls_url');
$x509cert = Options_Service::get_aad_option('saml_x509_cert');
$sp_acs_url = Options_Service::get_aad_option('saml_sp_acs_url');
/**
* @since 24.0 Filters the AAD Redirect URI e.g. to set it dynamically to the current host.
*/
$sp_acs_url = apply_filters('wpo365/aad/redirect_uri', $sp_acs_url);
$log_level = $validate ? 'WARN' : 'ERROR';
$has_errors = false;
$exit_on_error = function () use ($validate) {
if (!$validate) {
Authentication_Service::goodbye(Error_Service::SAML2_ERROR);
exit();
}
};
if (empty($base_url)) {
Log_Service::write_log($log_level, __METHOD__ . ' -> SAML 2.0 error (Base URL cannot be empty)');
$exit_on_error();
$has_errors = true;
}
if (empty($sp_entity_id)) {
Log_Service::write_log($log_level, __METHOD__ . ' -> SAML 2.0 error (Service Provider Entity ID cannot be empty)');
$exit_on_error();
$has_errors = true;
}
if (empty($sp_acs_url)) {
Log_Service::write_log($log_level, __METHOD__ . ' -> SAML 2.0 error (Service Provider Assertion Consumer Service URL cannot be empty)');
$exit_on_error();
$has_errors = true;
}
if (empty($sp_sls_url)) {
Log_Service::write_log($log_level, __METHOD__ . ' -> SAML 2.0 error (Service Provider Single Logout Service URL cannot be empty)');
$exit_on_error();
$has_errors = true;
}
if (empty($idp_entity_id)) {
Log_Service::write_log($log_level, __METHOD__ . ' -> SAML 2.0 error (Identity Provider Entity ID cannot be empty)');
$exit_on_error();
$has_errors = true;
}
if (empty($idp_ssos_url)) {
Log_Service::write_log($log_level, __METHOD__ . ' -> SAML 2.0 error (Identity Provider Single Sign-on Service URL cannot be empty)');
$exit_on_error();
$has_errors = true;
}
if (empty($idp_sls_url)) {
Log_Service::write_log($log_level, __METHOD__ . ' -> SAML 2.0 error (Identity Provider Single Logout Service URL cannot be empty)');
$exit_on_error();
$has_errors = true;
}
if (empty($x509cert)) {
if (!empty(Options_Service::get_aad_option('saml_idp_meta_data_url'))) {
$x509cert = 'X509 CERTIFICATE PLACEHOLDER';
} else {
Log_Service::write_log($log_level, __METHOD__ . ' -> SAML 2.0 error (X509 Certificate cannot be empty)');
$exit_on_error();
$has_errors = true;
}
}
if (true === $validate) {
return !$has_errors;
}
$settings = array(
'strict' => true,
'debug' => false,
'baseurl' => $base_url,
'sp' => array(
'entityId' => $sp_entity_id,
'assertionConsumerService' => array(
'url' => $sp_acs_url,
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
),
'singleLogoutService' => array(
'url' => $sp_sls_url,
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
),
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
'x509cert' => '',
'privateKey' => '',
),
'idp' => array(
'entityId' => $idp_entity_id,
'singleSignOnService' => array(
'url' => $idp_ssos_url,
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
),
'singleLogoutService' => array(
'url' => $idp_sls_url,
'responseUrl' => '',
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
),
'x509cert' => $x509cert,
),
);
/**
* @since 25.0 By default we disable the requested authentication context.
*/
if (!Options_Service::get_global_boolean_var('saml_enable_requested_authn_context')) {
$settings['security'] = array(
'requestedAuthnContext' => false
);
}
/**
* @since 11.14
*
* Example:
*
* define( 'WPO_SAML2_ADVANCED_SETTINGS',
* array(
* 'security' => array(
* 'requestedAuthnContext' => array (
* 'urn:federation:authentication:windows'
* )
* )
* )
* );
*/
if (defined('WPO_SAML2_ADVANCED_SETTINGS') && is_array(constant('WPO_SAML2_ADVANCED_SETTINGS'))) {
return array_merge($settings, constant('WPO_SAML2_ADVANCED_SETTINGS'));
}
return $settings;
}
public static function check_message_id($message_id)
{
$cache = Wpmu_Helpers::mu_get_transient('wpo365_saml_message_ids');
if (empty($cache) || !\is_array($cache)) {
$cache = array(
'last_write_index' => 0,
'slots' => array(
0 => array($message_id),
1 => array(),
2 => array(),
3 => array(),
4 => array(),
5 => array(),
),
);
return;
}
$minutes = intval(date('i'));
$mod = $minutes % 10;
$write_slot = $minutes - $mod / 10;
foreach ($cache['slots'] as $slot) {
$index = array_search($message_id, $slot);
if (false !== $index) {
Log_Service::write_log('ERROR', __METHOD__ . ' -> SAML 2.0 error (replay attack detected: SAML message ID already used)');
Authentication_Service::goodbye(Error_Service::TAMPERED_WITH);
exit();
}
}
$cache['slots'][$write_slot][] = $message_id;
$cache['slots'][(($write_slot + 1) % 6)] = array();
Wpmu_Helpers::mu_set_transient('wpo365_saml_message_ids', $cache);
}
/**
* Attempts to read the IdP metadata from the App Federation Metadata URL entered by the administrator and
* configure WPO365 accordingly.
*
* @since 25.0
*
* @return bool|WP_Error Returns true if the import was successful otherwise an error.
*/
public static function import_idp_meta()
{
if (empty($saml_idp_meta_data_url = Options_Service::get_aad_option('saml_idp_meta_data_url'))) {
return new WP_Error('ConfigurationException', sprintf('%s -> The App Federation Metadata URL is not configured', __METHOD__));
}
$response = wp_remote_get(
$saml_idp_meta_data_url,
array(
'method' => 'GET',
'headers' => array(
'Expect' => ''
),
'sslverify' => !Options_Service::get_global_boolean_var('skip_host_verification'),
)
);
if (is_wp_error($response)) {
return $response;
}
$idp_meta = wp_remote_retrieve_body($response);
if (empty($idp_meta)) {
return new WP_Error('BodyParseError', sprintf('%s -> No body was detected in the response retrieved from %s', __METHOD__, $saml_idp_meta_data_url));
}
$idp_meta_xml = simplexml_load_string($idp_meta);
if (false === $idp_meta_xml) {
return new WP_Error('XmlParseError', sprintf('%s -> Body retrieved from %s cannot be loaded as an XML document', __METHOD__, $saml_idp_meta_data_url));
}
$imported_idp_values = array(
'saml_idp_entity_id' => $idp_meta_xml->attributes()->entityID->__toString(),
'saml_x509_cert' => $idp_meta_xml->IDPSSODescriptor->KeyDescriptor->KeyInfo->X509Data->X509Certificate->__toString(),
'saml_idp_ssos_url' => $idp_meta_xml->IDPSSODescriptor->SingleSignOnService->attributes()->Location->__toString(),
'saml_idp_sls_url' => $idp_meta_xml->IDPSSODescriptor->SingleLogoutService->attributes()->Location->__toString(),
);
foreach ($imported_idp_values as $option_key => $value) {
if (!empty($value)) {
if (strcasecmp($option_key, 'saml_x509_cert') === 0) {
$value = Saml2_Service::format_x509_certificate($value);
}
if (strcasecmp($option_key, 'saml_idp_entity_id') === 0) {
$tenant_id = Saml2_Service::get_tenant_id_from_entity_id($value);
if (!empty($tenant_id)) {
Options_Service::add_update_option('tenant_id', $tenant_id);
Log_Service::write_log('DEBUG', sprintf('%s -> Derived tenant ID with value "%s" from SAML IdP Entity ID "%s"', __METHOD__, $tenant_id, $value));
}
}
Options_Service::add_update_option($option_key, $value);
Log_Service::write_log('DEBUG', sprintf('%s -> Imported SAML option "%s" with value "%s" from %s', __METHOD__, $option_key, $value, $saml_idp_meta_data_url));
} else {
Log_Service::write_log('ERROR', sprintf('%s -> Failed to import SAML option "%s" with value "%s" from %s', __METHOD__, $option_key, $value, $saml_idp_meta_data_url));
}
}
return true;
}
/**
*
* @return WP_Error|string
*/
public static function export_sp_meta()
{
$base_url = Options_Service::get_aad_option('saml_base_url');
if (empty($base_url)) {
$base_url = get_option('home');
if (empty($base_url)) {
return new WP_Error('ConfigurationException', sprintf('%s -> The SAML 2.0 Base URL is not configured', __METHOD__));
}
Options_Service::add_update_option('saml_base_url', $base_url);
}
$base_url = trailingslashit($base_url);
if (empty($sp_entity_id = Options_Service::get_aad_option('saml_sp_entity_id'))) {
$sp_entity_id = sprintf('%s%s', $base_url, uniqid());
Options_Service::add_update_option('saml_sp_entity_id', $sp_entity_id);
}
if (empty($sp_sls_url = Options_Service::get_aad_option('saml_sp_sls_url'))) {
$sp_sls_url = sprintf('%swp-login.php&action=loggedout', $base_url);
Options_Service::add_update_option('saml_sp_sls_url', $sp_sls_url);
}
if (empty($sp_acs_url = Options_Service::get_aad_option('saml_sp_acs_url'))) {
$sp_acs_url = $base_url;
Options_Service::add_update_option('saml_sp_acs_url', $sp_acs_url);
}
$sp_metadata_valid_until = date("Y-m-d\TH:i:s\Z", strtotime('+48 hours', time()));
$xml_chunks = array(
'<?xml version="1.0"?>',
'<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" validUntil="__##sp_metadata_valid_until##__" entityID="__##sp_entity_id##__">',
' <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">',
' <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="__##sp_sls_url##__" />',
' <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="__##sp_acs_url##__" index="1" />',
' </md:SPSSODescriptor>',
'</md:EntityDescriptor>',
);
$xml = implode(PHP_EOL, $xml_chunks);
$xml = str_replace('__##sp_metadata_valid_until##__', $sp_metadata_valid_until, $xml);
$xml = str_replace('__##sp_entity_id##__', $sp_entity_id, $xml);
$xml = str_replace('__##sp_sls_url##__', $sp_sls_url, $xml);
$xml = str_replace('__##sp_acs_url##__', $sp_acs_url, $xml);
return $xml;
}
/**
* To support multi-tenancy.
*
* @param array $issuers
* @return array
*/
public static function merge_allowed_issuers($issuers)
{
$_issuers = Options_Service::get_global_list_var('allowed_tenants');
$_issuers = array_map(function ($tenant_id) {
return sprintf('https://sts.windows.net/%s/', $tenant_id);
}, $_issuers);
$__issuers = array_merge($_issuers, $issuers);
return array_unique($__issuers);
}
/**
* Attempts to read the signing certificate from the IdP metadata / App Federation Metadata URL
*
* @since 26.1
*
* @return bool|WP_Error Returns true if the import was successful otherwise an error.
*/
public static function get_signing_certificate_for_tenant($tenant_id, $force = false)
{
if (empty($tenant_id)) {
return new WP_Error('MissingArgumentException', sprintf('%s -> Tenant ID cannot be empty', __METHOD__));
}
/**
* 3 scenarios:
* 1. Single tenant > Take saml_idp_meta_data_url (ours)
* 2. Multi tenancy > Retrieve from default meta data URL (theirs) when issuer is not us
* 3. Multiple IdPs > Take saml_idp_meta_data_url (ours)
*/
if (Options_Service::get_global_boolean_var('multi_tenanted')) {
$idp_entity_id = Options_Service::get_aad_option('saml_idp_entity_id');
$idp_tentant_id = Saml2_Service::get_tenant_id_from_entity_id($idp_entity_id);
$use_their_config = strcasecmp($idp_tentant_id, $tenant_id) !== 0;
}
if (!$force && empty($use_their_config)) {
$_x509_cert = Options_Service::get_aad_option('saml_x509_cert');
if (!empty($_x509_cert)) {
return $_x509_cert;
}
}
$certificates = get_site_option('wpo365_x509_certificates', array());
$request_service = Request_Service::get_instance();
$request = $request_service->get_request($GLOBALS['WPO_CONFIG']['request_id']);
$idp_id = $request->get_item('idp_id');
if (empty($idp_id)) {
$idp_id = 'default'; // Not to be confused with 'default' => true for an IdP Config
}
$cert_storage_key = sprintf('%s_%s', $tenant_id, $idp_id);
if (!$force) {
foreach ($certificates as $key => $certificate) {
if (strcasecmp($key, $cert_storage_key) === 0) {
return Saml2_Service::format_x509_certificate($certificate);
}
}
}
if (empty($use_their_config)) {
$federation_metadata_url = Options_Service::get_aad_option('saml_idp_meta_data_url');
}
if (empty($federation_metadata_url)) {
$tld = !empty($tld = Options_Service::get_global_string_var('tld')) ? $tld : '.com';
$federation_metadata_url = sprintf(
'https://login.microsoftonline%s/%s/FederationMetadata/2007-06/FederationMetadata.xml',
$tld,
$tenant_id
);
}
$response = wp_remote_get(
$federation_metadata_url,
array(
'method' => 'GET',
'headers' => array(
'Expect' => ''
),
'sslverify' => !Options_Service::get_global_boolean_var('skip_host_verification'),
)
);
if (is_wp_error($response)) {
return $response;
}
$idp_meta = wp_remote_retrieve_body($response);
if (empty($idp_meta)) {
return new WP_Error('BodyParseError', sprintf('%s -> No body was detected in the response retrieved from %s', __METHOD__, $federation_metadata_url));
}
$idp_meta_xml = simplexml_load_string($idp_meta);
if (false === $idp_meta_xml) {
return new WP_Error('XmlParseError', sprintf('%s -> Body retrieved from %s cannot be loaded as an XML document', __METHOD__, $federation_metadata_url));
}
$x509_cert = $idp_meta_xml->IDPSSODescriptor->KeyDescriptor->KeyInfo->X509Data->X509Certificate->__toString();
$certificates[$cert_storage_key] = $x509_cert;
update_site_option('wpo365_x509_certificates', $certificates);
return Saml2_Service::format_x509_certificate($x509_cert);
}
/**
* Gets the tenant / directory ID from the SAML entity ID
*
* @param mixed $entity_id
* @return string Tenant ID or empty string
*/
public static function get_tenant_id_from_entity_id($entity_id)
{
if (empty($entity_id)) {
return '';
}
$segments = explode('/', $entity_id);
foreach ($segments as $segment) {
if (!empty($segment) && strlen($segment) === 36) {
return $segment;
}
}
return '';
}
/**
* Rewrites the X509 string into the required format.
*
* @since 25.0
*
* @param mixed $certificate
* @return string
*/
private static function format_x509_certificate($certificate)
{
if (empty($certificate)) {
return '';
}
$chunks = str_split($certificate, 64);
array_unshift($chunks, '-----BEGIN CERTIFICATE-----');
$chunks[] = '-----END CERTIFICATE-----' . PHP_EOL;
return implode(PHP_EOL, $chunks);
}
}
}