-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
git-svn-id: https://plugins.svn.wordpress.org/post-smtp/trunk@1746635 b8457f37-d9ea-0310-8a92-e5e31aec5664
- Loading branch information
yehudah
committed
Oct 15, 2017
1 parent
2065680
commit edff34b
Showing
400 changed files
with
243,742 additions
and
0 deletions.
There are no files selected for viewing
168 changes: 168 additions & 0 deletions
168
Postman/Postman-Auth/PostmanAbstractAuthenticationManager.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
<?php | ||
if (! class_exists ( "PostmanAbstractAuthenticationManager" )) { | ||
|
||
require_once 'PostmanAuthenticationManager.php'; | ||
|
||
/** | ||
*/ | ||
abstract class PostmanAbstractAuthenticationManager implements PostmanAuthenticationManager { | ||
|
||
// constants | ||
const APPROVAL_PROMPT = 'force'; | ||
const ACCESS_TYPE = 'offline'; | ||
const ACCESS_TOKEN = 'access_token'; | ||
const REFRESH_TOKEN = 'refresh_token'; | ||
const EXPIRES = 'expires_in'; | ||
|
||
// the oauth authorization options | ||
private $clientId; | ||
private $clientSecret; | ||
private $authorizationToken; | ||
private $callbackUri; | ||
private $logger; | ||
|
||
/** | ||
* Constructor | ||
*/ | ||
public function __construct(PostmanLogger $logger, $clientId, $clientSecret, PostmanOAuthToken $authorizationToken, $callbackUri) { | ||
assert ( ! empty ( $clientId ) ); | ||
assert ( ! empty ( $clientSecret ) ); | ||
assert ( ! empty ( $authorizationToken ) ); | ||
assert ( ! empty ( $callbackUri ) ); | ||
$this->logger = $logger; | ||
$this->clientId = $clientId; | ||
$this->clientSecret = $clientSecret; | ||
$this->authorizationToken = $authorizationToken; | ||
$this->callbackUri = $callbackUri; | ||
} | ||
protected function getLogger() { | ||
return $this->logger; | ||
} | ||
protected function getClientId() { | ||
return $this->clientId; | ||
} | ||
protected function getClientSecret() { | ||
return $this->clientSecret; | ||
} | ||
protected function getAuthorizationToken() { | ||
return $this->authorizationToken; | ||
} | ||
|
||
/** | ||
* Create a state token to prevent request forgery. | ||
* Store it in the session for later validation. | ||
*/ | ||
public function generateRequestTransactionId() { | ||
return $state = md5 ( rand () ); | ||
} | ||
|
||
/** | ||
*/ | ||
public function isAccessTokenExpired() { | ||
$expireTime = ($this->authorizationToken->getExpiryTime () - self::FORCE_REFRESH_X_SECONDS_BEFORE_EXPIRE); | ||
$tokenHasExpired = time () > $expireTime; | ||
$this->logger->debug ( 'Access Token Expiry Time is ' . $expireTime . ', expires_in=' . ($expireTime - time ()) . ', expired=' . ($tokenHasExpired ? 'yes' : 'no') ); | ||
return $tokenHasExpired; | ||
} | ||
|
||
/** | ||
* Decoded the received token | ||
* This code is identical for Google and Hotmail | ||
* | ||
* @param unknown $response | ||
* @throws Exception | ||
*/ | ||
protected function processResponse($response) { | ||
$authToken = json_decode ( stripslashes ( $response ) ); | ||
if ($authToken === NULL) { | ||
$this->getLogger ()->error ( $response ); | ||
throw new Exception ( $response ); | ||
} else if (isset ( $authToken->{'error'} )) { | ||
if (isset ( $authToken->{'error_description'} )) { | ||
$this->getLogger ()->error ( $authToken->{'error'} . ' processing response: ' . $authToken->{'error_description'} ); | ||
throw new Exception ( $authToken->{'error_description'} . '(' . $authToken->{'error'} . ')' ); | ||
} else { | ||
// Yahoo doesn't give descriptions | ||
$this->getLogger ()->error ( $authToken->{'error'} . ' processing response' ); | ||
throw new Exception ( $authToken->{'error'} ); | ||
} | ||
} else { | ||
$this->getLogger ()->trace ( 'Processing response:' ); | ||
$this->getLogger ()->trace ( $response ); | ||
$this->decodeReceivedAuthorizationToken ( $authToken ); | ||
} | ||
} | ||
|
||
/** | ||
* Parses the authorization token and extracts the expiry time, accessToken, | ||
* and if this is a first-time authorization, a refresh token. | ||
* | ||
* This code is identical for Google and Hotmail | ||
* | ||
* @param unknown $client | ||
*/ | ||
protected function decodeReceivedAuthorizationToken($newtoken) { | ||
assert ( ! empty ( $newtoken ) ); | ||
assert ( ! empty ( $newtoken->{self::EXPIRES} ) ); | ||
assert ( ! empty ( $newtoken->{self::ACCESS_TOKEN} ) ); | ||
|
||
// update expiry time | ||
if (empty ( $newtoken->{self::EXPIRES} )) { | ||
throw new Exception ( '[expires_in] value is missing from the authentication token' ); | ||
} | ||
$newExpiryTime = time () + $newtoken->{self::EXPIRES}; | ||
$this->getAuthorizationToken ()->setExpiryTime ( $newExpiryTime ); | ||
$this->getLogger ()->debug ( 'Updating Access Token Expiry Time ' ); | ||
|
||
// update acccess token | ||
if (empty ( $newtoken->{self::ACCESS_TOKEN} )) { | ||
throw new Exception ( '[access_token] value is missing from the authentication token' ); | ||
} | ||
$newAccessToken = $newtoken->{self::ACCESS_TOKEN}; | ||
$this->getAuthorizationToken ()->setAccessToken ( $newAccessToken ); | ||
$this->getLogger ()->debug ( 'Updating Access Token' ); | ||
|
||
// update refresh token, if there is one | ||
if (isset ( $newtoken->{self::REFRESH_TOKEN} )) { | ||
$newRefreshToken = $newtoken->{self::REFRESH_TOKEN}; | ||
$this->getAuthorizationToken ()->setRefreshToken ( $newRefreshToken ); | ||
$this->getLogger ()->debug ( 'Updating Refresh Token ' ); | ||
} | ||
} | ||
|
||
/** | ||
* Given an OAuth provider-specific URL and redirectUri, | ||
* issue an HttpRequest to refresh the access token | ||
* | ||
* This code is identical for Google and Hotmail | ||
*/ | ||
public function refreshToken() { | ||
$this->getLogger ()->debug ( 'Refreshing Token' ); | ||
$refreshUrl = $this->getTokenUrl (); | ||
$callbackUrl = $this->getCallbackUri (); | ||
assert ( ! empty ( $refreshUrl ) ); | ||
assert ( ! empty ( $callbackUrl ) ); | ||
// the format of the URL is | ||
// client_id=CLIENT_ID&client_secret=CLIENT_SECRET&redirect_uri=REDIRECT_URI&grant_type=refresh_token&refresh_token=REFRESH_TOKEN | ||
$postvals = array ( | ||
'client_id' => $this->getClientId (), | ||
'client_secret' => $this->getClientSecret (), | ||
'redirect_uri' => $callbackUrl, | ||
'grant_type' => 'refresh_token', | ||
'refresh_token' => $this->getAuthorizationToken ()->getRefreshToken () | ||
); | ||
// example request string | ||
// client_id=0000000603DB0F&redirect_uri=http%3A%2F%2Fwww.contoso.com%2Fcallback.php&client_secret=LWILlT555GicSrIATma5qgyBXebRI&refresh_token=*LA9...//refresh token string shortened for example//...xRoX&grant_type=refresh_token | ||
$response = PostmanUtils::remotePostGetBodyOnly ( $refreshUrl, $postvals ); | ||
$this->processResponse ( $response ); | ||
} | ||
/** | ||
* (non-PHPdoc) | ||
* | ||
* @see PostmanAuthenticationManager::getCallbackUri() | ||
*/ | ||
public function getCallbackUri() { | ||
return $this->callbackUri; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
if (! interface_exists ( "PostmanAuthenticationManager" )) { | ||
interface PostmanAuthenticationManager { | ||
const POSTMAN_AUTHORIZATION_IN_PROGRESS = 'request_oauth_permission'; | ||
const FORCE_REFRESH_X_SECONDS_BEFORE_EXPIRE = 60; | ||
public function isAccessTokenExpired(); | ||
public function refreshToken(); | ||
public function generateRequestTransactionId(); | ||
public function requestVerificationCode($transactionId); | ||
public function processAuthorizationGrantCode($transactionId); | ||
public function getAuthorizationUrl(); | ||
public function getTokenUrl(); | ||
public function getCallbackUri(); | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
Postman/Postman-Auth/PostmanAuthenticationManagerFactory.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
if (! class_exists ( "PostmanAuthenticationManagerFactory" )) { | ||
|
||
require_once 'PostmanGoogleAuthenticationManager.php'; | ||
require_once 'PostmanMicrosoftAuthenticationManager.php'; | ||
require_once 'PostmanNonOAuthAuthenticationManager.php'; | ||
require_once 'PostmanYahooAuthenticationManager.php'; | ||
|
||
// | ||
class PostmanAuthenticationManagerFactory { | ||
private $logger; | ||
|
||
// singleton instance | ||
public static function getInstance() { | ||
static $inst = null; | ||
if ($inst === null) { | ||
$inst = new PostmanAuthenticationManagerFactory (); | ||
} | ||
return $inst; | ||
} | ||
private function __construct() { | ||
$this->logger = new PostmanLogger ( get_class ( $this ) ); | ||
} | ||
public function createAuthenticationManager() { | ||
$transport = PostmanTransportRegistry::getInstance ()->getSelectedTransport (); | ||
return $this->createManager ( $transport ); | ||
} | ||
private function createManager(PostmanZendModuleTransport $transport) { | ||
$options = PostmanOptions::getInstance (); | ||
$authorizationToken = PostmanOAuthToken::getInstance (); | ||
$authenticationType = $options->getAuthenticationType (); | ||
$hostname = $options->getHostname (); | ||
$clientId = $options->getClientId (); | ||
$clientSecret = $options->getClientSecret (); | ||
$senderEmail = $options->getMessageSenderEmail (); | ||
$scribe = $transport->getScribe (); | ||
$redirectUrl = $scribe->getCallbackUrl (); | ||
if ($transport->isOAuthUsed ( $options->getAuthenticationType () )) { | ||
if ($transport->isServiceProviderGoogle ( $hostname )) { | ||
$authenticationManager = new PostmanGoogleAuthenticationManager ( $clientId, $clientSecret, $authorizationToken, $redirectUrl, $senderEmail ); | ||
} else if ($transport->isServiceProviderMicrosoft ( $hostname )) { | ||
$authenticationManager = new PostmanMicrosoftAuthenticationManager ( $clientId, $clientSecret, $authorizationToken, $redirectUrl ); | ||
} else if ($transport->isServiceProviderYahoo ( $hostname )) { | ||
$authenticationManager = new PostmanYahooAuthenticationManager ( $clientId, $clientSecret, $authorizationToken, $redirectUrl ); | ||
} else { | ||
assert ( false ); | ||
} | ||
} else { | ||
$authenticationManager = new PostmanNonOAuthAuthenticationManager (); | ||
} | ||
$this->logger->debug ( 'Created ' . get_class ( $authenticationManager ) ); | ||
return $authenticationManager; | ||
} | ||
} | ||
} |
119 changes: 119 additions & 0 deletions
119
Postman/Postman-Auth/PostmanGoogleAuthenticationManager.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
<?php | ||
if (! class_exists ( "PostmanGoogleAuthenticationManager" )) { | ||
|
||
require_once 'PostmanAbstractAuthenticationManager.php'; | ||
require_once 'PostmanStateIdMissingException.php'; | ||
|
||
/** | ||
* https://developers.google.com/accounts/docs/OAuth2WebServer | ||
* https://developers.google.com/gmail/xoauth2_protocol | ||
* https://developers.google.com/gmail/api/auth/scopes | ||
*/ | ||
class PostmanGoogleAuthenticationManager extends PostmanAbstractAuthenticationManager implements PostmanAuthenticationManager { | ||
|
||
// This endpoint is the target of the initial request. It handles active session lookup, authenticating the user, and user consent. | ||
const GOOGLE_ENDPOINT = 'https://accounts.google.com/o/oauth2/auth'; | ||
const GOOGLE_REFRESH = 'https://www.googleapis.com/oauth2/v3/token'; | ||
|
||
// this scope doesn't work | ||
// Create, read, update, and delete drafts. Send messages and drafts. | ||
const SCOPE_COMPOSE = 'https://www.googleapis.com/auth/gmail.compose'; | ||
|
||
// this scope doesn't work | ||
// All read/write operations except immediate, permanent deletion of threads and messages, bypassing Trash. | ||
const SCOPE_MODIFY = 'https://www.googleapis.com/auth/gmail.modify'; | ||
|
||
// Full access to the account, including permanent deletion of threads and messages. This scope should only be requested if your application needs to immediately and permanently delete threads and messages, bypassing Trash; all other actions can be performed with less permissive scopes. | ||
const SCOPE_FULL_ACCESS = 'https://mail.google.com/'; | ||
const AUTH_TEMP_ID = 'GOOGLE_OAUTH_TEMP_ID'; | ||
const VENDOR_NAME = 'google'; | ||
// the sender email address | ||
private $senderEmail; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* Get a Client ID from https://account.live.com/developers/applications/index | ||
*/ | ||
public function __construct($clientId, $clientSecret, PostmanOAuthToken $authorizationToken, $callbackUri, $senderEmail) { | ||
assert ( ! empty ( $clientId ) ); | ||
assert ( ! empty ( $clientSecret ) ); | ||
assert ( ! empty ( $authorizationToken ) ); | ||
assert ( ! empty ( $senderEmail ) ); | ||
$logger = new PostmanLogger ( get_class ( $this ) ); | ||
$this->senderEmail = $senderEmail; | ||
parent::__construct ( $logger, $clientId, $clientSecret, $authorizationToken, $callbackUri ); | ||
} | ||
|
||
/** | ||
* The authorization sequence begins when your application redirects a browser to a Google URL; | ||
* the URL includes query parameters that indicate the type of access being requested. | ||
* | ||
* As in other scenarios, Google handles user authentication, session selection, and user consent. | ||
* The result is an authorization code, which Google returns to your application in a query string. | ||
* | ||
* (non-PHPdoc) | ||
* | ||
* @see PostmanAuthenticationManager::requestVerificationCode() | ||
*/ | ||
public function requestVerificationCode($transactionId) { | ||
$params = array ( | ||
'response_type' => 'code', | ||
'redirect_uri' => urlencode ( $this->getCallbackUri () ), | ||
'client_id' => $this->getClientId (), | ||
'scope' => urlencode ( self::SCOPE_FULL_ACCESS ), | ||
'access_type' => 'offline', | ||
'approval_prompt' => 'force', | ||
'state' => $transactionId, | ||
'login_hint' => $this->senderEmail | ||
); | ||
|
||
$authUrl = $this->getAuthorizationUrl () . '?' . build_query ( $params ); | ||
|
||
$this->getLogger ()->debug ( 'Requesting verification code from Google' ); | ||
PostmanUtils::redirect ( $authUrl ); | ||
} | ||
|
||
/** | ||
* After receiving the authorization code, your application can exchange the code | ||
* (along with a client ID and client secret) for an access token and, in some cases, | ||
* a refresh token. | ||
* | ||
* This code is identical for Google and Hotmail | ||
* | ||
* @see PostmanAuthenticationManager::processAuthorizationGrantCode() | ||
*/ | ||
public function processAuthorizationGrantCode($transactionId) { | ||
if (isset ( $_GET ['code'] )) { | ||
$this->getLogger ()->debug ( 'Found authorization code in request header' ); | ||
$code = filter_input( INPUT_GET, 'code', FILTER_SANITIZE_STRING ); | ||
if (isset ( $_GET ['state'] ) && $_GET ['state'] == $transactionId) { | ||
$this->getLogger ()->debug ( 'Found valid state in request header' ); | ||
} else { | ||
$this->getLogger ()->error ( 'The grant code from Google had no accompanying state and may be a forgery' ); | ||
throw new PostmanStateIdMissingException (); | ||
} | ||
$postvals = array ( | ||
'client_id' => $this->getClientId (), | ||
'client_secret' => $this->getClientSecret (), | ||
'grant_type' => 'authorization_code', | ||
'redirect_uri' => $this->getCallbackUri (), | ||
'code' => $code | ||
); | ||
$response = PostmanUtils::remotePostGetBodyOnly ( $this->getTokenUrl (), $postvals ); | ||
$this->processResponse ( $response ); | ||
$this->getAuthorizationToken ()->setVendorName ( self::VENDOR_NAME ); | ||
return true; | ||
} else { | ||
$this->getLogger ()->debug ( 'Expected code in the request header but found none - user probably denied request' ); | ||
return false; | ||
} | ||
} | ||
public function getAuthorizationUrl() { | ||
return self::GOOGLE_ENDPOINT; | ||
} | ||
public function getTokenUrl() { | ||
return self::GOOGLE_REFRESH; | ||
} | ||
} | ||
} |
Oops, something went wrong.