From f338099f8073e715b4a02f35a5cb05dec8b7824a Mon Sep 17 00:00:00 2001 From: egbill3eagle Date: Tue, 30 May 2017 13:47:05 -0500 Subject: [PATCH 1/4] Update B2C settings page for GA Admin control --- class-b2c-settings-page.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/class-b2c-settings-page.php b/class-b2c-settings-page.php index 15f32af..ebd841d 100644 --- a/class-b2c-settings-page.php +++ b/class-b2c-settings-page.php @@ -121,6 +121,14 @@ public function page_init() 'b2c-settings-page', // Page 'service_config_section' // Section ); + + add_settings_field( + 'b2c_GA_Grant_Admins', // ID + 'Grant B2C Global Admins Wordpress Administrator Permissions', // Title + array( $this, 'b2c_GA_Grant_Admins_callback'), // Callback + 'b2c-settings-page', // Page + 'service_config_section' // Section + ); } /** @@ -148,6 +156,8 @@ public function sanitize( $input ) $new_input['b2c_verify_tokens'] = $input['b2c_verify_tokens']; + $new_input['b2c_GA_Grant_Admins'] = $input['b2c_GA_Grant_Admins']; + return $new_input; } @@ -228,4 +238,17 @@ public function b2c_verify_tokens_callback() echo ''; } + + /** + * Get the settings option array and print one of its values + */ + public function b2c_GA_Grant_Admins_callback() + { + if(empty($this->options['b2c_GA_Grant_Admins'])) + $this->options['b2c_GA_Grant_Admins'] = 0; + + $current_value = $this->options['b2c_GA_Grant_Admins']; + + echo ''; + } } \ No newline at end of file From 798d4075aa99d4ec0d6d3f1dc51212ce9fada23e Mon Sep 17 00:00:00 2001 From: Bill Hughes Date: Tue, 12 Sep 2017 18:20:57 -0700 Subject: [PATCH 2/4] Adding Graph API helper classes Adding modified helper files to assist in calling the Graph API. Sourced from: https://github.com/Azure-Samples/active-directory-php-graphapi-web/commit/ddd6f5b9be3521cea0bff8383dd4b3ef04d174a8 --- AuthorizationHelperForGraph.php | 45 +++++++ GraphServiceAccessHelper.php | 232 ++++++++++++++++++++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 AuthorizationHelperForGraph.php create mode 100644 GraphServiceAccessHelper.php diff --git a/AuthorizationHelperForGraph.php b/AuthorizationHelperForGraph.php new file mode 100644 index 0000000..a114e78 --- /dev/null +++ b/AuthorizationHelperForGraph.php @@ -0,0 +1,45 @@ +{'token_type'}.' '.$tokenOutput->{'access_token'}; + } +} +?> \ No newline at end of file diff --git a/GraphServiceAccessHelper.php b/GraphServiceAccessHelper.php new file mode 100644 index 0000000..fb2d47d --- /dev/null +++ b/GraphServiceAccessHelper.php @@ -0,0 +1,232 @@ +{'value'}; + } + // Constructs a Http GET request to a linked feed based on the source entry and navigation property name. + public static function getLinkedFeed($sourceFeedName, $sourceEntryId, $navigationPropertyName){ + //initiaze curl which is used to make the http request + $ch = curl_init(); + // Add authorization and other headers. Also set some common settings. + self::AddRequiredHeadersAndSettings($ch); + // create URL to the linked feed by using the navigation property name and the key value for the source entry. + $feedURL = "https://graph.windows.net/".B2C_Settings::$tenant."/".$sourceFeedName .'(\''. $sourceEntryId . '\')' .'/'.$navigationPropertyName; + $feedURL = $feedURL."?".B2C_Settings::$api_version; + curl_setopt($ch, CURLOPT_URL, $feedURL); + //Enable fiddler to capture request + //curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8888'); + // $output contains the output string + $output = curl_exec($ch); + // close curl resource to free up system resources + curl_close($ch); + $jsonOutput = json_decode($output); + // There is a field for odata metadata that we ignore and just consume the value + return $jsonOutput->{'value'}; + } + // Constructs a Http GET request to a feed passed in as paremeter and uses the field information as the filter clause. + // Returns the json decoded respone as the objects that were recieved in feed. + public static function getFeedWithFilterClause($feedName, $fieldName, $fieldValue){ + //initiaze curl which is used to make the http request + $ch = curl_init(); + // Add authorization and other headers. Also set some common settings. + self::AddRequiredHeadersAndSettings($ch); + // set url based on the filter clause. This uses the standard OData syntax to create the filter clause. + $feedURL = "https://graph.windows.net/".B2C_Settings::$tenant."/".$feedName .'?$filter='.$fieldName.urlencode(' eq ') + .'(\''.urlencode($fieldValue).'\')'; + $feedURL = $feedURL."&api-version=2013-04-05"; + curl_setopt($ch, CURLOPT_URL, $feedURL); + //Enable fiddler to capture request + //curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8888'); + // $output contains the output string + $output = curl_exec($ch); + // close curl resource to free up system resources + curl_close($ch); + $jsonOutput = json_decode($output); + // There is a field for odata metadata that we ignore and just consume the value + return $jsonOutput->{'value'}; + } + // Constructs a Http GET request to fetch an entry based on the feed name and the key value passed in. + // Returns the json decoded respone as the objects that were recieved in feed. + public static function getEntry($feedName, $keyValue){ + // initiaze curl which is used to make the http request + $ch = curl_init(); + // Add authorization and other headers. Also set some common settings. + self::AddRequiredHeadersAndSettings($ch); + // Create url for the entry based on the feedname and the key value + $feedURL = 'https://graph.windows.net/'.B2C_Settings::$tenant.'/'.$feedName.'(\''. $keyValue .'\')'; + $feedURL = $feedURL."?".B2C_Settings::$api_version; + curl_setopt($ch, CURLOPT_URL, $feedURL); + //Enable fiddler to capture request + //curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8888'); + // $output contains the output string + $output = curl_exec($ch); + // close curl resource to free up system resources + curl_close($ch); + $jsonOutput = json_decode($output); + return $jsonOutput; + } + // Constructs a HTTP POST request for creating and adding an entry + // to a feed based on the feed name and data passed in. + public static function addEntryToFeed($feedName, $entry){ + //initiaze curl which is used to make the http request + $ch = curl_init(); + // Add authorization and other headers. Also set some common settings. + self::AddRequiredHeadersAndSettings($ch); + // set url + $feedURL = "https://graph.windows.net/".B2C_Settings::$tenant.'/'.$feedName; + $feedURL = $feedURL."?".B2C_Settings::$api_version; + curl_setopt($ch, CURLOPT_URL, $feedURL); + // Mark as Post request + curl_setopt($ch, CURLOPT_POST, 1); + $data = json_encode($entry); + // Set the data for the post request + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + //Enable fiddler to capture request + //curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8888'); + // read the output from the post request + $output = curl_exec($ch); + // close curl resource to free up system resources + curl_close($ch); + // decode the response json decoder + $createdEntry = json_decode($output); + return $createdEntry; + } + // Constructs a HTTP POST request for creating and adding a link + // between two existing entries using the source and target URLs and the navigation property name. + public static function addLinkForEntries($sourceEntryUrl, $targetEntryUrl, $navigationPropertyName){ + //initiaze curl which is used to make the http request + $ch = curl_init(); + // Add authorization and other headers. Also set some common settings. + self::AddRequiredHeadersAndSettings($ch); + // set url + $feedURL = 'https://graph.windows.net/'.B2C_Settings::$tenant.'/'.$sourceEntryUrl.'/$links/'.$navigationPropertyName; + $feedURL = $feedURL."?".B2C_Settings::$api_version; + curl_setopt($ch, CURLOPT_URL, $feedURL); + // Mark as Post request + curl_setopt($ch, CURLOPT_POST, 1); + $data = json_encode($targetEntryUrl); + // Set the data for the post request + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + //Enable fiddler to capture request + //curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8888'); + + // read the output from the post request + $output = curl_exec($ch); + // close curl resource to free up system resources + curl_close($ch); + // decode the response json decoder + $createdEntry = json_decode($output); + return $createdEntry; + } + // Constructs a HTTP PATCH request for updating an entry. + public static function updateEntry($feedName, $keyValue, $entry){ + //initiaze curl which is used to make the http request + $ch = curl_init(); + self::AddRequiredHeadersAndSettings($ch); + // set url + $feedURL = "https://graph.windows.net/".B2C_Settings::$tenant.'/'.$feedName.'(\''. $keyValue .'\')'; + $feedURL = $feedURL."?".B2C_Settings::$api_version; + curl_setopt($ch, CURLOPT_URL, $feedURL); + + // Mark as Patch request + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH'); + //Enable fiddler to capture request + //curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8888'); + $data = json_encode($entry); + // Set the data for the request + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + // read the output from the request + $output = curl_exec($ch); + // close curl resource to free up system resources + curl_close($ch); + // decode the response json decoder + $udpatedEntry = json_decode($output); + return $udpatedEntry; + } + // Constructs a HTTP DELETE request for deleting an entry. + public static function deleteEntry($feedName, $keyValue){ + //initiaze curl which is used to make the http request + $ch = curl_init(); + self::AddRequiredHeadersAndSettings($ch); + // set url + $feedURL = "https://graph.windows.net/".B2C_Settings::$tenant.'/'.$feedName.'(\''. $keyValue .'\')'; + $feedURL = $feedURL."?".B2C_Settings::$api_version; + curl_setopt($ch, CURLOPT_URL, $feedURL); + //Enable fiddler to capture request + //curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8888'); + // Mark as Post request + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + // read the output from the post request + $output = curl_exec($ch); + // close curl resource to free up system resources + curl_close($ch); + // decode the response json decoder + $deletedEntry = json_decode($output); + return $deletedEntry; + } + // Constructs a Http GET request get changes for the type passed in. + // Returns the json decoded respone as the objects that were recieved in feed. + public static function getDeltaLinkFeed($feedURL, $feedName){ + if ($feedName == NULL) + { + $feedName = "directoryObjects"; + } + // initiaze curl which is used to make the http request. + $ch = curl_init(); + // Add authorization and other headers. Also set some common settings. + self::AddRequiredHeadersAndSettings($ch); + // set url + if ($feedURL == NULL) + { + $feedURL = "https://graph.windows.net/".B2C_Settings::$tenant."/".$feedName; + $feedURL = $feedURL."?deltaLink="; + } + // add api-version always to indicate the target version + $feedURL = $feedURL."&api-version=2013-04-05"; + curl_setopt($ch, CURLOPT_URL, $feedURL); + // $output contains the output string + $output = curl_exec($ch); + // close curl resource to free up system resources + curl_close($ch); + $jsonOutput = json_decode($output); + // There is a field for odata metadata that we ignore and just consume the value + return $jsonOutput; + } + // Add required headers like authorization header, service version etc. + public static function AddRequiredHeadersAndSettings($ch) + { + //Generate the authentication header + $authHeader = AuthorizationHelperForAADGraphService::GetAuthenticationHeader(B2C_Settings::$tenant, B2C_Settings::$graphID, B2C_Settings::$clientSecret); + // Add authorization header, request/response format header( for json) and a header to request content for Update and delete operations. + curl_setopt($ch, CURLOPT_HTTPHEADER, array($authHeader, 'Accept:application/json;odata=minimalmetadata', + 'Content-Type:application/json;odata=minimalmetadata', 'Prefer:return-content')); + // Set the option to recieve the response back as string. + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + // By default https does not work for CURL. + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + } + } +?> \ No newline at end of file From 40de9bc1d9ce01b52747213221d1bffb7627a679 Mon Sep 17 00:00:00 2001 From: Bill Hughes Date: Tue, 12 Sep 2017 18:30:54 -0700 Subject: [PATCH 3/4] Add additional settings for admin controls Add additional settings required to grant B2C admins access to wordpress admin role --- class-b2c-settings-page.php | 46 ++++++++++++++++++++++++++++++++++++- class-b2c-settings.php | 8 +++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/class-b2c-settings-page.php b/class-b2c-settings-page.php index ebd841d..6673228 100644 --- a/class-b2c-settings-page.php +++ b/class-b2c-settings-page.php @@ -124,11 +124,27 @@ public function page_init() add_settings_field( 'b2c_GA_Grant_Admins', // ID - 'Grant B2C Global Admins Wordpress Administrator Permissions', // Title + 'Grant B2C Global Admins Wordpress Administrator Role', // Title array( $this, 'b2c_GA_Grant_Admins_callback'), // Callback 'b2c-settings-page', // Page 'service_config_section' // Section ); + + add_settings_field( + 'b2c_client_secret', // ID + 'Client Secret', //Title + array( $this, 'b2c_client_secret_callback' ), // Callback + 'b2c-settings-page', // Page + 'service_config_section' // Section + ); + + add_settings_field( + 'b2c_graph_id', // ID + 'Graph API Application ID (Not B2C Application ID)', //Title + array( $this, 'b2c_graph_id_callback' ), // Callback + 'b2c-settings-page', // Page + 'service_config_section' // Section + ); } /** @@ -158,6 +174,12 @@ public function sanitize( $input ) $new_input['b2c_GA_Grant_Admins'] = $input['b2c_GA_Grant_Admins']; + if( isset( $input['b2c_client_secret'] ) ) + $new_input['b2c_client_secret'] = sanitize_text_field( $input['b2c_client_secret'] ); + + if( isset( $input['b2c_graph_id'] ) ) + $new_input['b2c_graph_id'] = sanitize_text_field( $input['b2c_graph_id'] ); + return $new_input; } @@ -251,4 +273,26 @@ public function b2c_GA_Grant_Admins_callback() echo ''; } + + /** + * Get the settings option array and print one of its values + */ + public function b2c_graph_id_callback() + { + printf( + '', + isset( $this->options['b2c_graph_id'] ) ? esc_attr( $this->options['b2c_graph_id']) : '' + ); + } + + /** + * Get the settings option array and print one of its values + */ + public function b2c_client_secret_callback() + { + printf( + '', + isset( $this->options['b2c_client_secret'] ) ? esc_attr( $this->options['b2c_client_secret']) : '' + ); + } } \ No newline at end of file diff --git a/class-b2c-settings.php b/class-b2c-settings.php index c322767..5064aca 100644 --- a/class-b2c-settings.php +++ b/class-b2c-settings.php @@ -10,12 +10,16 @@ class B2C_Settings { public static $edit_profile_policy = ""; public static $redirect_uri = ""; public static $verify_tokens = 1; + public static $b2cAdmins = 1; + public static $graphID = ""; + public static $clientSecret = ""; // These settings define the authentication flow, but are not configurable on the settings page // because this plugin is made to support OpenID Connect implicit flow with form post responses public static $response_type = "id_token"; public static $response_mode = "form_post"; public static $scope = "openid"; + public static $api_version = "api-version=1.6"; function __construct() { @@ -33,6 +37,10 @@ function __construct() { self::$redirect_uri = urlencode(site_url().'/'); if ($config_elements['b2c_verify_tokens']) self::$verify_tokens = 1; else self::$verify_tokens = 0; + if ($config_elements['b2c_GA_Grant_Admins']) self::$b2cAdmins = 1; + else self::$b2cAdmins = 0; + self::$clientSecret = $config_elements['b2c_client_secret']; + self::$graphID = $config_elements['b2c_graph_id']; } } From 547c74e91a75a8ca94e529e79c9d5901c5e19402 Mon Sep 17 00:00:00 2001 From: Bill Hughes Date: Tue, 12 Sep 2017 18:42:32 -0700 Subject: [PATCH 4/4] Enable B2C GA Admin access Enable B2C Global Admins to be automatically granted the administrator role within wordpress --- b2c_authentication.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/b2c_authentication.php b/b2c_authentication.php index 69b730e..27c3c4e 100644 --- a/b2c_authentication.php +++ b/b2c_authentication.php @@ -19,6 +19,7 @@ */ require 'autoload.php'; require 'vendor/autoload.php'; +require 'GraphServiceAccessHelper.php'; /** * Defines the response string posted by B2C. @@ -104,6 +105,7 @@ function b2c_verify_token() { $email = $token_checker->get_claim('emails'); $email = $email[0]; $user = WP_User::get_data_by('email', $email); + $b2cID = $token_checker->get_claim('oid'); // Get the userID for the user if ($user == false) { // User doesn't exist yet, create new userID @@ -140,8 +142,21 @@ function b2c_verify_token() { } else { $userID = $user->ID; } - - // Check if the user is an admin and needs MFA + + //Load users group memberships + $userB2CGroups = GraphServiceAccessHelper::getLinkedFeed('users',$b2cID,'memberOf'); + foreach ($userB2CGroups as $group){ + + if ($group->{'roleTemplateId'} == '62e90394-69f5-4237-9190-012177145e10') { + //User is a Global Admin + if(B2C_Settings::$b2cAdmins){ + $wp_user_GA = new WP_User($userID); + $wp_user_GA->set_role('administrator'); + } + } + } + + // Check if the user is an admin and step up to correct policy if applicable IE: Prompt for MFA $wp_user = new WP_User($userID); if (in_array('administrator', $wp_user->roles)) {