File: //var/www/html/TriadGov/wp-content/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Auth.php
<?php
namespace WPMailSMTP\Pro\Providers\Outlook;
use WPMailSMTP\ConnectionInterface;
use WPMailSMTP\Debug;
use WPMailSMTP\Helpers\Helpers;
use WPMailSMTP\Providers\AuthAbstract;
use WPMailSMTP\Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use WPMailSMTP\Vendor\League\OAuth2\Client\Provider\GenericProvider;
use WPMailSMTP\Vendor\League\OAuth2\Client\Token\AccessToken;
use WPMailSMTP\Vendor\League\OAuth2\Client\Token\AccessTokenInterface;
use WPMailSMTP\WP;
use Exception;
/**
* Class Auth.
*
* @since 1.5.0
*/
class Auth extends AuthAbstract {
/**
* Scopes that we need to send emails.
*
* @since 1.5.0
*/
const SCOPES = [
'https://graph.microsoft.com/mail.send',
'https://graph.microsoft.com/mail.send.shared',
'https://graph.microsoft.com/mail.readwrite',
'https://graph.microsoft.com/user.read',
'offline_access',
];
/**
* Auth constructor.
*
* @since 1.0.0
*
* @param ConnectionInterface $connection The Connection object.
*/
public function __construct( $connection = null ) {
parent::__construct( $connection );
if ( $this->mailer_slug !== Options::SLUG ) {
return;
}
$this->options = $this->connection_options->get_group( $this->mailer_slug );
if ( ! empty( $this->options['one_click_setup_enabled'] ) ) {
return;
}
$this->get_client();
}
/**
* Init and get the OAuth2 Client object.
*
* @since 1.0.0
* @since 2.5.0 Add force method parameter.
*
* @param bool $force If the client should be forcefully reinitialized.
*
* @return GenericProvider
* @throws IdentityProviderException Emits exception on requests failure.
*/
public function get_client( $force = false ) {
// Doesn't load client twice + gives ability to overwrite.
if ( ! empty( $this->client ) && ! $force ) {
return $this->client;
}
$this->include_vendor_lib();
/**
* Filters auth authorize url.
*
* @since 2.8.0
*
* @param string $url Auth authorize url.
*/
$authorize_url = apply_filters(
'wp_mail_smtp_pro_providers_outlook_auth_authorize_url',
'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
);
/**
* Filters auth access token url.
*
* @since 2.8.0
*
* @param string $url Auth access token url.
*/
$access_token_url = apply_filters(
'wp_mail_smtp_pro_providers_outlook_auth_access_token_url',
'https://login.microsoftonline.com/common/oauth2/v2.0/token'
);
/**
* Filters auth resource owner details url.
*
* @since 2.8.0
*
* @param string $url Auth resource owner details url.
*/
$resource_owner_details_url = apply_filters(
'wp_mail_smtp_pro_providers_outlook_auth_resource_owner_details_url',
'https://graph.microsoft.com/v1.0/me'
);
$this->client = new GenericProvider(
[
'clientId' => $this->options['client_id'],
'clientSecret' => $this->options['client_secret'],
'redirectUri' => self::get_plugin_auth_url(),
'urlAuthorize' => $authorize_url,
'urlAccessToken' => $access_token_url,
'urlResourceOwnerDetails' => $resource_owner_details_url,
'scopeSeparator' => ' ',
]
);
// Do not process if we don't have both App ID & Password.
if ( ! $this->is_clients_saved() ) {
return $this->client;
}
if ( ! empty( $this->options['access_token'] ) ) {
$access_token = new AccessToken( (array) $this->options['access_token'] );
}
// We don't have tokens but have auth code.
if (
$this->is_auth_required() &&
! empty( $this->options['auth_code'] )
) {
// Try to get an access token using the authorization code grant.
$this->obtain_access_token();
} else { // We have tokens.
// Update the old token if needed.
if ( ! empty( $access_token ) && $access_token->hasExpired() ) {
$this->refresh_access_token( $access_token );
}
}
return $this->client;
}
/**
* Try to get an access token using the authorization code grant.
*
* @since 3.4.0
*/
private function obtain_access_token() {
if ( empty( $this->options['auth_code'] ) ) {
return;
}
try {
$access_token = $this->client->getAccessToken(
'authorization_code',
[ 'code' => $this->options['auth_code'] ]
);
$this->update_access_token( $access_token->jsonSerialize() );
$this->update_refresh_token( $access_token->getRefreshToken() );
$this->update_user_details( $access_token );
$this->update_scopes( $this->get_scopes() );
// Reset Auth code. It's valid for 5 minutes anyway.
$this->update_auth_code( '' );
// Set from email address.
$all = $this->connection_options->get_all();
$user_details = $all[ $this->mailer_slug ]['user_details'];
if ( ! empty( $user_details['email'] ) ) {
$all['mail']['from_email'] = $user_details['email'];
}
$this->connection_options->set( $all, false, true );
Debug::clear();
} catch ( IdentityProviderException $e ) {
$response = $e->getResponseBody();
Debug::set(
'Mailer: Outlook' . WP::EOL .
Helpers::format_error_message( $response['error_description'], $response['error'] )
);
// Reset Auth code. It's valid for 5 minutes anyway.
$this->update_auth_code( '' );
} catch ( Exception $e ) { // Catch any other general exceptions just in case.
Debug::set(
'Mailer: Outlook' . WP::EOL .
$e->getMessage()
);
// Reset Auth code. It's valid for 5 minutes anyway.
$this->update_auth_code( '' );
}
}
/**
* Refresh expired access token.
*
* @since 3.4.0
*
* @param AccessToken $access_token Expired access token.
*/
private function refresh_access_token( $access_token ) {
try {
$new_access_token = $this->client->getAccessToken(
'refresh_token',
[ 'refresh_token' => $access_token->getRefreshToken() ]
);
$this->update_access_token( $new_access_token->jsonSerialize() );
$this->update_refresh_token( $new_access_token->getRefreshToken() );
$this->update_user_details( $new_access_token );
} catch ( IdentityProviderException $e ) {
$response = $e->getResponseBody();
Debug::set(
'Mailer: Outlook' . WP::EOL .
Helpers::format_error_message( $response['error_description'], $response['error'] )
);
} catch ( Exception $e ) { // Catch any other general exception just in case.
Debug::set(
'Mailer: Outlook' . WP::EOL .
$e->getMessage()
);
}
}
/**
* Microsoft Apps doesn't support URLs with query params, and should be less than 256 characters.
* That's why we use /wp-admin/ page and will redirect user once again after processing MS request.
*
* @since 1.5.0
*
* @return string
*/
public static function get_plugin_auth_url() {
return apply_filters( 'wp_mail_smtp_outlook_get_plugin_auth_url', WP::admin_url() );
}
/**
* Check and process the auth code for this provider.
*
* @since 1.5.0
*
* @param string $code Auth code.
*
* @throws IdentityProviderException Emits exception on requests failure.
*/
public function process_auth( $code ) {
$this->update_auth_code( $code );
// Remove old errors.
Debug::clear();
// Retrieve the token and user details, save errors if any.
$this->get_client();
}
/**
* Get the auth URL used to proceed to Provider to request access to send emails.
*
* @since 1.5.0
*
* @return string
* @throws Exception Emitted when something went wrong.
*/
public function get_auth_url() {
$client = $this->get_client();
if (
! empty( $client ) &&
class_exists( '\WPMailSMTP\Vendor\League\OAuth2\Client\Provider\GenericProvider', false ) &&
$client instanceof GenericProvider
) {
$url_options = [
'state' => $this->get_state(),
'scope' => $this->get_scopes(),
];
$auth_url = $client->getAuthorizationUrl( $url_options );
return $auth_url;
}
return '#';
}
/**
* Get auth scopes.
*
* @since 2.8.0
*
* @return array
*/
protected function get_scopes() {
/**
* Filters auth scopes.
*
* @since 2.8.0
*
* @param array $scopes Auth scopes.
*/
return apply_filters( 'wp_mail_smtp_pro_providers_outlook_auth_get_scopes', self::SCOPES );
}
/**
* Get and update user-related details (display name and email).
*
* @since 1.5.0
*
* @param AccessTokenInterface $access_token The outlook action token object.
*
* @throws IdentityProviderException Emits exception when error occurs.
*/
protected function update_user_details( $access_token ) {
$connection_options = $this->connection->get_options();
if ( empty( $access_token ) ) {
$access_token = new AccessToken( (array) $connection_options->get( $this->mailer_slug, 'access_token' ) );
}
// Default values.
$user = [
'display_name' => '',
'email' => '',
];
try {
$resource_owner = $this->get_client()->getResourceOwner( $access_token );
$resource_data = $resource_owner->toArray();
$user = [
'display_name' => $resource_data['displayName'],
'email' => $resource_data['userPrincipalName'],
];
} catch ( IdentityProviderException $e ) {
$response = $e->getResponseBody();
Debug::set(
'Mailer: Outlook (requesting user details)' . WP::EOL .
Helpers::format_error_message( $response['error_description'], $response['error'] )
);
// Reset Auth code. It's valid for 5 minutes anyway.
$this->update_auth_code( '' );
} catch ( Exception $e ) {
// Catch general any other exception just in case.
Debug::set(
'Mailer: Outlook (requesting user details)' . WP::EOL .
$e->getMessage()
);
}
$all = $connection_options->get_all();
// To save in DB.
$all[ $this->mailer_slug ]['user_details'] = $user;
// To save in currently retrieved options array.
$this->options['user_details'] = $user;
// NOTE: These options need to be saved by overwriting all options, because WP automatic updates can cause an issue: GH #575!
$connection_options->set( $all, false, true );
}
/**
* Get the state value that should be used for the "state" parameter in the OAuth authorization request.
*
* @since 3.7.0
*
* @return string
*/
protected function get_state() {
/*
* Include "wp-mail-smtp" to the state to identify Outlook OAuth requests, since Outlook mailer doesn't use
* a regular auth route because of Microsoft API limitations.
*/
return 'wp-mail-smtp-' . parent::get_state();
}
/**
* Get user information associated with the current OAuth connection.
*
* @since 4.3.0
*
* @return array
*/
public function get_user_info() {
return $this->connection_options->get( $this->mailer_slug, 'user_details' );
}
}