class-form-access-control.php
2 months ago
class-form-captcha-handler.php
2 months ago
class-form-controller.php
2 months ago
class-form-email-config-check.php
2 months ago
class-form-email-handler.php
2 months ago
class-form-encryption.php
2 months ago
class-form-exporter.php
2 months ago
class-form-field-validator.php
2 months ago
class-form-file-handler.php
2 months ago
class-form-google-auth.php
2 months ago
class-form-integration-handler.php
2 months ago
class-form-math-parser.php
2 months ago
class-form-permissions.php
2 months ago
class-form-registry.php
2 months ago
class-form-settings.php
2 months ago
class-form-submission-cpt.php
2 months ago
class-form-submission-handler.php
2 months ago
class-form-google-auth.php
116 lines
| 1 | <?php |
| 2 | |
| 3 | namespace SuperbAddons\Gutenberg\Form; |
| 4 | |
| 5 | defined('ABSPATH') || exit(); |
| 6 | |
| 7 | class FormGoogleAuth |
| 8 | { |
| 9 | const TRANSIENT_KEY = 'spb_google_token'; |
| 10 | const TOKEN_URL = 'https://oauth2.googleapis.com/token'; |
| 11 | const SCOPE = 'https://www.googleapis.com/auth/spreadsheets'; |
| 12 | |
| 13 | /** |
| 14 | * Get a valid access token, using cached transient or fetching a new one. |
| 15 | * |
| 16 | * @return string|\WP_Error Access token string or WP_Error on failure. |
| 17 | */ |
| 18 | public static function GetAccessToken() |
| 19 | { |
| 20 | $cached = get_transient(self::TRANSIENT_KEY); |
| 21 | if (!empty($cached)) { |
| 22 | return $cached; |
| 23 | } |
| 24 | |
| 25 | $client_email = FormSettings::Get(FormSettings::OPTION_GOOGLE_SHEETS_CLIENT_EMAIL); |
| 26 | $private_key = FormSettings::Get(FormSettings::OPTION_GOOGLE_SHEETS_PRIVATE_KEY); |
| 27 | |
| 28 | if (empty($client_email) || empty($private_key)) { |
| 29 | return new \WP_Error('no_credentials', __('Google Sheets credentials are not configured.', 'superb-blocks')); |
| 30 | } |
| 31 | |
| 32 | $jwt = self::CreateJWT($client_email, $private_key, self::SCOPE); |
| 33 | if (is_wp_error($jwt)) { |
| 34 | return $jwt; |
| 35 | } |
| 36 | |
| 37 | $response = wp_remote_post(self::TOKEN_URL, array( |
| 38 | 'body' => array( |
| 39 | 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', |
| 40 | 'assertion' => $jwt, |
| 41 | ), |
| 42 | 'timeout' => 15, |
| 43 | )); |
| 44 | |
| 45 | if (is_wp_error($response)) { |
| 46 | return new \WP_Error('token_request_failed', $response->get_error_message()); |
| 47 | } |
| 48 | |
| 49 | $code = wp_remote_retrieve_response_code($response); |
| 50 | if ($code !== 200) { |
| 51 | $body = json_decode(wp_remote_retrieve_body($response), true); |
| 52 | $msg = isset($body['error_description']) ? $body['error_description'] : sprintf('HTTP %d', $code); |
| 53 | return new \WP_Error('token_error', $msg); |
| 54 | } |
| 55 | |
| 56 | $body = json_decode(wp_remote_retrieve_body($response), true); |
| 57 | $access_token = isset($body['access_token']) ? $body['access_token'] : ''; |
| 58 | if (empty($access_token)) { |
| 59 | return new \WP_Error('no_token', __('No access token in response.', 'superb-blocks')); |
| 60 | } |
| 61 | |
| 62 | // Cache for 50 minutes (tokens last 60 min) |
| 63 | set_transient(self::TRANSIENT_KEY, $access_token, 50 * 60); |
| 64 | |
| 65 | return $access_token; |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Create a signed JWT for Google Service Account authentication. |
| 70 | * |
| 71 | * @param string $client_email Service account email. |
| 72 | * @param string $private_key PEM-encoded private key. |
| 73 | * @param string $scopes Space-separated scopes. |
| 74 | * @return string|\WP_Error The signed JWT or WP_Error on failure. |
| 75 | */ |
| 76 | private static function CreateJWT($client_email, $private_key, $scopes) |
| 77 | { |
| 78 | $now = time(); |
| 79 | $header = array('alg' => 'RS256', 'typ' => 'JWT'); |
| 80 | $claims = array( |
| 81 | 'iss' => $client_email, |
| 82 | 'scope' => $scopes, |
| 83 | 'aud' => self::TOKEN_URL, |
| 84 | 'exp' => $now + 3600, |
| 85 | 'iat' => $now, |
| 86 | ); |
| 87 | |
| 88 | $segments = array(); |
| 89 | $segments[] = self::Base64UrlEncode(wp_json_encode($header)); |
| 90 | $segments[] = self::Base64UrlEncode(wp_json_encode($claims)); |
| 91 | |
| 92 | $signing_input = implode('.', $segments); |
| 93 | |
| 94 | $signature = ''; |
| 95 | $sign_result = openssl_sign($signing_input, $signature, $private_key, 'sha256WithRSAEncryption'); |
| 96 | if (!$sign_result) { |
| 97 | return new \WP_Error('jwt_sign_failed', __('Failed to sign JWT. Check that the private key is valid.', 'superb-blocks')); |
| 98 | } |
| 99 | |
| 100 | $segments[] = self::Base64UrlEncode($signature); |
| 101 | |
| 102 | return implode('.', $segments); |
| 103 | } |
| 104 | |
| 105 | /** |
| 106 | * Base64 URL-safe encode. |
| 107 | * |
| 108 | * @param string $data Raw data. |
| 109 | * @return string Base64url encoded string. |
| 110 | */ |
| 111 | private static function Base64UrlEncode($data) |
| 112 | { |
| 113 | return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); |
| 114 | } |
| 115 | } |
| 116 |