| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 | <?phpnamespace Aws\S3;use Aws\Api\Service;use Aws\Arn\AccessPointArnInterface;use Aws\Arn\ArnParser;use Aws\Arn\ObjectLambdaAccessPointArn;use Aws\Arn\Exception\InvalidArnException;use Aws\Arn\AccessPointArn as BaseAccessPointArn;use Aws\Arn\S3\OutpostsAccessPointArn;use Aws\Arn\S3\MultiRegionAccessPointArn;use Aws\Arn\S3\OutpostsArnInterface;use Aws\CommandInterface;use Aws\Endpoint\PartitionEndpointProvider;use Aws\Exception\InvalidRegionException;use Aws\Exception\UnresolvedEndpointException;use Aws\S3\Exception\S3Exception;use InvalidArgumentException;use Psr\Http\Message\RequestInterface;/** * Checks for access point ARN in members targeting BucketName, modifying * endpoint as appropriate * * @internal */class BucketEndpointArnMiddleware{    use EndpointRegionHelperTrait;    /** @var callable */    private $nextHandler;    /** @var array */    private $nonArnableCommands = ['CreateBucket'];    /** @var boolean */    private $isUseEndpointV2;    /**     * Create a middleware wrapper function.     *     * @param Service $service     * @param $region     * @param array $config     * @return callable     */    public static function wrap(        Service $service,        $region,        array $config,        $isUseEndpointV2    ) {        return function (callable $handler) use ($service, $region, $config, $isUseEndpointV2) {            return new self($handler, $service, $region, $config, $isUseEndpointV2);        };    }    public function __construct(        callable $nextHandler,        Service $service,        $region,        array $config = [],        $isUseEndpointV2 = false    ) {        $this->partitionProvider = PartitionEndpointProvider::defaultProvider();        $this->region = $region;        $this->service = $service;        $this->config = $config;        $this->nextHandler = $nextHandler;        $this->isUseEndpointV2 = $isUseEndpointV2;    }    public function __invoke(CommandInterface $cmd, RequestInterface $req)    {        $nextHandler = $this->nextHandler;        $op = $this->service->getOperation($cmd->getName())->toArray();        if (!empty($op['input']['shape'])) {            $service = $this->service->toArray();            if (!empty($input = $service['shapes'][$op['input']['shape']])) {                foreach ($input['members'] as $key => $member) {                    if ($member['shape'] === 'BucketName') {                        $arnableKey = $key;                        break;                    }                }                if (!empty($arnableKey) && ArnParser::isArn($cmd[$arnableKey])) {                    try {                        // Throw for commands that do not support ARN inputs                        if (in_array($cmd->getName(), $this->nonArnableCommands)) {                            throw new S3Exception(                                'ARN values cannot be used in the bucket field for'                                . ' the ' . $cmd->getName() . ' operation.',                                $cmd                            );                        }                        if (!$this->isUseEndpointV2) {                            $arn = ArnParser::parse($cmd[$arnableKey]);                            $partition = $this->validateArn($arn);                            $host = $this->generateAccessPointHost($arn, $req);                        }                        // Remove encoded bucket string from path                        $path = $req->getUri()->getPath();                        $encoded = rawurlencode($cmd[$arnableKey]);                        $len = strlen($encoded) + 1;                        if (trim(substr($path, 0, $len), '/') === "{$encoded}") {                            $path = substr($path, $len);                            if (substr($path, 0, 1) !== "/") {                                $path = '/' . $path;                            }                        }                        if (empty($path)) {                            $path = '';                        }                        // Set modified request                        if ($this->isUseEndpointV2) {                            $req = $req->withUri(                                $req->getUri()->withPath($path)                            );                            goto next;                        }                        $req = $req->withUri(                            $req->getUri()->withPath($path)->withHost($host)                        );                        // Update signing region based on ARN data if configured to do so                        if ($this->config['use_arn_region']->isUseArnRegion()                            && !$this->config['use_fips_endpoint']->isUseFipsEndpoint()                        ) {                            $region = $arn->getRegion();                        } else {                            $region = $this->region;                        }                        $endpointData = $partition([                            'region' => $region,                            'service' => $arn->getService()                        ]);                        $cmd['@context']['signing_region'] = $endpointData['signingRegion'];                        // Update signing service for Outposts and Lambda ARNs                        if ($arn instanceof OutpostsArnInterface                            || $arn instanceof ObjectLambdaAccessPointArn                        ) {                            $cmd['@context']['signing_service'] = $arn->getService();                        }                    } catch (InvalidArnException $e) {                        // Add context to ARN exception                        throw new S3Exception(                            'Bucket parameter parsed as ARN and failed with: '                            . $e->getMessage(),                            $cmd,                            [],                            $e                        );                    }                }            }        }        next:            return $nextHandler($cmd, $req);    }    private function generateAccessPointHost(        BaseAccessPointArn $arn,        RequestInterface $req    ) {        if ($arn instanceof OutpostsAccessPointArn) {            $accesspointName = $arn->getAccesspointName();        } else {            $accesspointName = $arn->getResourceId();        }        if ($arn instanceof MultiRegionAccessPointArn) {            $partition = $this->partitionProvider->getPartitionByName(                $arn->getPartition(),                's3'            );            $dnsSuffix = $partition->getDnsSuffix();            return "{$accesspointName}.accesspoint.s3-global.{$dnsSuffix}";        }        $host = "{$accesspointName}-" . $arn->getAccountId();        $useFips = $this->config['use_fips_endpoint']->isUseFipsEndpoint();        $fipsString = $useFips ? "-fips" : "";        if ($arn instanceof OutpostsAccessPointArn) {            $host .= '.' . $arn->getOutpostId() . '.s3-outposts';        } else if ($arn instanceof ObjectLambdaAccessPointArn) {            if (!empty($this->config['endpoint'])) {                return $host . '.' . $this->config['endpoint'];            } else {                $host .= ".s3-object-lambda{$fipsString}";            }        } else {            $host .= ".s3-accesspoint{$fipsString}";            if (!empty($this->config['dual_stack'])) {                $host .= '.dualstack';            }        }        if (!empty($this->config['use_arn_region']->isUseArnRegion())) {            $region = $arn->getRegion();        } else {            $region = $this->region;        }        $region = \Aws\strip_fips_pseudo_regions($region);        $host .= '.' . $region . '.' . $this->getPartitionSuffix($arn, $this->partitionProvider);        return $host;    }    /**     * Validates an ARN, returning a partition object corresponding to the ARN     * if successful     *     * @param $arn     * @return \Aws\Endpoint\Partition     */    private function validateArn($arn)    {        if ($arn instanceof AccessPointArnInterface) {            // Dualstack is not supported with Outposts access points            if ($arn instanceof OutpostsAccessPointArn                && !empty($this->config['dual_stack'])            ) {                throw new UnresolvedEndpointException(                    'Dualstack is currently not supported with S3 Outposts access'                    . ' points. Please disable dualstack or do not supply an'                    . ' access point ARN.');            }            if ($arn instanceof MultiRegionAccessPointArn) {                if (!empty($this->config['disable_multiregion_access_points'])) {                    throw new UnresolvedEndpointException(                        'Multi-Region Access Point ARNs are disabled, but one was provided.  Please'                        . ' enable them or provide a different ARN.'                    );                }                if (!empty($this->config['dual_stack'])) {                    throw new UnresolvedEndpointException(                        'Multi-Region Access Point ARNs do not currently support dual stack. Please'                        . ' disable dual stack or provide a different ARN.'                    );                }            }            // Accelerate is not supported with access points            if (!empty($this->config['accelerate'])) {                throw new UnresolvedEndpointException(                    'Accelerate is currently not supported with access points.'                    . ' Please disable accelerate or do not supply an access'                    . ' point ARN.');            }            // Path-style is not supported with access points            if (!empty($this->config['path_style'])) {                throw new UnresolvedEndpointException(                    'Path-style addressing is currently not supported with'                    . ' access points. Please disable path-style or do not'                    . ' supply an access point ARN.');            }            // Custom endpoint is not supported with access points            if (!is_null($this->config['endpoint'])                && !$arn instanceof  ObjectLambdaAccessPointArn            ) {                throw new UnresolvedEndpointException(                    'A custom endpoint has been supplied along with an access'                    . ' point ARN, and these are not compatible with each other.'                    . ' Please only use one or the other.');            }            // Dualstack is not supported with object lambda access points            if ($arn instanceof ObjectLambdaAccessPointArn                && !empty($this->config['dual_stack'])            ) {                throw new UnresolvedEndpointException(                    'Dualstack is currently not supported with Object Lambda access'                    . ' points. Please disable dualstack or do not supply an'                    . ' access point ARN.');            }            // Global endpoints do not support cross-region requests            if ($this->isGlobal($this->region)                && $this->config['use_arn_region']->isUseArnRegion() == false                && $arn->getRegion() != $this->region                && !$arn instanceof MultiRegionAccessPointArn            ) {                throw new UnresolvedEndpointException(                    'Global endpoints do not support cross region requests.'                    . ' Please enable use_arn_region or do not supply a global region'                    . ' with a different region in the ARN.');            }            // Get partitions for ARN and client region            $arnPart = $this->partitionProvider->getPartition(                $arn->getRegion(),                's3'            );            $clientPart = $this->partitionProvider->getPartition(                $this->region,                's3'            );            // If client partition not found, try removing pseudo-region qualifiers            if (!($clientPart->isRegionMatch($this->region, 's3'))) {                $clientPart = $this->partitionProvider->getPartition(                    \Aws\strip_fips_pseudo_regions($this->region),                    's3'                );            }            if (!$arn instanceof MultiRegionAccessPointArn) {                // Verify that the partition matches for supplied partition and region                if ($arn->getPartition() !== $clientPart->getName()) {                    throw new InvalidRegionException('The supplied ARN partition'                        . " does not match the client's partition.");                }                if ($clientPart->getName() !== $arnPart->getName()) {                    throw new InvalidRegionException('The corresponding partition'                        . ' for the supplied ARN region does not match the'                        . " client's partition.");                }                // Ensure ARN region matches client region unless                // configured for using ARN region over client region                $this->validateMatchingRegion($arn);                // Ensure it is not resolved to fips pseudo-region for S3 Outposts                $this->validateFipsConfigurations($arn);            }            return $arnPart;        }        throw new InvalidArnException('Provided ARN was not a valid S3 access'            . ' point ARN or S3 Outposts access point ARN.');    }    /**     * Checks if a region is global     *     * @param $region     * @return bool     */    private function isGlobal($region)    {        return $region == 's3-external-1' || $region == 'aws-global';    }}
 |