1 /**
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  * SPDX-License-Identifier: Apache-2.0.
4  */
5 
6 #include <cassert>
7 #include <aws/core/Region.h>
8 #include <aws/core/utils/DNS.h>
9 #include <aws/core/utils/Outcome.h>
10 #include <aws/core/utils/StringUtils.h>
11 #include <aws/s3control/S3ControlARN.h>
12 
13 namespace Aws
14 {
15     namespace S3Control
16     {
S3ControlARN(const Aws::String & arn)17         S3ControlARN::S3ControlARN(const Aws::String& arn) : Utils::ARN(arn)
18         {
19             ParseARNResource();
20         }
21 
Validate(const char * clientRegion) const22         S3ControlARNOutcome S3ControlARN::Validate(const char* clientRegion) const
23         {
24             // Take pseudo region into consideration here.
25             Aws::String region = clientRegion ? clientRegion : "";
26             Aws::StringStream ss;
27             if (this->GetResourceType() == ARNResourceType::OUTPOST && Aws::Region::IsFipsRegion(region))
28             {
29                 ss.str("");
30                 ss << "Outposts ARN do not support fips regions right now.";
31                 return S3ControlARNOutcome(Aws::Client::AWSError<S3ControlErrors>(S3ControlErrors::VALIDATION, "VALIDATION", ss.str(), false));
32             }
33             else if (this->GetRegion() != Aws::Region::ComputeSignerRegion(clientRegion))
34             {
35                 ss.str("");
36                 ss << "Region mismatch between \"" << this->GetRegion() << "\" defined in ARN and \""
37                     << clientRegion << "\" defined in client configuration. "
38                     << "You can specify AWS_S3_USE_ARN_REGION to ignore region defined in client configuration.";
39                 return S3ControlARNOutcome(Aws::Client::AWSError<S3ControlErrors>(S3ControlErrors::VALIDATION, "VALIDATION", ss.str(), false));
40             }
41             else
42             {
43                 return Validate();
44             }
45         }
46 
Validate() const47         S3ControlARNOutcome S3ControlARN::Validate() const
48         {
49             Aws::String errorMessage;
50             bool success = false;
51             Aws::StringStream ss;
52 
53             if (!*this)
54             {
55                 errorMessage = "Invalid ARN.";
56             }
57             // Validation on partition.
58             else if (this->GetPartition().find("aws") != 0)
59             {
60                 ss.str("");
61                 ss << "Invalid partition in ARN: " << this->GetPartition() << ". Valid options: aws, aws-cn, and etc.";
62             }
63             // Validation on service.
64             else if (this->GetService() != "s3" && this->GetService() != "s3-outposts")
65             {
66                 ss.str("");
67                 ss << "Invalid service in ARN: " << this->GetService() << ". Valid options: s3, s3-outposts";
68                 errorMessage = ss.str();
69             }
70             // Validation on region.
71             // TODO: Failure on different partitions.
72             else if (this->GetRegion().empty())
73             {
74                 errorMessage = "Invalid ARN with empty region.";
75             }
76             else if (!Utils::IsValidDnsLabel(this->GetRegion()))
77             {
78                 ss.str("");
79                 ss << "Invalid region in ARN: " << this->GetRegion() << ". Region should be a RFC 3986 Host label.";
80                 errorMessage = ss.str();
81             }
82             else if (Aws::Region::IsFipsRegion(this->GetRegion()))
83             {
84                 ss.str("");
85                 ss << "Invalid region in ARN: " << this->GetRegion() << ". FIPS region is not allowed in ARN.";
86                 errorMessage = ss.str();
87             }
88             // Validation on account ID
89             else if (!Utils::IsValidDnsLabel(this->GetAccountId()))
90             {
91                 ss.str("");
92                 ss << "Invalid account ID in ARN: " << this->GetAccountId() << ". Account ID should be a RFC 3986 Host label.";
93                 errorMessage = ss.str();
94             }
95             // Validation on Outposts ARN:
96             else if (this->GetResourceType() == ARNResourceType::OUTPOST)
97             {
98                 if (!Utils::IsValidDnsLabel(this->GetResourceId()))
99                 {
100                     ss.str("");
101                     ss << "Invalid outpost ID in Outposts ARN: " << this->GetResourceId() << ". Outpost ID should be a RFC 3986 Host label.";
102                     errorMessage = ss.str();
103                 }
104                 else if (this->GetSubResourceType() != ARNResourceType::ACCESSPOINT && this->GetSubResourceType() != ARNResourceType::BUCKET)
105                 {
106                     ss.str("");
107                     ss << "Invalid sub resource type in Outposts ARN: " << this->GetSubResourceType() << ". Valid options: " << ARNResourceType::ACCESSPOINT << ", " << ARNResourceType::BUCKET;
108                     errorMessage = ss.str();
109                 }
110                 else if (!Utils::IsValidDnsLabel(this->GetSubResourceId()))
111                 {
112                     ss.str("");
113                     ss << "Invalid " << this->GetSubResourceType() << " name in Outposts ARN: " << this->GetSubResourceId() << ", " << this->GetSubResourceType() << " name should be a RFC 3986 Host label.";
114                     errorMessage = ss.str();
115                 }
116                 else
117                 {
118                     success = true;
119                 }
120             }
121             // Neither Access Point ARN, Bucket ARN nor Outposts ARN.
122             else
123             {
124                 ss.str("");
125                 ss << "Invalid resource type in ARN: " << this->GetResourceType() << ". Valid options: " << ARNResourceType::ACCESSPOINT << ", " << ARNResourceType::OUTPOST;
126                 errorMessage = ss.str();
127             }
128 
129             if (success)
130             {
131                 return S3ControlARNOutcome(success);
132             }
133             else
134             {
135                 return S3ControlARNOutcome(Aws::Client::AWSError<S3ControlErrors>(S3ControlErrors::VALIDATION, "VALIDATION", errorMessage, false));
136             }
137         }
138 
ParseARNResource()139         void S3ControlARN::ParseARNResource()
140         {
141             if (!*this) return;
142 
143             Aws::String resource = this->GetResource();
144             Aws::Vector<Aws::String> resourceSegments;
145             if (resource.find(':') != std::string::npos)
146             {
147                 resourceSegments = Utils::StringUtils::Split(resource, ':', 4, Utils::StringUtils::SplitOptions::INCLUDE_EMPTY_ENTRIES);
148             }
149             else if (resource.find('/') != std::string::npos)
150             {
151                 resourceSegments = Utils::StringUtils::Split(resource, '/', 4, Utils::StringUtils::SplitOptions::INCLUDE_EMPTY_ENTRIES);
152             }
153             else
154             {
155                 resourceSegments.emplace_back(resource);
156             }
157 
158             switch (resourceSegments.size())
159             {
160             case 1:
161                 m_resourceId = resourceSegments[0];
162                 break;
163             case 2:
164                 m_resourceType = resourceSegments[0];
165                 m_resourceId = resourceSegments[1];
166                 break;
167             case 3:
168                 m_resourceType = resourceSegments[0];
169                 m_resourceId = resourceSegments[1];
170                 m_resourceQualifier = resourceSegments[2];
171                 break;
172             case 4:
173                 m_resourceType = resourceSegments[0];
174                 m_resourceId = resourceSegments[1];
175                 m_subResourceType = resourceSegments[2];
176                 m_subResourceId = resourceSegments[3];
177                 break;
178             default:
179                 assert(false);
180                 break;
181             }
182         }
183     }
184 }
185