59034c01 by Jeff Balicki

sso

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent 4f81f235
Showing 176 changed files with 4861 additions and 0 deletions
<?php
namespace Wpo\Blocks;
// Prevent public access to this script
defined('ABSPATH') or die();
use \Wpo\Core\Script_Helpers;
use \Wpo\Services\Options_Service;
if (!class_exists('\Wpo\Blocks\Loader')) {
class Loader
{
public function __construct($app, $edition, $plugins_dir, $plugins_url, $load_front_end = true)
{
add_action('enqueue_block_editor_assets', function () use ($app, $edition, $plugins_dir, $plugins_url) {
$this->enqueue_editor_assets($app, $edition, $plugins_dir, $plugins_url);
});
if ($load_front_end) {
add_action('enqueue_block_assets', function () use ($app, $edition, $plugins_dir, $plugins_url) {
$this->enqueue_assets($app, $edition, $plugins_dir, $plugins_url);
});
}
}
/**
* Enqueues js / css assets that will only be loaded for the back end.
*
* @since 1.0.0
*
* @return void
*/
private function enqueue_editor_assets($app, $edition, $plugins_dir, $plugins_url)
{
$editor_block_path = "/Blocks/dist/$app/editor-$edition.js";
$editor_block_asset_file = include($plugins_dir . "/Blocks/dist/$app/editor-$edition.asset.php");
// Enqueue the bundled block JS file
\wp_enqueue_script(
"wpo365-$app-$edition-editor",
$plugins_url . $editor_block_path,
$editor_block_asset_file['dependencies'],
$editor_block_asset_file['version']
);
\wp_add_inline_script("wpo365-$app-$edition-editor", 'window.wpo365 = window.wpo365 || {}; window.wpo365.blocks = ' . json_encode(array(
'nonce' => \wp_create_nonce('wp_rest'),
'apiUrl' => \trailingslashit($GLOBALS['WPO_CONFIG']['url_info']['wp_site_url']) . 'wp-json/wpo365/v1/graph',
)), 'before');
if ($app == 'aud') {
$audiences = Options_Service::get_global_list_var('audiences');
$auth_scenario = Options_Service::get_global_string_var('auth_scenario');
$keys = array();
foreach ($audiences as $index => $audience) {
$keys[$audience['key']] = $audience['title'];
}
\wp_add_inline_script("wpo365-$app-$edition-editor", 'window.wpo365 = window.wpo365 || {}; window.wpo365.blocks = ' . json_encode(array(
'nonce' => \wp_create_nonce('wp_rest'),
'apiUrl' => \trailingslashit($GLOBALS['WPO_CONFIG']['url_info']['wp_site_url']) . 'wp-json/wpo365/v1/graph',
)) . '; window.wpo365.aud = ' . \json_encode($keys) . ' ; window.wpo365.scenario = \'' . $auth_scenario . '\'', 'before');
}
}
/**
* Enqueues js / css assets that will be loaded for both front and back end.
*
* @since 1.0.0
*
* @return void
*/
private function enqueue_assets($app, $edition, $plugins_dir, $plugins_url)
{
$app_block_path = "/Blocks/dist/$app/app-$edition.js";
$app_block_asset_file = include($plugins_dir . "/Blocks/dist/$app/app-$edition.asset.php");
if (is_singular()) {
$id = get_the_ID();
$block_type = $edition == 'basic' ? $app . 'Basic' : $app;
if (has_block('wpo365/' . \strtolower($block_type), $id)) {
$react_urls = Script_Helpers::get_react_urls();
\wp_enqueue_script('wpo365-unpkg-react', $react_urls['react_url']);
\wp_enqueue_script('wpo365-unpkg-react-dom', $react_urls['react_dom_url']);
\wp_enqueue_script(
"wpo365-$app-$edition-block",
$plugins_url . $app_block_path,
\array_merge($app_block_asset_file['dependencies'], array('wpo365-unpkg-react', 'wpo365-unpkg-react-dom')),
$app_block_asset_file['version'],
true
); // Load in footer so the page has rendered and the block with the class can be found
\wp_add_inline_script("wpo365-$app-$edition-block", 'window.wpo365 = window.wpo365 || {}; window.wpo365.blocks = ' . json_encode(array(
'nonce' => \wp_create_nonce('wp_rest'),
'apiUrl' => \trailingslashit($GLOBALS['WPO_CONFIG']['url_info']['wp_site_url']) . 'wp-json/wpo365/v1/graph',
)), 'before');
}
}
}
}
}
<?php return array('dependencies' => array('wp-polyfill'), 'version' => '72b2bd50b39920906f5b6a272414a645');
\ No newline at end of file
This diff could not be displayed because it is too large.
<?php return array('dependencies' => array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-i18n', 'wp-polyfill'), 'version' => 'f3108bb05994449c9043cbfc999b93c4');
\ No newline at end of file
This diff could not be displayed because it is too large.
<?php
namespace Wpo\Core;
use \Wpo\Core\Wpmu_Helpers;
use \Wpo\Services\Log_Service;
use \Wpo\Services\Options_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Core\Compatibility_Helpers')) {
class Compatibility_Helpers
{
/**
* Writes the compatibility warning as an error to the log and remembers it for 24 hours
* to prevent flooding the log with the same error over and again.
*
* @since 20.0
*
* @param string $warning
*
* @return void
*/
public static function compat_warning($warning)
{
$compat_warnings = Wpmu_Helpers::mu_get_transient('wpo365_compat_warnings');
if (empty($compat_warnings) || !is_array($compat_warnings) || !in_array($warning, $compat_warnings)) {
Log_Service::write_log('ERROR', $warning);
if (is_array($compat_warnings)) {
$compat_warnings[] = $warning;
} else {
$compat_warnings = array($warning);
}
// Transient shall block the repetition of this warning for 24 hours.
Wpmu_Helpers::mu_set_transient('wpo365_compat_warnings', $compat_warnings, 86400);
}
}
/**
* Reduces the key of the extra_user_fields array by removing the name part for custom
* WordPress usermeta that was introduced with version 20.
*
* @since 20.0
*
* @param array $extra_user_fields The array of extra user fields that will be updated
*
* @return void
*/
public static function update_user_field_key($extra_user_fields)
{
if (!class_exists('\Wpo\Services\User_Details_Service') || method_exists('\Wpo\Services\User_Details_Service', 'parse_user_field_key')) {
return $extra_user_fields;
}
// Iterate over the configured graph fields and identify any supported expandable properties
$extra_user_fields = array_map(function ($kv_pair) {
$marker_pos = WordPress_Helpers::stripos($kv_pair['key'], ';#');
if ($marker_pos > 0) {
$kv_pair['key'] = substr($kv_pair['key'], 0, $marker_pos);
}
return $kv_pair;
}, $extra_user_fields);
$compat_warning = sprintf(
'%s -> The administrator configured <em>Azure AD user attributes to WordPress user meta mappings</em> on the plugin\'s <strong>User sync</strong> page. These mappings have been recently upgraded to allow administrators to specify their own name for the usermeta key. This new feature, however, breaks existing functionality. To remain compatible you should update your premium WPO365 extension and optionally update the existing mappings.',
__METHOD__
);
self::compat_warning($compat_warning);
return $extra_user_fields;
}
}
}
<?php
namespace Wpo\Core;
// Prevent public access to this script
defined( 'ABSPATH' ) or die();
use \Wpo\Core\Permissions_Helpers;
use \Wpo\Core\Config_Endpoints;
use \Wpo\Services\Log_Service;
use \Wpo\Services\Options_Service;
if( !class_exists( '\Wpo\Core\Config_Controller' ) ) {
class Config_Controller extends \WP_REST_Controller {
/**
* Register the routes for the objects of the controller.
*/
public function register_routes() {
$version = '1';
$namespace = 'wpo365/v' . $version;
register_rest_route( $namespace, '/users/search/unique',
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => function ( $request ) {
return Config_Endpoints::users_search_unique( $request );
},
'permission_callback' => array( $this, 'check_permissions' ),
),
)
);
}
/**
* Checks if the user can retrieve an access token for the requested scope.
*
* @param string $scope Scope for which the token must be valid.
* @return bool|WP_Error True if user can retrieve an access token for the requested scope otherwise a WP_Error is returned.
*/
public function check_permissions( $request, $allow_application = false ) {
if ( ! wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' ) ) {
return new \WP_Error( 'UnauthorizedException', 'The request cannot be validated.', array( 'status' => 401 ) );
}
$wp_usr = \wp_get_current_user();
if ( empty( $wp_usr ) ) {
return new \WP_Error( 'UnauthorizedException', 'Please sign in first before using this API.', array( 'status' => 401 ) );
}
if ( ! Permissions_Helpers::user_is_admin( $wp_usr ) ) {
return new \WP_Error( 'UnauthorizedException', 'Please sign in with administrative credentials before using this API.', array( 'status' => 403 ) );
}
return true;
}
}
}
\ No newline at end of file
<?php
namespace Wpo\Core;
// Prevent public access to this script
defined( 'ABSPATH' ) or die();
use \Wpo\Services\Log_Service;
use \Wpo\Services\Options_Service;
if( !class_exists( '\Wpo\Core\Config_Endpoints' ) ) {
class Config_Endpoints {
/**
* Register the routes for the objects of the controller.
*/
public static function users_search_unique( $rest_request ) {
$body = $rest_request->get_json_params();
if ( empty( $body ) || !\is_array( $body ) || empty( $body[ 'keyword' ] ) ) {
return new \WP_Error( 'InvalidArgumentException', 'Body is malformed JSON or the request header did not define the Content-type as application/json.', array( 'status' => 400 ) );
}
$users = new \WP_User_Query( array(
'count_total' => true,
'search' => '*' . esc_attr( $body[ 'keyword' ] ) . '*',
'search_columns' => array(
'user_login',
'user_nicename',
'user_email',
'user_url',
),
) );
if ( $users->get_total() != 1 ) {
return new \WP_Error( 'AmbigiousResultException', 'The query did not return exactly one unique user. Please update your input and try again.', array( 'status' => 404 ) );
}
$users_found = $users->get_results();
return array(
'ID' => $users_found[0]->ID,
'user_login' => $users_found[0]->user_login,
);
}
}
}
\ No newline at end of file
<?php
namespace Wpo\Core;
// Prevent public access to this script
defined( 'ABSPATH' ) or die( );
if ( !class_exists( '\Wpo\Core\Cron_Helpers' ) ) {
class Cron_Helpers {
/**
* Adds custom named cron schedules
*
* @since 10.0
*
* @param $schedules Array of already defined
*/
public static function add_cron_schedules( $schedules ) {
$schedules[ 'wpo_five_minutes' ] = array(
'interval' => 300,
'display' => __( 'Every 5 minutes', 'wpo365-login' )
);
$schedules[ 'wpo_daily' ] = array(
'interval' => 86400,
'display' => __( 'WPO365 Daily', 'wpo365-login' )
);
$schedules[ 'wpo_weekly' ] = array(
'interval' => 604800,
'display' => __( 'WPO365 Weekly', 'wpo365-login' )
);
return $schedules;
}
}
}
\ No newline at end of file
<?php
namespace Wpo\Core;
use \Wpo\Services\Log_Service;
use \Wpo\Services\Options_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Core\Domain_Helpers')) {
class Domain_Helpers
{
/**
* Gets the domain (host) part of an email address.
*
* @since 3.1
*
* @param string $email_address email address to analyze
* @return string Returns the email address' host part or an empty string if
* the email address appears to be invalid
*/
public static function get_smtp_domain_from_email_address($email_address)
{
$smpt_domain = '';
if (filter_var(trim($email_address), FILTER_VALIDATE_EMAIL) !== false) {
$smpt_domain = strtolower(trim(substr($email_address, strrpos($email_address, '@') + 1)));
}
return $smpt_domain;
}
/**
* Checks a user's smtp domain against the configured custom and default domains
*
* @since 4.0
*
* @return boolean true if a match is found otherwise false
*/
public static function is_tenant_domain($email_domain)
{
$custom_domain = array_change_key_case(array_flip(Options_Service::get_global_list_var('custom_domain')));
$default_domain = Options_Service::get_global_string_var('default_domain');
if (array_key_exists($email_domain, $custom_domain) || strtolower(trim($default_domain)) == $email_domain) {
return true;
}
return false;
}
}
}
<?php
namespace Wpo\Core;
use \Wpo\Core\Url_Helpers;
use \Wpo\Core\WordPress_Helpers;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Core\Globals')) {
class Globals
{
public static function set_global_vars(
$plugin_file,
$plugin_dir
) {
if (false === function_exists('get_plugin_data')) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_data = \get_plugin_data($plugin_file);
$base_name = plugin_basename($plugin_file);
$GLOBALS['WPO_CONFIG'] = array(
'options' => array(),
'plugin' => $base_name,
'plugin_dir' => $plugin_dir,
'plugin_url' => plugin_dir_url($plugin_file),
'plugin_file' => $plugin_file,
'extension_file' => '',
'extensions' => array(),
'slug' => substr($base_name, 0, WordPress_Helpers::stripos($base_name, '/')),
'store' => 'https://www.wpo365.com',
'store_item' => '',
'store_item_id' => '',
'request_id' => uniqid('', true),
'url_info' => self::get_url_info(),
'version' => $plugin_data['Version'],
'ina' => (array_key_exists('ina', $_POST) && true === filter_var($_POST['ina'], FILTER_VALIDATE_BOOLEAN)),
);
}
/**
* Sets a number of URL related globals (all normalized and not ending with a trailing space).
* Whether or not to force SSL is determined by the user override (option) use_ssl. If this
* option hast been configured, the plugin will assume the same protocol as used for the
* redirect url. If the redirect utl hasn't been configured yet, the plugin will assume the
* same protocol as used for the home url.
*
* @since 1.0
*
* @return array
*/
public static function get_url_info()
{
$home = get_option('home');
$scheme = (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1)) ||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
? 'https'
: 'http';
/**
* @since 12.10 Deal with reverse proxies
*/
if (WordPress_Helpers::stripos($home, 'http://') === 0 && $scheme == 'https') {
$home = preg_replace("/^http:/i", "https:", $home);
}
$request_uri = Url_Helpers::ensure_trailing_slash_path($_SERVER['REQUEST_URI']);
$home_path = Url_Helpers::ensure_trailing_slash_path(parse_url($home, PHP_URL_PATH));
$host = parse_url($home, PHP_URL_HOST);
$current_url = $scheme . '://' . $host . $request_uri;
return array(
'request_uri' => $request_uri,
'wp_site_url' => Url_Helpers::ensure_trailing_slash_url($home),
'wp_site_path' => $home_path,
'current_url' => $current_url,
'host' => $host,
);
}
}
}
<?php
namespace Wpo\Core;
use \Wpo\Core\WordPress_Helpers;
use \Wpo\Services\Log_Service;
use \Wpo\Services\Options_Service;
use \Wpo\Services\User_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Core\Permissions_Helpers')) {
class Permissions_Helpers
{
/**
* @since 7.12
*/
public static function user_is_admin($user)
{
if ($user instanceof \WP_User) {
return \in_array('administrator', $user->roles) || is_super_admin($user->ID);
}
return false;
}
/**
* Returns true when a user is allowed to change the password
*
* @since 1.0
* @return void
*
* @return boolean true when a user is allowed to change the password otherwise false
*/
public static function show_password_fields($show, $user)
{
return !self::block_password_update($user->ID);
}
/**
* Returns true when a user is allowed to change the password
*
* @since 1.5
*
* @param boolean $allow whether allowed or not
* @param int $user_id id of the user for which the action is triggered
*
* @return boolean true when a user is allowed to change the password otherwise false
*/
public static function allow_password_reset($allow, $user_id)
{
return !self::block_password_update($user_id);
}
/**
* Helper method to determin whether a user is allowed to change the password
*
* @since 1.5
*
* @param int $user_id id of the user for which the action is triggered
*
* @return boolean true when a user is not allowed to change the password otherwise false
*/
private static function block_password_update($user_id)
{
$block_password_change = Options_Service::get_global_boolean_var('block_password_change');
// Not configured or not blocked
if (false === $block_password_change) { // user is not logged on
Log_Service::write_log('DEBUG', __METHOD__ . ' -> Not blocking password update');
return false;
}
$wp_usr = get_user_by('ID', intval($user_id));
// Limit the blocking of password update only for O365 users
return User_Service::user_is_o365_user($user_id) === User_Service::IS_O365_USER && !self::user_is_admin($wp_usr) ? true : false;
}
/**
* Prevents users who cannot create new users to change their email address
*
* @since 1.0
* @param array errors => Existing errors ( from Wordpress )
* @param bool update => true when updating an existing user otherwise false
* @param WPUser usr_new => Updated user
* @return void
*/
public static function prevent_email_change($user_id)
{
// Don't block as per global settings configuration
if (
false === Options_Service::get_global_boolean_var('block_email_change')
|| User_Service::user_is_o365_user($user_id) !== User_Service::IS_O365_USER
) {
return;
}
$usr_old = get_user_by('ID', intval($user_id));
if ($usr_old === false) {
return;
}
// At this point the user is an O365 user and email change should be blocked as per config
if (isset($_POST['email']) && $_POST['email'] != $usr_old->user_email) {
// Prevent update
$_POST['email'] = $usr_old->user_email;
add_action('user_profile_update_errors', function ($errors) {
$errors->add('email_update_error', __('Updating your email address is currently not allowed', 'wpo365-login'));
});
}
}
/**
* Quick check whether the requested scope e.g. api.yammer.com requires delegated access.
*
* @since 17.0
*
* @param string $scope The scope the requested access must be valid for.
* @return boolean True if delegated access is required for the scope provide.
*/
public static function must_use_delegate_access_for_scope($scope)
{
return (false !== WordPress_Helpers::stripos($scope, 'api.yammer.com') ||
false !== WordPress_Helpers::stripos($scope, '.sharepoint.com') ||
(false === WordPress_Helpers::stripos($scope, 'user.read.all') && false !== WordPress_Helpers::stripos($scope, 'user.read'))
);
}
}
}
<?php
namespace Wpo\Core;
use \Wpo\Core\Extensions_Helpers;
use \Wpo\Services\Options_Service;
// Prevent public access to this script
defined( 'ABSPATH' ) or die();
if ( !class_exists( '\Wpo\Core\Plugin_Helpers' ) ) {
class Plugin_Helpers {
/**
* Helper to check if a premium WPO365 plugin edition is active.
*/
public static function is_premium_edition_active( $slug = null ) {
if ( empty( $slug ) ) {
$extensions = Extensions_Helpers::get_extensions();
foreach ( $extensions as $slug => $extension ) {
if ( true === $extension[ 'activated' ] ) {
return true;
}
}
return false;
}
if ( false === function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
return \is_plugin_active( $slug );
}
/**
* WPMU aware wp filter extension to show the action link on the plugins page. Will add
* the wpo365 configuration action link depending on the WPMU configuration
*
* @since 7.3
*
* @param Array $links The current action link collection
*
* @return Array The new action link collection
*/
public static function get_configuration_action_link( $links ) {
// Don't show the configuration link for subsite admin if subsite options shouldn't be used
if ( is_multisite() && !is_network_admin() && false === Options_Service::mu_use_subsite_options() )
return $links;
$wizard_link = '<a href="admin.php?page=wpo365-wizard">' . __( 'Configuration', 'wpo365-login' ) . '</a>';
array_push( $links, $wizard_link );
return $links;
}
}
}
<?php
namespace Wpo\Core;
// Prevent public access to this script
defined( 'ABSPATH' ) or die();
if ( !class_exists( '\Wpo\Core\Request' ) ) {
class Request {
private $id;
private $storage = array();
public function __construct( $id ) {
$this->id = $id;
}
public function current_request_id() {
return $this->id;
}
public function set_item( $key, $value ) {
if ( !is_string( $key ) ) {
return false;
}
if ( empty( $key ) ) {
return false;
}
$this->storage[ $key ] = $value;
return true;
}
public function get_item( $key ) {
if ( !is_string( $key ) || empty( $key ) ) {
return false;
}
if ( array_key_exists( $key, $this->storage ) ) {
return $this->storage[ $key ];
}
return false;
}
public function remove_item( $key ) {
if ( !is_string( $key ) || empty( $key ) ) {
return false;
}
if ( array_key_exists( $key, $this->storage ) ) {
unset( $this->storage[ $key ] );
return true;
}
return false;
}
public function clear() {
$this->storage = array();
}
}
}
<?php
namespace Wpo\Core;
use \Wpo\Core\WordPress_Helpers;
use \Wpo\Services\Options_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Core\Script_Helpers')) {
class Script_Helpers
{
/**
* Helper to enqueue the pintra redirect script.
*
* @since 8.6
*
* @since 15.4 Added inline script to globally define isWpLogin as true if WP login is detected.
*
* @return void
*/
public static function enqueue_pintra_redirect()
{
if (Options_Service::get_global_boolean_var('use_no_teams_sso')) {
wp_enqueue_script('pintraredirectjs', trailingslashit($GLOBALS['WPO_CONFIG']['plugin_url']) . 'apps/dist/pintra-redirect-wo-teams.js', array(), $GLOBALS['WPO_CONFIG']['version'], false);
} else if (Options_Service::get_global_boolean_var('use_ms_teams_sso_v1')) {
wp_enqueue_script('pintraredirectjs', trailingslashit($GLOBALS['WPO_CONFIG']['plugin_url']) . 'apps/dist/pintra-redirect-v1.js', array(), $GLOBALS['WPO_CONFIG']['version'], false);
} else {
wp_enqueue_script('pintraredirectjs', trailingslashit($GLOBALS['WPO_CONFIG']['plugin_url']) . 'apps/dist/pintra-redirect.js', array(), $GLOBALS['WPO_CONFIG']['version'], false);
}
if (class_exists('\Wpo\Core\Url_Helpers') && \Wpo\Core\Url_Helpers::is_wp_login()) {
\wp_add_inline_script('pintraredirectjs', 'window.wpo365 = window.wpo365 || {}; window.wpo365.isWpLogin = true;', 'before');
}
}
/**
* Helper to enqueue the wizard script.
*
* @since 8.6
*
* @return void
*/
public static function enqueue_wizard()
{
if (!(is_admin() || is_network_admin()) || !isset($_REQUEST['page']) || WordPress_Helpers::stripos($_REQUEST['page'], 'wpo365-wizard') === false) {
return;
}
global $wp_roles;
$extensions = array();
// Bundles
if (class_exists('\Wpo\Premium')) $extensions[] = 'wpo365LoginPremium';
if (class_exists('\Wpo\Intranet')) $extensions[] = 'wpo365LoginIntranet';
// Extensions
if (class_exists('\Wpo\Apps')) $extensions[] = 'wpo365Apps';
if (class_exists('\Wpo\Avatar')) $extensions[] = 'wpo365Avatar';
if (class_exists('\Wpo\Custom_Fields')) $extensions[] = 'wpo365CustomFields';
if (class_exists('\Wpo\Groups')) $extensions[] = 'wpo365Groups';
if (class_exists('\Wpo\Plus')) $extensions[] = 'wpo365LoginPlus';
if (class_exists('\Wpo\Professional')) $extensions[] = 'wpo365LoginProfessional';
if (class_exists('\Wpo\Mail')) $extensions[] = 'wpo365Mail';
if (class_exists('\Wpo\Roles_Access')) $extensions[] = 'wpo365RolesAccess';
if (class_exists('\Wpo\Scim')) $extensions[] = 'wpo365Scim';
// Plugins
if (class_exists('\Wpo\Login')) $extensions[] = 'wpo365Login';
if (class_exists('\Wpo\MsGraphMailer')) $extensions[] = 'wpo365MsGraphMailer';
if (!empty(get_option('mail_integration_365_plugin_ops'))) $extensions[] = 'mailIntegration';
$itthinx_groups = class_exists('\Wpo\Services\Mapped_Itthinx_Groups_Service') ? \Wpo\Services\Mapped_Itthinx_Groups_Service::get_groups_groups() : array();
$post_types = get_post_types();
$props = array(
'adminUrl' => get_site_url(null, '/wp-admin'),
'availableGroups' => json_encode($itthinx_groups),
'availablePostTypes' => json_encode($post_types),
'availableRoles' => json_encode($wp_roles->roles),
'extensions' => $extensions,
'nonce' => wp_create_nonce('wpo365_fx_nonce'),
'restNonce' => wp_create_nonce('wp_rest'),
'siteUrl' => get_home_url(),
'wpmu' => is_multisite() ? (defined('WPO_MU_USE_SUBSITE_OPTIONS') && true === constant('WPO_MU_USE_SUBSITE_OPTIONS') ? 'wpmuDedicated' : 'wpmuShared') : 'wpmuNone',
'ina' => is_network_admin(),
);
wp_enqueue_script('wizardjs', trailingslashit($GLOBALS['WPO_CONFIG']['plugin_url']) . 'apps/dist/wizard.js', array(), $GLOBALS['WPO_CONFIG']['version'], true);
\wp_add_inline_script('wizardjs', 'window.wpo365 = window.wpo365 || {}; window.wpo365.wizard = ' . json_encode(array(
'nonce' => wp_create_nonce('wpo365_fx_nonce'),
'wpAjaxAdminUrl' => admin_url() . 'admin-ajax.php',
'props' => $props,
)) . '; window.wpo365.blocks = ' . json_encode(array(
'nonce' => \wp_create_nonce('wp_rest'),
'apiUrl' => \trailingslashit($GLOBALS['WPO_CONFIG']['url_info']['wp_site_url']) . 'wp-json/wpo365/v1/graph',
)), 'before');
}
/**
* Helper to load the pintraredirectjs script asynchronously.
*
* @since 18.0
*
* @param mixed $tag
* @param mixed $handle
* @param mixed $src
* @return mixed
*/
public static function enqueue_script_asynchronously($tag, $handle, $src)
{
if ($handle === 'pintraredirectjs') {
$tag = str_replace('></script>', ' async></script>', $tag);
}
return $tag;
}
/**
* Helper to allow administrators to switch the CDN where to load the react/react-dom dependencies from.
* This option was added after unpkg.com was poorly available on 28 Oct. 2022.
*
* @since 20.2
*
* @return array Returns an assoc array with react_url and react_dom_url.
*/
public static function get_react_urls()
{
if (Options_Service::get_global_boolean_var('use_alternative_cdn')) {
// Administrator can define his / her own URLs e.g. when self-hosting the react files
if (defined('WPO_CDN') && is_array(constant('WPO_CDN'))) {
$react_url = !empty(constant('WPO_CDN')['react']) ? constant('WPO_CDN')['react'] : '';
$react_dom_url = !empty(constant('WPO_CDN')['react_dom']) ? constant('WPO_CDN')['react_dom'] : '';
}
// If not self-hosted then we take the react.js file from cdnjs.cloudflare.com instead
if (empty($react_url) || !filter_var($react_url, FILTER_VALIDATE_URL)) {
$react_url = 'https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js';
}
// If not self-hosted then we take the react-dom.js file from cdnjs.cloudflare.com instead
if (empty($react_dom_url) || !filter_var($react_dom_url, FILTER_VALIDATE_URL)) {
$react_dom_url = 'https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js';
}
}
// If the administrator did not configure the use of an alternative CDN we take the react.js file from unpkg.com
if (empty($react_url) || !filter_var($react_url, FILTER_VALIDATE_URL)) {
$react_url = 'https://unpkg.com/react@16/umd/react.production.min.js';
}
// If the administrator did not configure the use of an alternative CDN we take the react-dom.js file from unpkg.com
if (empty($react_dom_url) || !filter_var($react_dom_url, FILTER_VALIDATE_URL)) {
$react_dom_url = 'https://unpkg.com/react-dom@16/umd/react-dom.production.min.js';
}
return array(
'react_url' => $react_url,
'react_dom_url' => $react_dom_url
);
}
/**
* Helper to add admin styles.
*
* @since 21.8
*
* @param string $css
* @return void
*/
public static function add_admin_bar_styles($css)
{
wp_enqueue_style('wpo365-admin-bar-styles', plugins_url('/css/wpo365-admin-bar.css', dirname(__FILE__)));
}
}
}
<?php
namespace Wpo\Core;
use \Wpo\Core\Extensions_Helpers;
use \Wpo\Core\WordPress_Helpers;
use \Wpo\Services\Options_Service;
use \Wpo\Services\Error_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Core\Shortcode_Helpers')) {
class Shortcode_Helpers
{
/**
* Helper method to ensure that short codes are initialized
*
* @since 7.0
*
* @return void
*/
public static function ensure_pintra_short_code()
{
if (!shortcode_exists('pintra')) {
add_shortcode('pintra', '\Wpo\Core\Shortcode_Helpers::add_pintra_shortcode');
}
}
/**
* Adds a pintra app launcher into the page
*
* @since 5.0
*
* @param array short code parameters according to Wordpress codex
* @param string content found in between the short code start and end tag
* @param string text domain
*/
public static function add_pintra_shortcode($atts = array(), $content = null, $tag = '')
{
$atts = array_change_key_case((array)$atts, CASE_LOWER);
$props = '[]';
if (
isset($atts['props'])
&& strlen(trim($atts['props'])) > 0
) {
$result = array();
$props = html_entity_decode($atts['props']);
$prop_kv_pairs = explode(';', $props);
foreach ($prop_kv_pairs as $prop_kv_pair) {
$first_separator = WordPress_Helpers::stripos($prop_kv_pair, ',');
if (false === $first_separator) {
continue;
}
$result[\substr($prop_kv_pair, 0, $first_separator)] = \substr($prop_kv_pair, $first_separator + 1);
}
$props = json_encode($result);
}
$script_url = isset($atts['script_url']) ? html_entity_decode($atts['script_url']) : '';
ob_start();
include($GLOBALS['WPO_CONFIG']['plugin_dir'] . '/templates/pintra.php');
$content = ob_get_clean();
return wp_kses($content, WordPress_Helpers::get_allowed_html());
}
/**
* Helper method to ensure that short codes are initialized
*
* @since 8.0
*
* @return void
*/
public static function ensure_login_button_short_code_V2()
{
if ((class_exists('\Wpo\Premium') || class_exists('\Wpo\Intranet')) && !shortcode_exists('wpo365-sign-in-with-microsoft-v2-sc')) {
add_shortcode('wpo365-sign-in-with-microsoft-v2-sc', '\Wpo\Core\Shortcode_Helpers::add_sign_in_with_microsoft_shortcode_V2');
}
}
/**
* Adds the Sign in with Microsoft short code V2
*
* @since 8.0
*
* @param array short code parameters according to Wordpress codex
* @param string content found in between the short code start and end tag
* @param string text domain
*/
public static function add_sign_in_with_microsoft_shortcode_V2($params = array(), $content = null, $tag = '')
{
if (empty($content)) {
return $content;
}
$site_url = $GLOBALS['WPO_CONFIG']['url_info']['wp_site_url'];
// Load the js dependency
ob_start();
include(Extensions_Helpers::get_active_extension_dir(array('wpo365-login-premium/wpo365-login.php', 'wpo365-login-intranet/wpo365-login.php')) . '/templates/openid-ssolink.php');
$js_lib = ob_get_clean();
// Sanitize the HTML template
$dom = new \DOMDocument();
@$dom->loadHTML($content);
$script = $dom->getElementsByTagName('script');
$remove = array();
foreach ($script as $item)
$remove[] = $item;
foreach ($remove as $item)
$item->parentNode->removeChild($item);
// Concatenate the two
$output = $js_lib . $dom->saveHTML();
return str_replace("__##PLUGIN_BASE_URL##__", $GLOBALS['WPO_CONFIG']['plugin_url'], $output);
}
/**
* Helper method to ensure that short code for login button is initialized
*
* @since 11.0
*/
public static function ensure_login_button_short_code()
{
if (!shortcode_exists('wpo365-login-button')) {
add_shortcode('wpo365-login-button', '\Wpo\Core\Shortcode_Helpers::login_button');
}
}
/**
* Helper to display the Sign in with Microsoft button on a login form.
*
* @since 10.6
*
* @param
*
* @return void
*/
public static function login_button()
{
// Don't render a login button when sso is disabled
if (Options_Service::get_global_boolean_var('no_sso')) {
return;
}
// Used by the template that is rendered
$hide_login_button = Options_Service::get_global_boolean_var('hide_login_button');
$sign_in_with_microsoft = Options_Service::get_global_string_var('sign_in_with_microsoft');
if (empty($sign_in_with_microsoft) || $sign_in_with_microsoft == 'Sign in with Microsoft') {
$sign_in_with_microsoft = __('Sign in with Microsoft', 'wpo365-login');
}
ob_start();
include($GLOBALS['WPO_CONFIG']['plugin_dir'] . '/templates/login-button.php');
$content = ob_get_clean();
echo wp_kses($content, WordPress_Helpers::get_allowed_html());
}
/**
* Helper method to ensure that short code for displaying errors is initialized
*
* @since 7.8
*/
public static function ensure_display_error_message_short_code()
{
if ((class_exists('\Wpo\Professional') || class_exists('\Wpo\Premium') || class_exists('\Wpo\Intranet')) && !shortcode_exists('wpo365-display-error-message-sc'))
add_shortcode('wpo365-display-error-message-sc', '\Wpo\Core\Shortcode_Helpers::add_display_error_message_shortcode');
}
/**
* Adds the error message encapsulated in a div into the page
*
* @since 7.8
*
* @param array short code parameters according to Wordpress codex
* @param string content found in between the short code start and end tag
* @param string text domain
*/
public static function add_display_error_message_shortcode($atts = array(), $content = null, $tag = '')
{
$error_code = isset($_GET['login_errors'])
? sanitize_text_field($_GET['login_errors'])
: '';
$error_message = Error_Service::get_error_message($error_code);
if (empty($error_message)) {
return;
}
ob_start();
include(Extensions_Helpers::get_active_extension_dir(array('wpo365-login-professional/wpo365-login.php', 'wpo365-login-premium/wpo365-login.php', 'wpo365-login-intranet/wpo365-login.php')) . '/templates/error-message.php');
$content = ob_get_clean();
return wp_kses($content, WordPress_Helpers::get_allowed_html());
}
}
}
<?php
namespace Wpo\Core;
// prevent public access to this script
defined('ABSPATH') or die();
use \Wpo\Services\Log_Service;
if (!class_exists('\Wpo\Core\User')) {
class User
{
/**
* Email address of user
*
* @since 1.0.0
*
* @var string
*/
public $email = '';
/**
* Unique user's principal name
*
* @since 1.0.0
*
* @var string
*/
public $upn = '';
/**
* User's preferred name
*
* @since 1.0.0
*
* @var string
*/
public $preferred_username = '';
/**
* Name of user
*
* @since 1.0.0
*
* @var string
*/
public $name = '';
/**
* User's first name
*
* @since 1.0.0
*
* @var string
*/
public $first_name = '';
/**
* User's last name incl. middle name etc.
*
* @since 1.0.0
*
* @var string
*/
public $last_name = '';
/**
* User's full ( or display ) name
*
* @since 1.0.0
*
* @var string
*/
public $full_name = '';
/**
* Office 365 and/or Azure AD group ids
*/
public $groups = array();
/**
* User's tenant ID
*/
public $tid = '';
/**
* User's Azure AD object ID
*/
public $oid = '';
/**
* True is the user was created during the current script execution
*/
public $created = false;
/**
* True is the user was created from an ID Token / SAML response
*/
public $from_idp_token = false;
/**
* The Graph Resource for this user
*/
public $graph_resource = null;
/**
* The SAML attributes for this user
*/
public $saml_attributes = array();
}
}
<?php
namespace Wpo\Core;
// prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Core\Version')) {
class Version
{
public static $current = '23.1';
}
}
<?php
namespace Wpo\Core;
use Wpo\Services\Options_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Core\WordPress_Helpers')) {
class WordPress_Helpers
{
/**
* PHP 8.1 safe version of PHP's trim.
*
* @param mixed $str
* @param string $charlist
* @return string
*/
public static function trim($str, $charlist = " \n\r\t\v\x00")
{
$str = null === $str ? '' : $str;
return trim($str, $charlist);
}
/**
* PHP 8.1 safe version of PHP's ltrim.
*
* @param mixed $str
* @param string $charlist
* @return string
*/
public static function ltrim($str, $charlist = " \n\r\t\v\x00")
{
$str = null === $str ? '' : $str;
return ltrim($str, $charlist);
}
/**
* PHP 8.1 safe version of PHP's rtrim.
*
* @param mixed $str
* @param string $charlist
* @return string
*/
public static function rtrim($str, $charlist = " \n\r\t\v\x00")
{
$str = null === $str ? '' : $str;
return rtrim($str, $charlist);
}
/**
* PHP 8.1 safe version of PHP's stripos.
*
* @param mixed $haystack
* @param mixed $needle
* @param mixed $offset
* @return int|false
*/
public static function stripos($haystack, $needle, $offset = 0)
{
$haystack = null === $haystack ? '' : $haystack;
$needle = null === $needle ? '' : $needle;
return stripos($haystack, $needle, $offset);
}
/**
* PHP 8.1 safe version of PHP's strpos.
*
* @param mixed $haystack
* @param mixed $needle
* @param mixed $offset
* @return int|false
*/
public static function strpos($haystack, $needle, $offset = 0)
{
$haystack = null === $haystack ? '' : $haystack;
$needle = null === $needle ? '' : $needle;
return strpos($haystack, $needle, $offset);
}
/**
* Helper to base64 URL decode.
*
* @param string Input to be decoded.
*
* @return string Input decoded
*/
public static function base64_url_decode($arg)
{
$res = $arg;
$res = str_replace('-', '+', $res);
$res = str_replace('_', '/', $res);
switch (strlen($res) % 4) {
case 0:
break;
case 2:
$res .= "==";
break;
case 3:
$res .= "=";
break;
default:
break;
}
$res = base64_decode($res);
return $res;
}
/**
* Helper to base64 URL encode.
*
* @param mixed $arg
* @return string
*/
public static function base64_url_encode($arg)
{
return strtr(
base64_encode($arg),
[
'=' => '',
'+' => '-',
'/' => '_',
]
);
}
/**
* Helper for wp_kses to define the allowed HTML element names, attribute names, attribute
* values, and HTML entities
*
* @return mixed
*/
public static function get_allowed_html()
{
global $allowedposttags;
$allowed_atts = array(
'action' => array(),
'align' => array(),
'alt' => array(),
'class' => array(),
'data-nonce' => array(),
'data-props' => array(),
'data-wpajaxadminurl' => array(),
'data' => array(),
'dir' => array(),
'fill' => array(),
'for' => array(),
'height' => array(),
'href' => array(),
'html' => array(),
'id' => array(),
'lang' => array(),
'method' => array(),
'name' => array(),
'novalidate' => array(),
'onClick' => array(),
'onclick' => array(),
'rel' => array(),
'rev' => array(),
'src' => array(),
'style' => array(),
'tabindex' => array(),
'target' => array(),
'title' => array(),
'type' => array(),
'type' => array(),
'value' => array(),
'viewBox' => array(),
'width' => array(),
'x' => array(),
'xml:lang' => array(),
'xmlns' => array(),
'y' => array(),
);
// Add custom tags
$allowed_tags = array('script' => $allowed_atts);
$allowed_tags['!DOCTYPE'] = $allowed_atts;
$allowed_tags['body'] = $allowed_atts;
$allowed_tags['head'] = $allowed_atts;
$allowed_tags['html'] = $allowed_atts;
$allowed_tags['rect'] = $allowed_atts;
$allowed_tags['style'] = $allowed_atts;
$allowed_tags['svg'] = $allowed_atts;
$allowed_tags['title'] = $allowed_atts;
$allowed_tags['button'] = $allowed_atts;
$allowed_tags['a'] = $allowed_atts;
// Merge global and custom tags
$all_allowed_tags = array_merge($allowedposttags, $allowed_tags);
// Overwrite global ones with custom atts
$all_allowed_tags['div'] = array_merge($allowedposttags['div'], $allowed_atts);
return $all_allowed_tags;
}
/**
* Hides the WordPress Admin Bar for specific roles.
*
* @since 18.0
*
* @return void
*/
public static function hide_admin_bar()
{
// Don't hide for admin
if (is_admin()) {
return;
}
$roles = Options_Service::get_global_list_var('hide_admin_bar_roles');
if (!empty($roles) && get_current_user_id() > 0) {
$wp_usr = wp_get_current_user();
foreach ($roles as $role) {
if (in_array($role, $wp_usr->roles)) {
show_admin_bar(false);
break;
}
}
}
}
/**
* Simple helper to add save style (css) attributes.
*
* @since 18.1
*
* @param mixed $styles
* @return string
*/
public static function safe_css($styles)
{
$styles[] = 'list-style';
$styles[] = 'min-width';
$styles[] = 'max-width';
$styles[] = 'display';
return $styles;
}
}
}
<?php
namespace Wpo\Core;
use WP_Site_Query;
use \Wpo\Core\WordPress_Helpers;
use \Wpo\Services\Options_Service;
use \Wpo\Services\Log_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Core\Wpmu_Helpers')) {
class Wpmu_Helpers
{
/**
* Helper to get the global or local transient based on the
* WPMU configuration.
*
* @since 9.2
*
* @return mixed Returns the value of transient or false if not found
*/
public static function mu_get_transient($name)
{
if (!is_multisite() || (Options_Service::mu_use_subsite_options() && !self::mu_is_network_admin())) {
return get_transient($name);
}
return get_site_transient($name);
}
/**
* Helper to set the global or local transient based on the
* WPMU configuration.
*
* @since 9.2
*
* @param $name string Name of transient
* @param $value mixed Value of transient
* @param $duration int Time transient should be cached in seconds
*
* @return void
*/
public static function mu_set_transient($name, $value, $duration = 0)
{
if (!is_multisite() || (Options_Service::mu_use_subsite_options() && !self::mu_is_network_admin())) {
set_transient($name, $value, $duration);
} else {
set_site_transient($name, $value, $duration);
}
}
/**
* Helper to delete the global or local transient based on the
* WPMU configuration.
*
* @since 10.9
*
* @param $name string Name of transient
*
* @return void
*/
public static function mu_delete_transient($name)
{
if (!is_multisite() || (Options_Service::mu_use_subsite_options() && !self::mu_is_network_admin())) {
delete_transient($name);
} else {
delete_site_transient($name);
}
}
/**
* Helper to check if the current request is for a network admin page and it includes a simple
* check if the request is made from an AJAX call.
*
* @since 11.18
*
* @return boolean True if the request is for a network admin page other false.
*/
public static function mu_is_network_admin()
{
return (is_network_admin() || true === $GLOBALS['WPO_CONFIG']['ina']);
}
/**
* Helper to switch the current blog from the main site to a subsite in case
* of a multisite installation (shared scenario) when the user is redirected
* back to the main site whereas the state URL indicates that the target is
* a subsite.
*
* @since 11.0
*
* @param $state_url string The (Relay) state URL
*
* @return void
*/
public static function switch_blog($state_url)
{
Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
if (is_multisite() && !empty($state_url)) {
$redirect_url = Options_Service::get_aad_option('redirect_url');
$redirect_host = parse_url($redirect_url, PHP_URL_HOST);
$state_host = parse_url($state_url, PHP_URL_HOST);
$state_path = '/';
$redirect_path = '/';
if (!is_subdomain_install()) {
$redirect_path = parse_url($redirect_url, PHP_URL_PATH);
$state_path = parse_url($state_url, PHP_URL_PATH);
}
$state_blog_id = self::get_blog_id_from_host_and_path($state_host, $state_path);
$redirect_blog_id = self::get_blog_id_from_host_and_path($redirect_host, $redirect_path);
Log_Service::write_log('DEBUG', __METHOD__ . " -> Detected WPMU with state context (path: $state_path - ID: $state_blog_id) and AAD redirect context (path: $redirect_path - ID: $redirect_blog_id)");
if ($state_blog_id !== $redirect_blog_id) {
switch_to_blog($state_blog_id);
$GLOBALS['WPO_CONFIG']['url_info']['wp_site_url'] = get_option('home');
}
}
}
/**
* Helper to try and search for a matching blog by itteratively removing the last segment from the path.
*
* @since 16.0
*
* @param string $host The domain e.g. www.your-site.com
* @param string $path The path starting with a slash
*
* @return int The blog ID or 0 if not found
*/
public static function get_blog_id_from_host_and_path($host, $path)
{
Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
$blog_id = get_blog_id_from_url($host, $path);
if (!empty($blog_id)) {
return $blog_id;
}
$path = WordPress_Helpers::rtrim($path, '/');
$path = WordPress_Helpers::ltrim($path, '/');
$segments = explode('/', $path);
$segments[] = 'placeholder'; // Add empty string to start with full URL when popping elements from the end
while (NULL != ($last_element = array_pop($segments))) {
$path = '/' . implode('/', $segments);
if (strlen($path) > 1) {
$path = $path . '/';
}
$blog_id = get_blog_id_from_url($host, $path);
if ($blog_id > 0) {
return $blog_id;
}
}
return 0;
}
}
}
<?php
namespace Wpo\Firebase;
class BeforeValidException extends \UnexpectedValueException
{
}
\ No newline at end of file
<?php
namespace Wpo\Firebase;
class ExpiredException extends \UnexpectedValueException
{
}
\ No newline at end of file
<?php
namespace Wpo\Firebase;
class SignatureInvalidException extends \UnexpectedValueException
{
}
\ No newline at end of file
<?php
namespace Wpo\Graph;
// Prevent public access to this script
defined('ABSPATH') or die();
use \Wpo\Services\Log_Service;
use \Wpo\Services\Options_Service;
if (!class_exists('\Wpo\Graph\Current_User')) {
class Current_User
{
/**
* Returns basic information for the current user incl. the user's (Azure AD) UPN and Object ID.
*
* @since 13.0
*
* @return array The user's login, email, display name, ID, UPN and AAD Object ID.
*/
public static function get_current_user()
{
Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
$wp_usr_id = \get_current_user_id();
$user_info = \wp_get_current_user();
$user_login = !empty($user_info->user_login) ? $user_info->user_login : '';
$user_email = !empty($user_info->user_email) ? $user_info->user_email : '';
$display_name = !empty($user_info->display_name) ? $user_info->display_name : '';
$id = $user_info->ID;
$upn = \get_user_meta($wp_usr_id, 'userPrincipalName', true);
$aad_object_id = \get_user_meta($wp_usr_id, 'aadObjectId', true);
return array(
"user_login" => $user_login,
"user_email" => $user_email,
"display_name" => $display_name,
"id" => $id,
"upn" => !empty($upn) ? $upn : '',
"aad_object_id" => !empty($aad_object_id) ? $aad_object_id : '',
);
}
}
}
Copyright (c) 2010-2016 OneLogin, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
<?php
// Create an __autoload function
// (can conflicts other autoloaders)
// http://php.net/manual/en/language.oop5.autoload.php
$libDir = __DIR__ . '/lib/Saml2/';
$extlibDir = __DIR__ . '/extlib/';
// Load composer
if (file_exists(__DIR__ .'/vendor/autoload.php')) {
require __DIR__ . '/vendor/autoload.php';
}
// Load now external libs
require_once $extlibDir . 'xmlseclibs/xmlseclibs.php';
$folderInfo = scandir($libDir);
foreach ($folderInfo as $element) {
if (is_file($libDir.$element) && (substr($element, -4) === '.php')) {
include_once $libDir.$element;
//break;
}
}
Take care of this folder that could contain private key. Be sure that this folder never is published.
Onelogin PHP Toolkit expects certs for the SP stored at:
* sp.key Private Key
* sp.crt Public cert
* sp_new.crt Future Public cert
Also you can use other cert to sign the metadata of the SP using the:
* metadata.key
* metadata.crt
{
"name": "onelogin/php-saml",
"description": "OneLogin PHP SAML Toolkit",
"license": "MIT",
"homepage": "https://developers.onelogin.com/saml/php",
"keywords": [
"saml",
"saml2",
"onelogin"
],
"autoload": {
"classmap": [
"extlib/xmlseclibs",
"lib/Saml2"
]
},
"support": {
"email": "sixto.garcia@onelogin.com",
"issues": "https://github.com/onelogin/php-saml/issues",
"source": "https://github.com/onelogin/php-saml/"
},
"require": {
"php": ">=5.3.2 <7.2",
"ext-curl": "*",
"ext-openssl": "*",
"ext-dom": "*",
"ext-mcrypt": "*"
},
"require-dev": {
"phpunit/phpunit": "4.8",
"satooshi/php-coveralls": "1.0.1",
"sebastian/phpcpd": "*",
"phploc/phploc": "*",
"pdepend/pdepend": "1.1.0",
"squizlabs/php_codesniffer": "2.9.0"
},
"suggest": {
"ext-gettext": "Install gettext and php5-gettext libs to handle translations"
}
}
xmlseclibs.php
06, Nov 2019, 3.0.4
Security Improvements:
- Insure only a single SignedInfo element exists within a signature during
verification. Refs CVE-2019-3465.
Bug Fixes:
- Fix variable casing.
15, Nov 2018, 3.0.3
Bug Fixes:
- Fix casing of class name. (Willem Stuursma-Ruwen)
- Fix Xpath casing. (Tim van Dijen)
Improvements:
- Make PCRE2 compliant. (Stefan Winter)
- Add PHP 7.3 support. (Stefan Winter)
27, Sep 2018, 3.0.2
Security Improvements:
- OpenSSL is now a requirement rather than suggestion. (Slaven Bacelic)
- Filter input to avoid XPath injection. (Jaime Pérez)
Bug Fixes:
- Fix missing parentheses (Tim van Dijen)
Improvements:
- Use strict comparison operator to compare digest values. (Jaime Pérez)
- Remove call to file_get_contents that doesn't even work. (Jaime Pérez)
- Document potentially dangerous return value behaviour. (Thijs Kinkhorst)
31, Aug 2017, 3.0.1
Bug Fixes:
- Fixed missing () in function call. (Dennis Væversted)
Improvements:
- Add OneLogin to supported software.
- Add .gitattributes to remove unneeded files. (Filippo Tessarotto)
- Fix bug in example code. (Dan Church)
- Travis: add PHP 7.1, move hhvm to allowed failures. (Thijs Kinkhorst)
- Drop failing extract-win-cert test (Thijs Kinkhorst). (Thijs Kinkhorst)
- Add comments to warn about return values of verify(). (Thijs Kinkhorst)
- Fix tests to properly check return code of verify(). (Thijs Kinkhorst)
- Restore support for PHP >= 5.4. (Jaime Pérez)
25, May 2017, 3.0.0
Improvements:
- Remove use of mcrypt (skymeyer)
08, Sep 2016, 2.0.1
Bug Fixes:
- Strip whitespace characters when parsing X509Certificate. fixes #84
(klemen.bratec)
- Certificate 'subject' values can be arrays. fixes #80 (Andreas Stangl)
- HHVM signing node with ID attribute w/out namespace regenerates ID value.
fixes #88 (Milos Tomic)
Improvements:
- Fix typos and add some PHPDoc Blocks. (gfaust-qb)
- Update lightSAML link. (Milos Tomic)
- Update copyright dates.
23, Jun 2015, 1.4.0
Features:
- Support for PSR-0 standard.
- Support for X509SubjectName. (Milos Tomic)
- Add HMAC-SHA1 support.
Improvements:
- Add how to install to README. (Bernardo Vieira da Silva)
- Code cleanup. (Jaime Pérez)
- Normalilze tests. (Hidde Wieringa)
- Add basic usage to README. (Hidde Wieringa)
21, May 2015, 1.3.2
Bug Fixes:
- Fix Undefined variable notice. (dpieper85)
- Fix typo when setting MimeType attribute. (Eugene OZ)
- Fix validateReference() with enveloping signatures
Features:
- canonicalizeData performance optimization. (Jaime Pérez)
- Add composer support (Maks3w)
19, Jun 2013, 1.3.1
Features:
- return encrypted node from XMLSecEnc::encryptNode() when replace is set to
false. (Olav)
- Add support for RSA SHA384 and RSA_SHA512 and SHA384 digest. (Jaime PŽrez)
- Add options parameter to the add cert methods.
- Add optional issuerSerial creation with cert
Bug Fixes:
- Fix persisted Id when namespaced. (Koen Thomeer)
Improvements:
- Add LICENSE file
- Convert CHANGELOG.txt to UTF-8
26, Sep 2011, 1.3.0
Features:
- Add param to append sig to node when signing. Fixes a problem when using
inclusive canonicalization to append a signature within a namespaced subtree.
ex. $objDSig->sign($objKey, $appendToNode);
- Add ability to encrypt by reference
- Add support for refences within an encrypted key
- Add thumbprint generation capability (XMLSecurityKey->getX509Thumbprint() and
XMLSecurityKey::getRawThumbprint($cert))
- Return signature element node from XMLSecurityDSig::insertSignature() and
XMLSecurityDSig::appendSignature() methods
- Support for <ds:RetrievalMethod> with simple URI Id reference.
- Add XMLSecurityKey::getSymmetricKeySize() method (Olav)
- Add XMLSecEnc::getCipherValue() method (Olav)
- Improve XMLSecurityKey:generateSessionKey() logic (Olav)
Bug Fixes:
- Change split() to explode() as split is now depreciated
- ds:References using empty or simple URI Id reference should never include
comments in canonicalized data.
- Make sure that the elements in EncryptedData are emitted in the correct
sequence.
11 Jan 2010, 1.2.2
Features:
- Add support XPath support when creating signature. Provides support for
working with EBXML documents.
- Add reference option to force creation of URI attribute. For use
when adding a DOM Document where by default no URI attribute is added.
- Add support for RSA-SHA256
Bug Fixes:
- fix bug #5: createDOMDocumentFragment() in decryptNode when data is node
content (patch by Francois Wang)
08 Jul 2008, 1.2.1
Features:
- Attempt to use mhash when hash extension is not present. (Alfredo Cubitos).
- Add fallback to built-in sha1 if both hash and mhash are not available and
throw error for other for other missing hashes. (patch by Olav Morken).
- Add getX509Certificate method to retrieve the x509 cert used for Key.
(patch by Olav Morken).
- Add getValidatedNodes method to retrieve the elements signed by the
signature. (patch by Olav Morken).
- Add insertSignature method for precision signature insertion. Merge
functionality from appendSignature in the process. (Olav Morken, Rob).
- Finally add some tests
Bug Fixes:
- Fix canonicalization for Document node when using PHP < 5.2.
- Add padding for RSA_SHA1. (patch by Olav Morken).
27 Nov 2007, 1.2.0
Features:
- New addReference/List option (overwrite). Boolean flag indicating if URI
value should be overwritten if already existing within document.
Default is TRUE to maintain BC.
18 Nov 2007, 1.1.2
Bug Fixes:
- Remove closing PHP tag to fix extra whitespace characters from being output
11 Nov 2007, 1.1.1
Features:
- Add getRefNodeID() and getRefIDs() methods missed in previous release.
Provide functionality to find URIs of existing reference nodes.
Required by simpleSAMLphp project
Bug Fixes:
- Remove erroneous whitespace causing issues under certain circumastances.
18 Oct 2007, 1.1.0
Features:
- Enable creation of enveloping signature. This allows the creation of
managed information cards.
- Add addObject method for enveloping signatures.
- Add staticGet509XCerts method. Chained certificates within a PEM file can
now be added within the X509Data node.
- Add xpath support within transformations
- Add InclusiveNamespaces prefix list support within exclusive transformations.
Bug Fixes:
- Initialize random number generator for mcrypt_create_iv. (Joan Cornadó).
- Fix an interoperability issue with .NET when encrypting data in CBC mode.
(Joan Cornadó).
Copyright (c) 2007-2019, Robert Richards <rrichards@cdatazone.org>.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Robert Richards nor the names of his
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
<?php
/**
* SAML 2 Authentication Request
*
*/
class OneLogin_Saml2_AuthnRequest
{
/**
* Object that represents the setting info
* @var OneLogin_Saml2_Settings
*/
protected $_settings;
/**
* SAML AuthNRequest string
* @var string
*/
private $_authnRequest;
/**
* SAML AuthNRequest ID.
* @var string
*/
private $_id;
/**
* Constructs the AuthnRequest object.
*
* @param OneLogin_Saml2_Settings $settings Settings
* @param bool $forceAuthn When true the AuthNReuqest will set the ForceAuthn='true'
* @param bool $isPassive When true the AuthNReuqest will set the Ispassive='true'
* @param bool $setNameIdPolicy When true the AuthNReuqest will set a nameIdPolicy
* @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated
*/
public function __construct(OneLogin_Saml2_Settings $settings, $forceAuthn = false, $isPassive = false, $setNameIdPolicy = true, $nameIdValueReq = null)
{
$this->_settings = $settings;
$spData = $this->_settings->getSPData();
$security = $this->_settings->getSecurityData();
$id = OneLogin_Saml2_Utils::generateUniqueID();
$issueInstant = OneLogin_Saml2_Utils::parseTime2SAML(time());
$subjectStr = "";
if (isset($nameIdValueReq)) {
$subjectStr = <<<SUBJECT
<saml:Subject>
<saml:NameID Format="{$spData['NameIDFormat']}">{$nameIdValueReq}</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"></saml:SubjectConfirmation>
</saml:Subject>
SUBJECT;
}
$nameIdPolicyStr = '';
if ($setNameIdPolicy) {
$nameIDPolicyFormat = $spData['NameIDFormat'];
if (isset($security['wantNameIdEncrypted']) && $security['wantNameIdEncrypted']) {
$nameIDPolicyFormat = OneLogin_Saml2_Constants::NAMEID_ENCRYPTED;
}
$nameIdPolicyStr = <<<NAMEIDPOLICY
<samlp:NameIDPolicy
Format="{$nameIDPolicyFormat}"
AllowCreate="true" />
NAMEIDPOLICY;
}
$providerNameStr = '';
$organizationData = $settings->getOrganization();
if (!empty($organizationData)) {
$langs = array_keys($organizationData);
if (in_array('en-US', $langs)) {
$lang = 'en-US';
} else {
$lang = $langs[0];
}
if (isset($organizationData[$lang]['displayname']) && !empty($organizationData[$lang]['displayname'])) {
$providerNameStr = <<<PROVIDERNAME
ProviderName="{$organizationData[$lang]['displayname']}"
PROVIDERNAME;
}
}
$forceAuthnStr = '';
if ($forceAuthn) {
$forceAuthnStr = <<<FORCEAUTHN
ForceAuthn="true"
FORCEAUTHN;
}
$isPassiveStr = '';
if ($isPassive) {
$isPassiveStr = <<<ISPASSIVE
IsPassive="true"
ISPASSIVE;
}
$requestedAuthnStr = '';
if (isset($security['requestedAuthnContext']) && $security['requestedAuthnContext'] !== false) {
$authnComparison = 'exact';
if (isset($security['requestedAuthnContextComparison'])) {
$authnComparison = $security['requestedAuthnContextComparison'];
}
$authnComparisonAttr = '';
if (!empty($authnComparison)) {
$authnComparisonAttr = sprintf('Comparison="%s"', $authnComparison);
}
if ($security['requestedAuthnContext'] === true) {
$requestedAuthnStr = <<<REQUESTEDAUTHN
<samlp:RequestedAuthnContext $authnComparisonAttr>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
REQUESTEDAUTHN;
} else {
$requestedAuthnStr .= " <samlp:RequestedAuthnContext $authnComparisonAttr>\n";
foreach ($security['requestedAuthnContext'] as $contextValue) {
$requestedAuthnStr .= " <saml:AuthnContextClassRef>".$contextValue."</saml:AuthnContextClassRef>\n";
}
$requestedAuthnStr .= ' </samlp:RequestedAuthnContext>';
}
}
$spEntityId = htmlspecialchars($spData['entityId'], ENT_QUOTES);
$acsUrl = htmlspecialchars($spData['assertionConsumerService']['url'], ENT_QUOTES);
$destination = $this->_settings->getIdPSSOUrl();
$request = <<<AUTHNREQUEST
<samlp:AuthnRequest
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="$id"
Version="2.0"
{$providerNameStr}{$forceAuthnStr}{$isPassiveStr}
IssueInstant="$issueInstant"
Destination="{$destination}"
ProtocolBinding="{$spData['assertionConsumerService']['binding']}"
AssertionConsumerServiceURL="{$acsUrl}">
<saml:Issuer>{$spEntityId}</saml:Issuer>{$subjectStr}{$nameIdPolicyStr}{$requestedAuthnStr}
</samlp:AuthnRequest>
AUTHNREQUEST;
$this->_id = $id;
$this->_authnRequest = $request;
}
/**
* Returns deflated, base64 encoded, unsigned AuthnRequest.
*
* @param bool|null $deflate Whether or not we should 'gzdeflate' the request body before we return it.
*
* @return string
*/
public function getRequest($deflate = null)
{
$subject = $this->_authnRequest;
if (is_null($deflate)) {
$deflate = $this->_settings->shouldCompressRequests();
}
if ($deflate) {
$subject = gzdeflate($this->_authnRequest);
}
$base64Request = base64_encode($subject);
return $base64Request;
}
/**
* Returns the AuthNRequest ID.
*
* @return string
*/
public function getId()
{
return $this->_id;
}
/**
* Returns the XML that will be sent as part of the request
*
* @return string
*/
public function getXML()
{
return $this->_authnRequest;
}
}
<?php
/**
* Constants of OneLogin PHP Toolkit
*
* Defines all required constants
*/
class OneLogin_Saml2_Constants
{
// Value added to the current time in time condition validations
const ALLOWED_CLOCK_DRIFT = 180; // 3 min in seconds
// NameID Formats
const NAMEID_EMAIL_ADDRESS = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress';
const NAMEID_X509_SUBJECT_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName';
const NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName';
const NAMEID_UNSPECIFIED = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified';
const NAMEID_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos';
const NAMEID_ENTITY = 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity';
const NAMEID_TRANSIENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient';
const NAMEID_PERSISTENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent';
const NAMEID_ENCRYPTED = 'urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted';
// Attribute Name Formats
const ATTRNAME_FORMAT_UNSPECIFIED = 'urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified';
const ATTRNAME_FORMAT_URI = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri';
const ATTRNAME_FORMAT_BASIC = 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic';
// Namespaces
const NS_SAML = 'urn:oasis:names:tc:SAML:2.0:assertion';
const NS_SAMLP = 'urn:oasis:names:tc:SAML:2.0:protocol';
const NS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
const NS_MD = 'urn:oasis:names:tc:SAML:2.0:metadata';
const NS_XS = 'http://www.w3.org/2001/XMLSchema';
const NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance';
const NS_XENC = 'http://www.w3.org/2001/04/xmlenc#';
const NS_DS = 'http://www.w3.org/2000/09/xmldsig#';
// Bindings
const BINDING_HTTP_POST = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST';
const BINDING_HTTP_REDIRECT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect';
const BINDING_HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact';
const BINDING_SOAP = 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP';
const BINDING_DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE';
// Auth Context Class
const AC_UNSPECIFIED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified';
const AC_PASSWORD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password';
const AC_PASSWORD_PROTECTED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport';
const AC_X509 = 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509';
const AC_SMARTCARD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard';
const AC_SMARTCARD_PKI = 'urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI';
const AC_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos';
const AC_WINDOWS = 'urn:federation:authentication:windows';
const AC_TLS = 'urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient';
const AC_RSATOKEN = 'urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken';
// Subject Confirmation
const CM_BEARER = 'urn:oasis:names:tc:SAML:2.0:cm:bearer';
const CM_HOLDER_KEY = 'urn:oasis:names:tc:SAML:2.0:cm:holder-of-key';
const CM_SENDER_VOUCHES = 'urn:oasis:names:tc:SAML:2.0:cm:sender-vouches';
// Status Codes
const STATUS_SUCCESS = 'urn:oasis:names:tc:SAML:2.0:status:Success';
const STATUS_REQUESTER = 'urn:oasis:names:tc:SAML:2.0:status:Requester';
const STATUS_RESPONDER = 'urn:oasis:names:tc:SAML:2.0:status:Responder';
const STATUS_VERSION_MISMATCH = 'urn:oasis:names:tc:SAML:2.0:status:VersionMismatch';
const STATUS_NO_PASSIVE = 'urn:oasis:names:tc:SAML:2.0:status:NoPassive';
const STATUS_PARTIAL_LOGOUT = 'urn:oasis:names:tc:SAML:2.0:status:PartialLogout';
const STATUS_PROXY_COUNT_EXCEEDED = 'urn:oasis:names:tc:SAML:2.0:status:ProxyCountExceeded';
}
<?php
/**
* Error class of OneLogin PHP Toolkit
*
* Defines the Error class
*/
class OneLogin_Saml2_Error extends Exception
{
// Errors
const SETTINGS_FILE_NOT_FOUND = 0;
const SETTINGS_INVALID_SYNTAX = 1;
const SETTINGS_INVALID = 2;
const METADATA_SP_INVALID = 3;
const SP_CERTS_NOT_FOUND = 4;
// SP_CERTS_NOT_FOUND is deprecated, use CERT_NOT_FOUND instead
const CERT_NOT_FOUND = 4;
const REDIRECT_INVALID_URL = 5;
const PUBLIC_CERT_FILE_NOT_FOUND = 6;
const PRIVATE_KEY_FILE_NOT_FOUND = 7;
const SAML_RESPONSE_NOT_FOUND = 8;
const SAML_LOGOUTMESSAGE_NOT_FOUND = 9;
const SAML_LOGOUTREQUEST_INVALID = 10;
const SAML_LOGOUTRESPONSE_INVALID = 11;
const SAML_SINGLE_LOGOUT_NOT_SUPPORTED = 12;
const PRIVATE_KEY_NOT_FOUND = 13;
const UNSUPPORTED_SETTINGS_OBJECT = 14;
/**
* Constructor
*
* @param string $msg Describes the error.
* @param int $code The code error (defined in the error class).
* @param array|null $args Arguments used in the message that describes the error.
*/
public function __construct($msg, $code = 0, $args = null)
{
//assert('is_string($msg)');
//assert('is_int($code)');
$message = OneLogin_Saml2_Utils::t($msg, $args);
parent::__construct($message, $code);
}
}
/**
* This class implements another custom Exception handler,
* related to exceptions that happens during validation process.
*/
class OneLogin_Saml2_ValidationError extends Exception
{
# Validation Errors
const UNSUPPORTED_SAML_VERSION = 0;
const MISSING_ID = 1;
const WRONG_NUMBER_OF_ASSERTIONS = 2;
const MISSING_STATUS = 3;
const MISSING_STATUS_CODE = 4;
const STATUS_CODE_IS_NOT_SUCCESS = 5;
const WRONG_SIGNED_ELEMENT = 6;
const ID_NOT_FOUND_IN_SIGNED_ELEMENT = 7;
const DUPLICATED_ID_IN_SIGNED_ELEMENTS = 8;
const INVALID_SIGNED_ELEMENT = 9;
const DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS = 10;
const UNEXPECTED_SIGNED_ELEMENTS = 11;
const WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE = 12;
const WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION = 13;
const INVALID_XML_FORMAT = 14;
const WRONG_INRESPONSETO = 15;
const NO_ENCRYPTED_ASSERTION = 16;
const NO_ENCRYPTED_NAMEID = 17;
const MISSING_CONDITIONS = 18;
const ASSERTION_TOO_EARLY = 19;
const ASSERTION_EXPIRED = 20;
const WRONG_NUMBER_OF_AUTHSTATEMENTS = 21;
const NO_ATTRIBUTESTATEMENT = 22;
const ENCRYPTED_ATTRIBUTES = 23;
const WRONG_DESTINATION = 24;
const EMPTY_DESTINATION = 25;
const WRONG_AUDIENCE = 26;
const ISSUER_MULTIPLE_IN_RESPONSE = 27;
const ISSUER_NOT_FOUND_IN_ASSERTION = 28;
const WRONG_ISSUER = 29;
const SESSION_EXPIRED = 30;
const WRONG_SUBJECTCONFIRMATION = 31;
const NO_SIGNED_MESSAGE = 32;
const NO_SIGNED_ASSERTION = 33;
const NO_SIGNATURE_FOUND = 34;
const KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA = 35;
const CHILDREN_NODE_NOT_FOUND_IN_KEYINFO = 36;
const UNSUPPORTED_RETRIEVAL_METHOD = 37;
const NO_NAMEID = 38;
const EMPTY_NAMEID = 39;
const SP_NAME_QUALIFIER_NAME_MISMATCH = 40;
const DUPLICATED_ATTRIBUTE_NAME_FOUND = 41;
const INVALID_SIGNATURE = 42;
const WRONG_NUMBER_OF_SIGNATURES = 43;
const RESPONSE_EXPIRED = 44;
const UNEXPECTED_REFERENCE = 45;
const NOT_SUPPORTED = 46;
const KEY_ALGORITHM_ERROR = 47;
const MISSING_ENCRYPTED_ELEMENT = 48;
/**
* Constructor
*
* @param string $msg Describes the error.
* @param int $code The code error (defined in the error class).
* @param array|null $args Arguments used in the message that describes the error.
*/
public function __construct($msg, $code = 0, $args = null)
{
//assert('is_string($msg)');
//assert('is_int($code)');
$message = OneLogin_Saml2_Utils::t($msg, $args);
parent::__construct($message, $code);
}
}
<?php
/**
* Metadata lib of OneLogin PHP Toolkit
*
*/
class OneLogin_Saml2_Metadata
{
const TIME_VALID = 172800; // 2 days
const TIME_CACHED = 604800; // 1 week
/**
* Generates the metadata of the SP based on the settings
*
* @param array $sp The SP data
* @param bool|string $authnsign authnRequestsSigned attribute
* @param bool|string $wsign wantAssertionsSigned attribute
* @param DateTime|null $validUntil Metadata's valid time
* @param int|null $cacheDuration Duration of the cache in seconds
* @param array $contacts Contacts info
* @param array $organization Organization ingo
* @param array $attributes
*
* @return string SAML Metadata XML
*/
public static function builder($sp, $authnsign = false, $wsign = false, $validUntil = null, $cacheDuration = null, $contacts = array(), $organization = array(), $attributes = array())
{
if (!isset($validUntil)) {
$validUntil = time() + self::TIME_VALID;
}
$validUntilTime = gmdate('Y-m-d\TH:i:s\Z', $validUntil);
if (!isset($cacheDuration)) {
$cacheDuration = self::TIME_CACHED;
}
$sls = '';
if (isset($sp['singleLogoutService'])) {
$slsUrl = htmlspecialchars($sp['singleLogoutService']['url'], ENT_QUOTES);
$sls = <<<SLS_TEMPLATE
<md:SingleLogoutService Binding="{$sp['singleLogoutService']['binding']}"
Location="{$slsUrl}" />
SLS_TEMPLATE;
}
if ($authnsign) {
$strAuthnsign = 'true';
} else {
$strAuthnsign = 'false';
}
if ($wsign) {
$strWsign = 'true';
} else {
$strWsign = 'false';
}
$strOrganization = '';
if (!empty($organization)) {
$organizationInfoNames = array();
$organizationInfoDisplaynames = array();
$organizationInfoUrls = array();
foreach ($organization as $lang => $info) {
$organizationInfoNames[] = <<<ORGANIZATION_NAME
<md:OrganizationName xml:lang="{$lang}">{$info['name']}</md:OrganizationName>
ORGANIZATION_NAME;
$organizationInfoDisplaynames[] = <<<ORGANIZATION_DISPLAY
<md:OrganizationDisplayName xml:lang="{$lang}">{$info['displayname']}</md:OrganizationDisplayName>
ORGANIZATION_DISPLAY;
$organizationInfoUrls[] = <<<ORGANIZATION_URL
<md:OrganizationURL xml:lang="{$lang}">{$info['url']}</md:OrganizationURL>
ORGANIZATION_URL;
}
$orgData = implode("\n", $organizationInfoNames)."\n".implode("\n", $organizationInfoDisplaynames)."\n".implode("\n", $organizationInfoUrls);
$strOrganization = <<<ORGANIZATIONSTR
<md:Organization>
{$orgData}
</md:Organization>
ORGANIZATIONSTR;
}
$strContacts = '';
if (!empty($contacts)) {
$contactsInfo = array();
foreach ($contacts as $type => $info) {
$contactsInfo[] = <<<CONTACT
<md:ContactPerson contactType="{$type}">
<md:GivenName>{$info['givenName']}</md:GivenName>
<md:EmailAddress>{$info['emailAddress']}</md:EmailAddress>
</md:ContactPerson>
CONTACT;
}
$strContacts = "\n".implode("\n", $contactsInfo);
}
$strAttributeConsumingService = '';
if (isset($sp['attributeConsumingService'])) {
$attrCsDesc = '';
if (isset($sp['attributeConsumingService']['serviceDescription'])) {
$attrCsDesc = sprintf(
' <md:ServiceDescription xml:lang="en">%s</md:ServiceDescription>' . PHP_EOL,
$sp['attributeConsumingService']['serviceDescription']
);
}
if (!isset($sp['attributeConsumingService']['serviceName'])) {
$sp['attributeConsumingService']['serviceName'] = 'Service';
}
$requestedAttributeData = array();
foreach ($sp['attributeConsumingService']['requestedAttributes'] as $attribute) {
$requestedAttributeStr = sprintf(' <md:RequestedAttribute Name="%s"', $attribute['name']);
if (isset($attribute['nameFormat'])) {
$requestedAttributeStr .= sprintf(' NameFormat="%s"', $attribute['nameFormat']);
}
if (isset($attribute['friendlyName'])) {
$requestedAttributeStr .= sprintf(' FriendlyName="%s"', $attribute['friendlyName']);
}
if (isset($attribute['isRequired'])) {
$requestedAttributeStr .= sprintf(' isRequired="%s"', $attribute['isRequired'] === true ? 'true' : 'false');
}
$reqAttrAuxStr = " />";
if (isset($attribute['attributeValue']) && !empty($attribute['attributeValue'])) {
$reqAttrAuxStr = '>';
if (is_string($attribute['attributeValue'])) {
$attribute['attributeValue'] = array($attribute['attributeValue']);
}
foreach ($attribute['attributeValue'] as $attrValue) {
$reqAttrAuxStr .=<<<ATTRIBUTEVALUE
<saml:AttributeValue xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">{$attrValue}</saml:AttributeValue>
ATTRIBUTEVALUE;
}
$reqAttrAuxStr .= "\n </md:RequestedAttribute>";
}
$requestedAttributeData[] = $requestedAttributeStr . $reqAttrAuxStr;
}
$requestedAttributeStr = implode(PHP_EOL, $requestedAttributeData);
$strAttributeConsumingService = <<<METADATA_TEMPLATE
<md:AttributeConsumingService index="1">
<md:ServiceName xml:lang="en">{$sp['attributeConsumingService']['serviceName']}</md:ServiceName>
{$attrCsDesc}{$requestedAttributeStr}
</md:AttributeConsumingService>
METADATA_TEMPLATE;
}
$spEntityId = htmlspecialchars($sp['entityId'], ENT_QUOTES);
$acsUrl = htmlspecialchars($sp['assertionConsumerService']['url'], ENT_QUOTES);
$metadata = <<<METADATA_TEMPLATE
<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
validUntil="{$validUntilTime}"
cacheDuration="PT{$cacheDuration}S"
entityID="{$spEntityId}">
<md:SPSSODescriptor AuthnRequestsSigned="{$strAuthnsign}" WantAssertionsSigned="{$strWsign}" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
{$sls} <md:NameIDFormat>{$sp['NameIDFormat']}</md:NameIDFormat>
<md:AssertionConsumerService Binding="{$sp['assertionConsumerService']['binding']}"
Location="{$acsUrl}"
index="1" />
{$strAttributeConsumingService}
</md:SPSSODescriptor>{$strOrganization}{$strContacts}
</md:EntityDescriptor>
METADATA_TEMPLATE;
return $metadata;
}
/**
* Signs the metadata with the key/cert provided
*
* @param string $metadata SAML Metadata XML
* @param string $key x509 key
* @param string $cert x509 cert
* @param string $signAlgorithm Signature algorithm method
* @param string $digestAlgorithm Digest algorithm method
*
* @return string Signed Metadata
*
* @throws Exception
*/
public static function signMetadata($metadata, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA1, $digestAlgorithm = XMLSecurityDSig::SHA1)
{
return OneLogin_Saml2_Utils::addSign($metadata, $key, $cert, $signAlgorithm, $digestAlgorithm);
}
/**
* Adds the x509 descriptors (sign/encriptation) to the metadata
* The same cert will be used for sign/encrypt
*
* @param string $metadata SAML Metadata XML
* @param string $cert x509 cert
* @param bool $wantsEncrypted Whether to include the KeyDescriptor for encryption
*
* @return string Metadata with KeyDescriptors
*
* @throws Exception
*/
public static function addX509KeyDescriptors($metadata, $cert, $wantsEncrypted = true)
{
$xml = new DOMDocument();
$xml->preserveWhiteSpace = false;
$xml->formatOutput = true;
try {
$xml = OneLogin_Saml2_Utils::loadXML($xml, $metadata);
if (!$xml) {
throw new Exception('Error parsing metadata');
}
} catch (Exception $e) {
throw new Exception('Error parsing metadata. '.$e->getMessage());
}
$formatedCert = OneLogin_Saml2_Utils::formatCert($cert, false);
$x509Certificate = $xml->createElementNS(OneLogin_Saml2_Constants::NS_DS, 'X509Certificate', $formatedCert);
$keyData = $xml->createElementNS(OneLogin_Saml2_Constants::NS_DS, 'ds:X509Data');
$keyData->appendChild($x509Certificate);
$keyInfo = $xml->createElementNS(OneLogin_Saml2_Constants::NS_DS, 'ds:KeyInfo');
$keyInfo->appendChild($keyData);
$keyDescriptor = $xml->createElementNS(OneLogin_Saml2_Constants::NS_MD, "md:KeyDescriptor");
$SPSSODescriptor = $xml->getElementsByTagName('SPSSODescriptor')->item(0);
$SPSSODescriptor->insertBefore($keyDescriptor->cloneNode(), $SPSSODescriptor->firstChild);
if ($wantsEncrypted === true) {
$SPSSODescriptor->insertBefore($keyDescriptor->cloneNode(), $SPSSODescriptor->firstChild);
}
$signing = $xml->getElementsByTagName('KeyDescriptor')->item(0);
$signing->setAttribute('use', 'signing');
$signing->appendChild($keyInfo);
if ($wantsEncrypted === true) {
$encryption = $xml->getElementsByTagName('KeyDescriptor')->item(1);
$encryption->setAttribute('use', 'encryption');
$encryption->appendChild($keyInfo->cloneNode(true));
}
return $xml->saveXML();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
targetNamespace="urn:oasis:names:tc:SAML:2.0:ac"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:oasis:names:tc:SAML:2.0:ac"
blockDefault="substitution"
version="2.0">
<xs:annotation>
<xs:documentation>
Document identifier: saml-schema-authn-context-2.0
Location: http://docs.oasis-open.org/security/saml/v2.0/
Revision history:
V2.0 (March, 2005):
New core authentication context schema for SAML V2.0.
This is just an include of all types from the schema
referred to in the include statement below.
</xs:documentation>
</xs:annotation>
<xs:include schemaLocation="saml-schema-authn-context-types-2.0.xsd"/>
</xs:schema>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:metadata:attribute"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="2.0">
<annotation>
<documentation>
Document title: SAML V2.0 Metadata Extention for Entity Attributes Schema
Document identifier: sstc-metadata-attr.xsd
Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security
Revision history:
V1.0 (November 2008):
Initial version.
</documentation>
</annotation>
<import namespace="urn:oasis:names:tc:SAML:2.0:assertion"
schemaLocation="saml-schema-assertion-2.0.xsd"/>
<element name="EntityAttributes" type="mdattr:EntityAttributesType"/>
<complexType name="EntityAttributesType">
<choice maxOccurs="unbounded">
<element ref="saml:Attribute"/>
<element ref="saml:Assertion"/>
</choice>
</complexType>
</schema>
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:attribute:ext"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="2.0">
<annotation>
<documentation>
Document title: SAML V2.0 Attribute Extension Schema
Document identifier: sstc-saml-attribute-ext.xsd
Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security
Revision history:
V1.0 (October 2008):
Initial version.
</documentation>
</annotation>
<attribute name="OriginalIssuer" type="anyURI"/>
<attribute name="LastModified" type="dateTime"/>
</schema>
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:metadata:algsupport"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="1.0">
<annotation>
<documentation>
Document title: Metadata Extension Schema for SAML V2.0 Metadata Profile for Algorithm Support Version 1.0
Document identifier: sstc-saml-metadata-algsupport.xsd
Location: http://docs.oasis-open.org/security/saml/Post2.0/
Revision history:
V1.0 (June 2010):
Initial version.
</documentation>
</annotation>
<element name="DigestMethod" type="alg:DigestMethodType"/>
<complexType name="DigestMethodType">
<sequence>
<any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<element name="SigningMethod" type="alg:SigningMethodType"/>
<complexType name="SigningMethodType">
<sequence>
<any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
<attribute name="MinKeySize" type="positiveInteger"/>
<attribute name="MaxKeySize" type="positiveInteger"/>
</complexType>
</schema>
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:metadata:ui"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="1.0">
<annotation>
<documentation>
Document title: Metadata Extension Schema for SAML V2.0 Metadata Extensions for Login and Discovery User Interface Version 1.0
Document identifier: sstc-saml-metadata-ui-v1.0.xsd
Location: http://docs.oasis-open.org/security/saml/Post2.0/
Revision history:
16 November 2010:
Added Keywords element/type.
01 November 2010
Changed filename.
September 2010:
Initial version.
</documentation>
</annotation>
<import namespace="urn:oasis:names:tc:SAML:2.0:metadata"
schemaLocation="saml-schema-metadata-2.0.xsd"/>
<import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="xml.xsd"/>
<element name="UIInfo" type="mdui:UIInfoType" />
<complexType name="UIInfoType">
<choice minOccurs="0" maxOccurs="unbounded">
<element ref="mdui:DisplayName"/>
<element ref="mdui:Description"/>
<element ref="mdui:Keywords"/>
<element ref="mdui:Logo"/>
<element ref="mdui:InformationURL"/>
<element ref="mdui:PrivacyStatementURL"/>
<any namespace="##other" processContents="lax"/>
</choice>
</complexType>
<element name="DisplayName" type="md:localizedNameType"/>
<element name="Description" type="md:localizedNameType"/>
<element name="InformationURL" type="md:localizedURIType"/>
<element name="PrivacyStatementURL" type="md:localizedURIType"/>
<element name="Keywords" type="mdui:KeywordsType"/>
<complexType name="KeywordsType">
<simpleContent>
<extension base="mdui:listOfStrings">
<attribute ref="xml:lang" use="required"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="listOfStrings">
<list itemType="string"/>
</simpleType>
<element name="Logo" type="mdui:LogoType"/>
<complexType name="LogoType">
<simpleContent>
<extension base="anyURI">
<attribute name="height" type="positiveInteger" use="required"/>
<attribute name="width" type="positiveInteger" use="required"/>
<attribute ref="xml:lang"/>
</extension>
</simpleContent>
</complexType>
<element name="DiscoHints" type="mdui:DiscoHintsType"/>
<complexType name="DiscoHintsType">
<choice minOccurs="0" maxOccurs="unbounded">
<element ref="mdui:IPHint"/>
<element ref="mdui:DomainHint"/>
<element ref="mdui:GeolocationHint"/>
<any namespace="##other" processContents="lax"/>
</choice>
</complexType>
<element name="IPHint" type="string"/>
<element name="DomainHint" type="string"/>
<element name="GeolocationHint" type="anyURI"/>
</schema>
<?xml version="1.0" encoding="utf-8"?>
<schema xmlns='http://www.w3.org/2001/XMLSchema' version='1.0'
xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'
xmlns:ds='http://www.w3.org/2000/09/xmldsig#'
targetNamespace='http://www.w3.org/2001/04/xmlenc#'
elementFormDefault='qualified'>
<import namespace='http://www.w3.org/2000/09/xmldsig#'
schemaLocation='xmldsig-core-schema.xsd'/>
<complexType name='EncryptedType' abstract='true'>
<sequence>
<element name='EncryptionMethod' type='xenc:EncryptionMethodType'
minOccurs='0'/>
<element ref='ds:KeyInfo' minOccurs='0'/>
<element ref='xenc:CipherData'/>
<element ref='xenc:EncryptionProperties' minOccurs='0'/>
</sequence>
<attribute name='Id' type='ID' use='optional'/>
<attribute name='Type' type='anyURI' use='optional'/>
<attribute name='MimeType' type='string' use='optional'/>
<attribute name='Encoding' type='anyURI' use='optional'/>
</complexType>
<complexType name='EncryptionMethodType' mixed='true'>
<sequence>
<element name='KeySize' minOccurs='0' type='xenc:KeySizeType'/>
<element name='OAEPparams' minOccurs='0' type='base64Binary'/>
<any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>
</sequence>
<attribute name='Algorithm' type='anyURI' use='required'/>
</complexType>
<simpleType name='KeySizeType'>
<restriction base="integer"/>
</simpleType>
<element name='CipherData' type='xenc:CipherDataType'/>
<complexType name='CipherDataType'>
<choice>
<element name='CipherValue' type='base64Binary'/>
<element ref='xenc:CipherReference'/>
</choice>
</complexType>
<element name='CipherReference' type='xenc:CipherReferenceType'/>
<complexType name='CipherReferenceType'>
<choice>
<element name='Transforms' type='xenc:TransformsType' minOccurs='0'/>
</choice>
<attribute name='URI' type='anyURI' use='required'/>
</complexType>
<complexType name='TransformsType'>
<sequence>
<element ref='ds:Transform' maxOccurs='unbounded'/>
</sequence>
</complexType>
<element name='EncryptedData' type='xenc:EncryptedDataType'/>
<complexType name='EncryptedDataType'>
<complexContent>
<extension base='xenc:EncryptedType'>
</extension>
</complexContent>
</complexType>
<!-- Children of ds:KeyInfo -->
<element name='EncryptedKey' type='xenc:EncryptedKeyType'/>
<complexType name='EncryptedKeyType'>
<complexContent>
<extension base='xenc:EncryptedType'>
<sequence>
<element ref='xenc:ReferenceList' minOccurs='0'/>
<element name='CarriedKeyName' type='string' minOccurs='0'/>
</sequence>
<attribute name='Recipient' type='string'
use='optional'/>
</extension>
</complexContent>
</complexType>
<element name="AgreementMethod" type="xenc:AgreementMethodType"/>
<complexType name="AgreementMethodType" mixed="true">
<sequence>
<element name="KA-Nonce" minOccurs="0" type="base64Binary"/>
<!-- <element ref="ds:DigestMethod" minOccurs="0"/> -->
<any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
<element name="OriginatorKeyInfo" minOccurs="0" type="ds:KeyInfoType"/>
<element name="RecipientKeyInfo" minOccurs="0" type="ds:KeyInfoType"/>
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<!-- End Children of ds:KeyInfo -->
<element name='ReferenceList'>
<complexType>
<choice minOccurs='1' maxOccurs='unbounded'>
<element name='DataReference' type='xenc:ReferenceType'/>
<element name='KeyReference' type='xenc:ReferenceType'/>
</choice>
</complexType>
</element>
<complexType name='ReferenceType'>
<sequence>
<any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>
</sequence>
<attribute name='URI' type='anyURI' use='required'/>
</complexType>
<element name='EncryptionProperties' type='xenc:EncryptionPropertiesType'/>
<complexType name='EncryptionPropertiesType'>
<sequence>
<element ref='xenc:EncryptionProperty' maxOccurs='unbounded'/>
</sequence>
<attribute name='Id' type='ID' use='optional'/>
</complexType>
<element name='EncryptionProperty' type='xenc:EncryptionPropertyType'/>
<complexType name='EncryptionPropertyType' mixed='true'>
<choice maxOccurs='unbounded'>
<any namespace='##other' processContents='lax'/>
</choice>
<attribute name='Target' type='anyURI' use='optional'/>
<attribute name='Id' type='ID' use='optional'/>
<anyAttribute namespace="http://www.w3.org/XML/1998/namespace"/>
</complexType>
</schema>
<?xml version='1.0'?>
<?xml-stylesheet href="../2008/09/xsd.xsl" type="text/xsl"?>
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns ="http://www.w3.org/1999/xhtml"
xml:lang="en">
<xs:annotation>
<xs:documentation>
<div>
<h1>About the XML namespace</h1>
<div class="bodytext">
<p>
This schema document describes the XML namespace, in a form
suitable for import by other schema documents.
</p>
<p>
See <a href="http://www.w3.org/XML/1998/namespace.html">
http://www.w3.org/XML/1998/namespace.html</a> and
<a href="http://www.w3.org/TR/REC-xml">
http://www.w3.org/TR/REC-xml</a> for information
about this namespace.
</p>
<p>
Note that local names in this namespace are intended to be
defined only by the World Wide Web Consortium or its subgroups.
The names currently defined in this namespace are listed below.
They should not be used with conflicting semantics by any Working
Group, specification, or document instance.
</p>
<p>
See further below in this document for more information about <a
href="#usage">how to refer to this schema document from your own
XSD schema documents</a> and about <a href="#nsversioning">the
namespace-versioning policy governing this schema document</a>.
</p>
</div>
</div>
</xs:documentation>
</xs:annotation>
<xs:attribute name="lang">
<xs:annotation>
<xs:documentation>
<div>
<h3>lang (as an attribute name)</h3>
<p>
denotes an attribute whose value
is a language code for the natural language of the content of
any element; its value is inherited. This name is reserved
by virtue of its definition in the XML specification.</p>
</div>
<div>
<h4>Notes</h4>
<p>
Attempting to install the relevant ISO 2- and 3-letter
codes as the enumerated possible values is probably never
going to be a realistic possibility.
</p>
<p>
See BCP 47 at <a href="http://www.rfc-editor.org/rfc/bcp/bcp47.txt">
http://www.rfc-editor.org/rfc/bcp/bcp47.txt</a>
and the IANA language subtag registry at
<a href="http://www.iana.org/assignments/language-subtag-registry">
http://www.iana.org/assignments/language-subtag-registry</a>
for further information.
</p>
<p>
The union allows for the 'un-declaration' of xml:lang with
the empty string.
</p>
</div>
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="xs:language">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value=""/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="space">
<xs:annotation>
<xs:documentation>
<div>
<h3>space (as an attribute name)</h3>
<p>
denotes an attribute whose
value is a keyword indicating what whitespace processing
discipline is intended for the content of the element; its
value is inherited. This name is reserved by virtue of its
definition in the XML specification.</p>
</div>
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:NCName">
<xs:enumeration value="default"/>
<xs:enumeration value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="base" type="xs:anyURI"> <xs:annotation>
<xs:documentation>
<div>
<h3>base (as an attribute name)</h3>
<p>
denotes an attribute whose value
provides a URI to be used as the base for interpreting any
relative URIs in the scope of the element on which it
appears; its value is inherited. This name is reserved
by virtue of its definition in the XML Base specification.</p>
<p>
See <a
href="http://www.w3.org/TR/xmlbase/">http://www.w3.org/TR/xmlbase/</a>
for information about this attribute.
</p>
</div>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="id" type="xs:ID">
<xs:annotation>
<xs:documentation>
<div>
<h3>id (as an attribute name)</h3>
<p>
denotes an attribute whose value
should be interpreted as if declared to be of type ID.
This name is reserved by virtue of its definition in the
xml:id specification.</p>
<p>
See <a
href="http://www.w3.org/TR/xml-id/">http://www.w3.org/TR/xml-id/</a>
for information about this attribute.
</p>
</div>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attributeGroup name="specialAttrs">
<xs:attribute ref="xml:base"/>
<xs:attribute ref="xml:lang"/>
<xs:attribute ref="xml:space"/>
<xs:attribute ref="xml:id"/>
</xs:attributeGroup>
<xs:annotation>
<xs:documentation>
<div>
<h3>Father (in any context at all)</h3>
<div class="bodytext">
<p>
denotes Jon Bosak, the chair of
the original XML Working Group. This name is reserved by
the following decision of the W3C XML Plenary and
XML Coordination groups:
</p>
<blockquote>
<p>
In appreciation for his vision, leadership and
dedication the W3C XML Plenary on this 10th day of
February, 2000, reserves for Jon Bosak in perpetuity
the XML name "xml:Father".
</p>
</blockquote>
</div>
</div>
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>
<div xml:id="usage" id="usage">
<h2><a name="usage">About this schema document</a></h2>
<div class="bodytext">
<p>
This schema defines attributes and an attribute group suitable
for use by schemas wishing to allow <code>xml:base</code>,
<code>xml:lang</code>, <code>xml:space</code> or
<code>xml:id</code> attributes on elements they define.
</p>
<p>
To enable this, such a schema must import this schema for
the XML namespace, e.g. as follows:
</p>
<pre>
&lt;schema . . .>
. . .
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/xml.xsd"/>
</pre>
<p>
or
</p>
<pre>
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
</pre>
<p>
Subsequently, qualified reference to any of the attributes or the
group defined below will have the desired effect, e.g.
</p>
<pre>
&lt;type . . .>
. . .
&lt;attributeGroup ref="xml:specialAttrs"/>
</pre>
<p>
will define a type which will schema-validate an instance element
with any of those attributes.
</p>
</div>
</div>
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>
<div id="nsversioning" xml:id="nsversioning">
<h2><a name="nsversioning">Versioning policy for this schema document</a></h2>
<div class="bodytext">
<p>
In keeping with the XML Schema WG's standard versioning
policy, this schema document will persist at
<a href="http://www.w3.org/2009/01/xml.xsd">
http://www.w3.org/2009/01/xml.xsd</a>.
</p>
<p>
At the date of issue it can also be found at
<a href="http://www.w3.org/2001/xml.xsd">
http://www.w3.org/2001/xml.xsd</a>.
</p>
<p>
The schema document at that URI may however change in the future,
in order to remain compatible with the latest version of XML
Schema itself, or with the XML namespace itself. In other words,
if the XML Schema or XML namespaces change, the version of this
document at <a href="http://www.w3.org/2001/xml.xsd">
http://www.w3.org/2001/xml.xsd
</a>
will change accordingly; the version at
<a href="http://www.w3.org/2009/01/xml.xsd">
http://www.w3.org/2009/01/xml.xsd
</a>
will not change.
</p>
<p>
Previous dated (and unchanging) versions of this schema
document are at:
</p>
<ul>
<li><a href="http://www.w3.org/2009/01/xml.xsd">
http://www.w3.org/2009/01/xml.xsd</a></li>
<li><a href="http://www.w3.org/2007/08/xml.xsd">
http://www.w3.org/2007/08/xml.xsd</a></li>
<li><a href="http://www.w3.org/2004/10/xml.xsd">
http://www.w3.org/2004/10/xml.xsd</a></li>
<li><a href="http://www.w3.org/2001/03/xml.xsd">
http://www.w3.org/2001/03/xml.xsd</a></li>
</ul>
</div>
</div>
</xs:documentation>
</xs:annotation>
</xs:schema>
<?xml version="1.0" encoding="utf-8"?>
<!-- Schema for XML Signatures
http://www.w3.org/2000/09/xmldsig#
$Revision: 1.1 $ on $Date: 2002/02/08 20:32:26 $ by $Author: reagle $
Copyright 2001 The Internet Society and W3C (Massachusetts Institute
of Technology, Institut National de Recherche en Informatique et en
Automatique, Keio University). All Rights Reserved.
http://www.w3.org/Consortium/Legal/
This document is governed by the W3C Software License [1] as described
in the FAQ [2].
[1] http://www.w3.org/Consortium/Legal/copyright-software-19980720
[2] http://www.w3.org/Consortium/Legal/IPR-FAQ-20000620.html#DTD
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
targetNamespace="http://www.w3.org/2000/09/xmldsig#"
version="0.1" elementFormDefault="qualified">
<!-- Basic Types Defined for Signatures -->
<simpleType name="CryptoBinary">
<restriction base="base64Binary">
</restriction>
</simpleType>
<!-- Start Signature -->
<element name="Signature" type="ds:SignatureType"/>
<complexType name="SignatureType">
<sequence>
<element ref="ds:SignedInfo"/>
<element ref="ds:SignatureValue"/>
<element ref="ds:KeyInfo" minOccurs="0"/>
<element ref="ds:Object" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="SignatureValue" type="ds:SignatureValueType"/>
<complexType name="SignatureValueType">
<simpleContent>
<extension base="base64Binary">
<attribute name="Id" type="ID" use="optional"/>
</extension>
</simpleContent>
</complexType>
<!-- Start SignedInfo -->
<element name="SignedInfo" type="ds:SignedInfoType"/>
<complexType name="SignedInfoType">
<sequence>
<element ref="ds:CanonicalizationMethod"/>
<element ref="ds:SignatureMethod"/>
<element ref="ds:Reference" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="CanonicalizationMethod" type="ds:CanonicalizationMethodType"/>
<complexType name="CanonicalizationMethodType" mixed="true">
<sequence>
<any namespace="##any" minOccurs="0" maxOccurs="unbounded"/>
<!-- (0,unbounded) elements from (1,1) namespace -->
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<element name="SignatureMethod" type="ds:SignatureMethodType"/>
<complexType name="SignatureMethodType" mixed="true">
<sequence>
<element name="HMACOutputLength" minOccurs="0" type="ds:HMACOutputLengthType"/>
<any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
<!-- (0,unbounded) elements from (1,1) external namespace -->
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<!-- Start Reference -->
<element name="Reference" type="ds:ReferenceType"/>
<complexType name="ReferenceType">
<sequence>
<element ref="ds:Transforms" minOccurs="0"/>
<element ref="ds:DigestMethod"/>
<element ref="ds:DigestValue"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="URI" type="anyURI" use="optional"/>
<attribute name="Type" type="anyURI" use="optional"/>
</complexType>
<element name="Transforms" type="ds:TransformsType"/>
<complexType name="TransformsType">
<sequence>
<element ref="ds:Transform" maxOccurs="unbounded"/>
</sequence>
</complexType>
<element name="Transform" type="ds:TransformType"/>
<complexType name="TransformType" mixed="true">
<choice minOccurs="0" maxOccurs="unbounded">
<any namespace="##other" processContents="lax"/>
<!-- (1,1) elements from (0,unbounded) namespaces -->
<element name="XPath" type="string"/>
</choice>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<!-- End Reference -->
<element name="DigestMethod" type="ds:DigestMethodType"/>
<complexType name="DigestMethodType" mixed="true">
<sequence>
<any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<element name="DigestValue" type="ds:DigestValueType"/>
<simpleType name="DigestValueType">
<restriction base="base64Binary"/>
</simpleType>
<!-- End SignedInfo -->
<!-- Start KeyInfo -->
<element name="KeyInfo" type="ds:KeyInfoType"/>
<complexType name="KeyInfoType" mixed="true">
<choice maxOccurs="unbounded">
<element ref="ds:KeyName"/>
<element ref="ds:KeyValue"/>
<element ref="ds:RetrievalMethod"/>
<element ref="ds:X509Data"/>
<element ref="ds:PGPData"/>
<element ref="ds:SPKIData"/>
<element ref="ds:MgmtData"/>
<any processContents="lax" namespace="##other"/>
<!-- (1,1) elements from (0,unbounded) namespaces -->
</choice>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="KeyName" type="string"/>
<element name="MgmtData" type="string"/>
<element name="KeyValue" type="ds:KeyValueType"/>
<complexType name="KeyValueType" mixed="true">
<choice>
<element ref="ds:DSAKeyValue"/>
<element ref="ds:RSAKeyValue"/>
<any namespace="##other" processContents="lax"/>
</choice>
</complexType>
<element name="RetrievalMethod" type="ds:RetrievalMethodType"/>
<complexType name="RetrievalMethodType">
<sequence>
<element ref="ds:Transforms" minOccurs="0"/>
</sequence>
<attribute name="URI" type="anyURI"/>
<attribute name="Type" type="anyURI" use="optional"/>
</complexType>
<!-- Start X509Data -->
<element name="X509Data" type="ds:X509DataType"/>
<complexType name="X509DataType">
<sequence maxOccurs="unbounded">
<choice>
<element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/>
<element name="X509SKI" type="base64Binary"/>
<element name="X509SubjectName" type="string"/>
<element name="X509Certificate" type="base64Binary"/>
<element name="X509CRL" type="base64Binary"/>
<any namespace="##other" processContents="lax"/>
</choice>
</sequence>
</complexType>
<complexType name="X509IssuerSerialType">
<sequence>
<element name="X509IssuerName" type="string"/>
<element name="X509SerialNumber" type="string"/>
</sequence>
</complexType>
<!-- End X509Data -->
<!-- Begin PGPData -->
<element name="PGPData" type="ds:PGPDataType"/>
<complexType name="PGPDataType">
<choice>
<sequence>
<element name="PGPKeyID" type="base64Binary"/>
<element name="PGPKeyPacket" type="base64Binary" minOccurs="0"/>
<any namespace="##other" processContents="lax" minOccurs="0"
maxOccurs="unbounded"/>
</sequence>
<sequence>
<element name="PGPKeyPacket" type="base64Binary"/>
<any namespace="##other" processContents="lax" minOccurs="0"
maxOccurs="unbounded"/>
</sequence>
</choice>
</complexType>
<!-- End PGPData -->
<!-- Begin SPKIData -->
<element name="SPKIData" type="ds:SPKIDataType"/>
<complexType name="SPKIDataType">
<sequence maxOccurs="unbounded">
<element name="SPKISexp" type="base64Binary"/>
<any namespace="##other" processContents="lax" minOccurs="0"/>
</sequence>
</complexType>
<!-- End SPKIData -->
<!-- End KeyInfo -->
<!-- Start Object (Manifest, SignatureProperty) -->
<element name="Object" type="ds:ObjectType"/>
<complexType name="ObjectType" mixed="true">
<sequence minOccurs="0" maxOccurs="unbounded">
<any namespace="##any" processContents="lax"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="MimeType" type="string" use="optional"/> <!-- add a grep facet -->
<attribute name="Encoding" type="anyURI" use="optional"/>
</complexType>
<element name="Manifest" type="ds:ManifestType"/>
<complexType name="ManifestType">
<sequence>
<element ref="ds:Reference" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="SignatureProperties" type="ds:SignaturePropertiesType"/>
<complexType name="SignaturePropertiesType">
<sequence>
<element ref="ds:SignatureProperty" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="SignatureProperty" type="ds:SignaturePropertyType"/>
<complexType name="SignaturePropertyType" mixed="true">
<choice maxOccurs="unbounded">
<any namespace="##other" processContents="lax"/>
<!-- (1,1) elements from (1,unbounded) namespaces -->
</choice>
<attribute name="Target" type="anyURI" use="required"/>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<!-- End Object (Manifest, SignatureProperty) -->
<!-- Start Algorithm Parameters -->
<simpleType name="HMACOutputLengthType">
<restriction base="integer"/>
</simpleType>
<!-- Start KeyValue Element-types -->
<element name="DSAKeyValue" type="ds:DSAKeyValueType"/>
<complexType name="DSAKeyValueType">
<sequence>
<sequence minOccurs="0">
<element name="P" type="ds:CryptoBinary"/>
<element name="Q" type="ds:CryptoBinary"/>
</sequence>
<element name="G" type="ds:CryptoBinary" minOccurs="0"/>
<element name="Y" type="ds:CryptoBinary"/>
<element name="J" type="ds:CryptoBinary" minOccurs="0"/>
<sequence minOccurs="0">
<element name="Seed" type="ds:CryptoBinary"/>
<element name="PgenCounter" type="ds:CryptoBinary"/>
</sequence>
</sequence>
</complexType>
<element name="RSAKeyValue" type="ds:RSAKeyValueType"/>
<complexType name="RSAKeyValueType">
<sequence>
<element name="Modulus" type="ds:CryptoBinary"/>
<element name="Exponent" type="ds:CryptoBinary"/>
</sequence>
</complexType>
<!-- End KeyValue Element-types -->
<!-- End Signature -->
</schema>
{
"php-saml": {
"version": "2.19.1",
"released": "02/03/2021"
}
}
<phpunit bootstrap="./tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="OneLogin PHP-SAML Test Suite">
<directory>./tests/src</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory>./lib</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="./tests/build/coverage" charset="UTF-8" yui="true" highlight="false" lowUpperBound="35" highLowerBound="70"/>
<log type="test-xml" target="./tests/build/logfile.xml" logIncompleteSkipped="false"/>
<log type="coverage-clover" target="./tests/build/logs/clover.xml"/>
<log type="coverage-php" target="./tests/build/logs/coverage.cov"/>
</logging>
</phpunit>
<?php
$settings = array (
// If 'strict' is True, then the PHP Toolkit will reject unsigned
// or unencrypted messages if it expects them signed or encrypted
// Also will reject the messages if not strictly follow the SAML
// standard: Destination, NameId, Conditions ... are validated too.
'strict' => true,
// Enable debug mode (to print errors)
'debug' => false,
// Set a BaseURL to be used instead of try to guess
// the BaseURL of the view that process the SAML Message.
// Ex. http://sp.example.com/
// http://example.com/sp/
'baseurl' => '',
// Service Provider Data that we are deploying
'sp' => array (
// Identifier of the SP entity (must be a URI)
'entityId' => '',
// Specifies info about where and how the <AuthnResponse> message MUST be
// returned to the requester, in this case our SP.
'assertionConsumerService' => array (
// URL Location where the <Response> from the IdP will be returned
'url' => '',
// SAML protocol binding to be used when returning the <Response>
// message. Onelogin Toolkit supports for this endpoint the
// HTTP-POST binding only
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
),
// If you need to specify requested attributes, set a
// attributeConsumingService. nameFormat, attributeValue and
// friendlyName can be omitted. Otherwise remove this section.
"attributeConsumingService"=> array(
"serviceName" => "SP test",
"serviceDescription" => "Test Service",
"requestedAttributes" => array(
array(
"name" => "",
"isRequired" => false,
"nameFormat" => "",
"friendlyName" => "",
"attributeValue" => ""
)
)
),
// Specifies info about where and how the <Logout Response> message MUST be
// returned to the requester, in this case our SP.
'singleLogoutService' => array (
// URL Location where the <Response> from the IdP will be returned
'url' => '',
// SAML protocol binding to be used when returning the <Response>
// message. Onelogin Toolkit supports for this endpoint the
// HTTP-Redirect binding only
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
),
// Specifies constraints on the name identifier to be used to
// represent the requested subject.
// Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
// Usually x509cert and privateKey of the SP are provided by files placed at
// the certs folder. But we can also provide them with the following parameters
'x509cert' => '',
'privateKey' => '',
/*
* Key rollover
* If you plan to update the SP x509cert and privateKey
* you can define here the new x509cert and it will be
* published on the SP metadata so Identity Providers can
* read them and get ready for rollover.
*/
// 'x509certNew' => '',
),
// Identity Provider Data that we want connect with our SP
'idp' => array (
// Identifier of the IdP entity (must be a URI)
'entityId' => '',
// SSO endpoint info of the IdP. (Authentication Request protocol)
'singleSignOnService' => array (
// URL Target of the IdP where the SP will send the Authentication Request Message
'url' => '',
// SAML protocol binding to be used when returning the <Response>
// message. Onelogin Toolkit supports for this endpoint the
// HTTP-Redirect binding only
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
),
// SLO endpoint info of the IdP.
'singleLogoutService' => array (
// URL Location of the IdP where the SP will send the SLO Request
'url' => '',
// URL location of the IdP where the SP will send the SLO Response (ResponseLocation)
// if not set, url for the SLO Request will be used
'responseUrl' => '',
// SAML protocol binding to be used when returning the <Response>
// message. Onelogin Toolkit supports for this endpoint the
// HTTP-Redirect binding only
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
),
// Public x509 certificate of the IdP
'x509cert' => '',
/*
* Instead of use the whole x509cert you can use a fingerprint in
* order to validate the SAMLResponse, but we don't recommend to use
* that method on production since is exploitable by a collision
* attack.
* (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
* or add for example the -sha256 , -sha384 or -sha512 parameter)
*
* If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
* let the toolkit know which Algorithm was used. Possible values: sha1, sha256, sha384 or sha512
* 'sha1' is the default value.
*/
// 'certFingerprint' => '',
// 'certFingerprintAlgorithm' => 'sha1',
/* In some scenarios the IdP uses different certificates for
* signing/encryption, or is under key rollover phase and more
* than one certificate is published on IdP metadata.
* In order to handle that the toolkit offers that parameter.
* (when used, 'x509cert' and 'certFingerprint' values are
* ignored).
*/
// 'x509certMulti' => array(
// 'signing' => array(
// 0 => '<cert1-string>',
// ),
// 'encryption' => array(
// 0 => '<cert2-string>',
// )
// ),
),
);
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit39845345b23b103682945e29721176f1::getLoader();
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'OneLogin_Saml2_Auth' => $baseDir . '/lib/Saml2/Auth.php',
'OneLogin_Saml2_AuthnRequest' => $baseDir . '/lib/Saml2/AuthnRequest.php',
'OneLogin_Saml2_Constants' => $baseDir . '/lib/Saml2/Constants.php',
'OneLogin_Saml2_Error' => $baseDir . '/lib/Saml2/Error.php',
'OneLogin_Saml2_IdPMetadataParser' => $baseDir . '/lib/Saml2/IdPMetadataParser.php',
'OneLogin_Saml2_LogoutRequest' => $baseDir . '/lib/Saml2/LogoutRequest.php',
'OneLogin_Saml2_LogoutResponse' => $baseDir . '/lib/Saml2/LogoutResponse.php',
'OneLogin_Saml2_Metadata' => $baseDir . '/lib/Saml2/Metadata.php',
'OneLogin_Saml2_Response' => $baseDir . '/lib/Saml2/Response.php',
'OneLogin_Saml2_Settings' => $baseDir . '/lib/Saml2/Settings.php',
'OneLogin_Saml2_Utils' => $baseDir . '/lib/Saml2/Utils.php',
'OneLogin_Saml2_ValidationError' => $baseDir . '/lib/Saml2/Error.php',
'XMLSecEnc' => $baseDir . '/extlib/xmlseclibs/xmlseclibs.php',
'XMLSecurityDSig' => $baseDir . '/extlib/xmlseclibs/xmlseclibs.php',
'XMLSecurityKey' => $baseDir . '/extlib/xmlseclibs/xmlseclibs.php',
);
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit39845345b23b103682945e29721176f1
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit39845345b23b103682945e29721176f1', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit39845345b23b103682945e29721176f1', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit39845345b23b103682945e29721176f1::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit39845345b23b103682945e29721176f1
{
public static $classMap = array (
'OneLogin_Saml2_Auth' => __DIR__ . '/../..' . '/lib/Saml2/Auth.php',
'OneLogin_Saml2_AuthnRequest' => __DIR__ . '/../..' . '/lib/Saml2/AuthnRequest.php',
'OneLogin_Saml2_Constants' => __DIR__ . '/../..' . '/lib/Saml2/Constants.php',
'OneLogin_Saml2_Error' => __DIR__ . '/../..' . '/lib/Saml2/Error.php',
'OneLogin_Saml2_IdPMetadataParser' => __DIR__ . '/../..' . '/lib/Saml2/IdPMetadataParser.php',
'OneLogin_Saml2_LogoutRequest' => __DIR__ . '/../..' . '/lib/Saml2/LogoutRequest.php',
'OneLogin_Saml2_LogoutResponse' => __DIR__ . '/../..' . '/lib/Saml2/LogoutResponse.php',
'OneLogin_Saml2_Metadata' => __DIR__ . '/../..' . '/lib/Saml2/Metadata.php',
'OneLogin_Saml2_Response' => __DIR__ . '/../..' . '/lib/Saml2/Response.php',
'OneLogin_Saml2_Settings' => __DIR__ . '/../..' . '/lib/Saml2/Settings.php',
'OneLogin_Saml2_Utils' => __DIR__ . '/../..' . '/lib/Saml2/Utils.php',
'OneLogin_Saml2_ValidationError' => __DIR__ . '/../..' . '/lib/Saml2/Error.php',
'XMLSecEnc' => __DIR__ . '/../..' . '/extlib/xmlseclibs/xmlseclibs.php',
'XMLSecurityDSig' => __DIR__ . '/../..' . '/extlib/xmlseclibs/xmlseclibs.php',
'XMLSecurityKey' => __DIR__ . '/../..' . '/extlib/xmlseclibs/xmlseclibs.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->classMap = ComposerStaticInit39845345b23b103682945e29721176f1::$classMap;
}, null, ClassLoader::class);
}
}
<?php
namespace Wpo\Pages;
use \Wpo\Core\WordPress_Helpers;
use \Wpo\Services\Options_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Pages\Wizard_Page')) {
class Wizard_Page
{
/**
* Definition of the Options page (following default Wordpress practice).
*
* @since 2.0
*
* @return void
*/
public static function add_management_page()
{
/**
* @since 21.9 Administrators can restrict access to the WPO365 configuration
*/
if (defined('WPO_ADMINS')) {
$admins = constant('WPO_ADMINS');
if (!is_array($admins)) {
return;
}
$admins = array_flip($admins);
$admins = array_change_key_case($admins);
$current_user = wp_get_current_user();
if (!($current_user instanceof \WP_User)) {
return;
}
$user_login = strtolower($current_user->user_login);
if (!array_key_exists($user_login, $admins)) {
return;
}
}
// Don't add the WPO365 wizard in the subsite admin when subsite options has not been configured
if (
is_multisite()
&& !is_network_admin()
&& false === Options_Service::mu_use_subsite_options()
) {
return;
}
add_menu_page(
'WPO365',
'WPO365',
'delete_users',
'wpo365-wizard',
'\Wpo\Pages\Wizard_Page::wpo365_wizard_page'
);
}
/**
*
*/
public static function wpo365_wizard_page()
{
ob_start();
include($GLOBALS['WPO_CONFIG']['plugin_dir'] . '/templates/wizard.php');
$content = ob_get_clean();
echo '' . wp_kses($content, WordPress_Helpers::get_allowed_html());
}
}
}
<?php
namespace Wpo\Services;
// Prevent public access to this script
defined( 'ABSPATH' ) or die( );
if ( !class_exists( '\Wpo\Services\Dependency_Service' ) ) {
class Dependency_Service {
private static $instance = null;
private $dependencies = array();
private function __construct() {
}
public static function get_instance() {
if ( empty( self::$instance ) ) {
self::$instance = new Dependency_Service();
}
return self::$instance;
}
public function add( $name, $dependency ) {
$this->dependencies[ $name ] = $dependency;
}
public function get( $request_id, $name ) {
if ( array_key_exists( $name, $this->dependencies ) ) {
return $this->dependencies[ $name ];
}
return false;
}
public function remove( $request_id, $name ) {
if ( array_key_exists( $name, $this->dependencies ) ) {
unset( $this->dependencies[ $name ] );
}
}
}
}
\ No newline at end of file
<?php
namespace Wpo\Services;
use \Wpo\Core\Extensions_Helpers;
use \Wpo\Core\WordPress_Helpers;
use \Wpo\Services\Id_Token_Service;
use \Wpo\Services\Options_Service;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Services\Error_Service')) {
class Error_Service
{
const AADAPPREG_ERROR = 'AADAPPREG_ERROR';
const BASIC_VERSION = 'BASIC_VERSION';
const CHECK_LOG = 'CHECK_LOG';
const DEACTIVATED = 'DEACTIVATED';
const DUAL_LOGIN = 'DUAL_LOGIN';
const DUAL_LOGIN_V2 = 'DUAL_LOGIN_V2';
const ID_TOKEN_ERROR = 'ID_TOKEN_ERROR';
const ID_TOKEN_AUD = 'ID_TOKEN_AUD';
const LOGGED_OUT = 'LOGGED_OUT';
const NOT_CONFIGURED = 'NOT_CONFIGURED';
const NOT_FROM_DOMAIN = 'NOT_FROM_DOMAIN';
const NOT_IN_GROUP = 'NOT_IN_GROUP';
const SAML2_ERROR = 'SAML2_ERROR';
const TAMPERED_WITH = 'TAMPERED_WITH';
const USER_NOT_FOUND = 'USER_NOT_FOUND';
/**
* Checks for errors in the login messages container and display and unset immediatly after if any
*
* @since 1.0
* @return void
*/
public static function check_for_login_messages($message)
{
if (!isset($_GET['login_errors'])) {
return $message;
}
// Using $_GET here since wp_query is not loaded on login page
$login_error_codes = sanitize_text_field($_GET['login_errors']);
$result = '';
foreach (explode(',', $login_error_codes) as $login_error_code) {
$error_message = self::get_error_message($login_error_code);
if (empty($error_message)) {
continue;
}
$result .= '<p class="message">' . $error_message . '</p><br />';
}
// Return messages to display to hook
return $result;
}
/**
* Tries to get an error message for the error code provided either from
* the options or else from the hard coded backup dictionary provided.
*
* @since 0.1
*
* @param string $error_code Error code
* @return string Error message
*/
public static function get_error_message($error_code)
{
$deprecated_error_messages = array(
self::CHECK_LOG => 'Please contact your System Administrator and check log file.',
self::DEACTIVATED => 'Account deactivated.',
self::DUAL_LOGIN => 'Alternatively, you can click the following link to sign into this website with your corporate <a href="__##OAUTH_URL##__">network login (Office 365)</a>',
self::DUAL_LOGIN_V2 => 'Alternatively, you can click the following link to sign into this website with your corporate <span class="wpo365-dual-login-notice" style="cursor: pointer; text-decoration: underline; color: #000CD" onclick="window.wpo365.pintraRedirect.toMsOnline()">network login (Office 365)</span>',
self::ID_TOKEN_ERROR => 'Your ID token could not be processed. Please contact your System Administrator.',
self::ID_TOKEN_AUD => 'The ID token is intended for a different audience. Please contact your System Administrator.',
self::LOGGED_OUT => 'You are now logged out.',
self::NOT_CONFIGURED => 'Wordpress + Office 365 login not configured yet. Please contact your System Administrator.',
self::NOT_FROM_DOMAIN => 'Access Denied. Please contact your System Administrator.',
self::NOT_IN_GROUP => 'User not in group. Please contact your System Administrator.',
self::SAML2_ERROR => 'SAML authentication error',
self::TAMPERED_WITH => 'Your login might be tampered with. Please contact your System Administrator.',
self::USER_NOT_FOUND => 'Could not create or retrieve your login. Please contact your System Administrator.',
);
$error_messages = array(
self::AADAPPREG_ERROR => __('Could not create or retrieve your login. Most likely the authentication response received from Microsoft does not contain an email address. Consult the <a target="_blank" href="https://www.wpo365.com/troubleshooting-the-wpo365-login-plugin/#PARSING_ERROR">online documentation</a> for details.', 'wpo365-login'),
self::BASIC_VERSION => __('The BASIC edition of the WordPress + Office 365 plugin does not automatically create new users. See the following <a href="https://www.wpo365.com/basic-edition/">online documentation</a> for more info.', 'wpo365-login'),
self::CHECK_LOG => __('Please contact your System Administrator and check log file.', 'wpo365-login'),
self::DEACTIVATED => __('Account deactivated.', 'wpo365-login'),
self::DUAL_LOGIN => __('Alternatively, you can click the following link to sign into this website with your corporate <a href="__##OAUTH_URL##__">network login (Office 365)</a>', 'wpo365-login'),
self::DUAL_LOGIN_V2 => __('Alternatively, you can click the following link to sign into this website with your corporate <span class="wpo365-dual-login-notice" style="cursor: pointer; text-decoration: underline; color: #000CD" onclick="window.wpo365.pintraRedirect.toMsOnline()">network login (Office 365)</span>', 'wpo365-login'),
self::ID_TOKEN_ERROR => __('Your ID token could not be processed. Please contact your System Administrator.', 'wpo365-login'),
self::ID_TOKEN_AUD => __('The ID token is intended for a different audience. Please contact your System Administrator.', 'wpo365_login'),
self::LOGGED_OUT => __('You are now logged out.', 'wpo365-login'),
self::NOT_CONFIGURED => __('Wordpress + Office 365 login not configured yet. Please contact your System Administrator.', 'wpo365-login'),
self::NOT_FROM_DOMAIN => __('Access Denied. Please contact your System Administrator.', 'wpo365-login'),
self::NOT_IN_GROUP => __('Access Denied. Please contact your System Administrator.', 'wpo365-login'),
self::SAML2_ERROR => __('SAML authentication error.', 'wpo365-login'),
self::TAMPERED_WITH => __('Your login might be tampered with. Please contact your System Administrator.', 'wpo365-login'),
self::USER_NOT_FOUND => __('Could not create or retrieve your login. Please contact your System Administrator.', 'wpo365-login'),
);
if (class_exists('\Wpo\Services\Options_Service')) {
$error_message = Options_Service::get_global_string_var('wpo_error_' . strtolower($error_code));
}
// Backward compatible with the now deprecated possibility to update the error message through the configuration instead l18n
if (empty($error_message) || empty($deprecated_error_messages[$error_code]) || $deprecated_error_messages[$error_code] == $error_message) {
$error_message = !empty($error_messages[$error_code])
? $error_messages[$error_code]
: '';
}
// Optionally replace template tokens when error is DUAL_LOGIN or DUAL_LOGINV2
if (class_exists('\Wpo\Services\Options_Service') && WordPress_Helpers::stripos($error_code, 'DUAL_LOGIN') === 0) {
if (Options_Service::get_global_boolean_var('hide_sso_link')) {
return '';
}
$site_url = $GLOBALS['WPO_CONFIG']['url_info']['wp_site_url'];
if (false !== WordPress_Helpers::stripos($error_message, '__##OAUTH_URL##__')) {
$redirect_to = !empty($_GET['redirect_to'])
? esc_url_raw(strtolower(trim($_GET['redirect_to'])))
: null;
if (Options_Service::get_global_boolean_var('use_b2c') && \class_exists('\Wpo\Services\Id_Token_Service_B2c')) {
$oauth_url = \Wpo\Services\Id_Token_Service_B2c::get_openidconnect_url(null, $redirect_to);
} else {
$oauth_url = Id_Token_Service::get_openidconnect_url(null, $redirect_to);
}
$error_message = str_replace("__##OAUTH_URL##__", $oauth_url, $error_message);
}
}
return $error_message;
}
}
}
<?php
namespace Wpo\Services;
// Prevent public access to this script
defined('ABSPATH') or die();
use \Wpo\Services\Log_Service;
include_once(ABSPATH . 'wp-admin/includes/file.php');
if (!class_exists('\Wpo\Services\Files_Service')) {
class Files_Service
{
private static $instance = null;
private $wpo365_profile_images_dir = null;
private $wpo365_profile_images_url = null;
public $wpo365_profile_images_configured = false;
protected function __construct()
{
}
public static function get_instance()
{
if (empty(self::$instance)) {
self::$instance = new Files_Service();
}
return self::$instance;
}
public function configure_wpo365_profile_images()
{
if ($this->wpo365_profile_images_configured) {
return true;
}
global $wp_filesystem;
if (false === ($credentials = request_filesystem_credentials('')) || !WP_Filesystem($credentials)) {
Log_Service::write_log('ERROR', __METHOD__ . ' -> Missing / incorrect credentials to write to the file system');
$this->wpo365_profile_images_configured = false;
return false;
}
$upload_dir = wp_upload_dir();
$this->wpo365_profile_images_dir = trailingslashit($upload_dir['basedir']) . 'wpo365/profile-images/'; // C:\path\to\wordpress\wp-content\uploads\wpo365\profile-images\
if (!file_exists($this->wpo365_profile_images_dir)) {
if (!$wp_filesystem->mkdir($this->wpo365_profile_images_dir, FS_CHMOD_DIR) && !wp_mkdir_p($this->wpo365_profile_images_dir)) {
Log_Service::write_log('DEBUG', __METHOD__ . ' -> Files service could not create requested target directory ' . $this->wpo365_profile_images_dir);
$this->wpo365_profile_images_configured = false;
return false;
}
}
$this->wpo365_profile_images_url = trailingslashit($upload_dir['baseurl']) . 'wpo365/profile-images/'; // http://example.com/wp-content/uploads/wpo365/profile-images/
Log_Service::write_log('DEBUG', __METHOD__ . ' -> Files service has been configured successfully');
$this->wpo365_profile_images_configured = true;
return true;
}
public function get_wpo365_profile_images_dir()
{
return $this->wpo365_profile_images_dir;
}
public function get_wpo365_profile_images_url()
{
return $this->wpo365_profile_images_url;
}
public function save_wpo365_profile_image($path, $file_content)
{
global $wp_filesystem;
if ($this->wpo365_profile_images_configured) {
Log_Service::write_log('DEBUG', __METHOD__ . ' -> Trying to write a file to the file system');
return $wp_filesystem->put_contents($path, $file_content, FS_CHMOD_FILE);
}
return false;
}
}
}
<?php
namespace Wpo\Services;
use Wpo\Core\Wpmu_Helpers;
// Prevent public access to this script
defined('ABSPATH') or die();
if (!class_exists('\Wpo\Services\Nonce_Service')) {
class Nonce_Service
{
/**
* Creates a nonce to ensure the request for an Azure AD token
* originates from the current server.
*
* @since 21.6
*
* @return string
*/
public static function create_nonce()
{
$nonce_stack = Wpmu_Helpers::mu_get_transient('wpo365_nonces');
if (empty($nonce_stack)) {
$nonce_stack = array();
}
$nonce = uniqid();
$nonce_stack[] = $nonce;
// When the stack grows to 200 it's downsized to 150
if (sizeof($nonce_stack) > 200) {
array_splice($nonce_stack, 0, 50);
}
Wpmu_Helpers::mu_set_transient('wpo365_nonces', $nonce_stack, 300);
return $nonce;
}
/**
* Verifies the nonce that Microsoft returns together with the requested token.
*
* @param mixed $nonce
* @return bool
*/
public static function verify_nonce($nonce)
{
$nonce_stack = Wpmu_Helpers::mu_get_transient('wpo365_nonces');
if (empty($nonce_stack)) {
return false;
}
$index = array_search($nonce, $nonce_stack);
if (false === $index) {
return false;
}
array_splice($nonce_stack, $index, 1);
Wpmu_Helpers::mu_set_transient('wpo365_nonces', $nonce_stack, 300);
return true;
}
}
}
<?php
namespace Wpo\Services;
// Prevent public access to this script
defined('ABSPATH') or die();
use \Wpo\Core\Request;
use \Wpo\Services\Access_Token_Service;
use \Wpo\Services\Options_Service;
use \Wpo\Services\User_Service;
if (!class_exists('\Wpo\Services\Request_Service')) {
class Request_Service
{
private $requests = array();
private static $instance = null;
private function __construct()
{
}
public static function get_instance($create_new_request = false)
{
if (empty(self::$instance)) {
self::$instance = new Request_Service();
}
if ($create_new_request) {
$request = self::$instance->get_request($GLOBALS['WPO_CONFIG']['request_id']);
$request->set_item(
'request_log',
array(
'debug_log' => Options_Service::get_global_boolean_var('debug_log', false),
'log' => array(),
)
);
}
return self::$instance;
}
public function get_request($id)
{
if (!array_key_exists($id, $this->requests)) {
$request = new Request($id);
$this->requests[$id] = $request;
}
return $this->requests[$id];
}
public static function shutdown()
{
$request = self::$instance->get_request($GLOBALS['WPO_CONFIG']['request_id']);
$mode = $request->get_item('mode');
if (!empty($mode)) {
Log_Service::flush_log();
$request->clear();
return;
}
$id_token = $request->get_item('id_token');
if (!empty($id_token)) {
$request->remove_item('id_token');
}
$authorization_code = $request->get_item('code');
if (!empty($authorization_code)) {
Access_Token_Service::save_authorization_code($authorization_code);
$request->remove_item('authorization_code');
}
$access_tokens = $request->get_item('access_tokens');
if (!empty($access_tokens)) {
Access_Token_Service::save_access_tokens($access_tokens);
$request->remove_item('access_tokens');
}
$refresh_token = $request->get_item('refresh_token');
if (!empty($refresh_token)) {
Access_Token_Service::save_refresh_token($refresh_token);
$request->remove_item('refresh_token');
}
$pkce_code_verifier = $request->get_item('pkce_code_verifier');
if (Options_Service::get_global_boolean_var('use_pkce') && class_exists('\Wpo\Services\Pkce_Service') && !empty($pkce_code_verifier)) {
\Wpo\Services\Pkce_Service::save_personal_pkce_code_verifier($pkce_code_verifier);
$request->remove_item('pkce_code_verifier');
}
$wpo_usr = $request->get_item('wpo_usr');
if (!empty($wpo_usr)) {
User_Service::save_user_principal_name($wpo_usr->upn);
User_Service::save_user_tenant_id($wpo_usr->tid);
User_Service::save_user_object_id($wpo_usr->oid);
}
Log_Service::flush_log();
$request->clear();
}
}
}
<?php
namespace Wpo\Services;
// Prevent public access to this script
defined('ABSPATH') or die();
use \WP_Error;
use \Wpo\Core\WordPress_Helpers;
use \Wpo\Services\Options_Service;
use \Wpo\Services\Log_Service;
if (!class_exists('\Wpo\Services\Rest_Authentication_Service_Cookies')) {
class Rest_Authentication_Service_Cookies
{
/**
* Handles the WordPress rest_authentication_errors hook. It looks for the WP REST NONCE header and if found validates it.
*
* @param mixed $errors
* @return WP_Error|null|true
*/
public static function authenticate_request($errors)
{
Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
// Check if we have a rule that matches the current request URI
$wp_rest_cookies_protected_endpoints = Options_Service::get_global_list_var('wp_rest_cookies_protected_endpoints');
// Authenticated if no rules are found
if (empty($wp_rest_cookies_protected_endpoints)) {
Log_Service::write_log('DEBUG', __METHOD__ . ' -> No WordPress REST API cookies protected endpoints found');
return null;
}
$headers = array_change_key_case(getallheaders());
foreach ($wp_rest_cookies_protected_endpoints as $wp_rest_cookies_protected_endpoint) {
if (
empty($wp_rest_cookies_protected_endpoint['key'])
|| empty($wp_rest_cookies_protected_endpoint['value'])
) {
Log_Service::write_log('ERROR', __METHOD__ . '-> The following WordPress REST API cookies endpoint is invalid [' . print_r($wp_rest_cookies_protected_endpoint, true) . ']');
continue;
}
// 1. REQUEST TYPE
if (empty($_SERVER['REQUEST_METHOD']) || false === WordPress_Helpers::stripos($wp_rest_cookies_protected_endpoint['value'], $_SERVER['REQUEST_METHOD'])) {
Log_Service::write_log('DEBUG', __METHOD__ . '-> The type of the current request (' . $_SERVER['REQUEST_METHOD'] . ') does not match with the request type of the current rule (' . $wp_rest_cookies_protected_endpoint['value'] . ')');
continue;
}
// 2. PATH
if (WordPress_Helpers::stripos($GLOBALS['WPO_CONFIG']['url_info']['request_uri'], $wp_rest_cookies_protected_endpoint['key']) !== false) {
Log_Service::write_log('DEBUG', __METHOD__ . '-> The following WordPress REST API cookies endpoint configuration will be applied [' . print_r($wp_rest_cookies_protected_endpoint, true) . ']');
// Check if X-WP-Nonce header is present
if (empty($headers['x-wp-nonce'])) {
Log_Service::write_log('WARN', __METHOD__ . ' -> X-WP-NONCE header missing [apache or mod_security may have removed it]');
return new WP_Error(
'wpo365_rest_auth_error',
'403 FORBIDDEN: X-WP-NONCE header was not found',
array('status' => 403)
);
}
if (!wp_verify_nonce($headers['x-wp-nonce'], 'wp_rest')) {
Log_Service::write_log('WARN', __METHOD__ . ' Validation of the X-WP-NONCE header failed');
return new WP_Error(
'wpo365_rest_auth_error',
'401 UNAUTHORIZED: X-WP-NONCE header appears invalid',
array('status' => 401)
);
}
$wp_usr = \wp_get_current_user();
wp_set_current_user($wp_usr->ID);
Log_Service::write_log('DEBUG', sprintf('%s -> Impersonated WordPress user with ID %s ', __METHOD__, $wp_usr->ID));
// Exit loop as soon as the token can be validated
return true;
}
}
// None of the rules apply -> Another authentication handler should handle this request
if (Options_Service::get_global_boolean_var('wp_rest_block')) {
Log_Service::write_log('WARN', sprintf('%s -> Access to this WordPress REST API is forbidden [%s]', __METHOD__, $GLOBALS['WPO_CONFIG']['url_info']['request_uri']));
return new WP_Error(
'wpo365_rest_auth_error',
sprintf('403 FORBIDDEN: Access to this WordPress REST API is forbidden [%s]', $GLOBALS['WPO_CONFIG']['url_info']['request_uri']),
array('status' => 403)
);
}
return null;
}
}
}
<?php
namespace Wpo\Services;
// Prevent public access to this script
defined('ABSPATH') or die();
use \Wpo\Core\Url_Helpers;
use \Wpo\Core\Wpmu_Helpers;
use \Wpo\Services\Log_Service;
use \Wpo\Services\Options_Service;
if (!class_exists('\Wpo\Services\User_Create_Service')) {
class User_Create_Service
{
/**
* @since 11.0
*/
public static function create_user(&$wpo_usr)
{
Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
$user_login = !empty($wpo_usr->preferred_username)
? $wpo_usr->preferred_username
: $wpo_usr->upn;
/**
* @since 12.5
*
* Don't create a user when that user should not be added to a subsite in case of wpmu shared mode.
*/
if (is_multisite() && !Options_Service::mu_use_subsite_options() && !is_main_site() && Options_Service::get_global_boolean_var('skip_add_user_to_subsite')) {
$blog_id = get_current_blog_id();
// Not using subsite options and administrator has disabled automatic adding of users to subsites
Log_Service::write_log('WARN', __METHOD__ . " -> Skipped creating a user with login $user_login for blog with ID $blog_id because administrator has disabled adding a user to a subsite");
Authentication_Service::goodbye(Error_Service::USER_NOT_FOUND);
exit();
}
if (!Options_Service::get_global_boolean_var('create_and_add_users')) {
Log_Service::write_log('ERROR', __METHOD__ . ' -> User not found and settings prevented creating a new user on-demand for user ' . $user_login);
Authentication_Service::goodbye(Error_Service::USER_NOT_FOUND);
exit();
}
/**
* @since 23.0 Added possibility to hook up (custom) actions to pre-defined events for various WPO365 workloads.
*/
do_action(
'wpo365/user/creating',
$wpo_usr->preferred_username,
$wpo_usr->email,
$wpo_usr->groups
);
$usr_default_role = is_main_site()
? Options_Service::get_global_string_var('new_usr_default_role')
: Options_Service::get_global_string_var('mu_new_usr_default_role');
$password_length = Options_Service::get_global_numeric_var('password_length');
if (empty($password_length) || $password_length < 16) {
$password_length = 16;
}
$userdata = array(
'user_login' => $user_login,
'user_pass' => wp_generate_password($password_length, true, false),
'role' => $usr_default_role,
);
/**
* @since 9.4
*
* Optionally removing any user_register hooks as these more often than
* not interfer and cause unexpected behavior.
*/
$user_regiser_hooks = null;
if (Options_Service::get_global_boolean_var('skip_user_register_action') && isset($GLOBALS['wp_filter']) && isset($GLOBALS['wp_filter']['user_register'])) {
Log_Service::write_log('DEBUG', __METHOD__ . ' -> Temporarily removing all filters for the user_register action to avoid interference');
$user_regiser_hooks = $GLOBALS['wp_filter']['user_register'];
unset($GLOBALS['wp_filter']['user_register']);
}
// Insert in Wordpress DB
$wp_usr_id = wp_insert_user($userdata);
if (!empty($GLOBALS['wp_filter']) && !empty($user_regiser_hooks)) {
$GLOBALS['wp_filter']['user_register'] = $user_regiser_hooks;
}
if (is_wp_error($wp_usr_id)) {
Log_Service::write_log('ERROR', __METHOD__ . ' -> Could not create wp user. See next line for error information.');
Log_Service::write_log('ERROR', $wp_usr_id);
Authentication_Service::goodbye(Error_Service::CHECK_LOG);
exit();
}
/**
* @since 15.0
*/
do_action('wpo365/user/created', $wp_usr_id);
$wpo_usr->created = true;
Log_Service::write_log('DEBUG', __METHOD__ . ' -> Created new user with ID ' . $wp_usr_id);
self::wpmu_add_user_to_blog($wp_usr_id, $user_login);
// Try and send new user email
if (\class_exists('\Wpo\Services\Mail_Notifications_Service')) {
if (Options_Service::get_global_boolean_var('new_usr_send_mail')) {
$notify = Options_Service::get_global_boolean_var('new_usr_send_mail_admin_only')
? 'admin'
: 'both';
\Wpo\Services\Mail_Notifications_Service::new_user_notification($wp_usr_id, null, $notify);
Log_Service::write_log('DEBUG', __METHOD__ . ' -> Sent new user notification');
}
} else {
Log_Service::write_log('DEBUG', __METHOD__ . ' -> Did not sent new user notification');
}
Wpmu_Helpers::mu_delete_transient('wpo365_upgrade_dismissed');
Wpmu_Helpers::mu_set_transient('wpo365_user_created', date('d'), 1209600);
return $wp_usr_id;
}
/**
* @since 11.0
*/
public static function wpmu_add_user_to_blog($wp_usr_id, $preferred_user_name)
{
Log_Service::write_log('DEBUG', '##### -> ' . __METHOD__);
if (!is_multisite()) {
return;
}
$blog_id = get_current_blog_id();
$is_main_site = is_main_site();
$usr_default_role = $is_main_site
? Options_Service::get_global_string_var('new_usr_default_role')
: Options_Service::get_global_string_var('mu_new_usr_default_role');
if (!empty($usr_default_role)) {
if (!is_user_member_of_blog($wp_usr_id, $blog_id)) {
$use_subsite_options = Options_Service::mu_use_subsite_options();
$add_member_to_main_site = Options_Service::get_global_boolean_var('create_and_add_users');
$add_member_to_subsite = !Options_Service::get_global_boolean_var('skip_add_user_to_subsite');
// Settings don't allow adding member to main site [wpmu shared mode]
if (!$use_subsite_options && $is_main_site && !$add_member_to_main_site) {
Log_Service::write_log('ERROR', __METHOD__ . ' -> [WPMU shared / main site] User not a member of blog with id ' . $blog_id . ' and settings prevented adding user ' . $wp_usr_id);
Authentication_Service::goodbye(Error_Service::USER_NOT_FOUND);
exit();
}
// Settings don't allow adding member to sub site [wpmu shared mode]
if (!$use_subsite_options && !$is_main_site && !$add_member_to_subsite) {
Log_Service::write_log('ERROR', __METHOD__ . ' -> [WPMU shared / subsite] User not a member of blog with id ' . $blog_id . ' and settings prevented adding user ' . $wp_usr_id);
Authentication_Service::goodbye(Error_Service::USER_NOT_FOUND);
exit();
}
// Settings don't allow adding member to dedicated site [wpmu dedicated mode]
if ($use_subsite_options && !$add_member_to_main_site) {
Log_Service::write_log('ERROR', __METHOD__ . ' -> [WPMU dedicated] User not a member of blog with id ' . $blog_id . ' and settings prevented adding user ' . $wp_usr_id);
Authentication_Service::goodbye(Error_Service::USER_NOT_FOUND);
exit();
}
add_user_to_blog($blog_id, $wp_usr_id, $usr_default_role);
/**
* @since 15.0
*/
do_action('wpo365/wpmu/user_added', $blog_id, $wp_usr_id);
Log_Service::write_log('DEBUG', __METHOD__ . " -> Added user with ID $wp_usr_id as a member to blog with ID $blog_id");
} else {
Log_Service::write_log('DEBUG', __METHOD__ . " -> Skipped adding user with ID $wp_usr_id to blog with ID $blog_id because user already added");
}
} else {
Log_Service::write_log('WARN', __METHOD__ . ' -> Could not add user ' . $preferred_user_name . ' to current blog with ID ' . $blog_id . ' because the default role for the subsite is not valid');
}
}
}
}
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.