access_token = $access_token; $this->expires_at = $expires_at; $this->token_type = $token_type; $this->refresh_token = $refresh_token; $this->id_token = $id_token; $this->scope = $scope; $this->error_count = $error_count; } /** * Returns the access token. * * @return string */ public function get_access_token(): string { return $this->access_token; } /** * Returns the Unix timestamp at which the access token expires. * * @return int */ public function get_expires_at(): int { return $this->expires_at; } /** * Returns the token type. * * @return string */ public function get_token_type(): string { return $this->token_type; } /** * Returns the refresh token, or null if not available. * * @return string|null */ public function get_refresh_token(): ?string { return $this->refresh_token; } /** * Returns the OIDC ID token, or null if not available. * * @return string|null */ public function get_id_token(): ?string { return $this->id_token; } /** * Returns the granted scope string, or null if not available. * * @return string|null */ public function get_scope(): ?string { return $this->scope; } /** * Checks if the token set has the required scope(s). * Returns true if AT LEAST all required scopes are granted, false otherwise. * * @param string[] $required_scopes The required scopes as an array of strings. * * @return bool True if all required scopes are granted, false otherwise. */ public function has_scopes( array $required_scopes ): bool { if ( $this->scope === null ) { return \count( $required_scopes ) === 0; } $granted_scopes = \explode( ' ', $this->scope ); return \count( \array_diff( $required_scopes, $granted_scopes ) ) === 0; } /** * Returns the number of consecutive refresh errors. * * @return int */ public function get_error_count(): int { return $this->error_count; } /** * Returns a new Token_Set with an incremented error count. * * @return self */ public function with_incremented_error_count(): self { return new self( $this->access_token, $this->expires_at, $this->token_type, $this->refresh_token, $this->id_token, $this->scope, $this->error_count + 1, ); } /** * Whether the access token has expired. * * Uses a 60-second buffer to allow for request latency. * * @return bool */ public function is_expired(): bool { return \time() >= ( $this->expires_at - self::EXPIRY_BUFFER_SECONDS ); } /** * Converts the token set to an associative array for storage. * * @return array The token set as an array. */ public function to_array(): array { return [ 'access_token' => $this->access_token, 'expires_at' => $this->expires_at, 'token_type' => $this->token_type, 'refresh_token' => $this->refresh_token, 'id_token' => $this->id_token, 'scope' => $this->scope, 'error_count' => $this->error_count, ]; } /** * Creates a Token_Set from a stored array. * * @param array $data The stored array data. * * @return self */ public static function from_array( array $data ): self { return new self( (string) ( $data['access_token'] ?? '' ), (int) ( $data['expires_at'] ?? 0 ), ( $data['token_type'] ?? Auth_Token_Type::DPOP ), ( $data['refresh_token'] ?? null ), ( $data['id_token'] ?? null ), ( $data['scope'] ?? null ), (int) ( $data['error_count'] ?? 0 ), ); } /** * Creates a Token_Set from a token endpoint response. * * @param array $response The parsed JSON response from the token endpoint. * * @return self * * @throws InvalidArgumentException If the response is missing a valid access_token. */ public static function from_response( array $response ): self { if ( empty( $response['access_token'] ) || ! \is_string( $response['access_token'] ) ) { throw new InvalidArgumentException( 'Token response is missing a valid access_token.' ); } $expires_in = (int) ( $response['expires_in'] ?? 900 ); return new self( $response['access_token'], ( \time() + $expires_in ), ( $response['token_type'] ?? Auth_Token_Type::DPOP ), ( $response['refresh_token'] ?? null ), ( $response['id_token'] ?? null ), ( $response['scope'] ?? null ), ); } }