diff --git a/QuickBooks/Driver.php b/QuickBooks/Driver.php index 3c60c24b..bc32a6be 100755 --- a/QuickBooks/Driver.php +++ b/QuickBooks/Driver.php @@ -1374,9 +1374,9 @@ public function initialized() */ abstract protected function _initialized(); - public function oauthLoad($key, $app_username, $app_tenant) + public function oauthLoadV1($key, $app_tenant) { - if ($data = $this->_oauthLoad($app_username, $app_tenant)) + if ($data = $this->_oauthLoadV1($app_tenant)) { if (strlen($data['oauth_access_token']) > 0) { @@ -1392,49 +1392,99 @@ public function oauthLoad($key, $app_username, $app_tenant) return false; } - abstract protected function _oauthLoad($app_username, $app_tenant); - - public function oauthAccessWrite($key, $request_token, $token, $token_secret, $realm, $flavor) + abstract protected function _oauthLoadV1($app_tenant); + + public function oauthLoadV2($key, $app_tenant) + { + if ($data = $this->_oauthLoadV2($app_tenant)) + { + if (strlen($data['oauth_access_token']) > 0) + { + $AES = QuickBooks_Encryption_Factory::create('aes'); + + $data['oauth_access_token'] = $AES->decrypt($key, $data['oauth_access_token']); + $data['oauth_refresh_token'] = $AES->decrypt($key, $data['oauth_refresh_token']); + } + + return $data; + } + + return false; + } + + abstract protected function _oauthLoadV2($app_tenant); + + public function oauthAccessWriteV1($key, $request_token, $token, $token_secret, $realm, $flavor) { $AES = QuickBooks_Encryption_Factory::create('aes'); - + $encrypted_token = $AES->encrypt($key, $token); $encrypted_token_secret = $AES->encrypt($key, $token_secret); - - return $this->_oauthAccessWrite($request_token, $encrypted_token, $encrypted_token_secret, $realm, $flavor); + + return $this->_oauthAccessWriteV1($request_token, $encrypted_token, $encrypted_token_secret, $realm, $flavor); } - - abstract protected function _oauthAccessWrite($request_token, $token, $token_secret, $realm, $flavor); - - + + abstract protected function _oauthAccessWriteV1($request_token, $token, $token_secret, $realm, $flavor); + + public function oauthAccessWriteV2($encryption_key, $state, $access_token, $refresh_token, $access_expiry, $refresh_expiry, $qb_realm) + { + $AES = QuickBooks_Encryption_Factory::create('aes'); + + $encrypted_access_token = $AES->encrypt($encryption_key, $access_token); + $encrypted_refresh_token = $AES->encrypt($encryption_key, $refresh_token); + + return $this->_oauthAccessWriteV2($state, $encrypted_access_token, $encrypted_refresh_token, $access_expiry, $refresh_expiry, $qb_realm); + } + + abstract protected function _oauthAccessWriteV2($state, $access_token, $refresh_token, $access_expiry, $refresh_expiry, $qb_realm); + + + public function oauthAccessRefreshV2($encryption_key, $oauthv2_id, $access_token, $refresh_token, $access_expiry, $refresh_expiry) + { + $AES = QuickBooks_Encryption_Factory::create('aes'); + + $encrypted_access_token = $AES->encrypt($encryption_key, $access_token); + $encrypted_refresh_token = $AES->encrypt($encryption_key, $refresh_token); + + return $this->_oauthAccessRefreshV2($oauthv2_id, $encrypted_access_token, $encrypted_refresh_token, $access_expiry, $refresh_expiry); + } + + abstract protected function _oauthAccessRefreshV2($oauthv2_id, $access_token, $refresh_token, $access_expiry, $refresh_expiry); + public function oauthAccessDelete($app_username, $app_tenant) { return $this->_oauthAccessDelete($app_username, $app_tenant); } abstract protected function _oauthAccessDelete($app_username, $app_tenant); - - - public function oauthRequestWrite($app_username, $app_tenant, $token, $token_secret) + + public function oauthRequestWriteV2($app_tenant, $state) { - /* - $AES = QuickBooks_Encryption_Factory::create('aes'); - - $token = $AES->encrypt($key, $token); - $token_secret = $AES->encrypt($key, $token_secret); - */ - - return $this->_oauthRequestWrite($app_username, $app_tenant, $token, $token_secret); + return $this->_oauthRequestWriteV2($app_tenant, $state); + } + + abstract protected function _oauthRequestWriteV2($app_tenant, $state); + + public function oauthRequestWriteV1($app_tenant, $token, $token_secret) + { + return $this->_oauthRequestWriteV1($app_tenant, $token, $token_secret); } - abstract protected function _oauthRequestWrite($app_username, $app_tenant, $token, $token_secret); - - public function oauthRequestResolve($token) + abstract protected function _oauthRequestWriteV1($app_tenant, $token, $token_secret); + + public function oauthRequestResolveV1($token) + { + return $this->_oauthRequestResolveV1($token); + } + + abstract protected function _oauthRequestResolveV1($token); + + public function oauthRequestResolveV2($state) { - return $this->_oauthRequestResolve($token); + return $this->_oauthRequestResolveV2($state); } - abstract protected function _oauthRequestResolve($token); + abstract protected function _oauthRequestResolveV2($state); /** * Log a message to the QuickBooks log diff --git a/QuickBooks/Driver/Sql.php b/QuickBooks/Driver/Sql.php index 959e7fb6..8ed4b869 100755 --- a/QuickBooks/Driver/Sql.php +++ b/QuickBooks/Driver/Sql.php @@ -201,7 +201,13 @@ * Default table name for OAuth stuff * @var string */ -define('QUICKBOOKS_DRIVER_SQL_OAUTHTABLE', 'oauth'); +define('QUICKBOOKS_DRIVER_SQL_OAUTHV1TABLE', 'oauthv1'); + +/** + * Default table name for OAuth stuff + * @var string + */ +define('QUICKBOOKS_DRIVER_SQL_OAUTHV2TABLE', 'oauthv2'); /** * @@ -2117,7 +2123,7 @@ protected function _truncate($table, $max_history) return; } - protected function _oauthRequestResolve($request_token) + protected function _oauthRequestResolveV1($request_token) { $errnum = 0; $errmsg = ''; @@ -2126,12 +2132,29 @@ protected function _oauthRequestResolve($request_token) SELECT * FROM - " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHTABLE) . " + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV1TABLE) . " WHERE oauth_request_token = '%s' ", $errnum, $errmsg, null, null, array( $request_token ))); } - protected function _oauthLoad($app_username, $app_tenant) + protected function _oauthRequestResolveV2($state) + { + $errnum = 0; + $errmsg = ''; + + return $this->fetch($this->query(" + SELECT + * + FROM + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV2TABLE) . " + WHERE + oauth_state = '%s' AND + request_datetime >= '%s' + ", $errnum, $errmsg, null, null, array( $state, date('Y-m-d H:i:s', strtotime('-30 minutes')) ))); + } + + + protected function _oauthLoadV1($app_tenant) { $errnum = 0; $errmsg = ''; @@ -2140,17 +2163,17 @@ protected function _oauthLoad($app_username, $app_tenant) SELECT * FROM - " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHTABLE) . " + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV1TABLE) . " WHERE - app_username = '%s' AND app_tenant = '%s' ", $errnum, $errmsg, null, null, array( $app_username, $app_tenant )))) + app_tenant = '%s' ", $errnum, $errmsg, null, null, array( $app_tenant )))) { $this->query(" UPDATE - " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHTABLE) . " + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV1TABLE) . " SET touch_datetime = '%s' WHERE - app_username = '%s' AND app_tenant = '%s' ", $errnum, $errmsg, null, null, array( date('Y-m-d H:i:s'), $app_username, $app_tenant )); + app_tenant = '%s' ", $errnum, $errmsg, null, null, array( date('Y-m-d H:i:s'), $app_tenant )); return $arr; } @@ -2158,33 +2181,102 @@ protected function _oauthLoad($app_username, $app_tenant) return false; } - protected function _oauthRequestWrite($app_username, $app_tenant, $token, $token_secret) + /** + * Load OAuth v2 tokens from MySQL + * + * @param string $app_tenant The tenant to load tokens for + * + * @return array|bool + */ + protected function _oauthLoadV2($app_tenant) + { + $errnum = 0; + $errmsg = ''; + + if ($arr = $this->fetch($this->query(" + SELECT + * + FROM + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV2TABLE) . " + WHERE + app_tenant = '%s' ", $errnum, $errmsg, null, null, array( $app_tenant )))) + { + $this->query(" + UPDATE + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV2TABLE) . " + SET + touch_datetime = '%s' + WHERE + app_tenant = '%s' ", $errnum, $errmsg, null, null, array( date('Y-m-d H:i:s'), $app_tenant )); + + return $arr; + } + + return false; + } + + protected function _oauthRequestWriteV2($app_tenant, $state) + { + $errnum = 0; + $errmsg = ''; + + // Check if it exists or not first + if ($arr = $this->_oauthLoadV2($app_tenant)) + { + // Exists... UPDATE! + return $this->query(" + UPDATE + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV2TABLE) . " + SET + oauth_state = '%s', + request_datetime = '%s' + WHERE + app_tenant = '%s' ", $errnum, $errmsg, null, null, array( $state, date('Y-m-d H:i:s'), $app_tenant )); + } + else + { + // Insert it + return $this->query(" + INSERT INTO + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV2TABLE) . " + ( + app_tenant, + oauth_state, + request_datetime + ) VALUES ( + '%s', + '%s', + '%s' + )", $errnum, $errmsg, null, null, array( $app_tenant, $state, date('Y-m-d H:i:s') )); + } + } + + protected function _oauthRequestWriteV1($app_tenant, $token, $token_secret) { $errnum = 0; $errmsg = ''; // Check if it exists or not first - if ($arr = $this->_oauthLoad($app_username, $app_tenant)) + if ($arr = $this->_oauthLoadV1($app_tenant)) { // Exists... UPDATE! return $this->query(" UPDATE - " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHTABLE) . " + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV1TABLE) . " SET oauth_request_token = '%s', oauth_request_token_secret = '%s', request_datetime = '%s' WHERE - app_username = '%s' AND app_tenant = '%s' ", $errnum, $errmsg, null, null, array( $token, $token_secret, date('Y-m-d H:i:s'), $app_username, $app_tenant )); + app_tenant = '%s' ", $errnum, $errmsg, null, null, array( $token, $token_secret, date('Y-m-d H:i:s'), $app_tenant )); } else { // Insert it return $this->query(" INSERT INTO - " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHTABLE) . " + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV1TABLE) . " ( - app_username, app_tenant, oauth_request_token, oauth_request_token_secret, @@ -2193,19 +2285,18 @@ protected function _oauthRequestWrite($app_username, $app_tenant, $token, $token '%s', '%s', '%s', - '%s', '%s' - )", $errnum, $errmsg, null, null, array( $app_username, $app_tenant, $token, $token_secret, date('Y-m-d H:i:s') )); + )", $errnum, $errmsg, null, null, array( $app_tenant, $token, $token_secret, date('Y-m-d H:i:s') )); } } - protected function _oauthAccessWrite($request_token, $token, $token_secret, $realm, $flavor) + protected function _oauthAccessWriteV1($request_token, $token, $token_secret, $realm, $flavor) { $errnum = 0; $errmsg = ''; // Check if it exists or not first - if ($arr = $this->_oauthRequestResolve($request_token)) + if ($arr = $this->_oauthRequestResolveV1($request_token)) { $vars = array( $token, $token_secret, date('Y-m-d H:i:s') ); @@ -2228,7 +2319,7 @@ protected function _oauthAccessWrite($request_token, $token, $token_secret, $rea // Exists... UPDATE! return $this->query(" UPDATE - " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHTABLE) . " + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV1TABLE) . " SET oauth_access_token = '%s', oauth_access_token_secret = '%s', @@ -2241,6 +2332,85 @@ protected function _oauthAccessWrite($request_token, $token, $token_secret, $rea return false; } + protected function _oauthAccessRefreshV2($oauthv2_id, $encrypted_access_token, $encrypted_refresh_token, $access_expiry, $refresh_expiry) + { + $errnum = 0; + $errmsg = ''; + + $vars = array( + $encrypted_access_token, + $encrypted_refresh_token, + date('Y-m-d H:i:s', strtotime($access_expiry)), + date('Y-m-d H:i:s', strtotime($refresh_expiry)), + date('Y-m-d H:i:s'), + date('Y-m-d H:i:s') + ); + + $vars[] = (int) $oauthv2_id; + + // Exists... UPDATE! + return $this->query(" + UPDATE + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV2TABLE) . " + SET + oauth_access_token = '%s', + oauth_refresh_token = '%s', + oauth_access_expiry = '%s', + oauth_refresh_expiry = '%s', + last_access_datetime = '%s', + last_refresh_datetime = '%s' + WHERE + quickbooks_oauthv2_id = %d ", $errnum, $errmsg, null, null, $vars); + } + + protected function _oauthAccessWriteV2($state, $encrypted_access_token, $encrypted_refresh_token, $access_expiry, $refresh_expiry, $qb_realm) + { + $errnum = 0; + $errmsg = ''; + + // Check if it exists or not first + if ($arr = $this->_oauthRequestResolveV2($state)) + { + $vars = array( + $encrypted_access_token, + $encrypted_refresh_token, + date('Y-m-d H:i:s', strtotime($access_expiry)), + date('Y-m-d H:i:s', strtotime($refresh_expiry)), + date('Y-m-d H:i:s'), + date('Y-m-d H:i:s'), + date('Y-m-d H:i:s') + ); + + $more = ""; + + if ($qb_realm) + { + $more .= ", qb_realm = '%s' "; + $vars[] = $qb_realm; + } + + $vars[] = $state; + + // Exists... UPDATE! + return $this->query(" + UPDATE + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV2TABLE) . " + SET + oauth_access_token = '%s', + oauth_refresh_token = '%s', + oauth_access_expiry = '%s', + oauth_refresh_expiry = '%s', + access_datetime = '%s', + last_access_datetime = '%s', + last_refresh_datetime = '%s' + " . $more . " + WHERE + oauth_state = '%s' ", $errnum, $errmsg, null, null, $vars); + } + + return false; + } + protected function _oauthAccessDelete($app_username, $app_tenant) { $errnum = 0; @@ -2249,7 +2419,7 @@ protected function _oauthAccessDelete($app_username, $app_tenant) // Exists... DELETE! $this->query(" DELETE FROM - " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHTABLE) . " + " . $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV1TABLE) . " WHERE app_username = '%s' AND app_tenant = '%s' ", $errnum, $errmsg, null, null, array( $app_username, $app_tenant )); @@ -3026,9 +3196,10 @@ protected function _initialize($init_options = array()) $arr_sql = array_merge($arr_sql, $this->_generateCreateTable($table, $def, $primary, $keys, $uniques)); */ - $table = $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHTABLE); + // OAuth1 + $table = $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV1TABLE); $def = array( - 'quickbooks_oauth_id' => array( QUICKBOOKS_DRIVER_SQL_SERIAL ), + 'quickbooks_oauthv1_id' => array( QUICKBOOKS_DRIVER_SQL_SERIAL ), 'app_username' => array( QUICKBOOKS_DRIVER_SQL_VARCHAR, 255 ), 'app_tenant' => array( QUICKBOOKS_DRIVER_SQL_VARCHAR, 255 ), 'oauth_request_token' => array( QUICKBOOKS_DRIVER_SQL_VARCHAR, 255, 'null' ), @@ -3042,12 +3213,35 @@ protected function _initialize($init_options = array()) 'access_datetime' => array( QUICKBOOKS_DRIVER_SQL_DATETIME, null, 'null' ), 'touch_datetime' => array( QUICKBOOKS_DRIVER_SQL_DATETIME, null, 'null' ), ); - $primary = 'quickbooks_oauth_id'; + $primary = 'quickbooks_oauthv1_id'; $keys = array( ); $uniques = array( array( 'app_username', 'app_tenant' ) ); $arr_sql = array_merge($arr_sql, $this->_generateCreateTable($table, $def, $primary, $keys, $uniques)); + // OAuth2 + $table = $this->_mapTableName(QUICKBOOKS_DRIVER_SQL_OAUTHV2TABLE); + $def = array( + 'quickbooks_oauthv2_id' => array( QUICKBOOKS_DRIVER_SQL_SERIAL ), + 'app_tenant' => array( QUICKBOOKS_DRIVER_SQL_VARCHAR, 255 ), + 'oauth_state' => array( QUICKBOOKS_DRIVER_SQL_VARCHAR, 255 ), + 'oauth_access_token' => array( QUICKBOOKS_DRIVER_SQL_TEXT, null ), + 'oauth_refresh_token' => array( QUICKBOOKS_DRIVER_SQL_TEXT, null ), + 'oauth_access_expiry' => array( QUICKBOOKS_DRIVER_SQL_DATETIME, null, 'null' ), + 'oauth_refresh_expiry' => array( QUICKBOOKS_DRIVER_SQL_DATETIME, null, 'null' ), + 'qb_realm' => array( QUICKBOOKS_DRIVER_SQL_VARCHAR, 32, 'null' ), + 'request_datetime' => array( QUICKBOOKS_DRIVER_SQL_DATETIME ), + 'access_datetime' => array( QUICKBOOKS_DRIVER_SQL_DATETIME, null, 'null' ), + 'last_access_datetime' => array( QUICKBOOKS_DRIVER_SQL_DATETIME, null, 'null' ), + 'last_refresh_datetime' => array( QUICKBOOKS_DRIVER_SQL_DATETIME, null, 'null' ), + 'touch_datetime' => array( QUICKBOOKS_DRIVER_SQL_DATETIME, null, 'null' ), + ); + $primary = 'quickbooks_oauthv2_id'; + $keys = array( ); + $uniques = array( array( 'app_tenant' ) ); + + $arr_sql = array_merge($arr_sql, $this->_generateCreateTable($table, $def, $primary, $keys, $uniques)); + //header('Content-Type: text/plain'); //print_r($arr_sql); //exit; diff --git a/QuickBooks/IPP.php b/QuickBooks/IPP.php index 73bd0c9e..6181c430 100755 --- a/QuickBooks/IPP.php +++ b/QuickBooks/IPP.php @@ -88,7 +88,8 @@ class QuickBooks_IPP const API_GETENTITLEMENTVALUESANDUSERROLE = 'API_GetEntitlementValuesAndUserRole'; const AUTHMODE_FEDERATED = 'federated'; - const AUTHMODE_OAUTH = 'oauth'; + const AUTHMODE_OAUTHV1 = 'oauthv1'; + const AUTHMODE_OAUTHV2 = 'oauthv2'; /** * @@ -252,7 +253,7 @@ class QuickBooks_IPP */ public $boundary = 'Asrf456BGe4hacebdf13572468'; - public function __construct($dsn = null, $encryption_key = null, $config = array(), $log_level = QUICKBOOKS_LOG_NORMAL) + public function __construct($dsn, $encryption_key, $config = array(), $log_level = QUICKBOOKS_LOG_NORMAL) { // Are we in sandbox mode? $this->_sandbox = false; @@ -302,75 +303,6 @@ public function __construct($dsn = null, $encryption_key = null, $config = array // Encryption key (used for database storage) $this->_key = $encryption_key; - - // Default to QuickBooks desktop - //$this->flavor(QuickBooks_IPP_IDS::FLAVOR_DESKTOP); - } - - /** - * Authenticate to the IPP web service - * - * IMPORTANT NOTE: - * Intuit disallows this method within live applications! You can use it to - * test your application, but when you go live you'll need to instead use - * a SAML gateway for single-sign-on authentication. Take a look at the - * QuickBooks_IPP_Federator class for a working SAML gateway. - * - * @param string $username - * @param string $password - * @param string $token - * @return boolean - */ - public function authenticate($username, $password, $token) - { - $this->_username = $username; - $this->_password = $password; - $this->_token = $token; - - $url = 'https://workplace.intuit.com/db/main?act=API_Authenticate'; - $action = 'API_Authenticate'; - - $xml = ' - - ' . $username . ' - ' . $password . ' - ' . $token . ' - '; - - $Context = null; - $response = $this->_request($Context, QuickBooks_IPP::REQUEST_IPP, $url, $action, $xml); - - if (!$this->_hasErrors($response) and - $ticket = QuickBooks_XML::extractTagContents('ticket', $response)) - { - $this->_ticket = $ticket; - - $cookies = array( - 'scache', - 'ptest', - 'stest', - 'luid', - 'TICKET', - 'qbn.ticket', - 'qbn.tkt', - 'qbn.authid', - 'qbn.gauthid', - 'qbn.agentid', - 'iamValidationTime' - ); - - foreach ($cookies as $cookie) - { - if ($value = $this->_extractCookie($cookie, $response)) - { - $this->_cookies[$cookie] = $value; - } - } - - return new QuickBooks_IPP_Context($this, $ticket, $token); - } - - return false; } /** @@ -378,100 +310,22 @@ public function authenticate($username, $password, $token) * * */ - public function context($ticket = null, $token = null, $check_if_valid = true) + public function context() { $Context = null; - if ($this->_authmode == QuickBooks_IPP::AUTHMODE_OAUTH) + if ($this->_authmode == QuickBooks_IPP::AUTHMODE_OAUTHV1) { - $Context = new QuickBooks_IPP_Context($this, null, $token); - - // @todo Support for checking if it's valid or not + $Context = new QuickBooks_IPP_Context($this, null, null); } - else + else if ($this->_authmode == QuickBooks_IPP::AUTHMODE_OAUTHV2) { - if (is_null($ticket)) - { - $ticket = QuickBooks_IPP_Federator::getCookie(); - } - - $Context = new QuickBooks_IPP_Context($this, $ticket, $token); - - //print('check if valid [' . $check_if_valid . ']'); - - if ($check_if_valid) - { - // Now, let's check to make sure the context is valid - $User = $this->getUserInfo($Context); - - if (!$User or - !is_object($User) or - $User->isAnonymous()) - { - return null; - } - } + $Context = new QuickBooks_IPP_Context($this, null, null); } - //print_r($Context); - return $Context; } - /** - * - * - * @deprecated - * - */ - public function cookies($glob_them_together = false) - { - if ($glob_them_together) - { - $tmp = array(); - foreach ($this->_cookies as $cookie => $value) - { - $tmp[] = $cookie . '=' . $value; - } - - return implode('; ', $tmp); - } - - return $this->_cookies; - } - - public function username() - { - return $this->_username; - } - - public function password() - { - return $this->_password; - } - - /* - public function ticket($ticket = null) - { - if ($ticket) - { - $this->_ticket = $ticket; - } - - return $this->_ticket; - } - - public function token($token = null) - { - if ($token) - { - $this->_token = $token; - } - - return $this->_token; - } - */ - /** * * @@ -522,12 +376,11 @@ public function authcreds() * @param string $authmode The new auth mode * @return string The currently set auth mode */ - public function authMode($authmode = null, $authuser = null, $authcred = null, $authsign = null, $authkey = null) + public function authMode($authmode = null, $authcred = null, $authsign = null, $authkey = null) { if ($authmode) { $this->_authmode = $authmode; - $this->_authuser = $authuser; $this->_authcred = $authcred; $this->_authsign = $authsign; @@ -1008,6 +861,9 @@ public function IDS($Context, $realm, $resource, $optype, $xml = '', $ID = null) { $IPP = $Context->IPP(); + // Do any renewals we need to do first + $this->_handleRenewal(); + switch ($IPP->version()) { case QuickBooks_IPP_IDS::VERSION_3: @@ -1016,6 +872,63 @@ public function IDS($Context, $realm, $resource, $optype, $xml = '', $ID = null) } } + /** + * Do we need to renew the OAuth access token? If so, renew it + */ + protected function _handleRenewal() + { + static $attempted_renew = false; + + if (!$attempted_renew and + is_object($this->_driver) and + $this->_authmode == QuickBooks_IPP::AUTHMODE_OAUTHV2 and + strtotime($this->_authcred['oauth_access_expiry']) + 60 < time()) + { + $attempted_renew = true; + + if ($discover = QuickBooks_IPP_IntuitAnywhere::discover($this->_sandbox)) + { + $ch = curl_init($discover['token_endpoint']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // Do not follow; security risk here + + curl_setopt($ch, CURLOPT_USERPWD, $this->_authcred['oauth_client_id'] . ':' . $this->_authcred['oauth_client_secret']); + + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + 'grant_type' => 'refresh_token', + 'refresh_token' => $this->_authcred['oauth_refresh_token'] + ))); + + $retr = curl_exec($ch); + $info = curl_getinfo($ch); + + if ($info['http_code'] == 200) + { + $json = json_decode($retr, true); + + $this->_driver->oauthAccessRefreshV2( + $this->_key, + $this->_authcred['quickbooks_oauthv2_id'], + $json['access_token'], + $json['refresh_token'], + date('Y-m-d H:i:s', time() + (int) $json['expires_in']), + date('Y-m-d H:i:s', time() + (int) $json['x_refresh_token_expires_in'])); + + // Replace our auth creds with the new ones + $this->_authcred = $this->_driver->oauthLoadV2($this->_key, $this->_authcred['app_tenant']); + + // Successfully renewed! + return true; + } + } + + return false; + } + + // No renewal needed + return true; + } + protected function _IDS_v3($Context, $realm, $resource, $optype, $xml_or_query, $ID) { // All v3 URLs have the same baseURL @@ -1142,164 +1055,6 @@ protected function _IDS_v3($Context, $realm, $resource, $optype, $xml_or_query, return $parsed; } - protected function _IDS_v2($Context, $realmID, $resource, $optype, $xml, $ID) - { - if (substr($resource, 0, 6) == 'Report') - { - $resource = substr($resource, 6); - } - - // This is because IDS v2 with QuickBooks Online is retarded - if ($this->flavor() == QuickBooks_IPP_IDS::FLAVOR_ONLINE and - $resource == QuickBooks_IPP_IDS::RESOURCE_PAYMENTMETHOD) - { - $resource = 'payment-method'; - } - else if ($this->flavor() == QuickBooks_IPP_IDS::FLAVOR_ONLINE and - $resource == QuickBooks_IPP_IDS::RESOURCE_SALESRECEIPT) - { - $resource = 'sales-receipt'; - } - else if ($this->flavor() == QuickBooks_IPP_IDS::FLAVOR_ONLINE and - $resource == QuickBooks_IPP_IDS::RESOURCE_TIMEACTIVITY) - { - $resource = 'time-activity'; - } - else if ($this->flavor() == QuickBooks_IPP_IDS::FLAVOR_ONLINE and - $resource == QuickBooks_IPP_IDS::RESOURCE_JOURNALENTRY) - { - $resource = 'journal-entries'; - } - else if ($this->flavor() == QuickBooks_IPP_IDS::FLAVOR_ONLINE and - $resource == QuickBooks_IPP_IDS::RESOURCE_BILLPAYMENT) - { - $resource = 'bill-payment'; - } - - if ($this->flavor() == QuickBooks_IPP_IDS::FLAVOR_ONLINE and - $optype == QuickBooks_IPP_IDS::OPTYPE_QUERY) - { - // Make the resource plural... (unless it's the changedatadeleted) *sigh* - if ($resource == QuickBooks_IPP_IDS::RESOURCE_TIMEACTIVITY) - { - $resource = 'time-activities'; - } - else if ($resource == QuickBooks_IPP_IDS::RESOURCE_CLASS) - { - $resource .= 'es'; - } - else if ($resource != QuickBooks_IPP_IDS::RESOURCE_CHANGEDATADELETED) - { - $resource .= 's'; - } - } - - $post = true; - if ($resource == QuickBooks_IPP_IDS::RESOURCE_COMPANY or // QuickBooks desktop - $resource == QuickBooks_IPP_IDS::RESOURCE_COMPANYMETADATA) // QuickBooks online - { - $post = false; - $xml = ''; - } - - //$url = 'https://services.intuit.com/sb/' . strtolower($resource) . '/' . $this->_ids_version . '/' . $realmID; - - if ($this->flavor() == QuickBooks_IPP_IDS::FLAVOR_ONLINE) - { - if ($optype == QuickBooks_IPP_IDS::OPTYPE_FINDBYID) - { - $parse = QuickBooks_IPP_IDS::parseIDType($xml); - - $url = $this->_baseurl . '/' . strtolower($resource) . '/' . $this->_ids_version . '/' . $realmID . '/' . $parse[1]; - - $post = false; - $xml = null; - } - else if ($optype == QuickBooks_IPP_IDS::OPTYPE_MOD) - { - $parse = QuickBooks_IPP_IDS::parseIDType($ID); - - $url = $this->_baseurl . '/' . strtolower($resource) . '/' . $this->_ids_version . '/' . $realmID . '/' . $parse[1]; - } - else - { - $url = $this->_baseurl . '/' . strtolower($resource) . '/' . $this->_ids_version . '/' . $realmID; - } - } - - if ($optype == QuickBooks_IPP_IDS::OPTYPE_SYNCSTATUS) - { - $url = $this->_baseurl . '/status/v2/' . $realmID; - } - else - { - // Case matters on "syncActivity" #fun (everything else is lower cased) - if (strtolower($resource) == 'syncactivity') - { - $resource = 'syncActivity'; - } - else - { - $resource = strtolower($resource); // everything else should be lowercase - } - - $url = $this->_baseurl . '/' . $resource . '/' . $this->_ids_version . '/' . $realmID; - } - - //print('hitting URL [' . $url . ']'); - //print($xml); - $response = $this->_request($Context, QuickBooks_IPP::REQUEST_IDS, $url, $optype, $xml, $post); - //print($response); - - // Check for generic IPP errors and HTTP errors - if ($this->_hasErrors($response)) - { - return false; - } - - $data = $this->_stripHTTPHeaders($response); - - if (!$this->_ids_parser) - { - // If they don't want the responses parsed into objects, then just return the raw XML data - return $data; - } - - $start = microtime(true); - - //$Parser = new QuickBooks_IPP_Parser(); - $Parser = $this->_parserInstance(); - - $xml_errnum = null; - $xml_errmsg = null; - $err_code = null; - $err_desc = null; - $err_db = null; - - // Try to parse the responses into QuickBooks_IPP_Object_* classes - $parsed = $Parser->parseIDS($data, $optype, $this->flavor(), QuickBooks_IPP_IDS::VERSION_2, $xml_errnum, $xml_errmsg, $err_code, $err_desc, $err_db); - - $this->_setLastDebug(__CLASS__, array( 'ids_parser_duration' => microtime(true) - $start )); - - if ($xml_errnum != QuickBooks_XML::ERROR_OK) - { - // Error parsing the returned XML? - $this->_setError(QuickBooks_IPP::ERROR_XML, 'XML parser said: ' . $xml_errnum . ': ' . $xml_errmsg); - - return false; - } - else if ($err_code != QuickBooks_IPP::ERROR_OK) - { - // Some other IPP error - $this->_setError($err_code, $err_desc, 'Database error code: ' . $err_db); - - return false; - } - - // Return the parsed response - return $parsed; - } - /** * * @@ -1322,25 +1077,6 @@ protected function _stripHTTPHeaders($response) return $stripped; } - protected function _extractCookie($name, $response) - { - $lines = explode("\r\n", $response); - - foreach ($lines as $line) - { - // Set-Cookie: qbn.ticket=V1-47-U2v1RYBuM02GHgOYfulVmQ; expires=Tue, 19-Jan-2038 00:00:00 GMT; path=/; domain=.intuit.com; secure; HttpOnly - $line = substr($line, 12); - - if (substr($line, 0, strlen($name)) == $name and - false !== ($pos = strpos($line, ';'))) - { - return substr($line, strlen($name) + 1, $pos - strlen($name) - 1); - } - } - - return null; - } - protected function _parserInstance() { static $Parser = null; @@ -1495,80 +1231,38 @@ protected function _request($Context, $type, $url, $action, $data, $post = true) $headers = array( ); - //print('[' . $this->_flavor . '], ACTION [' . $action . ']'); - if ($this->_isUpload($url)) { $headers['Content-Type'] = 'multipart/form-data; boundary=' . $this->boundary; } - else if ($Context->IPP()->version() == QuickBooks_IPP_IDS::VERSION_3) - { - if ($action == QuickBooks_IPP_IDS::OPTYPE_ADD or + elseif ($action == QuickBooks_IPP_IDS::OPTYPE_ADD or $action == QuickBooks_IPP_IDS::OPTYPE_MOD or $action == QuickBooks_IPP_IDS::OPTYPE_VOID or $action == QuickBooks_IPP_IDS::OPTYPE_DELETE) - { - $headers['Content-Type'] = 'application/xml'; - } - else - { - $headers['Content-Type'] = 'text/plain'; - } + { + $headers['Content-Type'] = 'application/xml'; } else { - // Old v2 stuff - if ($type == QuickBooks_IPP::REQUEST_IPP) - { - $headers['Content-Type'] = 'application/xml'; - $headers['QUICKBASE-ACTION'] = $action; - } - else if ($type == QuickBooks_IPP::REQUEST_IDS) - { - if ($this->_flavor == QuickBooks_IPP_IDS::FLAVOR_DESKTOP) - { - $headers['Content-Type'] = 'text/xml'; - } - else if ($this->_flavor == QuickBooks_IPP_IDS::FLAVOR_ONLINE) - { - if ($action == QuickBooks_IPP_IDS::OPTYPE_ADD or $action == QuickBooks_IPP_IDS::OPTYPE_MOD or $action == QuickBooks_IPP_IDS::OPTYPE_DELETE) - { - $headers['Content-Type'] = 'application/xml'; - } - else - { - $headers['Content-Type'] = 'application/x-www-form-urlencoded'; - } - } - } + $headers['Content-Type'] = 'text/plain'; } // Authorization stuff - if ($this->_authmode == QuickBooks_IPP::AUTHMODE_OAUTH) + if ($this->_authmode == QuickBooks_IPP::AUTHMODE_OAUTHV2) + { + if ($this->_authcred['oauth_access_token']) + { + $headers['Authorization'] = 'Bearer ' . $this->_authcred['oauth_access_token']; + } + } + else if ($this->_authmode == QuickBooks_IPP::AUTHMODE_OAUTHV1) { // If we have credentials, sign the request if ($this->_authcred['oauth_access_token'] and $this->_authcred['oauth_access_token_secret']) { - /* - //// **** TEST STUFF **** //// - $url = 'https://api.twitter.com/1/statuses/update.json?include_entities=true'; - - $this->_authcred['oauth_consumer_key'] = 'xvz1evFS4wEEPTGEFPHBog'; - $this->_authcred['oauth_consumer_secret'] = 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw'; - - $this->_authcred['oauth_access_token'] = '370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb'; - $this->_authcred['oauth_access_token_secret'] = 'LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE'; - - $data = http_build_query(array('status' => 'Hello Ladies + Gentlemen, a signed OAuth request!')); - $post = true; - */ - - //print('URL [' . $url . ']' . "\n"); - //print('what is POST [' . $post . ']' . "\n"); - // Sign the request - $OAuth = new QuickBooks_IPP_OAuth($this->_authcred['oauth_consumer_key'], $this->_authcred['oauth_consumer_secret']); + $OAuth = new QuickBooks_IPP_OAuthv1($this->_authcred['oauth_consumer_key'], $this->_authcred['oauth_consumer_secret']); // Different than default signature method? if ($this->_authsign) @@ -1576,15 +1270,13 @@ protected function _request($Context, $type, $url, $action, $data, $post = true) $OAuth->signature($this->_authsign, $this->_authkey); } - //print('signing with method and key ' . $this->_authsign . ', ' . $this->_authkey); - if ($post) { - $action = QuickBooks_IPP_OAuth::METHOD_POST; + $action = QuickBooks_IPP_OAuthv1::METHOD_POST; } else { - $action = QuickBooks_IPP_OAuth::METHOD_GET; + $action = QuickBooks_IPP_OAuthv1::METHOD_GET; } $signdata = null; @@ -1602,27 +1294,13 @@ protected function _request($Context, $type, $url, $action, $data, $post = true) parse_str($data, $signdata); } - /* - print('signing ['); - print($action . "\n"); - print($url . "\n"); - print_r($this->_authcred); - print('[[' . $signdata . ']]'); - print(' all done ]'); - */ - $signed = $OAuth->sign($action, $url, $this->_authcred['oauth_access_token'], $this->_authcred['oauth_access_token_secret'], $signdata); - //print_r($signed); - // Always use the header, regardless of POST or GET $headers['Authorization'] = $signed[3]; if ($post) { - // Add the OAuth headers - //$headers['Authorization'] = $signed[3]; - // Remove any whitespace padding before checking $data = trim($data); @@ -1637,24 +1315,10 @@ protected function _request($Context, $type, $url, $action, $data, $post = true) } else { - // Replace the URL with the signed URL - //$url = $signed[2]; + ; } } } - else if (is_object($Context)) - { - // FEDERATED authentication - - $headers['Authorization'] = 'INTUITAUTH intuit-app-token="' . $Context->token() . '", intuit-token="' . $Context->ticket() . '"'; - $headers['Cookie'] = $this->cookies(true); - } - - //print_r($headers); - //exit; - - //$url = str_replace("SELECT * FROM customer", "SELECT+*+FROM+customer", $url); - //print('NEW URL [' . $url . ']' . "\n\n"); // Our HTTP requestor $HTTP = new QuickBooks_HTTP($url); @@ -1668,12 +1332,6 @@ protected function _request($Context, $type, $url, $action, $data, $post = true) // $HTTP->setRawBody($data); - - if ($this->_certificate) - { - $HTTP->setCertificate($this->_certificate); - } - // We need the headers back $HTTP->returnHeaders(true); @@ -1689,19 +1347,6 @@ protected function _request($Context, $type, $url, $action, $data, $post = true) $this->_setLastRequestResponse($HTTP->lastRequest(), $HTTP->lastResponse()); $this->_setLastDebug(__CLASS__, array( 'http_request_response_duration' => $HTTP->lastDuration() )); - //$this->_last_request = $HTTP->lastRequest(); - //$this->_last_response = $HTTP->lastResponse(); - - //print($HTTP->getLog()); - - /* - print("\n\n\n\n"); - print($this->_last_request); - print("\n\n\n\n"); - print($this->_last_response); - print("\n\n\n\n"); - exit; - */ // $this->_log($HTTP->getLog(), QUICKBOOKS_LOG_DEBUG); diff --git a/QuickBooks/IPP/Federator.php b/QuickBooks/IPP/Federator.php deleted file mode 100644 index 5dbbcd7b..00000000 --- a/QuickBooks/IPP/Federator.php +++ /dev/null @@ -1,809 +0,0 @@ - - * - * @package QuickBooks - * @subpackage IPP - */ - -/* -function federator_callback($ticket, $target_url, $auth_id) -{ - - - return TRUE; // return TRUE to let the Federator instance redirect to $target_url - return FALSE; // return FALSE to handle whatever happens next yourself -} - -if you don't provide a callback function, a default callback will be used -which stores the data in the database defined by $dsn, and then forwards -the user on to the target URL. - -*/ - -if (!defined('QUICKBOOKS_IPP_FEDERATOR_MAX_SAML_LENGTH')) -{ - /** - * - */ - define('QUICKBOOKS_IPP_FEDERATOR_MAX_SAML_LENGTH', 8200); -} - -if (!defined('QUICKBOOKS_IPP_FEDERATOR_TEST_SAML')) -{ - define('QUICKBOOKS_IPP_FEDERATOR_TEST_SAML', 'PHNhbWxwOlJlc3BvbnNlIElzc3VlSW5zdGFudD0iMjAxMC0wNC0yMlQxNDozMDo0Ni44MDJaIiBJRD0iWmJpeWVyTGExWGxPajdZejdxempwZlhYdGZrIiBWZXJzaW9uPSIyLjAiIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiPjxzYW1sOklzc3VlciB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5JREZFRF9QUk9EX0lEUF9TUF9BUFA8L3NhbWw6SXNzdWVyPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxMC0wNC0yMlQxNDozMDo0Ni44MzhaIiBJRD0iTmpmMnouQWZ3bFhXMUZxVU9PZmFFX2lkaWNQIiB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj48c2FtbDpJc3N1ZXI+SURGRURfUFJPRF9JRFBfU1BfQVBQPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8+CjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4KPGRzOlJlZmVyZW5jZSBVUkk9IiNOamYyei5BZndsWFcxRnFVT09mYUVfaWRpY1AiPgo8ZHM6VHJhbnNmb3Jtcz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+CjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KPC9kczpUcmFuc2Zvcm1zPgo8ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz4KPGRzOkRpZ2VzdFZhbHVlPnp1ampTSUFHdnZQd01pUTdIV2ZOM1ZZTHI3WT08L2RzOkRpZ2VzdFZhbHVlPgo8L2RzOlJlZmVyZW5jZT4KPC9kczpTaWduZWRJbmZvPgo8ZHM6U2lnbmF0dXJlVmFsdWU+CmpzY3hPRnk2T3dwc0ZoNVJ3Wk5RQXBBRFBFWVcrZytKWDlwUEtrTnJ2c0ExYnVkRDJFb3Y4bjNSaDFHVTVwTU84RzM0YWNMZnJhTysKWWVMcWNpTnYvMkVQYzRKUUFqdnhuOWtiekN5eXMvaGlvZGk2QmN6eHJHRFVlZ1JWRUtLUnhieFNHOHVoUmdZd1FIdHQ4VW1FSUt3NApzbGlXUGM4SE9XUUxIZHk5bG9TUjVoVVpSRGMxVGpBSEJVQlFYdnNydXNKdDBGa1g3aHk3MVE0R2lrOE9NOUFFKzF6OEVzR2J6b1RwCnIweXlYUFBFZVlDMCtaRGZLUUZDZ081b2hJZEdZRWtJY1hRRjNxajQ1VVVlQUVnZVM3SmdGSzB3a2NWSVRIU1MxOFIxanlCQ3RFeWUKOEtnQWdKUEwzVjF2V3kzeTM2aWFxOUNNOUE0M2RLeDFaeEJ1aUE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj5iMjUwM2Y3OS0wOTFmLTRjNmQtYWJkZS0wZmJmNTQxYzM4ZjQ8L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMTAtMDQtMjJUMTQ6MzU6NDYuODM4WiIgUmVjaXBpZW50PSJodHRwczovL3NlY3VyZS5jb25zb2xpYnl0ZS5jb20vZGV2ZWwvc2FtbC90ZXN0LnBocCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90T25PckFmdGVyPSIyMDEwLTA0LTIyVDE0OjM1OjQ2LjgzOFoiIE5vdEJlZm9yZT0iMjAxMC0wNC0yMlQxNDoyODo0Ni44MzhaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPnBocC5jb25zb2xpYnl0ZS5wcm9kLWludHVpdC5pcHAucHJvZDwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTAtMDQtMjJUMTQ6MzA6NDYuODM4WiIgU2Vzc2lvbkluZGV4PSJOamYyei5BZndsWFcxRnFVT09mYUVfaWRpY1AiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3Nlczp1bnNwZWNpZmllZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIj48c2FtbDpBdHRyaWJ1dGUgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyIgTmFtZT0iSW50dWl0LkZlZGVyYXRpb24ucmVhbG1JRFBzZXVkb255bSI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSI+Yjc2NWZkM2QtYzJhNC00ZDEyLTlmNTUtMTAzMzdiMGQ3NWMzPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6RW5jcnlwdGVkQXR0cmlidXRlPjx4ZW5jOkVuY3J5cHRlZERhdGEgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCIgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIj48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIi8+PGRzOktleUluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPgo8eGVuYzpFbmNyeXB0ZWRLZXkgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIj48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjcnNhLTFfNSIgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIi8+PHhlbmM6Q2lwaGVyRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjx4ZW5jOkNpcGhlclZhbHVlIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI+V1dLY3RxaVlOUUYrdTc3MklvUm5ONnAvZHlOUDYrU2FRZ0R2TE5wNEd4SEphR3l2a2kvYjEzNGs4OG9meVduM2sxT05qdHJXbm9MUApiRCttRFY5Q2lOeS9Qbk1NZEdWS0xGUjdFV1JFV1prNDVKck1jN2FnZkF3SE1TTGVCOGxXaFJ3R3NkQmdnNlJGU0c1aHdveThKZXUzClN0blIrS21VclNHMURtRVJweDg9PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWRLZXk+PC9kczpLZXlJbmZvPjx4ZW5jOkNpcGhlckRhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIj48eGVuYzpDaXBoZXJWYWx1ZSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjhUT3pyRjlUNUdqM3ZXdkh2NmM1UCttZE9TR3Qyd0N0ek11LzgxVmxUNC9NVEZmM1J1dXJ1U01JSTcvUUF4RUNvd3lIbHpUeWxyRzgKS2k0dEE1T3dzVkhvd3Fneitmbng3UTU4MTdQUyszKzBTR2JERkFwblQ5a3ZRVEZkZWZGS3d4UXhnL25kTUNHZCtzRDFEZWk0VEIyMwpJTXVpRkVURExNWDhqOCtjQWZKTDdUdTA4WkZIQVZTaWF1enM5bkNtUHVvZzcxR0tIcHpmSkdocDNvZXdGbnN6M010ZWVYUm8yWHVUCmNWTjhsOElnWUV5OHl2b2RPQTlNc3RpSnFWZWNYWVFLODR4NTJtUjdsOFpPb1JENHFaZzZsRzBaNHNnZDRjcVFUVlY1QUtvNldlS0oKbi9wL3NvUmhBOTB1TWMyMGl5c1JHd0doS0JoOENHZHlKOXV2c0h4c3cxVHVXUGRSM0N0VXB2NVlZejhxK0UzeWppV0VsVCtPaEhXOQpoYWN4b2x5cTA5M2Npbm9sdUQycTNLWjVjSkxWQndpcXV3TklJWkNvbzIvWHVaYjIrd0x3QmpyN1pkdDlUSUI5V1lRelcxcnJWTUpBCk9VMmFBOVNWdEhxcytqTitQMnVGODYwVWxsdEd5YmlxWjFiQ2MyOWk1VHR5Y000STVuWVhVcC9ET2FlZGZWTllQSlBpYmgzRVBoSDEKZVk5SDdDeENSWURYL1V5SkJiTUtldXozNEtWQ1ByeWN0TzFzQ1hQUmtlN0lIS3A4Z2NodjRLYjZ6UERhcEVJUFdEeUxtRzRXOCtnNgpWdnFoMVNsY1Q4TkRhdTF0UEZzOURtT3NNTkdGYTBrVDwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkRGF0YT48L3NhbWw6RW5jcnlwdGVkQXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIiBOYW1lPSJyZWFsbUlEIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIj4xNzM2NDI0Mzg8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyIgTmFtZT0idGFyZ2V0VXJsIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIj5odHRwczovL3NlY3VyZS5jb25zb2xpYnl0ZS5jb20vZGV2ZWwvc2FtbC90ZXN0Mi5waHA8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4='); -} - -if (!defined('QUICKBOOKS_IPP_FEDERATOR_TEST_KEY')) -{ - define('QUICKBOOKS_IPP_FEDERATOR_TEST_KEY', '-----BEGIN RSA PRIVATE KEY-----' . "\n" . 'MIICXgIBAAKBgQDJ44e+mLkoqSeEwy81RajedaZ6UbGsS1LTVFyZp+0S6JTISmoT' . "\n" . 'ZpkuiDsvMxWrYnGQmA/SHUySx41KQWsMd13JjGOVQp569xlu1O/q7/5cPGiUkCb/' . "\n" . 'j+OdBI5KWgsGo6G5KMHEL8FcXNKWsZaldKLObyx5mUeFXYJZIxSIgThGcQIDAQAB' . "\n" . 'AoGBAJnao+BEUxcBkfRDKv7WD1M5JZ2iFFzRKlWSvN78clcul/Prgds3HRWxDCl0' . "\n" . 'LNdnNlSTDbt6SJizKqGkKQhfD0DmzPRC6JW2hXFIbr4xBIHQ4g4sH/v7AphxFk0R' . "\n" . '7zyjfa/kVd7EJgnf1mZubqYm3wu7iEPvUVsZ3p4/3DnshdKBAkEA7IuaQULUnhXt' . "\n" . 'h74xgLLRA6baWRquQtACBPqtENjwqEhSek196/0MXLmRhmTeGGjx3yD6MSSuEtLq' . "\n" . '9jAZwYzaTQJBANp+Pmt03YFY6+MHnkpAvqgRaCFoDBvV9LNP4c484LN+svuuCTJQ' . "\n" . 'kBqBD6Q3yHrprn/zOZNpRtoyfBWMGMFkprUCQG9z6496TLHbxRpjW/G2z1K4KEM5' . "\n" . 'lgf2+CyebDL29JVl1i64Gm+5wDxkVxQKrLa1o9ktMZU8IiTOalTrHweaNTUCQQCx' . "\n" . 'pXVQ3yr98PORmm8bxjp94fE9QCCgPTyA0lEw4xR7PGd/9Eer7g7MTeUOywAo13i2' . "\n" . 'tWY5sZ4W6Hc0+bxi+VgFAkEAk2GLtkjfpdE87HZ1wOUF6Hy2BRiY/ENDPo4oC+00' . "\n" . '07OqlunePjrq/GvJDha6EKkrsdPdBVaPiArLpPZ14HKVkQ==' . "\n" . '-----END RSA PRIVATE KEY-----'); -} - -// -QuickBooks_Loader::load('/QuickBooks/XML/Parser.php'); - -// -QuickBooks_Loader::load('/QuickBooks/Callbacks.php'); - -/** - * - * - * - */ -class QuickBooks_IPP_Federator -{ - /** - * SAML authorization - * @var string - */ - const TYPE_SAML = 'saml'; - - /** - * No error, everything is OK - * @var integer - */ - const ERROR_OK = QUICKBOOKS_ERROR_OK; - - /** - * Error indicating there's a problem with the key file - * @var integer - */ - const ERROR_KEY = 1; - - const ERROR_XML = 2; - - const ERROR_SAML = 3; - - const ERROR_INTERNAL = 4; - - const ERROR_CALLBACK = 5; - - const ERROR_COOKIE = 6; - - const URL_OAUTH = 'https://oauth.intuit.com/oauth/v1/get_access_token_by_intuit_pseudonym'; - - const TIMEOUT_OAUTH = 3000; // Expires after almost an hour - - protected $_type; - - protected $_key; - - protected $_callback; - - protected $_driver; - - protected $_debug; - - protected $_errnum; - - protected $_errmsg; - - protected $_config; - - protected $_log; - - public function __construct($private_key, $dsn = null, $callback = null, $config = array()) - { - $this->_key = $private_key; - - $this->_driver = null; - if ($dsn) - { - // @todo Logging - $this->_driver = QuickBooks_Driver_Factory::create($dsn); - } - - $this->_log = ''; - - $this->_errnum = QuickBooks_IPP_Federator::ERROR_OK; - $this->_errmsg = ''; - - $this->_callback = $callback; - - $this->_config = $this->_defaults($config); - } - - protected function _defaults($config) - { - $defaults = array( - 'cookie_httponly' => true, - 'cookie_secure' => null, // null is for auto-detection based on $_SERVER['HTTPS'] - 'cookie_domain' => '', // www.php.net/setcookie() says we can send an empty string for the default value of the current domain - 'cookie_path' => '/', - 'cookie_expire' => 0, // expire when the browser closes - - 'http_redirect_method' => null, - 'http_redirect_delay' => 0, - - 'test_username' => '', - 'test_password' => '', - 'test_target' => '', - 'test_param_dbid' => '', - 'test_param_realm' => '', - 'test_param_state' => '', - - 'segfault_openssl_private_decrypt' => null, - - 'log_string' => true, - 'log_level' => QUICKBOOKS_LOG_NORMAL, - ); - - $config = array_merge($defaults, (array) $config); - - if (is_null($config['cookie_secure'])) - { - $config['cookie_secure'] = (boolean) isset($_SERVER['HTTPS']); - } - - return $config; - } - - protected function _log($msg, $level = QUICKBOOKS_LOG_NORMAL) - { - if ($this->_config['log_level'] >= $level) - { - if ($this->_debug) - { - print(date('Y-m-d H:i:s') . ': ' . $msg . QUICKBOOKS_CRLF); - } - - if ($this->_config['log_string']) - { - $this->_log .= $msg . QUICKBOOKS_CRLF . QUICKBOOKS_CRLF . QUICKBOOKS_CRLF; - } - - if ($this->_driver) - { - - } - } - } - - public function useDebugMode($true_or_false) - { - $this->_debug = (boolean) $true_or_false; - } - - /** - * Get the last error number - * - * @return integer - */ - public function errorNumber() - { - return $this->_errnum; - } - - /** - * Get the last error message - * - * @return string - */ - public function errorMessage() - { - return $this->_errmsg; - } - - /** - * Set an error message - * - * @param integer $errnum The error number/code - * @param string $errmsg The text error message - * @return void - */ - protected function _setError($errnum, $errmsg = '') - { - $this->_errnum = $errnum; - $this->_errmsg = $errmsg; - } - - public function lastLog() - { - return $this->_log; - } - - public function handle($input = null) - { - // We only support SAML for now - return $this->_handleSAML($input); - } - - /** - * Check if an OAuth token auth/realm psuedonym for the given user - * - * @param string $token Your Intuit application token - * @param string $encryption_key Your internal encryption key - * @param string $user The username or user ID from within your app - * @param string $tenant The tenant ID from within your app - * @return boolean TRUE if OAuth credentials exist, FALSE if they do not - */ - public function checkOAuth($token, $encryption_key, $user, $tenant) - { - /* - if ($arr = $this->loadOAuth($token, $encryption_key, $user, $tenant)) - { - return true; - } - */ - - if (!$this->_driver) - { - return false; - } - - if ($arr = $this->_driver->oauthLoad($encryption_key, $user, $tenant)) - { - return true; - } - - return false; - } - - /** - * - * - * @param string $token Your Intuit application token - * @param string $encryption_key Your internal encryption key - * @param string $user The username or user ID from within your app - * @param string $tenant The tenant ID from within your app - * @return array An array of OAuth credentials - */ - public function loadOAuth($token, $encryption_key, $user, $tenant) - { - if (!$this->_driver) - { - return false; - } - - if ($arr = $this->_driver->oauthLoad($encryption_key, $user, $tenant) and - strlen($arr['oauth_access_token']) > 0 and - strlen($arr['oauth_access_token_secret']) > 0) - { - $arr['oauth_consumer_key'] = $token; - $arr['oauth_consumer_secret'] = null; - - return $arr; - } - - return false; - } - - /** - * - * - * - */ - public function refreshOAuth($provider, $token, $pem_key, $encryption_key, $app_username, $app_tenant) - { - if (!$this->_driver) - { - $this->_log('Could not connect to OAuth, no DRIVER storage instance.'); - return false; - } - - // Load from OAuth - $arr = $this->_driver->oauthLoad($encryption_key, $app_username, $app_tenant); - - if ($arr) - { - // Check the timestamps to see if they are more than 1 HOUR old - if (time() - strtotime($arr['access_datetime']) < QuickBooks_IPP_Federator::TIMEOUT_OAUTH) - { - // Use the existing tokens - - print('USING EXISTING TOKEN' . "\n\n"); - - return true; - } - else - { - // Otherwise, fetch a new OAuth token - - //print('we need to fetch a new token,e xpired!'); - //print_r($arr); - - print('REFRESHING TOKEN!' . "\n\n"); - - $connected = $this->connectOAuth($provider, $token, $pem_key, $encryption_key, $app_username, $app_tenant, - $arr['oauth_request_token'], - $arr['oauth_request_token_secret'], - null, - null); - - return $connected; - } - } - - return false; - } - - /** - * Fetch OAuth tokens with the data provided to you in the SAML request - * - * Federated applications can use OAuth for unattended access to IDS data. - * (i.e. access data even if the user isn't logged in) Before you start - * using this, you have to make sure Intuit onboards you for OAuth access. - * - * @param string $provider Your federated provider id (Intuit should have given you this) - * @param string $token Your application token - * @param string $key The full path to your .pem file (e.g. /path/to/file.pem) - * @param string $user The username or user ID of the authenticating user - * @param string $tenant The tenant ID of the authenticating user - * @param string $auth_id_pseudonym The Auth ID Pseudonym extracted from the SAML message - * @param string $realm_id_pseudonym The Realm ID Pseudonym extracted from the SAML message - * @return boolean - */ - public function connectOAuth($provider, $token, $pem_key, $encryption_key, $app_username, $app_tenant, $auth_id_pseudonym, $realm_id_pseudonym, $realm, $flavor) - { - if (!$this->_driver) - { - $this->_log('Could not connect to OAuth, no DRIVER storage instance.'); - return false; - } - - $url = QuickBooks_IPP_Federator::URL_OAUTH; - - // First we need to push the request data into the OAuth storage - $this->_driver->oauthRequestWrite( - $app_username, - $app_tenant, - $auth_id_pseudonym, - $realm_id_pseudonym); - - $params = array( - 'xoauth_service_provider_id' => $provider, - 'xoauth_auth_id_pseudonym' => $auth_id_pseudonym, - 'xoauth_realm_id_pseudonym' => $realm_id_pseudonym, - ); - - // Create our OAuth instance class - $OAuth = new QuickBooks_IPP_OAuth( - $token, // The "consumer key" in this case is our application token - ''); // There is no consumer secret - - $OAuth->signature(QuickBooks_IPP_OAuth::SIGNATURE_RSA, $pem_key); - - // Sign the request - $sign = $OAuth->sign(QuickBooks_IPP_OAuth::METHOD_GET, $url, null, null, $params); - - // Now make our HTTP request to get the OAuth tokens - $HTTP = new QuickBooks_HTTP($sign[2]); - - $HTTP->useDebugMode($this->_debug); - - $data = $HTTP->GET(); - - $this->_log('OAuth HTTP request: [' . $HTTP->lastRequest() . ']'); - $this->_log('OAuth HTTP response: [' . $HTTP->lastResponse() . ']'); - - if ($data) - { - $tmp = array(); - parse_str($data, $tmp); - - if (!empty($tmp['oauth_token']) and - !empty($tmp['oauth_token_secret'])) - { - // Store the OAuth tokens - - $this->_log('Storing OAuth tokens...'); - - return $this->_driver->oauthAccessWrite( - $encryption_key, - $auth_id_pseudonym, - $tmp['oauth_token'], - $tmp['oauth_token_secret'], - $realm, - $flavor); - } - } - - return false; - } - - protected function _handleSAML($SAML = null) - { - $this->_log('Starting up (initialized with ' . strlen($SAML) . ' bytes)'); - - if ($this->_config['test_username'] and - $this->_config['test_password']) - { - $SAML = QUICKBOOKS_IPP_FEDERATOR_TEST_SAML; - $private_key_data = QUICKBOOKS_IPP_FEDERATOR_TEST_KEY; - } - - if (!$SAML) - { - if (!empty($_POST['SAMLResponse'])) - { - $SAML = base64_decode($_POST['SAMLResponse']); - } - else - { - $msg = 'No SAML request in $_POST vars.'; - $this->_log($msg); - $this->_setError(QuickBooks_IPP_Federator::ERROR_SAML, $msg); - return false; - } - } - - if (strlen($SAML) > QUICKBOOKS_IPP_FEDERATOR_MAX_SAML_LENGTH) - { - $msg = 'SAML request seems unusually large, at ' . strlen($SAML) . ' bytes.'; - $this->_log($msg); - $this->_setError(QuickBooks_IPP_Federator::ERROR_SAML, $msg); - return false; - } - - if ($this->_config['test_username'] and - $this->_config['test_password']) - { - ; // Do nothing, we already fetched our private key data up there ^^^ - } - else - { - $fp = fopen($this->_key, 'r'); - $private_key_data = fread($fp, 8192); - fclose($fp); - } - - // Decode the SAML request if it's base64 encoded - if (false === strpos($SAML, '<')) - { - $SAML = base64_decode($SAML); - } - - $this->_log('Incoming SAML request: ' . substr($SAML, 0, 128) . '...'); - $this->_log($SAML, QUICKBOOKS_LOG_DEBUG); - - //print("\n\n" . $SAML . "\n\n"); - // - $private_key = openssl_get_privatekey($private_key_data); - //$public_key = openssl_get_publickey($__publicKey); - - $use_backend = QuickBooks_XML_Parser::BACKEND_BUILTIN; - $Parser = new QuickBooks_XML_Parser($SAML, $use_backend); - if ($Doc = $Parser->parse($errnum, $errmsg)) - { - $Root = $Doc->getRoot(); - - $auth_id = $Root->getChildDataAt('samlp:Response saml:Assertion saml:Subject saml:NameID'); - $this->_log('Auth ID: [' . $auth_id . ']'); - - if (!$auth_id) - { - $this->_setError(QuickBooks_IPP_Federator::ERROR_INTERNAL, 'Could not extract Auth ID from SAML response.'); - return false; - } - - /* - $AttributeStatement = $Root->getChildAt('samlp:Response saml:Assertion saml:AttributeStatement'); - - foreach ($AttributeStatement->children() as $Node) - { - if ($Node->name() == 'saml:Attribute') - { - $Attribute = $Node; - - print_r($Attribute); - } - } - exit; - */ - - $encrypted_key = $Root->getChildDataAt('samlp:Response saml:Assertion saml:AttributeStatement saml:EncryptedAttribute xenc:EncryptedData ds:KeyInfo xenc:EncryptedKey xenc:CipherData xenc:CipherValue'); - $this->_log('Encrypted key: [' . $encrypted_key . ']'); - - if (!$encrypted_key) - { - $this->_setError(QuickBooks_IPP_Federator::ERROR_INTERNAL, 'Could not extract encrypted key from SAML response.'); - return false; - } - - $encrypted_ticket = $Root->getChildDataAt('samlp:Response saml:Assertion saml:AttributeStatement saml:EncryptedAttribute xenc:EncryptedData xenc:CipherData xenc:CipherValue'); - $this->_log('Encrypted ticket: [' . $encrypted_ticket . ']'); - - if (!$encrypted_ticket) - { - $this->_setError(QuickBooks_IPP_Federator::ERROR_INTERNAL, 'Could not extract encrypted ticket from SAML response.'); - return false; - } - - // Loop through the nodes, fetching the attributes from the SAML request - $Node = $Root->getChildAt('samlp:Response saml:Assertion saml:AttributeStatement'); - - $target_url = null; - $realm_id_pseudonym = null; - - foreach ($Node->children() as $ChildNode) - { - if ($ChildNode->name() == 'saml:Attribute') - { - $Attribute = $ChildNode; - - if ($Attribute->getAttribute('Name') == 'targetUrl') - { - $ChildChildNode = $Attribute->getChild(0); - $target_url = $ChildChildNode->data(); - } - else if ($Attribute->getAttribute('Name') == 'Intuit.Federation.realmIDPseudonym') - { - $ChildChildNode = $Attribute->getChild(0); - $realm_id_pseudonym = $ChildChildNode->data(); - } - } - } - - $this->_log('Target URL: [' . $target_url . ']'); - $this->_log('Realm ID Pseudonym: [' . $realm_id_pseudonym . ']'); - //exit; - - if (!$target_url) - { - $this->_setError(QuickBooks_IPP_Federator::ERROR_INTERNAL, 'Could not extract target URL from SAML response.'); - return false; - } - - /* - // Get the signatureValue - $node = $xml->xpath('/samlp:Response/saml:Assertion/ds:Signature/ds:SignatureValue'); - $signatureValue = $node[0]; - - # Get the signed node - $signInfo = $xml->xpath('/samlp:Response/saml:Assertion/ds:Signature/ds:SignedInfo'); - */ - - // The key and ticket are base64 encoded, decode them - $decoded_key = base64_decode($encrypted_key); - $decoded_ticket = base64_decode($encrypted_ticket); - - // Decrypt the key - $decrypted_key = null; - $result = $this->_segfault_openssl_private_decrypt($decoded_key, $decrypted_key, $private_key_data); - - $this->_log('Key: [' . $decrypted_key . ']'); - - if (!$decrypted_key) - { - $this->_setError(QuickBooks_IPP_Federator::ERROR_INTERNAL, 'Could not extract decrypted key from SAML response.'); - return false; - } - - // @todo Swap out for QuickBooks_Encrypt implementation - // Get the key size for decryption - $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); - - // $decoded_ticket is stored as: - // 16-byte IV - // CONCAT WITH - // XX-byte actual encrypted ticket in XML format - - // Get the IV - $iv = substr($decoded_ticket, 0, $iv_size); - - // This is the actual encrypted ticket - $cipher = substr($decoded_ticket, $iv_size); - - // @todo Swap out for QuickBooks_Encrypt implementation - // Decrypt the ticket - $decrypted_ticket = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $decrypted_key, $cipher, MCRYPT_MODE_CBC, $iv); - - // Remove the padding from the ticket - $last_byte = substr($decrypted_ticket, -1, 1); - $padding = -ord($last_byte); - $decrypted_ticket = substr($decrypted_ticket, 0, $padding); - - $this->_log('Decrypted ticket is ' . strlen($decrypted_ticket) . ' bytes long...'); - $this->_log($decrypted_ticket, QUICKBOOKS_LOG_DEBUG); - - // Parse the XML format to get at the actual ticket value - $ticket = null; - $errnum = null; - $errmsg = null; - $use_backend = QuickBooks_XML_Parser::BACKEND_BUILTIN; - $Parser = new QuickBooks_XML_Parser($decrypted_ticket, $use_backend); - if ($Doc = $Parser->parse($errnum, $errmsg)) - { - $Root = $Doc->getRoot(); - - $ticket = $Root->getChildDataAt('Attribute saml:AttributeValue'); - $this->_log('Ticket: [' . $ticket . ']'); - - // Check for test mode overrides - if ($this->_config['test_username'] and - $this->_config['test_password']) - { - $username = $this->_config['test_username']; - $password = $this->_config['test_password']; - $token = 'blablabla'; - - $test_replace = array( - '{dbid}' => $this->_config['test_param_dbid'], - '{realm}' => $this->_config['test_param_realm'], - '{state}' => $this->_config['test_param_state'], - ); - - $target_url = str_replace(array_keys($test_replace), array_values($test_replace), $this->_config['test_target']); - - // Grab a ticket - $IPP = new QuickBooks_IPP(); - $Context = $IPP->authenticate($username, $password, $token); - - $ticket = $Context->ticket(); - - $this->_log('TEST MODE [authid=' . $auth_id . ', ticket=' . $ticket . ', target_url=' . $target_url . ']'); - } - - return $this->_doCallback($auth_id, $ticket, $target_url, $realm_id_pseudonym); - } - else - { - $this->_setError(QuickBooks_IPP_Federator::ERROR_XML, 'XML parser error while parsing SAML ticket: ' . $errnum . ':' . $errmsg); - return false; - } - } - else - { - $this->_setError(QuickBooks_IPP_Federator::ERROR_XML, 'XML parser error while parsing SAML response: ' . $errnum . ': ' . $errmsg); - return false; - } - } - - protected function _doCallback($auth_id, $ticket, $target_url, $realm_id_pseudonym) - { - if ($this->_callback) - { - $err = ''; - - $redirect = QuickBooks_Callbacks::callSAMLCallback( - $this->_driver, - $this->_callback, - $auth_id, - $ticket, - $target_url, - $realm_id_pseudonym, - $this->_config, - $err); - - if ($err) - { - $this->_setError(QuickBooks_IPP_Federator::ERROR_CALLBACK, 'Callback said: ' . $err); - return false; - } - } - else - { - // Just set the cookie - $cookie_expire = (int) $this->_config['cookie_expire']; - $cookie_path = $this->_config['cookie_path']; - $cookie_domain = $this->_config['cookie_domain']; - $cookie_secure = (boolean) $this->_config['cookie_secure']; - $cookie_httponly = (boolean) $this->_config['cookie_httponly']; - - //print('setting cookie: ' . print_r($this->_config, true)); - - if (QuickBooks_IPP_Federator::setCookie($ticket, $cookie_expire, $cookie_path, $cookie_domain, $cookie_secure, $cookie_httponly)) - { - $redirect = true; - } - else - { - // Cookie failed to set for some reason - $this->_setError(QuickBooks_IPP_Federator::ERROR_COOKIE, 'Could not set the IPP context cookie (did you make sure *not* to send any output yet?)'); - return false; - } - } - - if ($redirect) - { - $this->_doRedirect($target_url); - } - - return true; - } - - protected function _doRedirect($target_url) - { - $html = ''; - print($html); - return true; - } - - /** - * - * - */ - static public function setCookie($value, $expire = 0, $path = '/', $domain = '', $secure = null, $httponly = true) - { - if (is_null($secure)) - { - $secure = (boolean) isset($_SERVER['HTTPS']); - } - - //$secure = true; // This is required per IPP security guidelines - //$httponly = true; - - // P3P header to make Internet Exploder set cookies in iFrames - header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'); - - return setcookie(QuickBooks_IPP::COOKIE, $value, $expire, $path, $domain, $secure, $httponly); - } - - /** - * - * - */ - static public function getCookie() - { - if (isset($_COOKIE[QuickBooks_IPP::COOKIE])) - { - return $_COOKIE[QuickBooks_IPP::COOKIE]; - } - - return null; - } - - protected function _segfault_openssl_private_decrypt($decoded_key, &$decrypted_key, $private_key_data) - { - if ($this->_config['segfault_openssl_private_decrypt']) - { - $filename = tempnam('/tmp', 'segfault_openssl_private_decrypt__'); - $fp = fopen($filename, 'w+'); - fwrite($fp, serialize(array( $decoded_key, $private_key_data ))); - fclose($fp); - - $command = $this->_config['segfault_openssl_private_decrypt'] . ' ' . $filename; - - $this->_log('Using segfault_openssl_private_decrypt work-around [' . $command . ']'); - - $output = array(); - $return_var = null; - exec($command, $output, $return_var); - - //print("\n\n\n\n-----------------\n"); - //print_r(implode(PHP_EOL, $output)); - //print("\n---------------------\n\n\n\n"); - - $this->_log('The segfault_openssl_private_decrypt work-around returned [' . $return_var . ']'); - - $decrypted_key = implode(PHP_EOL, $output); - return true; - } - else - { - // Decrypt the key - $decrypted_key = null; - $result = openssl_private_decrypt($decoded_key, $decrypted_key, $private_key_data); - return $result; - } - } -} diff --git a/QuickBooks/IPP/IntuitAnywhere.php b/QuickBooks/IPP/IntuitAnywhere.php index ff6a712f..82a9ee6d 100644 --- a/QuickBooks/IPP/IntuitAnywhere.php +++ b/QuickBooks/IPP/IntuitAnywhere.php @@ -2,41 +2,50 @@ /** * QuickBooks PHP DevKit - * + * * Copyright (c) 2010 Keith Palmer / ConsoliBYTE, LLC. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.opensource.org/licenses/eclipse-1.0.php - * + * * @author Keith Palmer - * @license LICENSE.txt - * + * @license LICENSE.txt + * * @package QuickBooks */ -class QuickBooks_IPP_IntuitAnywhere +class QuickBooks_IPP_IntuitAnywhere { + protected $_oauth_version; + protected $_oauth_scope; + + protected $_sandbox; + protected $_this_url; protected $_that_url; - + protected $_consumer_key; protected $_consumer_secret; - + + protected $_client_id; + protected $_client_secret; + protected $_errnum; protected $_errmsg; - + protected $_debug; - + + protected $_dsn; protected $_driver; - + protected $_crypt; - + protected $_key; protected $_last_request; protected $_last_response; - + const URL_REQUEST_TOKEN = 'https://oauth.intuit.com/oauth/v1/get_request_token'; const URL_ACCESS_TOKEN = 'https://oauth.intuit.com/oauth/v1/get_access_token'; const URL_CONNECT_BEGIN = 'https://appcenter.intuit.com/Connect/Begin'; @@ -44,67 +53,79 @@ class QuickBooks_IPP_IntuitAnywhere const URL_CONNECT_RECONNECT = 'https://appcenter.intuit.com/api/v1/Connection/Reconnect'; const URL_APP_MENU = 'https://appcenter.intuit.com/api/v1/Account/AppMenu'; + const URL_DISCOVERY_SANDBOX = 'https://developer.api.intuit.com/.well-known/openid_sandbox_configuration'; + const URL_DISCOVERY_PRODUCTION = 'https://developer.api.intuit.com/.well-known/openid_configuration'; + const EXPIRY_EXPIRED = 'expired'; const EXPIRY_NOTYET = 'notyet'; const EXPIRY_SOON = 'soon'; const EXPIRY_UNKNOWN = 'unknown'; - + + const OAUTH_V1 = 'oauthv1'; + const OAUTH_V2 = 'oauthv2'; + /** - * + * * * @param string $consumer_key The OAuth consumer key Intuit gives you * @param string $consumer_secret The OAuth consumer secret Intuit gives you * @param string $this_url The URL of your QuickBooks_IntuitAnywhere class instance - * @param string $that_url The URL the user should be sent to after authenticated + * @param string $that_url The URL the user should be sent to after authenticated */ - public function __construct($dsn, $encryption_key, $consumer_key, $consumer_secret, $this_url = null, $that_url = null) + public function __construct($oauth_version, $sandbox, $scope, $dsn, $encryption_key, $consumer_key_or_client_id, $consumer_secret_or_client_secret, $this_url = null, $that_url = null) { + $this->_dsn = $dsn; $this->_driver = QuickBooks_Driver_Factory::create($dsn); - + $this->_key = $encryption_key; - + $this->_this_url = $this_url; $this->_that_url = $that_url; - - $this->_consumer_key = $consumer_key; - $this->_consumer_secret = $consumer_secret; - + + $this->_oauth_version = $oauth_version; + $this->_oauth_scope = $scope; + + $this->_sandbox = (bool) $sandbox; + + $this->_consumer_key = $this->_client_id = $consumer_key_or_client_id; + $this->_consumer_secret = $this->_client_secret = $consumer_secret_or_client_secret; + $this->_debug = false; } /** * Turn on/off debug mode - * + * * @param boolean $true_or_false */ public function useDebugMode($true_or_false) { $this->_debug = (boolean) $true_or_false; } - + /** * Get the last error number - * + * * @return integer */ public function errorNumber() { return $this->_errnum; } - + /** * Get the last error message - * + * * @return string */ public function errorMessage() { return $this->_errmsg; } - + /** * Set an error message - * + * * @param integer $errnum The error number/code * @param string $errmsg The text error message * @return void @@ -124,52 +145,64 @@ public function lastResponse() { return $this->_last_response; } - + /** * Returns TRUE if an OAuth token exists for this user, FALSE otherwise - * - * @param string $app_username - * @return bool + * + * @param string $app_tenant The tenant to check to see if they are connected/auth'd + * @return bool */ - public function check($app_username, $app_tenant) + public function check($app_tenant) { - if ($arr = $this->load($app_username, $app_tenant)) + if ($arr = $this->load($app_tenant)) { return true; } - + return false; } - + /** * Test to see if a connection actually works (make sure you haven't been disconnected on Intuit's end) * + * @param string $app_tenant + * */ - public function test($app_username, $app_tenant) + public function test($app_tenant) { - if ($creds = $this->load($app_username, $app_tenant)) + if ($creds = $this->load($app_tenant)) { - $IPP = new QuickBooks_IPP(); - + $IPP = new QuickBooks_IPP($this->_dsn, $this->_key); + + if ($this->_oauth_version == self::OAUTH_V1) + { + $authmode = QuickBooks_IPP::AUTHMODE_OAUTHV1; + } + else if ($this->_oauth_version == self::OAUTH_V2) + { + $authmode = QuickBooks_IPP::AUTHMODE_OAUTHV2; + } + $IPP->authMode( - QuickBooks_IPP::AUTHMODE_OAUTH, - $app_username, + $authmode, $creds); - + + if ($this->_sandbox) + { + $IPP->sandbox(true); + } + if ($Context = $IPP->context()) { - // Set the DBID - $IPP->dbid($Context, 'something'); - // Set the IPP flavor $IPP->flavor($creds['qb_flavor']); - + // Get the base URL if it's QBO if ($creds['qb_flavor'] == QuickBooks_IPP_IDS::FLAVOR_ONLINE) { $cur_version = $IPP->version(); - $IPP->version(QuickBooks_IPP_IDS::VERSION_3); // Need v3 for this + $IPP->version(QuickBooks_IPP_IDS::VERSION_3); // Need v3 for this $CustomerService = new QuickBooks_IPP_Service_Customer(); $customers = $CustomerService->query($Context, $creds['qb_realm'], "SELECT * FROM Customer MAXRESULTS 1"); @@ -180,57 +213,69 @@ public function test($app_username, $app_tenant) $this->_setError($CustomerService->errorCode(), $CustomerService->errorMessage()); } - $IPP->version($cur_version); // Revert back to whatever they set - - //$IPP->baseURL($IPP->getBaseURL($Context, $creds['qb_realm'])); + $IPP->version($cur_version); // Revert back to whatever they set } else { $companies = $IPP->getAvailableCompanies($Context); } - - //print('[[' . $IPP->lastRequest() . ']]' . "\n\n"); - //print('[[' . $IPP->lastResponse() . ']]' . "\n\n"); - //print('here we are! [' . $IPP->errorCode() . ']'); - - // Check the last error code now... + + // Check the last error code now... if ($IPP->errorCode() == 401 or // most calls return this + $IPP->errorCode() == 3100 or // OAuth token error $IPP->errorCode() == 3200) // but for some stupid reason the getAvailableCompanies call returns this { return false; } - + return true; } } - + return false; } - + /** * Load OAuth credentials from the database * * @param string $app_username * @return array */ - public function load($app_username, $app_tenant) + public function load($app_tenant) { - if ($arr = $this->_driver->oauthLoad($this->_key, $app_username, $app_tenant) and - strlen($arr['oauth_access_token']) > 0 and - strlen($arr['oauth_access_token_secret']) > 0) + if ($this->_oauth_version == self::OAUTH_V1) { - $arr['oauth_consumer_key'] = $this->_consumer_key; - $arr['oauth_consumer_secret'] = $this->_consumer_secret; - - return $arr; + if ($arr = $this->_driver->oauthLoadV1($this->_key, $app_tenant) and + strlen($arr['oauth_access_token']) > 0 and + strlen($arr['oauth_access_token_secret']) > 0) + { + $arr['oauth_consumer_key'] = $this->_consumer_key; + $arr['oauth_consumer_secret'] = $this->_consumer_secret; + + return $arr; + } + } + else if ($this->_oauth_version == self::OAUTH_V2) + { + if ($arr = $this->_driver->oauthLoadV2($this->_key, $app_tenant) and + strlen($arr['oauth_access_token']) > 0 and + strlen($arr['oauth_refresh_token']) > 0) + { + $arr['oauth_client_id'] = $this->_client_id; + $arr['oauth_client_secret'] = $this->_client_secret; + + $arr['qb_flavor'] = QuickBooks_IPP_IDS::FLAVOR_ONLINE; + + return $arr; + } } - + return false; } - + /** * Check whether a connection is due for refresh/reconnect - * + * * @param string $app_username * @param string $app_tenant * @param integer $within @@ -240,8 +285,8 @@ public function expiry($app_username, $app_tenant, $within = 2592000) { $lifetime = 15552000; - if ($arr = $this->_driver->oauthLoad($this->_key, $app_username, $app_tenant) and - strlen($arr['oauth_access_token']) > 0 and + if ($arr = $this->_driver->oauthLoad($this->_key, $app_username, $app_tenant) and + strlen($arr['oauth_access_token']) > 0 and strlen($arr['oauth_access_token_secret']) > 0) { $expires = $lifetime + strtotime($arr['access_datetime']); @@ -265,31 +310,31 @@ public function expiry($app_username, $app_tenant, $within = 2592000) } /** - * Reconnect/refresh the OAuth tokens - * - * For this to succeed, the token expiration must be within 30 days of the - * date that this method is called (6 months after original token was - * created). This is an Intuit-imposed security restriction. Calls outside + * Reconnect/refresh the OAuth tokens + * + * For this to succeed, the token expiration must be within 30 days of the + * date that this method is called (6 months after original token was + * created). This is an Intuit-imposed security restriction. Calls outside * of that date range will fail with an error. - * + * * @param string $app_username * @param string $app_tenant */ public function reconnect($app_username, $app_tenant) { - if ($arr = $this->_driver->oauthLoad($this->_key, $app_username, $app_tenant) and - strlen($arr['oauth_access_token']) > 0 and + if ($arr = $this->_driver->oauthLoad($this->_key, $app_username, $app_tenant) and + strlen($arr['oauth_access_token']) > 0 and strlen($arr['oauth_access_token_secret']) > 0) { $arr['oauth_consumer_key'] = $this->_consumer_key; $arr['oauth_consumer_secret'] = $this->_consumer_secret; - - $retr = $this->_request(QuickBooks_IPP_OAuth::METHOD_GET, - QuickBooks_IPP_IntuitAnywhere::URL_CONNECT_RECONNECT, - array(), - $arr['oauth_access_token'], + + $retr = $this->_request(QuickBooks_IPP_OAuthv1::METHOD_GET, + QuickBooks_IPP_IntuitAnywhere::URL_CONNECT_RECONNECT, + array(), + $arr['oauth_access_token'], $arr['oauth_access_token_secret']); - + // Extract the error code $code = (int) QuickBooks_XML::extractTagContents('ErrorCode', $retr); $message = QuickBooks_XML::extractTagContents('ErrorMessage', $retr); @@ -301,16 +346,16 @@ public function reconnect($app_username, $app_tenant) } else { - // Success! Update the tokens + // Success! Update the tokens $token = QuickBooks_XML::extractTagContents('OAuthToken', $retr); $secret = QuickBooks_XML::extractTagContents('OAuthTokenSecret', $retr); $this->_driver->oauthAccessWrite( - $this->_key, - $arr['oauth_request_token'], - $token, + $this->_key, + $arr['oauth_request_token'], + $token, $secret, - null, + null, null); return true; @@ -326,47 +371,47 @@ public function disconnect($app_username, $app_tenant, $force = false) { $arr['oauth_consumer_key'] = $this->_consumer_key; $arr['oauth_consumer_secret'] = $this->_consumer_secret; - - $retr = $this->_request(QuickBooks_IPP_OAuth::METHOD_GET, - QuickBooks_IPP_IntuitAnywhere::URL_CONNECT_DISCONNECT, - array(), - $arr['oauth_access_token'], + + $retr = $this->_request(QuickBooks_IPP_OAuthv1::METHOD_GET, + QuickBooks_IPP_IntuitAnywhere::URL_CONNECT_DISCONNECT, + array(), + $arr['oauth_access_token'], $arr['oauth_access_token_secret']); - + // Extract the error code $code = (int) QuickBooks_XML::extractTagContents('ErrorCode', $retr); - - if ($code == 0 or + + if ($code == 0 or $code == 270 or // Sometimes it returns "270: OAuth Token rejected" for some reason? $force) { return $this->_driver->oauthAccessDelete($arr['app_username'], $arr['app_tenant']); } } - + return false; } - + public function fudge($request_token, $access_token, $access_token_secret, $realm, $flavor) { $this->_driver->oauthAccessWrite( - $this->_key, - $request_token, - $access_token, + $this->_key, + $request_token, + $access_token, $access_token_secret, - $realm, + $realm, $flavor); } - + /** * Handle an OAuth request login thing * - * + * */ - public function handle($app_username, $app_tenant) + public function handle($app_tenant) { - if ($this->check($app_username, $app_tenant) and // We have tokens ... - $this->test($app_username, $app_tenant)) // ... and they are valid + if ($this->check($app_tenant) and // We have tokens ... + $this->test($app_tenant)) // ... and they are valid { // They are already logged in, send them on to exchange data header('Location: ' . $this->_that_url); @@ -374,59 +419,30 @@ public function handle($app_username, $app_tenant) } else { - if (isset($_GET['oauth_token'])) + error_log(print_r($_REQUEST, true)); + + if ($this->_oauth_version == self::OAUTH_V1 and + isset($_GET['oauth_token'])) { - // We're in the middle of an OAuth token session - - /* - $arr = mysql_fetch_array(mysql_query(" - SELECT - * - FROM - quickbooks_oauth - WHERE - oauth_request_token = '" . $_REQUEST['oauth_token'] . "' ")); - */ - - if ($arr = $this->_driver->oauthRequestResolve($_GET['oauth_token'])) + // We're in the middle of an OAuth v1 token session + + if ($arr = $this->_driver->oauthRequestResolveV1($_GET['oauth_token'])) { $info = $this->_getAccessToken( - $arr['oauth_request_token'], - $arr['oauth_request_token_secret'], + $arr['oauth_request_token'], + $arr['oauth_request_token_secret'], $_GET['oauth_verifier']); - - //print('got back [' . $info . ']'); - //print_r($info); - //exit; - + if ($info) { - /* - mysql_query(" - UPDATE - quickbooks_oauth - SET - oauth_access_token = '" . $info['oauth_token'] . "', - oauth_access_token_secret = '" . $info['oauth_token_secret'] . "', - qb_realm = '" . $_REQUEST['realmId'] . "', - qb_flavor = '" . $_REQUEST['dataSource'] . "' - WHERE - quickbooks_oauth_id = " . $arr['quickbooks_oauth_id']); - */ - - $this->_driver->oauthAccessWrite( - $this->_key, - $arr['oauth_request_token'], - $info['oauth_token'], + $this->_driver->oauthAccessWriteV1( + $this->_key, + $arr['oauth_request_token'], + $info['oauth_token'], $info['oauth_token_secret'], - $_GET['realmId'], + $_GET['realmId'], $_GET['dataSource']); - - //print_r($_REQUEST); - //exit; - //print_r($info); - - //print('authd now, go here exchange_data.php'); + header('Location: ' . $this->_that_url); exit; } @@ -441,81 +457,180 @@ public function handle($app_username, $app_tenant) print('something went wrong... invalid oauth token?'); } } + else if ($this->_oauth_version == self::OAUTH_V2 and + !empty($_GET['code']) and + !empty($_GET['state']) and + $info = $this->_driver->oauthRequestResolveV2($_GET['state'])) + { + // Try to get an access/refresh token here + + if ($discover = $this->_discover()) + { + $ch = curl_init($discover['token_endpoint']); + + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + 'code' => $_GET['code'], + 'redirect_uri' => $this->_this_url, + 'grant_type' => 'authorization_code', + ))); + + curl_setopt($ch, CURLOPT_USERPWD, $this->_client_id . ':' . $this->_client_secret); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // Do not follow; security risk here + $retr = curl_exec($ch); + $info = curl_getinfo($ch); + + if ($info['http_code'] == 200) + { + $json = json_decode($retr, true); + + $this->_driver->oauthAccessWriteV2( + $this->_key, + $_GET['state'], + $json['access_token'], + $json['refresh_token'], + date('Y-m-d H:i:s', time() + (int) $json['expires_in']), + date('Y-m-d H:i:s', time() + (int) $json['x_refresh_token_expires_in']), + $_GET['realmId']); + + header('Location: ' . $this->_that_url); + exit; + } + else + { + print('An error occurred fetching the access/refresh token.'); + return false; + } + } + + } else { - $auth_url = $this->_getAuthenticateURL($app_username, $app_tenant, $this->_this_url); - + if ($this->_oauth_version == self::OAUTH_V1) + { + $auth_url = $this->_getAuthenticateURLV1($app_tenant, $this->_this_url); + } + else + { + $auth_url = $this->_getAuthenticateURLV2($app_tenant, $this->_this_url); + } + + if (!$auth_url) + { + print('Could not build an authorization URL.'); + return false; + } + // Forward them to the auth page header('Location: ' . $auth_url); exit; } } - + return true; } + protected function _getAuthenticateURLV2($app_tenant, $url) + { + if ($discover = $this->_discover()) + { + // Write the request to the database + $state = md5(mt_rand() . microtime(true)); + + $this->_driver->oauthRequestWriteV2($app_tenant, $state); + + $url = $discover['authorization_endpoint'] . '?' . http_build_query(array( + 'client_id' => $this->_client_id, + 'scope' => $this->_oauth_scope, + 'redirect_uri' => $url, + 'response_type' => 'code', + 'state' => $state, + )); + + return $url; + } + + return false; + } + + protected function _discover() + { + return self::discover($this->_sandbox); + } + + static public function discover($sandbox) + { + $url = self::URL_DISCOVERY_PRODUCTION; + if ($sandbox) + { + $url = self::URL_DISCOVERY_SANDBOX; + } + + // Make a request to the discovery URL + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // Do not follow; security risk here + $retr = curl_exec($ch); + $info = curl_getinfo($ch); + + if ($info['http_code'] == 200) + { + $out = json_decode($retr, true); + + return $out; + } + + return false; + } + /** - * - * + * + * * @param string $url * @return string - */ - protected function _getAuthenticateURL($app_username, $app_tenant, $url) + */ + protected function _getAuthenticateURLV1($app_tenant, $url) { // Fetch a request token from the OAuth service - $info = $this->_request(QuickBooks_IPP_OAuth::METHOD_GET, QuickBooks_IPP_IntuitAnywhere::URL_REQUEST_TOKEN, array( 'oauth_callback' => $url )); - - //print('info [' . $info . ']'); - + $info = $this->_request(QuickBooks_IPP_OAuthv1::METHOD_GET, QuickBooks_IPP_IntuitAnywhere::URL_REQUEST_TOKEN, array( 'oauth_callback' => $url )); + $vars = array(); parse_str($info, $vars); - + // Write the request tokens to the database - $this->_driver->oauthRequestWrite($app_username, $app_tenant, $vars['oauth_token'], $vars['oauth_token_secret']); - - /* - mysql_query(" - INSERT INTO - quickbooks_oauth - ( - oauth_request_token, - oauth_request_token_secret - ) VALUES ( - '" . $vars['oauth_token'] . "', - '" . $vars['oauth_token_secret'] . "' - )"); - */ - + $this->_driver->oauthRequestWriteV1($app_tenant, $vars['oauth_token'], $vars['oauth_token_secret']); + // Return the auth URL - return QuickBooks_IPP_IntuitAnywhere::URL_CONNECT_BEGIN . '?oauth_callback=' . urlencode($url) . '&oauth_consumer_key=' . $this->_consumer_key . '&oauth_token=' . $vars['oauth_token']; + return QuickBooks_IPP_IntuitAnywhere::URL_CONNECT_BEGIN . '?oauth_callback=' . urlencode($url) . '&oauth_consumer_key=' . $this->_consumer_key . '&oauth_token=' . $vars['oauth_token']; } - - protected function _getAccessToken($oauth_token, $oauth_token_secret, $verifier) + + protected function _getAccessToken($oauth_token, $oauth_token_secret, $verifier) { - if ($str = $this->_request(QuickBooks_IPP_OAuth::METHOD_GET, QuickBooks_IPP_IntuitAnywhere::URL_ACCESS_TOKEN, - array( - 'oauth_token' => $oauth_token, - 'oauth_secret' => $oauth_token_secret, - 'oauth_verifier' => $verifier, + if ($str = $this->_request(QuickBooks_IPP_OAuthv1::METHOD_GET, QuickBooks_IPP_IntuitAnywhere::URL_ACCESS_TOKEN, + array( + 'oauth_token' => $oauth_token, + 'oauth_secret' => $oauth_token_secret, + 'oauth_verifier' => $verifier, ))) { $info = array(); parse_str($str, $info); - - return $info; + + return $info; } - + return false; } - + public function widgetConnect() { - + } - + /** * This function returns the html for displaying the "Blue Dot" menu - * + * * As per Intuit's recommendation, your app should call this function before the user clicks the * blue dot menu and cache it. This will improve the user's experience and prevent unnecessary API * calls to Intuit's web service. See: @@ -529,7 +644,7 @@ public function widgetConnect() * print $html; * exit; * } - * + * * @param string $app_username * @param string $app_tenant * @return html string @@ -538,69 +653,69 @@ public function widgetMenu($app_username, $app_tenant) { $token = null; $secret = null; - + if ($creds = $this->load($app_username, $app_tenant)) { return $this->_request( - QuickBooks_IPP_OAuth::METHOD_GET, - QuickBooks_IPP_IntuitAnywhere::URL_APP_MENU, - array(), - $creds['oauth_access_token'], + QuickBooks_IPP_OAuthv1::METHOD_GET, + QuickBooks_IPP_IntuitAnywhere::URL_APP_MENU, + array(), + $creds['oauth_access_token'], $creds['oauth_access_token_secret']); } - + return ''; } - protected function _request($method, $url, $params = array(), $token = null, $secret = null, $data = null) + protected function _request($method, $url, $params = array(), $token = null, $secret = null, $data = null) { - $OAuth = new QuickBooks_IPP_OAuth($this->_consumer_key, $this->_consumer_secret); - + $OAuth = new QuickBooks_IPP_OAuthv1($this->_consumer_key, $this->_consumer_secret); + // This returns a signed request - // + // // 0 => signature base string // 1 => signature // 2 => normalized url // 3 => header string $signed = $OAuth->sign($method, $url, $token, $secret, $params); - + //print_r($signed); - + // Create the new HTTP object //$HTTP = new QuickBooks_HTTP($url); $HTTP = new QuickBooks_HTTP($signed[2]); - + $headers = array( - //'Authorization' => $signed[3], + //'Authorization' => $signed[3], ); - + $HTTP->setHeaders($headers); - - // + + // $HTTP->setRawBody($data); - + // We need the headers back //$HTTP->returnHeaders(true); - + // Send the request $return = $HTTP->GET(); - + $errnum = $HTTP->errorNumber(); $errmsg = $HTTP->errorMessage(); $this->_last_request = $HTTP->lastRequest(); $this->_last_response = $HTTP->lastResponse(); - + if ($errnum) { // An error occurred! $this->_setError(QuickBooks_IPP::ERROR_HTTP, $errnum . ': ' . $errmsg); return false; } - + // Everything is good, return the data! $this->_setError(QuickBooks_IPP::ERROR_OK, ''); - return $return; + return $return; } } diff --git a/QuickBooks/IPP/OAuth.php b/QuickBooks/IPP/OAuthv1.php similarity index 84% rename from QuickBooks/IPP/OAuth.php rename to QuickBooks/IPP/OAuthv1.php index fd9d57f6..7d6e0a1d 100755 --- a/QuickBooks/IPP/OAuth.php +++ b/QuickBooks/IPP/OAuthv1.php @@ -2,26 +2,26 @@ /** * QuickBooks PHP DevKit - * + * * Copyright (c) 2010 Keith Palmer / ConsoliBYTE, LLC. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.opensource.org/licenses/eclipse-1.0.php - * + * * @author Keith Palmer - * @license LICENSE.txt - * + * @license LICENSE.txt + * * @package QuickBooks */ -class QuickBooks_IPP_OAuth +class QuickBooks_IPP_OAuthv1 { private $_secrets; protected $_oauth_consumer_key; protected $_oauth_consumer_secret; - + protected $_oauth_access_token; protected $_oauth_access_token_secret; @@ -30,7 +30,7 @@ class QuickBooks_IPP_OAuth protected $_keyfile; /** - * + * */ const NONCE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; @@ -46,80 +46,80 @@ class QuickBooks_IPP_OAuth const SIGNATURE_HMAC = 'HMAC-SHA1'; const SIGNATURE_RSA = 'RSA-SHA1'; - /** + /** * Create our OAuth instance */ public function __construct($oauth_consumer_key, $oauth_consumer_secret) { $this->_oauth_consumer_key = $oauth_consumer_key; $this->_oauth_consumer_secret = $oauth_consumer_secret; - - $this->_version = QuickBooks_IPP_OAuth::DEFAULT_VERSION; - $this->_signature = QuickBooks_IPP_OAuth::DEFAULT_SIGNATURE; + + $this->_version = self::DEFAULT_VERSION; + $this->_signature = self::DEFAULT_SIGNATURE; } - + /** * Set the signature method - * - * + * + * */ public function signature($method, $keyfile = null) { $this->_signature = $method; $this->_keyfile = $keyfile; } - + /** * Sign an OAuth request and return the signing data (auth string, URL, etc.) * - * + * */ - public function sign($method, $url, $oauth_token = null, $oauth_token_secret = null, $params = array()) + public function sign($method, $url, $oauth_token = null, $oauth_token_secret = null, $params = array()) { /* print('got in: [' . $method . '], ' . $url); print_r($params); print('


'); */ - + if (!is_array($params)) { $params = array(); } - + $params = array_merge($params, array( - 'oauth_consumer_key' => $this->_oauth_consumer_key, - 'oauth_signature_method' => $this->_signature, - 'oauth_nonce' => $this->_nonce(), - 'oauth_timestamp' => $this->_timestamp(), + 'oauth_consumer_key' => $this->_oauth_consumer_key, + 'oauth_signature_method' => $this->_signature, + 'oauth_nonce' => $this->_nonce(), + 'oauth_timestamp' => $this->_timestamp(), 'oauth_version' => $this->_version, )); - + // Add in the tokens if they were passed in if ($oauth_token) { $params['oauth_token'] = $oauth_token; } - + if ($oauth_token_secret) { $params['oauth_secret'] = $oauth_token_secret; } - + // Generate the signature $signature_and_basestring = $this->_generateSignature($this->_signature, $method, $url, $params); - + $params['oauth_signature'] = $signature_and_basestring[1]; - + /* print('
');
 		print('BASE STRING IS [' . $signature_and_basestring[0] . ']' . "\n\n");
 		print('SIGNATURE IS: [' . $params['oauth_signature'] . ']');
 		print('
'); */ - + $normalized = $this->_normalize($params); - + /* print('NORMALIZE 1 [' . $normalized . ']' . "\n"); print('NORMZLIZE 2 [' . $this->_normalize2($params) . ']' . "\n"); @@ -129,43 +129,43 @@ public function sign($method, $url, $oauth_token = null, $oauth_token_secret = n { $url = substr($url, 0, $pos); } - + $normalized_url = $url . '?' . $normalized; // normalized URL return array ( 0 => $signature_and_basestring[0], // signature basestring 1 => $signature_and_basestring[1], // signature - 2 => $normalized_url, + 2 => $normalized_url, 3 => $this->_generateHeader($params, $normalized), // header string ); } - protected function _generateHeader($params, $normalized) + protected function _generateHeader($params, $normalized) { - // oauth_signature="' . $this->_escape($params['oauth_signature']) . '", + // oauth_signature="' . $this->_escape($params['oauth_signature']) . '", - $str = 'OAuth realm="", - oauth_signature_method="' . $params['oauth_signature_method'] . '", - oauth_signature="' . $this->_escape($params['oauth_signature']) . '", - oauth_nonce="' . $params['oauth_nonce'] . '", + $str = 'OAuth realm="", + oauth_signature_method="' . $params['oauth_signature_method'] . '", + oauth_signature="' . $this->_escape($params['oauth_signature']) . '", + oauth_nonce="' . $params['oauth_nonce'] . '", oauth_timestamp="' . $params['oauth_timestamp'] . '", '; - + if (isset($params['oauth_token'])) { $str .= ' oauth_token="' . $params['oauth_token'] . '", '; } - - $str .= ' oauth_consumer_key="' . $params['oauth_consumer_key'] . '", + + $str .= ' oauth_consumer_key="' . $params['oauth_consumer_key'] . '", oauth_version="' . $params['oauth_version'] . '"'; - + return str_replace(array(' ', ' '), ' ', str_replace(array("\r", "\n", "\t"), ' ', $str)); } /** - * - * + * + * */ - protected function _escape($str) + protected function _escape($str) { if ($str === false) { @@ -176,28 +176,28 @@ protected function _escape($str) return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode($str))); } } - + protected function _timestamp() { //return 1326976195; - + //return 1318622958; return time(); } - protected function _nonce($len = 5) + protected function _nonce($len = 5) { //return '1234'; - - $tmp = str_split(QuickBooks_IPP_OAuth::NONCE); + + $tmp = str_split(self::NONCE); shuffle($tmp); - + //return 'kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg'; return substr(implode('', $tmp), 0, $len); } - + protected function _normalize($params) - { + { $normalized = array(); ksort($params); @@ -221,25 +221,25 @@ protected function _normalize($params) } } } - + return implode('&', $normalized); } - protected function _generateSignature($signature, $method, $url, $params = array()) + protected function _generateSignature($signature, $method, $url, $params = array()) { /* print('
params for signing');
 		print_r($params);
 		print('
'); */ - + //if (false !== strpos($url, 'get_access')) /*if (true) { print($url . '
' . "\r\n\r\n"); die('NORMALIZE MINE [' . $this->_normalize($params) . ']'); }*/ - + /* print('
');
 		print('NORMALIZING [' . "\n");
@@ -247,7 +247,7 @@ protected function _generateSignature($signature, $method, $url, $params = array
 		print('SECRET KEY FOR SIGNING [' . $secret . ']' . "\n");
 		print('
'); */ - + if (false !== ($pos = strpos($url, '?'))) { $tmp = array(); @@ -274,83 +274,83 @@ protected function _generateSignature($signature, $method, $url, $params = array //print_r($params); $sbs = $this->_escape($method) . '&' . $this->_escape($url) . '&' . $this->_escape($this->_normalize($params)); - + //print('sbs [' . $sbs . ']' . "\n"); - // Which signature method? + // Which signature method? switch ($signature) { - case QuickBooks_IPP_OAuth::SIGNATURE_HMAC: - return $this->_generateSignature_HMAC($sbs, $method, $url, $params); - case QuickBooks_IPP_OAuth::SIGNATURE_RSA: + case self::SIGNATURE_HMAC: + return $this->_generateSignature_HMAC($sbs, $method, $url, $params); + case self::SIGNATURE_RSA: return $this->_generateSignature_RSA($sbs, $method, $url, $params); } - + return false; } - + /* // Pull the private key ID from the certificate $privatekeyid = openssl_get_privatekey($cert); - + // Sign using the key $sig = false; - $ok = openssl_sign($base_string, $sig, $privatekeyid); - + $ok = openssl_sign($base_string, $sig, $privatekeyid); + // Release the key resource openssl_free_key($privatekeyid); - + base64_encode($sig) */ - - + + protected function _generateSignature_RSA($sbs, $method, $url, $params = array()) { - // $res = ... + // $res = ... $res = openssl_pkey_get_private('file://' . $this->_keyfile); - + /* print('key id is: ['); print_r($res); print(']'); print("\n\n\n"); */ - + $signature = null; $retr = openssl_sign($sbs, $signature, $res); - + openssl_free_key($res); - + return array( - 0 => $sbs, - 1 => base64_encode($signature), + 0 => $sbs, + 1 => base64_encode($signature), ); } - - - + + + /* $key = $request->urlencode($consumer_secret).'&'.$request->urlencode($token_secret); $signature = base64_encode(hash_hmac("sha1", $base_string, $key, true)); - */ - + */ + protected function _generateSignature_HMAC($sbs, $method, $url, $params = array()) { $secret = $this->_escape($this->_oauth_consumer_secret); - + $secret .= '&'; - + if (!empty($params['oauth_secret'])) { $secret .= $this->_escape($params['oauth_secret']); } //print('generating signature from [' . $secret . ']' . "\n\n"); - + return array( - 0 => $sbs, - 1 => base64_encode(hash_hmac('sha1', $sbs, $secret, true)), + 0 => $sbs, + 1 => base64_encode(hash_hmac('sha1', $sbs, $secret, true)), ); } } diff --git a/QuickBooks/IPP/Service/Estimate.php b/QuickBooks/IPP/Service/Estimate.php index f7e6b56f..ce8cfaf8 100644 --- a/QuickBooks/IPP/Service/Estimate.php +++ b/QuickBooks/IPP/Service/Estimate.php @@ -1,17 +1,17 @@ - * + * * @package QuickBooks * @subpackage IPP */ @@ -26,17 +26,22 @@ public function findAll($Context, $realmID) $xml = null; return parent::_findAll($Context, $realmID, QuickBooks_IPP_IDS::RESOURCE_ESTIMATE, $xml); } - + public function add($Context, $realmID, $Object) { return parent::_add($Context, $realmID, QuickBooks_IPP_IDS::RESOURCE_ESTIMATE, $Object); - } - + } + + public function update($Context, $realmID, $IDType, $Object) + { + return parent::_update($Context, $realmID, QuickBooks_IPP_IDS::RESOURCE_ESTIMATE, $Object, $IDType); + } + /** - * Get an estimate by ID - * - * @param QuickBooks_IPP_Context $Context - * @param string $realmID + * Get an estimate by ID + * + * @param QuickBooks_IPP_Context $Context + * @param string $realmID * @param string $ID The ID of the estimate (this expects an IdType, which includes the domain) * @return QuickBooks_IPP_Object_Employee The estimate object */ @@ -44,7 +49,7 @@ public function findById($Context, $realmID, $ID) { $xml = null; return parent::_findById($Context, $realmID, QuickBooks_IPP_IDS::RESOURCE_ESTIMATE, $ID, null, $xml); - } + } public function query($Context, $realm, $query) { diff --git a/README.md b/README.md index 4b6441f8..511d7495 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,45 @@ QuickBooks PHP DevKit ===================== -QuickBooks integration support for PHP 5.x+ - +QuickBooks integration support for PHP 5.x+ + The package you've downloaded contains code and documentation for connecting various versions and editions of QuickBooks to PHP, allowing your PHP applications to do fancy things like: - Automatically send orders placed on your website to QuickBooks Online or QuickBooks for Windows - Charge credit cards using Intuit Payments / QuickBooks Merchant Services - Connect to QuickBooks v3 APIs via OAuth -- Get access to QuickBooks reports -- Pull information out of QuickBooks and display it online +- Get access to QuickBooks reports +- Pull information out of QuickBooks and display it online - Connect to all Microsoft Windows versions of QuickBooks - Connect to all QuickBooks Online versions - Authenticate via OAuth - etc. etc. etc. -Almost anything you can do in the QuickBooks GUI, in QuickBooks Online Edition, and with QuickBooks Merchant Service can be accomplished via this framework. +Almost anything you can do in the QuickBooks GUI, in QuickBooks Online Edition, and with QuickBooks Merchant Service can be accomplished via this framework. ## Quick Start Guides -* QuickBooks FOR WINDOWS (via QuickBooks Web Connector) - read the [quick start guide for the Web Connector/QuickBooks for Windows](http://www.consolibyte.com/docs/index.php/PHP_DevKit_for_QuickBooks_-_Quick-Start) +* QuickBooks FOR WINDOWS (via QuickBooks Web Connector) - read the [quick start guide for the Web Connector/QuickBooks for Windows](http://www.consolibyte.com/docs/index.php/PHP_DevKit_for_QuickBooks_-_Quick-Start) * QuickBooks ONLINE (via Intuit Partner Platform/Intuit Anywhere) - read the [quick start guide for Intuit Partner Platform/QuickBooks Online] (http://www.consolibyte.com/docs/index.php/PHP_DevKit_for_QuickBooks_-_Intuit_Partner_Platform_Quick-Start) +## OAuth 1.0 to OAuth 2.0 migration + +You can find information on how to migrate your app from OAuth v1.0 to OAuth v2.0 below. We are also working on getting OpenID Connect and an automated token migration process ready -- coming soon. + +* + ## Updates and Improvements -Please follow me on Twitter to be notified about updates/improvements: +Please follow me on Twitter to be notified about updates/improvements: - https://twitter.com/keith_palmer_jr ## Support -If you have questions, suggestions, or find a bug, the absolute best way to get support, report bugs, or ask for help is to ask on the forums: +If you have questions, suggestions, or find a bug, the absolute best way to get support, report bugs, or ask for help is to ask on the forums: - http://stackoverflow.com/ (This is the best place to get support -- *make sure you post your code*) - https://intuitpartnerplatform.lc.intuit.com/ @@ -41,10 +47,10 @@ If you have questions, suggestions, or find a bug, the absolute best way to get ## Examples -You will find examples in the docs/ folder. +You will find examples in the docs/ folder. -### Examples for QuickBooks ONLINE +### Examples for QuickBooks ONLINE If you are using *QuickBooks ONLINE*, then you need to look in this folder for examples: @@ -53,21 +59,21 @@ If you are using *QuickBooks ONLINE*, then you need to look in this folder for e Make sure you look at the [quick start guide for Intuit Partner Platform/QuickBooks Online] (http://www.consolibyte.com/docs/index.php/PHP_DevKit_for_QuickBooks_-_Intuit_Partner_Platform_Quick-Start) -### Examples for QuickBooks FOR WINDOWS +### Examples for QuickBooks FOR WINDOWS If you are using *QuickBooks FOR WINDOWS*, then you need to look in this folder for examples: * docs/web_connector/ -Make sure you look at the [quick start guide for the Web Connector/QuickBooks for Windows](http://www.consolibyte.com/docs/index.php/PHP_DevKit_for_QuickBooks_-_Quick-Start) +Make sure you look at the [quick start guide for the Web Connector/QuickBooks for Windows](http://www.consolibyte.com/docs/index.php/PHP_DevKit_for_QuickBooks_-_Quick-Start) ### Additional Info -There is additional documentation and additional examples on our legacy and new wikis: +There is additional documentation and additional examples on our legacy and new wikis: -- http://wiki.consolibyte.com/wiki/doku.php/quickbooks (legacy) -- http://www.consolibyte.com/docs/index.php/QuickBooks (new wiki) +- http://wiki.consolibyte.com/wiki/doku.php/quickbooks (legacy) +- http://www.consolibyte.com/docs/index.php/QuickBooks (new wiki) diff --git a/README_OAUTHV1_TO_OAUTHV2.md b/README_OAUTHV1_TO_OAUTHV2.md new file mode 100644 index 00000000..feb7d518 --- /dev/null +++ b/README_OAUTHV1_TO_OAUTHV2.md @@ -0,0 +1,142 @@ + +# Migrating from OAuth 1.0 to OAuth 2.0 + +## Automatic migration (auto-migrate existing OAuth 1.0 tokens to OAuth 2.0 tokens) + +@todo + +## Manual migration (have people re-connect manually to get OAuth 2.0 tokens) + +0. Make a backup of your code +0. Make a backup of your existing OAuth v1.0 tokens +0. `git clone` the repository to get the new code +0. Make code changes as detailed below +0. Go get your `Client ID` and `Client Secret` from `developer.intuit.com` for your app + +## Code change - IntuitAnywhere constructor + +This constructor for the `QuickBooks_IPP_IntuitAnywhere` class has changed. + +Anywhere you see this: + +``` +new QuickBooks_IPP_IntuitAnywhere($dsn, $encryption_key, $oauth_consumer_key, $oauth_consumer_secret, $quickbooks_oauth_url, $quickbooks_success_url); +``` + +Needs to change to this (note hte new parameters): + +``` +new QuickBooks_IPP_IntuitAnywhere(QuickBooks_IPP_IntuitAnywhere::OAUTH_V2, $sandbox, $scope, $dsn, $encryption_key, $oauth_client_id, $oauth_client_secret, $quickbooks_oauth_url, $quickbooks_success_url); +``` + +## Database changes + +``` +ALTER TABLE `quickbooks_oauth` +RENAME TO `quickbooks_oauthv1`; +``` + +``` +ALTER TABLE `quickbooks_oauthv1` +CHANGE `quickbooks_oauth_id` `quickbooks_oauthv1_id` int(10) unsigned NOT NULL AUTO_INCREMENT FIRST; +``` + +``` +CREATE TABLE `quickbooks_oauthv2` ( + `quickbooks_oauthv2_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `app_tenant` varchar(255) NOT NULL, + `oauth_state` varchar(255) DEFAULT NULL, + `oauth_access_token` text, + `oauth_refresh_token` text, + `oauth_access_expiry` datetime DEFAULT NULL, + `oauth_refresh_expiry` datetime DEFAULT NULL, + `qb_realm` varchar(32) DEFAULT NULL, + `request_datetime` datetime NOT NULL, + `access_datetime` datetime DEFAULT NULL, + `last_access_datetime` datetime DEFAULT NULL, + `last_refresh_datetime` datetime DEFAULT NULL, + `touch_datetime` datetime DEFAULT NULL, + PRIMARY KEY (`quickbooks_oauthv2_id`) +); +``` + +## Code change - IntuitAnywhere->load(...) + +The `->load(...)` method has changed, dropping the `$the_username` parameter. Where you see this: + +``` +$creds = $IntuitAnywhere->load($the_username, $the_tenant); +``` + +Change it to this: + + ``` +$creds = $IntuitAnywhere->load($the_tenant); +``` + +## Code change - IntuitAnywhere->check(...) + +The `->check(...)` method has changed, dropping the `$the_username` parameter. Change: + +``` +if ($IntuitAnywhere->check($the_username, $the_tenant) +``` + +To this: + +``` +if ($IntuitAnywhere->check($the_username, $the_tenant) +``` + +## Code change - IntuitAnywhere->test(...) + +The `->test(...)` method has changed, dropping the `$the_username` parameter. Change: + +``` +$IntuitAnywhere->test($the_tenant)) +``` + +To this: + +``` +$IntuitAnywhere->test($the_tenant)) +``` + +## Code change - IPP->authMode(...) + +The parameters to the `->authMode(...)` method have changed. Change: + +``` +$IPP->authMode( + QuickBooks_IPP::AUTHMODE_OAUTH, + $creds); +``` + +To this: + +``` +$IPP->authMode( + QuickBooks_IPP::AUTHMODE_OAUTHV2, + $creds); +``` + +## Code change - QuickBooks_IPP(...) constructor + +The `QuickBooks_IPP(...)` constructor needs an additional parameter. Change this: + +``` +// Set up the IPP instance +$IPP = new QuickBooks_IPP($dsn); +``` + +To this: + +``` +// Set up the IPP instance +$IPP = new QuickBooks_IPP($dsn, $encryption_key); +``` + +## Reconnect + +1. Disconnect from QuickBooks (or clear your token from the `quickbooks_oauthv1` SQL table). +1. Reconnect to QuickBooks (you should see a slightly different OAuth process, and see entries appear in the `quickbooks_oauthv2` SQL table). \ No newline at end of file diff --git a/docs/partner_platform/example_app_ipp_v3/config.php b/docs/partner_platform/example_app_ipp_v3/config_oauthv1.php similarity index 65% rename from docs/partner_platform/example_app_ipp_v3/config.php rename to docs/partner_platform/example_app_ipp_v3/config_oauthv1.php index 111cea5e..2a87a3ae 100755 --- a/docs/partner_platform/example_app_ipp_v3/config.php +++ b/docs/partner_platform/example_app_ipp_v3/config_oauthv1.php @@ -2,9 +2,9 @@ /** * Intuit Partner Platform configuration variables - * - * See the scripts that use these variables for more details. - * + * + * See the scripts that use these variables for more details. + * * @package QuickBooks * @subpackage Documentation */ @@ -17,36 +17,36 @@ require_once dirname(__FILE__) . '/../../../QuickBooks.php'; // Your application token (Intuit will give you this when you register an Intuit Anywhere app) -$token = '95555248baf11b43fbb944ab97de9134ad85'; +$token = '83e26868b5134b49beb97d8bd2a3e57755ca'; // Your OAuth consumer key and secret (Intuit will give you both of these when you register an Intuit app) -// +// // IMPORTANT: -// To pass your tech review with Intuit, you'll have to AES encrypt these and -// store them somewhere safe. -// -// The OAuth request/access tokens will be encrypted and stored for you by the -// PHP DevKit IntuitAnywhere classes automatically. -$oauth_consumer_key = 'qyprdfkqo3bikN2vLrLu4FWHv6GbQp'; -$oauth_consumer_secret = 'WDH56afDb1jr0ismQZAwdPuq4oDqpTmrKXc0oORz'; +// To pass your tech review with Intuit, you'll have to AES encrypt these and +// store them somewhere safe. +// +// The OAuth request/access tokens will be encrypted and stored for you by the +// PHP DevKit IntuitAnywhere classes automatically. +$oauth_consumer_key = 'qyprdlGJ4gWv4sMW0syilH2o4KirQe'; +$oauth_consumer_secret = '49ou99QiG47KhvY6AaMPSnHhXXNMAJxLv7QXNm4L'; // If you're using DEVELOPMENT TOKENS, you MUST USE SANDBOX MODE!!! If you're in PRODUCTION, then DO NOT use sandbox. $sandbox = true; // When you're using development tokens //$sandbox = false; // When you're using production tokens // This is the URL of your OAuth auth handler page -$quickbooks_oauth_url = 'http://quickbooks.v3.com:8888/quickbooks-php/docs/partner_platform/example_app_ipp_v3/oauth.php'; +$quickbooks_oauth_url = 'http://sandbox.com/quickbooks-php/docs/partner_platform/example_app_ipp_v3/oauth.php'; // This is the URL to forward the user to after they have connected to IPP/IDS via OAuth -$quickbooks_success_url = 'http://quickbooks.v3.com:8888/quickbooks-php/docs/partner_platform/example_app_ipp_v3/success.php'; +$quickbooks_success_url = 'http://sandbox.com/quickbooks-php/docs/partner_platform/example_app_ipp_v3/success.php'; -// This is the menu URL script -$quickbooks_menu_url = 'http://quickbooks.v3.com:8888/quickbooks-php/docs/partner_platform/example_app_ipp_v3/menu.php'; +// This is the menu URL script +$quickbooks_menu_url = 'http://sandbox.com/quickbooks-php/docs/partner_platform/example_app_ipp_v3/menu.php'; -// This is a database connection string that will be used to store the OAuth credentials +// This is a database connection string that will be used to store the OAuth credentials // $dsn = 'pgsql://username:password@hostname/database'; // $dsn = 'mysql://username:password@hostname/database'; -$dsn = 'mysqli://quickbooks:password@localhost/quickbooks_php'; +$dsn = 'mysqli://dev:password@localhost/quickbooks'; // You should set this to an encryption key specific to your app $encryption_key = 'bcde1234'; @@ -57,6 +57,9 @@ // The tenant that user is accessing within your own app $the_tenant = 12345; +// Scope is n/a for OAuth1 +$scope = null; // n/a for OAuth1 + // Initialize the database tables for storing OAuth information if (!QuickBooks_Utilities::initialized($dsn)) { @@ -64,39 +67,47 @@ QuickBooks_Utilities::initialize($dsn); } -// Instantiate our Intuit Anywhere auth handler -// +// Instantiate our Intuit Anywhere auth handler +// // The parameters passed to the constructor are: -// $dsn +// $dsn // $oauth_consumer_key Intuit will give this to you when you create a new Intuit Anywhere application at AppCenter.Intuit.com // $oauth_consumer_secret Intuit will give this to you too // $this_url This is the full URL (e.g. http://path/to/this/file.php) of THIS SCRIPT // $that_url After the user authenticates, they will be forwarded to this URL -// -$IntuitAnywhere = new QuickBooks_IPP_IntuitAnywhere($dsn, $encryption_key, $oauth_consumer_key, $oauth_consumer_secret, $quickbooks_oauth_url, $quickbooks_success_url); - -// Are they connected to QuickBooks right now? -if ($IntuitAnywhere->check($the_username, $the_tenant) and - $IntuitAnywhere->test($the_username, $the_tenant)) +// +$IntuitAnywhere = new QuickBooks_IPP_IntuitAnywhere( + QuickBooks_IPP_IntuitAnywhere::OAUTH_V1, + $sandbox, + $scope, + $dsn, + $encryption_key, + $oauth_consumer_key, + $oauth_consumer_secret, + $quickbooks_oauth_url, + $quickbooks_success_url); + +// Are they connected to QuickBooks right now? +if ($IntuitAnywhere->check($the_tenant) and + $IntuitAnywhere->test($the_tenant)) { - // Yes, they are + // Yes, they are $quickbooks_is_connected = true; // Set up the IPP instance - $IPP = new QuickBooks_IPP($dsn); + $IPP = new QuickBooks_IPP($dsn, $encryption_key); // Get our OAuth credentials from the database - $creds = $IntuitAnywhere->load($the_username, $the_tenant); + $creds = $IntuitAnywhere->load($the_tenant); // Tell the framework to load some data from the OAuth store $IPP->authMode( - QuickBooks_IPP::AUTHMODE_OAUTH, - $the_username, + QuickBooks_IPP::AUTHMODE_OAUTHV1, $creds); if ($sandbox) { - // Turn on sandbox mode/URLs + // Turn on sandbox mode/URLs $IPP->sandbox(true); } diff --git a/docs/partner_platform/example_app_ipp_v3/config_oauthv2.php b/docs/partner_platform/example_app_ipp_v3/config_oauthv2.php new file mode 100644 index 00000000..d3c2c80c --- /dev/null +++ b/docs/partner_platform/example_app_ipp_v3/config_oauthv2.php @@ -0,0 +1,126 @@ +check($the_tenant) and + $IntuitAnywhere->test($the_tenant)) +{ + // Yes, they are + $quickbooks_is_connected = true; + + // Set up the IPP instance + $IPP = new QuickBooks_IPP($dsn, $encryption_key); + + // Get our OAuth credentials from the database + $creds = $IntuitAnywhere->load($the_tenant); + + // Tell the framework to load some data from the OAuth store + $IPP->authMode( + QuickBooks_IPP::AUTHMODE_OAUTHV2, + $creds); + + if ($sandbox) + { + // Turn on sandbox mode/URLs + $IPP->sandbox(true); + } + + // Print the credentials we're using + //print_r($creds); + + // This is our current realm + $realm = $creds['qb_realm']; + + // Load the OAuth information from the database + $Context = $IPP->context(); + + // Get some company info + $CompanyInfoService = new QuickBooks_IPP_Service_CompanyInfo(); + $quickbooks_CompanyInfo = $CompanyInfoService->get($Context, $realm); +} +else +{ + // No, they are not + $quickbooks_is_connected = false; +} \ No newline at end of file diff --git a/docs/partner_platform/example_app_ipp_v3/example_access_with_xpath_objects.php b/docs/partner_platform/example_app_ipp_v3/example_access_with_xpath_objects.php index 08241ced..4f0226dc 100644 --- a/docs/partner_platform/example_app_ipp_v3/example_access_with_xpath_objects.php +++ b/docs/partner_platform/example_app_ipp_v3/example_access_with_xpath_objects.php @@ -1,6 +1,6 @@ lastError() . ']'); print("\n\n\n\n"); -*/ +*/ ?> diff --git a/docs/partner_platform/example_app_ipp_v3/example_customer_update.php b/docs/partner_platform/example_app_ipp_v3/example_customer_update.php index e9c3bb33..29270b19 100644 --- a/docs/partner_platform/example_app_ipp_v3/example_customer_update.php +++ b/docs/partner_platform/example_app_ipp_v3/example_customer_update.php @@ -1,6 +1,6 @@ lastRequest() . ']'); -print("\n\n\n\n"); -print('Response [' . $IPP->lastResponse() . ']'); -print("\n\n\n\n"); -*/ + +//print("\n\n\n\n"); +//print('Request [' . $IPP->lastRequest() . ']'); +//print("\n\n\n\n"); +//print('Response [' . $IPP->lastResponse() . ']'); +//print("\n\n\n\n"); + ?> diff --git a/docs/partner_platform/example_app_ipp_v3/example_invoice_with_custom_fields_add.php b/docs/partner_platform/example_app_ipp_v3/example_invoice_with_custom_fields_add.php index db02ccee..24d1a608 100644 --- a/docs/partner_platform/example_app_ipp_v3/example_invoice_with_custom_fields_add.php +++ b/docs/partner_platform/example_app_ipp_v3/example_invoice_with_custom_fields_add.php @@ -1,6 +1,6 @@ handle($the_username, $the_tenant)) +// For OAuth2 (all new application, and what you should be migrating to) +require_once dirname(__FILE__) . '/config_oauthv2.php'; + +// For old/legacy applications +//require_once dirname(__FILE__) . '/config_oauthv1.php'; + +// Try to handle the OAuth request +if ($IntuitAnywhere->handle($the_tenant)) { - ; // The user has been connected, and will be redirected to $that_url automatically. + ; // The user has been connected, and will be redirected to $that_url automatically. } else {