Skip to content

Commit

Permalink
Updates to 1.9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Woo committed Oct 15, 2024
1 parent 9138b24 commit 967e01a
Show file tree
Hide file tree
Showing 707 changed files with 21,650 additions and 7,356 deletions.
9 changes: 9 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
*** Xero Integration ***

2024-10-14 - version 1.9.0
* Fix - Use a separate country list for the report tax type when creating a tax rate in Xero.
* Dev - Update secret key encryption to use the Sodium library for token encryption.
* Dev - Upgrade the `xeroapi/xero-php-oauth2` SDK from version 2.20.0 to version 7.2.0.
* Dev - Bump WooCommerce "tested up to" version 9.4.
* Dev - Bump WooCommerce minimum supported version to 9.2.
* Dev - Bump WordPress minimum supported version to 6.5.
* Dev - Remove Xero OAuth 1.0 Authentication as it is fully deprecated.

2024-08-19 - version 1.8.9
* Fix - PHPCompatibility errors reported by the QIT test.
* Dev - Bump WooCommerce "tested up to" version 9.2.
Expand Down
62 changes: 51 additions & 11 deletions includes/class-wc-xr-data-encryption.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,32 +118,72 @@ private function get_default_salt(): string {
*
* @param string $value Value to encryption.
*
* @since 1.9.0 Updated to use sodium_crypto_secretbox.
* @since 1.7.51
*
* @return string|bool Encrypted value. If encryption fails, returns the original value or false under certain conditions.
*/
public function encrypt( string $value ): string {
if ( ! extension_loaded( 'openssl' ) ) {
try {
$key = sodium_crypto_generichash( $this->key, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES );
$nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
} catch ( Exception $e ) {
// Return the original value if fail to generate nonce and key.
return $value;
}

$method = 'aes-256-ctr';
$ivlen = openssl_cipher_iv_length( $method );
$iv = openssl_random_pseudo_bytes( $ivlen );

$raw_value = openssl_encrypt( $value . $this->salt, $method, $this->key, 0, $iv );
if ( ! $raw_value ) {
try {
$encrypted = sodium_crypto_secretbox( $value . $this->salt, $nonce, $key );
return sodium_bin2base64( $nonce . $encrypted, SODIUM_BASE64_VARIANT_ORIGINAL );
} catch ( Exception $e ) {
// Return false if encryption fails.
return false;
}

return base64_encode( $iv . $raw_value ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}

/**
* The decryption method.
*
* @param string $raw_value Encrypted value for decryption.
* @param string $encrypted Encrypted value for decryption.
*
* @since 1.9.0 Updated to use sodium_crypto_secretbox_open.
* @since 1.7.51
*
* @return string|false Decrypted value. If decryption fails, returns the original value or false under certain conditions.
*/
public function decrypt( string $encrypted ): string {
try {
$decoded = sodium_base642bin( $encrypted, SODIUM_BASE64_VARIANT_ORIGINAL );
$key = sodium_crypto_generichash( $this->key, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES );

$nonce = mb_substr( $decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit' );
$encrypted_result = mb_substr( $decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit' );
} catch ( Exception $e ) {
// Return the original value if fail to get decoded value or nonce and key.
return $encrypted;
}

try {
$value = sodium_crypto_secretbox_open( $encrypted_result, $nonce, $key );
if ( ! $value || substr( $value, - strlen( $this->salt ) ) !== $this->salt ) {
return false;
}
return substr( $value, 0, - strlen( $this->salt ) );
} catch ( Exception $e ) {
return false;
}
}

/**
* The Legacy decryption method.
*
* @param string $raw_value Encrypted value for decryption.
*
* @since 1.9.0
*
* @return string|false
*/
public function decrypt( string $raw_value ): string {
public function legacy_decrypt( string $raw_value ): string {
if ( ! extension_loaded( 'openssl' ) ) {
return $raw_value;
}
Expand Down
95 changes: 91 additions & 4 deletions includes/class-wc-xr-encrypt-legacy-tokens-migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
* @since 1.7.51
*/
class WC_XR_Encrypt_Legacy_Tokens_Migration {

/**
* Token data option name.
*
* @var string
*/
private $token_data_option_name = 'xero_oauth_options';

/**
* Migration id.
*
Expand All @@ -25,6 +33,13 @@ class WC_XR_Encrypt_Legacy_Tokens_Migration {
*/
private $migration_id = 'wc_xr_encrypt_legacy_tokens_migration';

/**
* Encryption migration id.
*
* @var string
*/
private $encryption_migration_id = 'wc_xr_legacy_encrypted_tokens_migration';

/**
* Register migration.
*
Expand All @@ -33,6 +48,7 @@ class WC_XR_Encrypt_Legacy_Tokens_Migration {
*/
public function setup_hook() {
add_action( 'init', array( $this, 'run' ) );
add_action( 'init', array( $this, 'migrate_legacy_encrypted_tokens' ), 20 );
}

/**
Expand All @@ -47,8 +63,7 @@ public function run() {
return;
}

$token_data_option_name = 'xero_oauth_options';
$tokens_data = get_option( $token_data_option_name, false );
$tokens_data = get_option( $this->token_data_option_name, false );

// Encrypt token only if token data exist.
if ( $tokens_data ) {
Expand All @@ -65,11 +80,83 @@ public function run() {
}

// Store token data with encrypted token values.
update_option( $token_data_option_name, $tokens_data, false );
update_option( $this->token_data_option_name, $tokens_data, false );
}

// Complete migration.
update_option( $this->migration_id, 1, true );
}

/**
* Migrate legacy encrypted tokens to new encryption.
*
* @since 1.9.0
* @return void
*/
public function migrate_legacy_encrypted_tokens() {
// Exit if migration completed.
if ( get_option( $this->encryption_migration_id, false ) ) {
return;
}

$tokens_data = get_option( $this->token_data_option_name, false );

/*
* Migrate encrypted tokens only if token data exist and openssl extension is loaded,
* openssl extension is required for the decryption of legacy encrypted tokens.
*/
if ( $tokens_data && extension_loaded( 'openssl' ) ) {
$wc_xr_data_encryption = new WC_XR_Data_Encryption();

// Encrypt token only if value is non-empty after the legacy decryption.
if ( $tokens_data['token'] ) {
$token = $wc_xr_data_encryption->legacy_decrypt( $tokens_data['token'] );
if ( $token ) {
$tokens_data['token'] = $wc_xr_data_encryption->encrypt( $token );
}
}

// Encrypt refresh_token only if value is non-empty after the legacy decryption.
if ( $tokens_data['refresh_token'] ) {
$refresh_token = $wc_xr_data_encryption->legacy_decrypt( $tokens_data['refresh_token'] );
if ( $refresh_token ) {
$tokens_data['refresh_token'] = $wc_xr_data_encryption->encrypt( $refresh_token );
}
}

// Store token data with encrypted token values.
update_option( $this->token_data_option_name, $tokens_data, false );
} elseif ( $tokens_data && ! extension_loaded( 'openssl' ) ) {
/*
* Handle token encryption for the sites where openssl extension is not installed.
*
* Legacy encryption was dependent on openssl extension and if openssl extension is not installed
* there is a possibility that the tokens are not encrypted, so this migration will encrypt the tokens.
*/
$wc_xr_data_encryption = new WC_XR_Data_Encryption();

// Encrypt token only if it is not-empty and not already encrypted.
if ( $tokens_data['token'] ) {
$decrypted_token = $wc_xr_data_encryption->decrypt( $tokens_data['token'] );
if ( $tokens_data['token'] === $decrypted_token || ! $decrypted_token ) {
$tokens_data['token'] = $wc_xr_data_encryption->encrypt( $tokens_data['token'] );
}
}

// Encrypt token only if it is not-empty and not already encrypted.
if ( $tokens_data['refresh_token'] ) {
$decrypted_refresh_token = $wc_xr_data_encryption->decrypt( $tokens_data['refresh_token'] );
if ( $tokens_data['refresh_token'] === $decrypted_refresh_token || ! $decrypted_refresh_token ) {
$tokens_data['refresh_token'] = $wc_xr_data_encryption->encrypt( $tokens_data['refresh_token'] );
}
}

// Store token data with encrypted token values.
update_option( $this->token_data_option_name, $tokens_data, false );
}

// Complete migration.
update_option( $this->migration_id, 1, 'no' );
update_option( $this->encryption_migration_id, 1, true );
}

/**
Expand Down
52 changes: 34 additions & 18 deletions includes/class-wc-xr-line-item.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ public function set_is_digital_good( $is_digital_good ) {

/**
* @version 1.7.38
*
*
* @return float
*/
public function get_discount_rate() {
Expand All @@ -209,7 +209,7 @@ public function get_discount_rate() {
/**
* Get the discount amount of current line item.
* @since 1.7.38
*
*
* @return float
*/
public function get_discount_amount() {
Expand All @@ -218,7 +218,7 @@ public function get_discount_amount() {

/**
* @version 1.7.38
*
*
* @param float $discount_rate
*/
public function set_discount_rate( $discount_rate ) {
Expand All @@ -227,7 +227,7 @@ public function set_discount_rate( $discount_rate ) {

/**
* Set the discount amount of current line item.
*
*
* @since 1.7.38
*
* @param float $discount_amount Discount ammount.
Expand Down Expand Up @@ -375,9 +375,10 @@ public function get_tax_type() {
// If no tax rate was found, ask Xero to add one for us

// First, see if we need a ReportTaxType
if ( ! empty( $report_tax_type ) ) {
$tax_rate['report_tax_type'] = $report_tax_type;
$logger->write( " - Setting ReportTaxType to ($report_tax_type)" );
$report_tax_type_for_create = $this->get_report_tax_type_for_base_country( true );
if ( ! empty( $report_tax_type_for_create ) ) {
$tax_rate['report_tax_type'] = $report_tax_type_for_create;
$logger->write( " - Setting ReportTaxType to ($report_tax_type_for_create)" );
}

$tax_type_create_request = new WC_XR_Request_Create_Tax_Rate( $this->settings, $tax_rate );
Expand Down Expand Up @@ -475,26 +476,41 @@ protected function get_tax_exempt_type_for_base_country() {
* @since 1.7.7
* @version 1.7.39
*
* @param bool $create_tax_rate Whether to return the report tax type for creating a tax rate in Xero.
*
* @return string (empty) | OUTPUT | INPUT | MOSSSALES | (filtered report tax type)
*/
protected function get_report_tax_type_for_base_country() {
protected function get_report_tax_type_for_base_country( $create_tax_rate = false ) {
$tax_rate = $this->get_tax_rate();

$is_shipping_line_item = array_key_exists( 'is_shipping_line_item', $tax_rate ) && $tax_rate['is_shipping_line_item'];
$is_fee_line_item = array_key_exists( 'is_fee_line_item', $tax_rate ) && $tax_rate['is_fee_line_item'];

$base_country = WC()->countries->get_base_country();

/**
* Filter the countries list for the report tax type.
*
* @since 1.8.5
*
* @param array $countries_list The countries list.
*
* @return array The countries list for the report tax type.
*/
$countries_list = apply_filters( 'woocommerce_xero_report_tax_type_countries', array( 'AU', 'NZ', 'GB', 'US', 'ZA', 'MT' ) );
if ( $create_tax_rate ) {
/**
* Filter the countries list for the report tax type for creating tax rate in the Xero.
*
* @since 1.9.0
*
* @param array $countries_list The countries list.
*
* @return array The countries list for the report tax type for creating tax rate in the Xero.
*/
$countries_list = apply_filters( 'woocommerce_xero_report_tax_type_countries_for_create_tax_rate', array( 'AU', 'NZ', 'GB' ) );
} else {
/**
* Filter the countries list for the report tax type.
*
* @since 1.8.5
*
* @param array $countries_list The countries list.
*
* @return array The countries list for the report tax type.
*/
$countries_list = apply_filters( 'woocommerce_xero_report_tax_type_countries', array( 'AU', 'NZ', 'GB', 'US', 'ZA', 'MT' ) );
}

$report_tax_type = '';
if ( in_array( $base_country, $countries_list, true ) ) {
Expand Down
Loading

0 comments on commit 967e01a

Please sign in to comment.