diff --git a/login-google.php b/login-google.php index c2a36ea..01f785b 100644 --- a/login-google.php +++ b/login-google.php @@ -12,10 +12,14 @@ define('CLIENT_ID', get_option('wpoa_google_api_id')); define('CLIENT_SECRET', get_option('wpoa_google_api_secret')); define('REDIRECT_URI', rtrim(site_url(), '/') . '/'); -define('SCOPE', 'profile'); // PROVIDER SPECIFIC: 'profile' is the minimum scope required to get the user's id from Google define('URL_AUTH', "https://accounts.google.com/o/oauth2/auth?"); -define('URL_TOKEN', "https://accounts.google.com/o/oauth2/token?"); +define('URL_TOKEN', "https://accounts.google.com/o/oauth2/token"); define('URL_USER', "https://www.googleapis.com/plus/v1/people/me?"); +// PROVIDER SPECIFIC: profile minimum and emails for matching users +if(get_option('wpoa_email_linking')) + define('SCOPE', 'https://www.googleapis.com/auth/plus.profile.emails.read'); +else + define('SCOPE', 'profile'); # END OF DEFINE THE OAUTH PROVIDER AND SETTINGS TO USE # // remember the user's last url so we can redirect them back to there after the login ends: @@ -75,6 +79,12 @@ # END OF AUTHENTICATION FLOW # # AUTHENTICATION FLOW HELPER FUNCTIONS # + +/** + * Gets the oauth code. + * + * @param WPOA $wpoa + */ function get_oauth_code($wpoa) { $params = array( 'response_type' => 'code', @@ -89,6 +99,12 @@ function get_oauth_code($wpoa) { exit; } +/** + * Gets the oauth token. + * + * @param WPOA $wpoa + * @return boolean + */ function get_oauth_token($wpoa) { $params = array( 'grant_type' => 'authorization_code', @@ -100,7 +116,7 @@ function get_oauth_token($wpoa) { $url_params = http_build_query($params); switch (strtolower(HTTP_UTIL)) { case 'curl': - $url = URL_TOKEN . $url_params; + $url = URL_TOKEN; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); @@ -146,6 +162,12 @@ function get_oauth_token($wpoa) { } } +/** + * Gets the oauth identity. + * + * @param WPOA $wpoa + * @return array + */ function get_oauth_identity($wpoa) { // here we exchange the access token for the user info... // set the access token param: @@ -156,12 +178,14 @@ function get_oauth_identity($wpoa) { // perform the http request: switch (strtolower(HTTP_UTIL)) { case 'curl': - $url = URL_USER . $url_params; // TODO: we probably want to send this using a curl_setopt... + $url = URL_USER . $url_params; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); - // PROVIDER NORMALIZATION: Github/Reddit require a User-Agent here... - // PROVIDER NORMALIZATION: PayPal/Reddit require that we send the access token via a bearer header, PayPal also requires a Content-Type: application/json header, LinkedIn requires an x-li-format: json header... curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // curl_setopt($curl, CURLOPT_POST, 1); + // curl_setopt($curl, CURLOPT_POSTFIELDS, $params); + // curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, (get_option('wpoa_http_util_verify_ssl') == 1 ? 1 : 0)); + // curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, (get_option('wpoa_http_util_verify_ssl') == 1 ? 2 : 0)); $result = curl_exec($curl); $result_obj = json_decode($result, true); break; @@ -185,8 +209,10 @@ function get_oauth_identity($wpoa) { // parse and return the user's oauth identity: $oauth_identity = array(); $oauth_identity['provider'] = $_SESSION['WPOA']['PROVIDER']; - $oauth_identity['id'] = $result_obj['id']; // PROVIDER SPECIFIC: Google returns the user's OAuth identity as id - //$oauth_identity['email'] = $result_obj['emails'][0]['value']; // PROVIDER SPECIFIC: Google returns an array of email addresses. To respect privacy we currently don't collect the user's email address. + // PROVIDER SPECIFIC: Google returns the user's OAuth identity as id + $oauth_identity['id'] = $result_obj['id']; + // PROVIDER SPECIFIC: Google returns an array of email addresses. To respect privacy we currently only collect if the registration setting is enabled. + $oauth_identity['email'] = (isset($result_obj['emails'])) ? $result_obj['emails'][0]['value'] : ''; if (!$oauth_identity['id']) { $wpoa->wpoa_end_login("Sorry, we couldn't log you in. User identity was not found. Please notify the admin or try again later."); } diff --git a/login-office365.php b/login-office365.php new file mode 100644 index 0000000..ad93490 --- /dev/null +++ b/login-office365.php @@ -0,0 +1,218 @@ +wpoa_end_login("This third-party authentication provider has not been enabled. Please notify the admin or try again later."); +} +/* Do not proceed if id or secret is null */ +elseif (!CLIENT_ID || !CLIENT_SECRET) { + $this->wpoa_end_login("This third-party authentication provider has not been configured with an API key/secret. Please notify the admin or try again later."); +} +/* Do not proceed if an error was detected */ +elseif (isset($_GET['error_description'])) { + $this->wpoa_end_login($_GET['error_description']); +} +/* Do not proceed if an error was detected */ +elseif (isset($_GET['error_message'])) { + $this->wpoa_end_login($_GET['error_message']); +} +/* POST-auth phase, verify the state */ +elseif (isset($_GET['code'])) { + if ($_SESSION['WPOA']['STATE'] == $_GET['state']) { + // get an access token from the third party provider: + get_oauth_token($this); + // get the user's third-party identity and attempt to login/register a matching wordpress user account: + $oauth_identity = get_oauth_identity($this); + $this->wpoa_login_user($oauth_identity); + } + /* Possible CSRF attack, end the login with a generic message + to the user and a detailed message to the admin/logs in case of abuse */ + else { + // TODO: report detailed message to admin/logs here... + $this->wpoa_end_login("Sorry, we couldn't log you in. Please notify the admin or try again later."); + } +} +/* PRE-auth, start the auth process */ +else { + if ((empty($_SESSION['WPOA']['EXPIRES_AT'])) || (time() > $_SESSION['WPOA']['EXPIRES_AT'])) { + // expired token; clear the state: + $this->wpoa_clear_login_state(); + } + get_oauth_code($this); +} + +/* NOTE: If we reach here something went wrong and was not accounted for */ +$this->wpoa_end_login("Sorry, we couldn't log you in. The authentication flow terminated in an unexpected way. Please notify the admin or try again later."); +### END OF AUTHENTICATION FLOW ### + +### AUTHENTICATION FLOW HELPER FUNCTIONS ### +function get_oauth_code($wpoa) { + $params = array( + 'client_id' => CLIENT_ID, + 'redirect_uri' => REDIRECT_URI, + 'scope' => SCOPE, + 'response_type' => 'code', + 'response_mode' => 'query', + 'state' => uniqid('', true), + ); + $_SESSION['WPOA']['STATE'] = $params['state']; + $url = URL_AUTH . http_build_query($params); + header("Location: $url"); + exit; + #TODO: Add Error handling - https://azure.microsoft.com/en-us/documentation/articles/active-directory-protocols-oauth-code/#_error-response +} + +/** + * Gets the oauth token. + * + * @param WPOA $wpoa + * @return array + */ +function get_oauth_token($wpoa) { + $params = array( + 'client_id' => CLIENT_ID, + 'client_secret' => CLIENT_SECRET, + 'code' => $_GET['code'], + 'redirect_uri' => REDIRECT_URI, + 'grant_type' => 'authorization_code', + ); + $url_params = http_build_query($params); + switch (strtolower(HTTP_UTIL)) { + case 'curl': + $url = URL_TOKEN; + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $url_params); // TODO: for Google we use $params... + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, (get_option('wpoa_http_util_verify_ssl') == 1 ? 1 : 0)); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, (get_option('wpoa_http_util_verify_ssl') == 1 ? 2 : 0)); + $result = curl_exec($curl); + break; + case 'stream-context': + $url = rtrim(URL_TOKEN, "?"); + $opts = array('http' => + array( + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'content' => $url_params, + ) + ); + $context = $context = stream_context_create($opts); + $result = @file_get_contents($url, false, $context); + if ($result === false) { + $wpoa->wpoa_end_login("Sorry, we couldn't log you in. Could not retrieve access token via stream context. Please notify the admin or try again later."); + } + break; + } + # PROVIDER SPECIFIC: Outlook REST encodes the access token result as json by default + $result_obj = json_decode($result, true); + # PROVIDER SPECIFIC: this is how Outlook REST returns the access token KEEP THIS PROTECTED! + $access_token = isset($result_obj['access_token']) ? $result_obj['access_token'] : ''; + # PROVIDER SPECIFIC: this is how Outlook REST returns the access token's expiration + $expires_in = isset($result_obj['expires_in']) ? $result_obj['expires_in'] : ''; + $expires_at = time() + $expires_in; + # PROVIDER SPECIFIC: this is how Outlook REST returns the users data (keep for email matching) + $id_token = isset($result_obj['id_token']) ? $result_obj['id_token'] : ''; + # Handle the result: + if (!$access_token || !$expires_in) { + // malformed access token result detected: + $wpoa->wpoa_end_login("Sorry, we couldn't log you in. Malformed access token result detected. Please notify the admin or try again later."); + } else { + $_SESSION['WPOA']['ACCESS_TOKEN'] = $access_token; + $_SESSION['WPOA']['EXPIRES_IN'] = $expires_in; + $_SESSION['WPOA']['EXPIRES_AT'] = $expires_at; + $_SESSION['WPOA']['ID_TOKEN'] = $id_token; + return true; + } +} + +/** + * Gets the oauth identity. + * + * @param WPOA $wpoa + * @return array + */ +function get_oauth_identity($wpoa) { + // here we exchange the access token for the user info... + // set the access token param: + $params = array( + 'access_token' => $_SESSION['WPOA']['ACCESS_TOKEN'], // PROVIDER SPECIFIC: the access token is passed to Outlook REST using this key name + ); + $url_params = http_build_query($params); + // perform the http request: + switch (strtolower(HTTP_UTIL)) { + case 'curl': + $url = rtrim(URL_USER, "?"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // TODO: does Outlook REST require this? + curl_setopt($curl, CURLOPT_HTTPHEADER, array("Authorization: bearer " . $_SESSION['WPOA']['ACCESS_TOKEN'])); // PROVIDER SPECIFIC: do we have to do this for Outlook REST? + //curl_setopt($curl, CURLOPT_HTTPHEADER, array('x-li-format: json')); // PROVIDER SPECIFIC: I think this is only for LinkedIn... + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + $result = curl_exec($curl); + $result_obj = json_decode($result, true); + break; + case 'stream-context': + $url = rtrim(URL_USER, "?"); + $opts = array('http' => + array( + 'method' => 'GET', + // PROVIDER NORMALIZATION: Reddit/Github requires User-Agent here... + 'header' => "Authorization: Bearer " . $_SESSION['WPOA']['ACCESS_TOKEN'] . "\r\n" . "x-li-format: json\r\n", // PROVIDER SPECIFIC: i think only LinkedIn uses x-li-format... + ) + ); + $context = $context = stream_context_create($opts); + $result = @file_get_contents($url, false, $context); + if ($result === false) { + $wpoa->wpoa_end_login("Sorry, we couldn't log you in. Could not retrieve user identity via stream context. Please notify the admin or try again later."); + } + $result_obj = json_decode($result, true); + break; + } + // parse and return the user's oauth identity: + $oauth_identity = array(); + $oauth_identity['provider'] = (isset($_SESSION['WPOA']['PROVIDER'])) ? $_SESSION['WPOA']['PROVIDER'] : ''; + // PROVIDER SPECIFIC: Outlook REST returns the user's unique id + $oauth_identity['id'] = (isset($result_obj['Id'])) ? $result_obj['Id'] : ''; + // PROVIDER SPECIFIC: Outlook REST returns the user's email address + $oauth_identity['email'] = (isset($result_obj['EmailAddress'])) ? $result_obj['EmailAddress'] : ''; + if (!$oauth_identity['id']) { + $wpoa->wpoa_end_login("Sorry, we couldn't log you in. User identity was not found. Please notify the admin or try again later."); + } + return $oauth_identity; +} +### END OF AUTHENTICATION FLOW HELPER FUNCTIONS ### + +?> \ No newline at end of file diff --git a/wp-oauth-settings.php b/wp-oauth-settings.php index 0e34fa3..b58a470 100644 --- a/wp-oauth-settings.php +++ b/wp-oauth-settings.php @@ -473,6 +473,14 @@ function wpoa_cc_ux() {

Prevents WordPress from sending an email to newly registered users by default, which contains their username and password.

+ + + Allow email linking when loggin in: [?] + + /> +

Increases permission request from user to include email in order to match authenticated user with registered users. Because this exposes more user data, please use at your discretion.

+ + Assign new users to the following role: [?] @@ -782,6 +790,55 @@ function wpoa_cc_ux() { + + +
+

Login with Office 365

+
+ + + + + + + + + + + + + + + + + + + + +
Enabled: + /> +
Client ID: + ' /> +
Client Secret: + ' /> +
Tenant: + ' /> +

Used to control who can sign into the application. More Details

+
+

+ Instructions: +

    +
  1. Register as an Outlook Developer at apps.dev.microsoft.com.
  2. +
  3. At Application Registration Portal, click "Add an app". This will enable your site to access the Outlook REST API.
  4. +
  5. Within your application, click "Add Platform" and add a "Web" platform providing your site's homepage URL () for the new App's Redirect URI(s). Don't forget the trailing slash!
  6. +
  7. Paste your Client ID (Application Id)/Secret (Application Password) provided by Application Registration Portal into the fields above, then click the Save all settings button.
  8. +
  9. For more details checkout the documenation +
+

+ +
+
+
diff --git a/wp-oauth.php b/wp-oauth.php index d353e8c..9ffa87e 100644 --- a/wp-oauth.php +++ b/wp-oauth.php @@ -80,6 +80,7 @@ public static function get_instance() { ), ), 'wpoa_suppress_welcome_email' => 0, // 0, 1 + 'wpoa_email_linking' => 0, // 0, 1 'wpoa_new_user_role' => 'contributor', // role 'wpoa_google_api_enabled' => 0, // 0, 1 'wpoa_google_api_id' => '', // any string @@ -99,6 +100,10 @@ public static function get_instance() { 'wpoa_reddit_api_enabled' => 0, // 0, 1 'wpoa_reddit_api_id' => '', // any string 'wpoa_reddit_api_secret' => '', // any string + 'wpoa_office365_api_enabled' => 0, // 0, 1 + 'wpoa_office365_api_id' => '', // any string + 'wpoa_office365_api_secret' => '', // any string + 'wpoa_office365_tenant' => 'common', // any string 'wpoa_windowslive_api_enabled' => 0, // 0, 1 'wpoa_windowslive_api_id' => '', // any string 'wpoa_windowslive_api_secret' => '', // any string @@ -407,13 +412,34 @@ function wpoa_match_wordpress_user($oauth_identity) { $user = get_user_by('id', $query_result); return $user; } - - // login (or register and login) a wordpress user based on their oauth identity: + + /** + * Check for existing WP user by email + * + * @since 0.4.1 + * + * @param Array $oauth_identity + * @return WP_User|false + */ + function wpoa_match_wordpress_user_by_email($oauth_identity) { + $user = get_user_by('email', $oauth_identity['email']); + return $user; + } + + /** + * Login (or register and login) a wordpress user based on their oauth identity: + * + * @param Array $oauth_identity + */ function wpoa_login_user($oauth_identity) { // store the user info in the user session so we can grab it later if we need to register the user: $_SESSION["WPOA"]["USER_ID"] = $oauth_identity["id"]; // try to find a matching wordpress user for the now-authenticated user's oauth identity: $matched_user = $this->wpoa_match_wordpress_user($oauth_identity); + // If user is not found by oauth identity, then attempt by email (if enabled). + if(get_option('wpoa_email_linking') && !$matched_user) { + $matched_user = $this->wpoa_match_wordpress_user_by_email($oauth_identity); + } // handle the matched user if there is one: if ( $matched_user ) { // there was a matching wordpress user account, log it in now: @@ -429,7 +455,7 @@ function wpoa_login_user($oauth_identity) { if ( is_user_logged_in() ) { // there was a wordpress user logged in, but it is not associated with the now-authenticated user's email address, so associate it now: global $current_user; - get_currentuserinfo(); + wp_get_current_user(); $user_id = $current_user->ID; $this->wpoa_link_account($user_id); // after linking the account, redirect user to their last url @@ -541,7 +567,7 @@ function wpoa_unlink_account() { $wpoa_identity_row = $_POST['wpoa_identity_row']; // SANITIZED via $wpdb->prepare() // get the current user: global $current_user; - get_currentuserinfo(); + wp_get_current_user(); $user_id = $current_user->ID; // delete the wpoa_identity record from the wp_usermeta table: global $wpdb; @@ -708,7 +734,7 @@ function wpoa_login_buttons($icon_set, $button_prefix) { // generate the atts once (cache them), so we can use it for all buttons without computing them each time: $site_url = get_bloginfo('url'); if( force_ssl_admin() ) { $site_url = set_url_scheme( $site_url, 'https' ); } - $redirect_to = urlencode($_GET['redirect_to']); + $redirect_to = isset($_GET['redirect_to']) ? urlencode($_GET['redirect_to']) : ''; if ($redirect_to) {$redirect_to = "&redirect_to=" . $redirect_to;} // get shortcode atts that determine how we should build these buttons: $icon_set_path = plugins_url('icons/' . $icon_set . '/', __FILE__); @@ -728,6 +754,7 @@ function wpoa_login_buttons($icon_set, $button_prefix) { $html .= $this->wpoa_login_button("github", "GitHub", $atts); $html .= $this->wpoa_login_button("itembase", "itembase", $atts); $html .= $this->wpoa_login_button("reddit", "Reddit", $atts); + $html .= $this->wpoa_login_button("office365", "Office 365", $atts); $html .= $this->wpoa_login_button("windowslive", "Windows Live", $atts); $html .= $this->wpoa_login_button("paypal", "PayPal", $atts); $html .= $this->wpoa_login_button("instagram", "Instagram", $atts); @@ -821,7 +848,7 @@ function wpoa_login_form_design_exists($design_name) { function wpoa_linked_accounts() { // get the current user: global $current_user; - get_currentuserinfo(); + wp_get_current_user(); $user_id = $current_user->ID; // get the wpoa_identity records: global $wpdb;