Skip to content

Instantly share code, notes, and snippets.

@mmollick
Created October 16, 2017 01:26
Show Gist options
  • Select an option

  • Save mmollick/1d760a7b9beae48cde4f3df8b95e0ad8 to your computer and use it in GitHub Desktop.

Select an option

Save mmollick/1d760a7b9beae48cde4f3df8b95e0ad8 to your computer and use it in GitHub Desktop.
Class to generate V4 style signing keys for direct browser upload to S3 bucket
<?php namespace NeverBounce\Repositories;
use Carbon\Carbon;
class DirectS3Upload {
/**
* Supported regions
* @var array
*/
protected $supported = [
'default' => [
'human' => 'US Standard', // Human friendly name
'region' => 'us-west-2', // AWS region code
'bucket' => 'my-default-bucket', // S3 bucket name
],
'eu-frankfurt' => [
'human' => 'EU Frankfurt',
'region' => 'eu-central-1',
'bucket' => 'my-eu-bucket'
],
];
/**
* Stores region short string
* @var bool|void
*/
protected $region;
/**
* S3Repository constructor.
* @param \User $user
*/
public function __construct(\User $user)
{
$this->region = $user->getMeta('data_region', 'default');
}
/**
* Returns human friendly name
* @return mixed
*/
public function getHuman()
{
return $this->supported[$this->region]['human'];
}
/**
* Returns AWS region code
* @return mixed
*/
public function getRegionCode()
{
return $this->supported[$this->region]['region'];
}
/**
* Returns S3 bucket name
* @return mixed
*/
public function getBucketName()
{
return $this->supported[$this->region]['bucket'];
}
public function getDataRegion()
{
return $this->region;
}
/**
* Returns the url for accessing and uploading to the bucket
* @return string
*/
public function getUrl()
{
return sprintf("https://%s.s3.amazonaws.com/", $this->getBucketName());
}
/**
* Get all the necessary details to directly upload a private file to S3
* asynchronously with JavaScript using the Signature V4.
*
* @return array ['url', 'inputs'] the forms url to s3 and any inputs the form will need.
*/
public function getS3Details() {
// Options and Settings
$awsKey = env('aws.key');
$awsSecret = env('aws.secret');
$acl = 'private';
$algorithm = "AWS4-HMAC-SHA256";
$service = "s3";
$date = gmdate('Ymd\THis\Z');
$shortDate = gmdate("Ymd");
$requestType = "aws4_request";
$expires = "86400"; // 24 Hours
$successStatus = "201";
// Step 1: Generate the Scope
$scope = [
$awsKey,
$shortDate,
$this->getRegionCode(),
$service,
$requestType
];
$credentials = implode('/', $scope);
// Step 2: Making a Base64 Policy
$policy = [
'expiration' => gmdate('Y-m-d\TG:i:s\Z', strtotime('+6 hours')),
'conditions' => [
['bucket' => $this->getBucketName()],
['acl' => $acl],
['starts-with', '$key', 'uploads/'],
['starts-with', '$Content-Type', ''],
['success_action_status' => $successStatus],
['x-amz-credential' => $credentials],
['x-amz-algorithm' => $algorithm],
['x-amz-date' => $date],
['x-amz-expires' => $expires],
]
];
$base64Policy = base64_encode(json_encode($policy));
// Step 3: Signing your Request (Making a Signature)
$dateKey = hash_hmac('sha256', $shortDate, 'AWS4' . $awsSecret, true);
$dateRegionKey = hash_hmac('sha256', $this->getRegionCode(), $dateKey, true);
$dateRegionServiceKey = hash_hmac('sha256', $service, $dateRegionKey, true);
$signingKey = hash_hmac('sha256', $requestType, $dateRegionServiceKey, true);
$signature = hash_hmac('sha256', $base64Policy, $signingKey);
// Step 4: Build form inputs
// This is the data that will get sent with the form to S3
return [
'Content-Type' => '',
'key' => 'uploads/${filename}',
'acl' => $acl,
'success_action_status' => $successStatus,
'policy' => $base64Policy,
'X-amz-credential' => $credentials,
'X-amz-algorithm' => $algorithm,
'X-amz-date' => $date,
'X-amz-expires' => $expires,
'X-amz-signature' => $signature
];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment