Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Blob Functions #3

Merged
merged 5 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

[![PHP CI](https://github.com/sjspereira/azure-storage-php-sdk/actions/workflows/CI.yaml/badge.svg)](https://github.com/sjspereira/azure-storage-php-sdk/actions/workflows/CI.yaml)

## Description

Integrate with Azure's cloud storage services
Expand Down
76 changes: 76 additions & 0 deletions src/Authentication/MicrosoftEntraId.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Sjpereira\AzureStoragePhpSdk\Authentication;

use DateTime;
use GuzzleHttp\Client;
use Psr\Http\Client\RequestExceptionInterface;
use Sjpereira\AzureStoragePhpSdk\BlobStorage\Enums\HttpVerb;
use Sjpereira\AzureStoragePhpSdk\Contracts\Authentication\Auth;
use Sjpereira\AzureStoragePhpSdk\Exceptions\RequestException;
use Sjpereira\AzureStoragePhpSdk\Http\{Headers};

final class MicrosoftEntraId implements Auth
{
protected string $token = '';

protected ?DateTime $tokenExpiresAt = null;

public function __construct(
protected string $account,
protected string $directoryId,
protected string $applicationId,
protected string $applicationSecret,
) {
//
}

public function getDate(): string
{
return gmdate('D, d M Y H:i:s T');
}

public function getAccount(): string
{
return $this->account;
}

public function getAuthentication(
HttpVerb $verb,
Headers $headers,
string $resource,
): string {
if (!empty($this->token) && $this->tokenExpiresAt > new DateTime()) {
return $this->token;
}

$this->authenticate();

return $this->token;
}

protected function authenticate(): void
{
try {
$response = (new Client())->post("https://login.microsoftonline.com/{$this->directoryId}/oauth2/v2.0/token", [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => $this->applicationId,
'client_secret' => $this->applicationSecret,
'scope' => 'https://storage.azure.com/.default',
],
]);
} catch (RequestExceptionInterface $e) {
throw RequestException::createFromRequestException($e);
}

/** @var array{token_type: string, expires_in: int, access_token: string} */
$body = json_decode((string) $response->getBody(), true);

$this->token = "{$body['token_type']} {$body['access_token']}";

$this->tokenExpiresAt = (new DateTime())->modify("+{$body['expires_in']} seconds");
}
}
14 changes: 9 additions & 5 deletions src/Authentication/SharedKeyAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@

namespace Sjpereira\AzureStoragePhpSdk\Authentication;

use Sjpereira\AzureStoragePhpSdk\BlobStorage\Config;
use Sjpereira\AzureStoragePhpSdk\BlobStorage\Enums\HttpVerb;
use Sjpereira\AzureStoragePhpSdk\Contracts\Authentication\Auth;
use Sjpereira\AzureStoragePhpSdk\Http\Headers;

final class SharedKeyAuth implements Auth
{
public function __construct(protected Config $config)
public function __construct(protected string $account, protected string $key)
{
//
}
Expand All @@ -21,12 +20,17 @@ public function getDate(): string
return gmdate('D, d M Y H:i:s T');
}

public function getAccount(): string
{
return $this->account;
}

public function getAuthentication(
HttpVerb $verb,
Headers $headers,
string $resource,
): string {
$key = base64_decode($this->config->key);
$key = base64_decode($this->key);

$stringToSign = $this->getSigningString(
$verb->value,
Expand All @@ -37,11 +41,11 @@ public function getAuthentication(

$signature = base64_encode(hash_hmac('sha256', $stringToSign, $key, true));

return "SharedKey {$this->config->account}:{$signature}";
return "SharedKey {$this->account}:{$signature}";
}

protected function getSigningString(string $verb, string $headers, string $canonicalHeaders, string $resource): string
{
return "{$verb}\n{$headers}\n{$canonicalHeaders}\n/{$this->config->account}{$resource}";
return "{$verb}\n{$headers}\n{$canonicalHeaders}\n/{$this->account}{$resource}";
}
}
11 changes: 0 additions & 11 deletions src/BlobStorage/BlobStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@

use Sjpereira\AzureStoragePhpSdk\BlobStorage\Managers\Blob\BlobManager;
use Sjpereira\AzureStoragePhpSdk\BlobStorage\Managers\{AccountManager, ContainerManager};
use Sjpereira\AzureStoragePhpSdk\Contracts\Authentication\Auth;
use Sjpereira\AzureStoragePhpSdk\Contracts\Http\Request as RequestContract;
use Sjpereira\AzureStoragePhpSdk\Contracts\{Converter, Parser};
use Sjpereira\AzureStoragePhpSdk\Http\Request;

final class BlobStorage
{
Expand All @@ -18,14 +15,6 @@ public function __construct(protected RequestContract $request)
//
}

/** @param array{account: string, key: string, version?: string, parser?: Parser, converter?: Converter, auth?: Auth} $options */
public static function client(array $options, ?RequestContract $request = null): self
{
$config = new Config($options);

return new self($request ?? new Request($config));
}

public function account(): AccountManager
{
return new AccountManager($this->request);
Expand Down
22 changes: 2 additions & 20 deletions src/BlobStorage/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,31 @@

namespace Sjpereira\AzureStoragePhpSdk\BlobStorage;

use Sjpereira\AzureStoragePhpSdk\Authentication\SharedKeyAuth;
use Sjpereira\AzureStoragePhpSdk\Contracts\Authentication\Auth;
use Sjpereira\AzureStoragePhpSdk\Contracts\{Converter, Parser};
use Sjpereira\AzureStoragePhpSdk\Converter\XmlConverter;
use Sjpereira\AzureStoragePhpSdk\Exceptions\InvalidArgumentException;
use Sjpereira\AzureStoragePhpSdk\Parsers\XmlParser;

/**
* @phpstan-type ConfigType array{account: string, key: string, version?: string, parser?: Parser, converter?: Converter, auth?: Auth}
* @phpstan-type ConfigType array{version?: string, parser?: Parser, converter?: Converter}
*/
final readonly class Config
{
public string $account;

public string $key;

public string $version;

public Parser $parser;

public Converter $converter;

public Auth $auth;

/**
* @param ConfigType $config
* @throws InvalidArgumentException
*/
public function __construct(array $config)
public function __construct(public Auth $auth, array $config = [])
{
if (empty($config['account'] ?? null)) { // @phpstan-ignore-line
throw InvalidArgumentException::create('Account name must be provided.');
}

if (empty($config['key'] ?? null)) { // @phpstan-ignore-line
throw InvalidArgumentException::create('Account key must be provided.');
}

$this->account = $config['account'];
$this->key = $config['key'];
$this->version = $config['version'] ?? Resource::VERSION;
$this->parser = $config['parser'] ?? new XmlParser();
$this->converter = $config['converter'] ?? new XmlConverter();
$this->auth = $config['auth'] ?? new SharedKeyAuth($this);
}
}
82 changes: 76 additions & 6 deletions src/BlobStorage/Entities/Blob/Blob.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Sjpereira\AzureStoragePhpSdk\BlobStorage\Entities\Blob;

use DateTime;
use DateTimeImmutable;
use Sjpereira\AzureStoragePhpSdk\BlobStorage\Managers\Blob\BlobManager;
use Sjpereira\AzureStoragePhpSdk\BlobStorage\Enums\ExpirationOption;
use Sjpereira\AzureStoragePhpSdk\BlobStorage\Managers\Blob\{BlobLeaseManager, BlobManager, BlobTagManager};
use Sjpereira\AzureStoragePhpSdk\Concerns\HasManager;
use Sjpereira\AzureStoragePhpSdk\Exceptions\RequiredFieldException;

Expand All @@ -21,10 +23,14 @@ final class Blob

public readonly string $name;

public readonly DateTimeImmutable $snapshot;
public readonly ?DateTimeImmutable $snapshot;

public readonly ?string $snapshotOriginalRaw;

public readonly DateTimeImmutable $versionId;

public readonly ?string $versionIdOriginalRaw;

public readonly bool $isCurrentVersion;

public readonly bool $deleted;
Expand All @@ -41,10 +47,12 @@ public function __construct(array $blob)
throw RequiredFieldException::missingField('Name');
}

$this->name = $name;
$this->snapshot = new DateTimeImmutable($blob['Snapshot'] ?? 'now');
$this->versionId = new DateTimeImmutable($blob['Version'] ?? 'now');
$this->isCurrentVersion = to_boolean($blob['IsCurrentVersion'] ?? true);
$this->name = $name;
$this->snapshot = isset($blob['Snapshot']) ? new DateTimeImmutable($blob['Snapshot']) : null;
$this->snapshotOriginalRaw = $blob['Snapshot'] ?? null;
$this->versionId = new DateTimeImmutable($blob['Version'] ?? 'now');
$this->versionIdOriginalRaw = $blob['Version'] ?? null;
$this->isCurrentVersion = to_boolean($blob['IsCurrentVersion'] ?? true);

$this->properties = new Properties($blob['Properties'] ?? []);

Expand All @@ -58,4 +66,66 @@ public function get(array $options = []): File

return $this->getManager()->get($this->name, $options);
}

/** @param array<string, scalar> $options */
public function getProperties(array $options = []): BlobProperty
{
$this->ensureManagerIsConfigured();

return $this->getManager()->properties($this->name)->get($options);
}

/**
* @param boolean $force If true, Delete the base blob and all of its snapshots.
*/
public function delete(bool $force = false): bool
{
$this->ensureManagerIsConfigured();

return $this->getManager()->delete($this->name, $this->snapshotOriginalRaw, $force);
}

/** @param array<string, scalar> $options */
public function copy(string $destination, array $options = []): bool
{
$this->ensureManagerIsConfigured();

return $this->getManager()->copy($this->name, $destination, $options, $this->snapshotOriginalRaw);
}

public function restore(): bool
{
$this->ensureManagerIsConfigured();

return $this->getManager()->restore($this->name);
}

public function createSnapshot(): bool
{
$this->ensureManagerIsConfigured();

return $this->getManager()->createSnapshot($this->name);
}

public function tags(): BlobTagManager
{
$this->ensureManagerIsConfigured();

return $this->getManager()->tags($this->name);
}

public function lease(): BlobLeaseManager
{
$this->ensureManagerIsConfigured();

return $this->getManager()->lease($this->name);
}

/** @param array<string, scalar> $options */
public function setExpiry(ExpirationOption $option, null|int|DateTime $expiry, array $options = []): bool
{
$this->ensureManagerIsConfigured();

return $this->getManager()->setExpiry($this->name, $option, $expiry, $options);
}
}
Loading