/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include <cassert>
#include <aws/core/Region.h>
#include <aws/core/utils/DNS.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/s3/S3ARN.h>
namespace Aws
{
namespace S3
{
S3ARN::S3ARN(const Aws::String& arn) : Utils::ARN(arn)
{
ParseARNResource();
}
S3ARNOutcome S3ARN::Validate(const char* clientRegion) const
{
// Take pseudo region into consideration here.
Aws::String region = clientRegion ? clientRegion : "";
Aws::StringStream ss;
if (this->GetResourceType() == ARNResourceType::OUTPOST && region.find("fips") != Aws::String::npos)
{
ss.str("");
ss << "Outposts ARN do not support fips regions right now.";
return S3ARNOutcome(Aws::Client::AWSError<S3Errors>(S3Errors::VALIDATION, "VALIDATION", ss.str(), false));
}
else if (this->GetRegion() != Aws::Region::ComputeSignerRegion(clientRegion))
{
ss.str("");
ss << "Region mismatch between \"" << this->GetRegion() << "\" defined in ARN and \""
<< clientRegion << "\" defined in client configuration. "
<< "You can specify AWS_S3_USE_ARN_REGION to ignore region defined in client configuration.";
return S3ARNOutcome(Aws::Client::AWSError<S3Errors>(S3Errors::VALIDATION, "VALIDATION", ss.str(), false));
}
else
{
return Validate();
}
}
S3ARNOutcome S3ARN::Validate() const
{
Aws::String errorMessage;
bool success = false;
Aws::StringStream ss;
if (!*this)
{
errorMessage = "Invalid ARN.";
}
// Validation on partition.
else if (this->GetPartition().find("aws") != 0)
{
ss.str("");
ss << "Invalid partition in ARN: " << this->GetPartition() << ". Valid options: aws, aws-cn, and etc.";
}
// Validation on service.
else if (this->GetService() != ARNService::S3 && this->GetService() != ARNService::S3_OUTPOSTS && this->GetService() != ARNService::S3_OBJECT_LAMBDA)
{
ss.str("");
ss << "Invalid service in ARN: " << this->GetService() << ". Valid options: " << ARNService::S3 << ", " << ARNService::S3_OUTPOSTS << ", " << ARNService::S3_OBJECT_LAMBDA << ".";
errorMessage = ss.str();
}
// Validation on region.
// TODO: Failure on different partitions.
else if (this->GetRegion().empty())
{
errorMessage = "Invalid ARN with empty region.";
}
else if (!Utils::IsValidDnsLabel(this->GetRegion()))
{
ss.str("");
ss << "Invalid region in ARN: " << this->GetRegion() << ". Region should be a RFC 3986 Host label.";
errorMessage = ss.str();
}
// Validation on account ID
else if (!Utils::IsValidDnsLabel(this->GetAccountId()))
{
ss.str("");
ss << "Invalid account ID in ARN: " << this->GetAccountId() << ". Account ID should be a RFC 3986 Host label.";
errorMessage = ss.str();
}
// Validation on Access Point ARN and Object Lambda Access Point ARN:
else if (this->GetResourceType() == ARNResourceType::ACCESSPOINT)
{
if (!Utils::IsValidDnsLabel(this->GetResourceId()))
{
ss.str("");
ss << "Invalid resource ID in accesspoint ARN: " << this->GetResourceId() << ". Resource ID should be a RFC 3986 Host label.";
errorMessage = ss.str();
}
else if (!this->GetResourceQualifier().empty())
{
ss.str("");
ss << "Invalid accesspoint ARN with non empty resource qualifier: " << this->GetResourceQualifier();
errorMessage = ss.str();
}
else if (!this->GetSubResourceType().empty() || !this->GetSubResourceId().empty())
{
ss.str("");
ss << "Invalid accesspoint ARN with non empty sub resource type: " << this->GetSubResourceType() << ", sub resource ID: " << this->GetSubResourceId();
errorMessage = ss.str();
}
else
{
success = true;
}
}
// Validation on Outposts ARN:
else if (this->GetResourceType() == ARNResourceType::OUTPOST)
{
if (this->GetRegion().find("fips") != Aws::String::npos)
{
ss.str("");
ss << "Outposts ARN do not support fips regions right now.";
errorMessage = ss.str();
}
else if (!Utils::IsValidDnsLabel(this->GetResourceId()))
{
ss.str("");
ss << "Invalid outpost ID in Outposts ARN: " << this->GetResourceId() << ". Outpost ID should be a RFC 3986 Host label.";
errorMessage = ss.str();
}
else if (this->GetSubResourceType() != ARNResourceType::ACCESSPOINT)
{
ss.str("");
ss << "Invalid sub resource type in Outposts ARN: " << this->GetSubResourceType() << ". Valid options: " << ARNResourceType::ACCESSPOINT;
errorMessage = ss.str();
}
else if (!Utils::IsValidDnsLabel(this->GetSubResourceId()))
{
ss.str("");
ss << "Invalid accesspoint name in Outposts ARN: " << this->GetSubResourceId() << ", accesspoint name should be a RFC 3986 Host label.";
errorMessage = ss.str();
}
else
{
success = true;
}
}
// ARN with unknown resource type.
else
{
ss.str("");
ss << "Invalid resource type in ARN: " << this->GetResourceType() << ". Valid options: " << ARNResourceType::ACCESSPOINT << ", " << ARNResourceType::OUTPOST << ".";
errorMessage = ss.str();
}
if (success)
{
return S3ARNOutcome(success);
}
else
{
return S3ARNOutcome(Aws::Client::AWSError<S3Errors>(S3Errors::VALIDATION, "VALIDATION", errorMessage, false));
}
}
void S3ARN::ParseARNResource()
{
if (!*this) return;
Aws::String resource = this->GetResource();
Aws::Vector<Aws::String> resourceSegments;
if (resource.find(':') != std::string::npos)
{
resourceSegments = Utils::StringUtils::Split(resource, ':', 4, Utils::StringUtils::SplitOptions::INCLUDE_EMPTY_ENTRIES);
}
else if (resource.find('/') != std::string::npos)
{
resourceSegments = Utils::StringUtils::Split(resource, '/', 4, Utils::StringUtils::SplitOptions::INCLUDE_EMPTY_ENTRIES);
}
else
{
resourceSegments.emplace_back(resource);
}
switch (resourceSegments.size())
{
case 1:
m_resourceId = resourceSegments[0];
break;
case 2:
m_resourceType = resourceSegments[0];
m_resourceId = resourceSegments[1];
break;
case 3:
m_resourceType = resourceSegments[0];
m_resourceId = resourceSegments[1];
m_resourceQualifier = resourceSegments[2];
break;
case 4:
m_resourceType = resourceSegments[0];
m_resourceId = resourceSegments[1];
m_subResourceType = resourceSegments[2];
m_subResourceId = resourceSegments[3];
break;
default:
assert(false);
break;
}
}
}
}