Files
roi-theme/wp-content/plugins/w3-total-cache/CdnEngine_Azure_MI_Utility.php
root a22573bf0b Commit inicial - WordPress Análisis de Precios Unitarios
- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 21:04:30 -06:00

699 lines
18 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* File: CdnEngine_Azure_MI_Utility.php
*
* Microsoft Azure Managed Identities are available only for services running on Azure when a "system assigned" identity is enabled.
*
* A system assigned managed identity is restricted to one per resource and is tied to the lifecycle of a resource.
* You can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC).
* The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code.
*
* @package W3TC
* @since 2.7.7
*/
namespace W3TC;
/**
* Class: CdnEngine_Azure_MI_Utility
*
* This class defines utility functions for Azure blob storage access using Managed Identity.
*
* @since 2.7.7
* @author Zubair <zmohammed@microsoft.com>
* @author BoldGrid <development@boldgrid.com>
*
* phpcs:disable WordPress.WP.AlternativeFunctions
*/
class CdnEngine_Azure_MI_Utility {
/**
* Entra API version.
*
* @since 2.7.7
*
* @var string
*/
const ENTRA_API_VERSION = '2019-08-01';
/**
* Entra resource URI.
*
* @since 2.7.7
*
* @var string
*/
const ENTRA_RESOURCE_URI = 'https://storage.azure.com';
/**
* Blob API version.
*
* @since 2.7.7
*
* @var string
*/
const BLOB_API_VERSION = '2020-10-02';
/**
* Retrieves an access token from the Managed Identity endpoint.
*
* @since 2.7.7
*
* @param string $entra_client_id Entra ID.
* @return string $access_token
* @throws \RuntimeException Runtine Exception.
*/
public static function get_access_token( string $entra_client_id ): string {
// Get environment variables.
$identity_header = \getenv( 'IDENTITY_HEADER' );
$identity_endpoint = \getenv( 'IDENTITY_ENDPOINT' );
// Validate variables.
if ( empty( $identity_endpoint ) || empty( $identity_header ) || empty( $entra_client_id ) ) {
throw new \RuntimeException( \esc_html__( 'Error: get_access_token - missing required environment variables.', 'w3-total-cache' ) );
}
// Construct URL for cURL request.
$url = $identity_endpoint . '?' . http_build_query(
array(
'api-version' => self::ENTRA_API_VERSION,
'resource' => self::ENTRA_RESOURCE_URI,
'client_id' => $entra_client_id,
)
);
// Initialize and execute cURL request.
$ch = \curl_init();
\curl_setopt( $ch, CURLOPT_URL, $url );
\curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'GET' );
\curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'X-IDENTITY-HEADER: ' . $identity_header ) );
\curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
\curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
$response = \curl_exec( $ch );
if ( \curl_errno( $ch ) ) {
$error = \curl_error( $ch );
\curl_close( $ch );
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Error message.
\__( 'Error: get_access_token - cURL request failed: %1$s', 'w3-total-cache' ),
$error
)
)
);
}
$http_code = \curl_getinfo( $ch, CURLINFO_HTTP_CODE );
\curl_close( $ch );
if ( 200 !== $http_code ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 HTTP status code.
\__( 'Error: get_access_token - HTTP request failed with status code: %1$s', 'w3-total-cache' ),
$http_code
)
)
);
}
if ( empty( $response ) ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Response.
\__( 'Error: get_access_token - invalid response data: %1$s', 'w3-total-cache' ),
$response
)
)
);
}
// Parse JSON response and extract access_token.
$json_response = \json_decode( $response, true );
if ( \json_last_error() !== JSON_ERROR_NONE ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 JSON last error message.
\__( 'Error: get_access_token - failed to parse the JSON response: %1$s', 'w3-total-cache' ),
\json_last_error_msg()
)
)
);
}
if ( empty( $json_response['access_token'] ) ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Response.
\__( 'Error: get_access_token - no token found in response data: %1$s', 'w3-total-cache' ),
$response
)
)
);
}
return $json_response['access_token'];
}
/**
* Get Azure Blob Storage blob properties.
*
* @since 2.7.7
*
* @param string $entra_client_id Entra ID.
* @param string $storage_account Storage account name.
* @param string $container_id Container ID.
* @param string $blob Blob ID.
* @return array
* @throws \RuntimeException Runtine Exception.
*/
public static function get_blob_properties( string $entra_client_id, string $storage_account, string $container_id, $blob ): array {
$ch = \curl_init();
\curl_setopt( $ch, CURLOPT_URL, 'https://' . $storage_account . '.blob.core.windows.net/' . $container_id . '/' . $blob );
\curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
\curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
\curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
array(
'Authorization: Bearer ' . self::get_access_token( $entra_client_id ),
'x-ms-version: ' . self::BLOB_API_VERSION,
'x-ms-date: ' . \gmdate( 'D, d M Y H:i:s T', time() ),
)
);
\curl_setopt( $ch, CURLOPT_HEADER, true );
\curl_setopt( $ch, CURLOPT_NOBODY, true );
$response = \curl_exec( $ch );
if ( \curl_errno( $ch ) ) {
$error = \curl_error( $ch );
\curl_close( $ch );
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Error message.
\__( 'Error: get_blob_properties - cURL request failed: %1$s', 'w3-total-cache' ),
$error
)
)
);
}
$http_code = \curl_getinfo( $ch, CURLINFO_HTTP_CODE );
\curl_close( $ch );
if ( 200 !== $http_code ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 HTTP status code.
\__( 'Error: get_blob_properties - HTTP request failed with status code: %1$s', 'w3-total-cache' ),
$http_code
)
)
);
}
if ( empty( $response ) ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Response.
\__( 'Error: get_blob_properties - invalid response data: %1$s', 'w3-total-cache' ),
$response
)
)
);
}
return self::parse_header( $response );
}
/**
* Create block blob.
*
* @since 2.7.7
*
* @param string $entra_client_id Entra ID.
* @param string $storage_account Storage account name.
* @param string $container_id Container ID.
* @param string $blob Blob ID.
* @param mixed $contents Contents.
* @param string $content_type Content type.
* @param string $content_md5 Content MD5 hash.
* @param string $cache_control Cache control header value.
* @return array
* @throws \RuntimeException Runtine Exception.
*/
public static function create_block_blob(
string $entra_client_id,
string $storage_account,
string $container_id,
string $blob,
$contents,
string $content_type = null,
string $content_md5 = null,
string $cache_control = null
): array {
$headers = array(
'Authorization: Bearer ' . self::get_access_token( $entra_client_id ),
'x-ms-version: ' . self::BLOB_API_VERSION,
'x-ms-date: ' . \gmdate( 'D, d M Y H:i:s T', \time() ),
'x-ms-blob-type: BlockBlob',
);
if ( $content_type ) {
$headers[] = 'x-ms-blob-content-type: ' . $content_type;
}
if ( $content_md5 ) {
$headers[] = 'x-ms-blob-content-md5: ' . $content_md5;
}
if ( $cache_control ) {
$headers[] = 'x-ms-blob-cache-control: ' . $cache_control;
}
$ch = \curl_init();
\curl_setopt( $ch, CURLOPT_URL, 'https://' . $storage_account . '.blob.core.windows.net/' . $container_id . '/' . $blob );
\curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
\curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'PUT' );
\curl_setopt( $ch, CURLOPT_POSTFIELDS, $contents );
\curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
\curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
\curl_setopt( $ch, CURLOPT_HEADER, true );
$response = \curl_exec( $ch );
if ( \curl_errno( $ch ) ) {
$error = \curl_error( $ch );
\curl_close( $ch );
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Error message.
\__( 'Error: create_block_blob - cURL request failed: %1$s', 'w3-total-cache' ),
$error
)
)
);
}
$http_code = \curl_getinfo( $ch, CURLINFO_HTTP_CODE );
\curl_close( $ch );
if ( 201 !== $http_code ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 HTTP status code.
\__( 'Error: create_block_blob - HTTP request failed with status code: %1$s', 'w3-total-cache' ),
$http_code
)
)
);
}
if ( empty( $response ) ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Response.
\__( 'Error: create_block_blob - invalid response data: %1$s', 'w3-total-cache' ),
$response
)
)
);
}
return self::parse_header( $response );
}
/**
* Delete blob.
*
* @since 2.7.7
*
* @param string $entra_client_id Entra ID.
* @param string $storage_account Storage account name.
* @param string $container_id Container ID.
* @param string $blob Blob ID.
* @return array
* @throws \RuntimeException Runtine Exception.
*/
public static function delete_blob( string $entra_client_id, string $storage_account, string $container_id, string $blob ) {
$ch = \curl_init();
\curl_setopt( $ch, CURLOPT_URL, 'https://' . $storage_account . '.blob.core.windows.net/' . $container_id . '/' . $blob );
\curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
\curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'DELETE' );
\curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
array(
'Authorization: Bearer ' . self::get_access_token( $entra_client_id ),
'x-ms-version: ' . self::BLOB_API_VERSION,
'x-ms-date: ' . \gmdate( 'D, d M Y H:i:s T', \time() ),
)
);
\curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
\curl_setopt( $ch, CURLOPT_HEADER, true );
$response = \curl_exec( $ch );
if ( \curl_errno( $ch ) ) {
$error = \curl_error( $ch );
\curl_close( $ch );
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Error message.
\__( 'Error: delete_blob - cURL request failed: %1$s', 'w3-total-cache' ),
$error
)
)
);
}
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
curl_close( $ch );
if ( 202 !== $http_code ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 HTTP status code.
\__( 'Error: delete_blob - HTTP request failed with status code: %1$s', 'w3-total-cache' ),
$http_code
)
)
);
}
if ( empty( $response ) ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Response.
\__( 'Error: delete_blob - invalid response data: %1$s', 'w3-total-cache' ),
$response
)
)
);
}
return self::parse_header( $response );
}
/**
* Create an Azure Blob Storage container/bucket.
*
* @since 2.7.7
*
* @param string $entra_client_id Entra ID.
* @param string $storage_account Storage account name.
* @param string $container_id Container ID.
* @param string $public_access_type Public access type.
* @return array
* @throws \RuntimeException Runtine Exception.
*/
public static function create_container(
string $entra_client_id,
string $storage_account,
string $container_id,
string $public_access_type = 'blob'
): array {
$headers = array(
'Authorization: Bearer ' . self::get_access_token( $entra_client_id ),
'x-ms-version: ' . self::BLOB_API_VERSION,
'x-ms-date: ' . \gmdate( 'D, d M Y H:i:s T', \time() ),
'Content-Length: 0',
);
if ( $public_access_type ) {
$headers[] = "x-ms-blob-public-access: $public_access_type";
}
$ch = \curl_init();
\curl_setopt( $ch, CURLOPT_URL, 'https://' . $storage_account . '.blob.core.windows.net/' . $container_id . '?restype=container' );
\curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
\curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'PUT' );
\curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
\curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
\curl_setopt( $ch, CURLOPT_HEADER, true );
$response = \curl_exec( $ch );
if ( \curl_errno( $ch ) ) {
$error = \curl_error( $ch );
\curl_close( $ch );
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Error message.
\__( 'Error: create_container - cURL request failed: %1$s', 'w3-total-cache' ),
$error
)
)
);
}
$http_code = \curl_getinfo( $ch, CURLINFO_HTTP_CODE );
\curl_close( $ch );
if ( 201 !== $http_code ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 HTTP status code.
\__( 'Error: create_container - HTTP request failed with status code: %1$s', 'w3-total-cache' ),
$http_code
)
)
);
}
if ( empty( $response ) ) {
throw new \RuntimeException( \esc_html__( 'Error: create_container - empty response.', 'w3-total-cache' ) );
}
return self::parse_header( $response );
}
/**
* List Azure Blob Storage containers.
*
* @since 2.7.7
*
* @param string $entra_client_id Entra ID.
* @param string $storage_account Storage account name.
* @return array
* @throws \RuntimeException Runtine Exception.
*/
public static function list_containers( string $entra_client_id, string $storage_account ): array {
$ch = \curl_init();
\curl_setopt( $ch, CURLOPT_URL, 'https://' . $storage_account . '.blob.core.windows.net/?comp=list' );
\curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
\curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
array(
'Authorization: Bearer ' . self::get_access_token( $entra_client_id ),
'x-ms-version: ' . self::BLOB_API_VERSION,
'x-ms-date: ' . \gmdate( 'D, d M Y H:i:s T', \time() ),
)
);
\curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
$response = \curl_exec( $ch );
if ( \curl_errno( $ch ) ) {
$error = \curl_error( $ch );
\curl_close( $ch );
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Error message.
\__( 'Error: list_containers - cURL request failed: %1$s', 'w3-total-cache' ),
$error
)
)
);
}
$http_code = \curl_getinfo( $ch, CURLINFO_HTTP_CODE );
\curl_close( $ch );
if ( 200 !== $http_code ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 HTTP status code.
\__( 'Error: list_containers - HTTP request failed with status code: %1$s', 'w3-total-cache' ),
$http_code
)
)
);
}
if ( empty( $response ) ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Response.
\__( 'Error: list_containers - Invalid response data: %1$s', 'w3-total-cache' ),
$response
)
)
);
}
// Parse XML response to array.
$xml = \simplexml_load_string( $response );
$json = \json_encode( $xml );
$response = \json_decode( $json, true );
$array_response = array();
if ( ! empty( $response['Containers']['Container'] ) ) {
$array_response = self::get_array( $response['Containers']['Container'] );
}
return $array_response;
}
/**
* Get blob.
*
* @since 2.7.7
*
* @param string $entra_client_id Entra ID.
* @param string $storage_account Storage account name.
* @param string $container_id Container ID.
* @param string $blob Blob ID.
* @return array
* @throws \RuntimeException Runtine Exception.
*/
public static function get_blob( string $entra_client_id, string $storage_account, string $container_id, string $blob ): array {
$ch = \curl_init();
\curl_setopt( $ch, CURLOPT_URL, 'https://' . $storage_account . '.blob.core.windows.net/' . $container_id . '/' . $blob );
\curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
\curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
array(
'Authorization: Bearer ' . self::get_access_token( $entra_client_id ),
'x-ms-version: ' . self::BLOB_API_VERSION,
'x-ms-date: ' . \gmdate( 'D, d M Y H:i:s T', \time() ),
)
);
\curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
\curl_setopt( $ch, CURLOPT_HEADER, true );
$response = \curl_exec( $ch );
if ( \curl_errno( $ch ) ) {
$error = \curl_error( $ch );
\curl_close( $ch );
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Error message.
\__( 'Error: get_blob - cURL request failed: %1$s', 'w3-total-cache' ),
$error
)
)
);
}
$header_size = \curl_getinfo( $ch, CURLINFO_HEADER_SIZE );
$http_code = \curl_getinfo( $ch, CURLINFO_HTTP_CODE );
\curl_close( $ch );
if ( 200 !== $http_code ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 HTTP status code.
\__( 'Error: get_blob - HTTP request failed with status code: %1$s', 'w3-total-cache' ),
$http_code
)
)
);
}
if ( empty( $response ) ) {
throw new \RuntimeException(
\esc_html(
sprintf(
// Translators: 1 Response.
\__( 'Error: get_blob - Invalid response data: %1$s', 'w3-total-cache' ),
$response
)
)
);
}
return array(
'headers' => self::parse_header( \substr( $response, 0, $header_size ) ),
'data' => \substr( $response, $header_size ),
);
}
/**
* Get array.
*
* @since 2.7.7
* @access private
*
* @param mixed $input Variable used to get array.
*
* @return array
*/
private static function get_array( $input ): array {
if ( empty( $input ) || ! \is_array( $input ) ) {
return array();
}
foreach ( $input as $value ) {
if ( ! \is_array( $value ) ) {
return array( $input );
}
return $input;
}
}
/**
* Parse header from string to array.
*
* @since 2.7.7
* @access private
*
* @param string $header Header.
* @return array
*/
private static function parse_header( string $header ): array {
$headers = array();
$header_text = \substr( $header, 0, \strpos( $header, "\r\n\r\n" ) );
$header_parts = \explode( "\r\n", $header_text );
foreach ( $header_parts as $header ) {
if ( \strpos( $header, ':' ) !== false ) {
$header_parts = \explode( ':', $header );
$headers[ $header_parts[0] ] = \trim( $header_parts[1] );
}
}
return $headers;
}
}