Skip to content

Commit

Permalink
add account level sas (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
brecht-vermeersch authored Jul 19, 2024
1 parent c0c620a commit af9904c
Show file tree
Hide file tree
Showing 20 changed files with 599 additions and 111 deletions.
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,14 @@
"laravel/pint": "^1.16",
"phpbench/phpbench": "^1.3",
"phpstan/phpstan": "^1.11",
"phpstan/phpstan-strict-rules": "^1.6",
"phpstan/phpstan-deprecation-rules": "^1.2",
"phpunit/phpunit": "^10.5"
},
"config": {
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"phpstan/extension-installer": true
}
}
}
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ parameters:
paths:
- src
- tests
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/phpstan/phpstan-deprecation-rules/rules.neon
27 changes: 25 additions & 2 deletions src/Blob/BlobServiceClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@

use AzureOss\Storage\Blob\Exceptions\BlobStorageExceptionFactory;
use AzureOss\Storage\Blob\Exceptions\InvalidConnectionStringException;
use AzureOss\Storage\Blob\Exceptions\UnableToGenerateSasException;
use AzureOss\Storage\Blob\Helpers\BlobUriParserHelper;
use AzureOss\Storage\Blob\Models\BlobContainer;
use AzureOss\Storage\Blob\Models\TaggedBlob;
use AzureOss\Storage\Blob\Responses\FindBlobsByTagBody;
use AzureOss\Storage\Blob\Responses\ListContainersResponseBody;
use AzureOss\Storage\Common\Auth\StorageSharedKeyCredential;
use AzureOss\Storage\Common\Helpers\ConnectionStringHelper;
use AzureOss\Storage\Common\Middleware\ClientFactory;
use AzureOss\Storage\Common\Sas\AccountSasBuilder;
use AzureOss\Storage\Common\Sas\AccountSasServices;
use AzureOss\Storage\Common\Sas\SasProtocol;
use AzureOss\Storage\Common\Serializer\SerializerFactory;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Uri;
use JMS\Serializer\SerializerInterface;
use Psr\Http\Message\UriInterface;

Expand All @@ -32,8 +38,8 @@ public function __construct(
public readonly ?StorageSharedKeyCredential $sharedKeyCredentials = null,
) {
// must always include the forward slash (/) to separate the host name from the path and query portions of the URI.
$this->uri = $uri->withPath(ltrim($uri->getPath(), '/') . "/");
$this->client = (new ClientFactory())->create($uri, $sharedKeyCredentials);
$this->uri = $uri->withPath(rtrim($uri->getPath(), '/') . "/");
$this->client = (new ClientFactory())->create($this->uri, $sharedKeyCredentials);
$this->serializer = (new SerializerFactory())->create();
$this->exceptionFactory = new BlobStorageExceptionFactory($this->serializer);
}
Expand Down Expand Up @@ -134,4 +140,21 @@ public function findBlobsByTag(string $tagFilterSqlExpression): \Generator
throw $this->exceptionFactory->create($e);
}
}

public function generateAccountSasUri(AccountSasBuilder $accountSasBuilder): UriInterface
{
if ($this->sharedKeyCredentials === null) {
throw new UnableToGenerateSasException();
}

if (BlobUriParserHelper::isDevelopmentUri($this->uri)) {
$accountSasBuilder->setProtocol(SasProtocol::HTTPS_AND_HTTP);
}

$sas = $accountSasBuilder
->setServices(new AccountSasServices(blob: true))
->build($this->sharedKeyCredentials);

return new Uri("$this->uri?$sas");
}
}
7 changes: 7 additions & 0 deletions src/Blob/Exceptions/AuthenticationFailedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

namespace AzureOss\Storage\Blob\Exceptions;

final class AuthenticationFailedException extends BlobStorageException {}
2 changes: 1 addition & 1 deletion src/Blob/Exceptions/BlobStorageException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

namespace AzureOss\Storage\Blob\Exceptions;

class BlobStorageException extends \RunTimeException {}
class BlobStorageException extends \RuntimeException {}
1 change: 1 addition & 0 deletions src/Blob/Exceptions/BlobStorageExceptionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public function create(RequestException $e): \Exception
}

return match ($error->code) {
'AuthenticationFailed' => new AuthenticationFailedException($error->message, previous: $e),
'AuthorizationFailure' => new AuthorizationFailedException($error->message, previous: $e),
'ContainerNotFound' => new ContainerNotFoundException($error->message, previous: $e),
'ContainerAlreadyExists' => new ContainerAlreadyExistsException($error->message, previous: $e),
Expand Down
18 changes: 18 additions & 0 deletions src/Blob/Helpers/DateHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace AzureOss\Storage\Blob\Helpers;

/**
* @internal
*/
final class DateHelper
{
public static function formatAs8601Zulu(\DateTimeInterface $date): string
{
return \DateTime::createFromInterface($date)
->setTimezone(new \DateTimeZone('UTC'))
->format('Y-m-d\TH:i:s\Z');
}
}
71 changes: 71 additions & 0 deletions src/Blob/Sas/BlobContainerSasPermissions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace AzureOss\Storage\Blob\Sas;

final class BlobContainerSasPermissions
{
public function __construct(
public bool $read = false,
public bool $add = false,
public bool $create = false,
public bool $write = false,
public bool $delete = false,
public bool $deleteVersion = false,
public bool $list = false,
public bool $find = false,
public bool $move = false,
public bool $execute = false,
public bool $ownership = false,
public bool $permissions = false,
public bool $setImmutabilityPolicy = false,
) {}

public function __toString(): string
{
$permissions = "";

if($this->read) {
$permissions .= "r";
}
if($this->add) {
$permissions .= "a";
}
if($this->create) {
$permissions .= "c";
}
if($this->write) {
$permissions .= "w";
}
if($this->delete) {
$permissions .= "d";
}
if($this->deleteVersion) {
$permissions .= "x";
}
if($this->list) {
$permissions .= "l";
}
if($this->find) {
$permissions .= "f";
}
if($this->move) {
$permissions .= "m";
}
if($this->execute) {
$permissions .= "e";
}
if($this->ownership) {
$permissions .= "o";
}
if($this->permissions) {
$permissions .= "p";
}
if($this->setImmutabilityPolicy) {
$permissions .= "i";
}

return $permissions;
}
}
21 changes: 7 additions & 14 deletions src/Blob/Sas/BlobSasBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace AzureOss\Storage\Blob\Sas;

use AzureOss\Storage\Blob\Exceptions\UnableToGenerateSasException;
use AzureOss\Storage\Blob\Helpers\DateHelper;
use AzureOss\Storage\Common\ApiVersion;
use AzureOss\Storage\Common\Auth\StorageSharedKeyCredential;
use AzureOss\Storage\Common\Sas\SasIpRange;
Expand Down Expand Up @@ -56,9 +57,9 @@ public function setExpiresOn(\DateTimeInterface $value): self
return $this;
}

public function setPermissions(string $value): self
public function setPermissions(string|BlobSasPermissions|BlobContainerSasPermissions $value): self
{
$this->permissions = $value;
$this->permissions = (string) $value;

return $this;
}
Expand Down Expand Up @@ -153,13 +154,13 @@ public function build(StorageSharedKeyCredential $sharedKeyCredential): string
throw new UnableToGenerateSasException();
}

$signedStart = $this->startsOn !== null ? $this->dateTo8601Zulu($this->startsOn) : null;
$signedExpiry = $this->dateTo8601Zulu($this->expiresOn);
$signedResource = $this->blobName ? "b" : "c";
$signedStart = $this->startsOn !== null ? DateHelper::formatAs8601Zulu($this->startsOn) : null;
$signedExpiry = DateHelper::formatAs8601Zulu($this->expiresOn);
$signedResource = $this->blobName !== null ? "b" : "c";
$signedIp = $this->ipRange !== null ? (string) $this->ipRange : null;
$signedProtocol = $this->protocol?->value;
$signedVersion = $this->version ?? ApiVersion::LATEST->value;
$signedSnapshotTime = $this->snapshotTime ? (string) $this->snapshotTime->getTimestamp() : null;
$signedSnapshotTime = $this->snapshotTime !== null ? (string) $this->snapshotTime->getTimestamp() : null;
$canonicalizedResource = $this->getCanonicalizedResource($sharedKeyCredential->accountName);

$stringToSign = [
Expand All @@ -180,7 +181,6 @@ public function build(StorageSharedKeyCredential $sharedKeyCredential): string
$this->contentLanguage,
$this->contentType,
];

$stringToSign = array_map(fn($str) => urldecode($str ?? ""), $stringToSign);
$stringToSign = implode("\n", $stringToSign);

Expand Down Expand Up @@ -216,11 +216,4 @@ private function getCanonicalizedResource(string $accountName): string

return $resource;
}

private function dateTo8601Zulu(\DateTimeInterface $date): string
{
return \DateTime::createFromInterface($date)
->setTimezone(new \DateTimeZone('UTC'))
->format('Y-m-d\TH:i:s\Z');
}
}
75 changes: 75 additions & 0 deletions src/Blob/Sas/BlobSasPermissions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace AzureOss\Storage\Blob\Sas;

final class BlobSasPermissions
{
public function __construct(
public bool $read = false,
public bool $add = false,
public bool $create = false,
public bool $write = false,
public bool $delete = false,
public bool $deleteVersion = false,
public bool $permanentDelete = false,
public bool $tags = false,
public bool $list = false,
public bool $move = false,
public bool $execute = false,
public bool $ownership = false,
public bool $permissions = false,
public bool $setImmutabilityPolicy = false,
) {}

public function __toString(): string
{
$permissions = "";

if($this->read) {
$permissions .= "r";
}
if($this->add) {
$permissions .= "a";
}
if($this->create) {
$permissions .= "c";
}
if($this->write) {
$permissions .= "w";
}
if($this->delete) {
$permissions .= "d";
}
if($this->deleteVersion) {
$permissions .= "x";
}
if($this->permanentDelete) {
$permissions .= "y";
}
if($this->list) {
$permissions .= "l";
}
if($this->tags) {
$permissions .= "t";
}
if($this->move) {
$permissions .= "m";
}
if($this->execute) {
$permissions .= "e";
}
if($this->ownership) {
$permissions .= "o";
}
if($this->permissions) {
$permissions .= "p";
}
if($this->setImmutabilityPolicy) {
$permissions .= "i";
}

return $permissions;
}
}
3 changes: 1 addition & 2 deletions src/Common/Helpers/ConnectionStringHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ private static function getSegments(string $connectionString): array
{
$segments = [];
foreach (explode(';', $connectionString) as $segment) {
// todo exception if invalid (no = sign)
if (!empty($segment)) {
if ($segment !== "") {
[$key, $value] = explode('=', $segment, 2);
$segments[$key] = $value;
}
Expand Down
Loading

0 comments on commit af9904c

Please sign in to comment.