/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using Amazon.Runtime;
using Amazon.Runtime.Internal;
using Amazon.Runtime.Internal.Util;
using Amazon.S3Control.Model;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
namespace Amazon.S3Control.Internal
{
///
/// Shared helper methods for S3 ARNs
///
public static class S3ArnUtils
{
private static readonly char[] separators = new char[] { '/', '?' };
///
/// An array of the characters which split sections of an ARN
///
public static readonly char[] ArnSplit = new char[] { '/', ':' };
///
/// Service name of S3 outposts
///
public const string S3OutpostsService = "s3-outposts";
///
/// Service name of S3
///
public const string S3Service = "s3";
///
/// If the ARN contains an account ID and the request does not, then return it.
/// Throw an exception if there is a mismatch, or if neither the ARN or the
/// request contain an account Id.
///
/// The account ID in the request
/// The account ID in the ARN
/// A string Account ID
internal static string GetAccountIdBasedOnArn(string requestAccountId, string arnAccountId)
{
if (!string.IsNullOrEmpty(arnAccountId) && !string.IsNullOrEmpty(requestAccountId)
&& !string.Equals(arnAccountId, requestAccountId))
{
throw new AmazonClientException("Account ID mismatch, the Account ID cannot be specified in an ARN and in the accountId field");
}
if (string.IsNullOrEmpty(arnAccountId) && string.IsNullOrEmpty(requestAccountId))
{
throw new AmazonClientException("Invalid ARN, Account ID not set");
}
if (string.IsNullOrEmpty(requestAccountId) && !(arnAccountId.Length == 12 && arnAccountId.ToCharArray().All(x => char.IsDigit(x))))
{
throw new AmazonAccountIdException();
}
return !string.IsNullOrEmpty(requestAccountId) ? requestAccountId : arnAccountId;
}
///
/// Get the endpoint for an outpost request which doesn't use
/// an ARN.
///
/// The client config object for this request
/// The Uri Endpoint for the outpost request
internal static Uri GetNonStandardOutpostIdEndpoint(IClientConfig config)
{
return new UriBuilder($"{(config.UseHttp ? "http" : "https")}" +
$"://s3-outposts.{config.RegionEndpoint.SystemName}.{config.RegionEndpoint.PartitionDnsSuffix}").Uri;
}
///
/// Check if the request resource is an outpost resource.
///
/// The S3 request object
/// A boolean value of whether or not the resource path contains an outpost resource
internal static bool ResourcePathContainsOutpostsResource(IRequest request)
{
Arn arn;
return RequestContainsArn(request, out arn)
&& arn.IsOutpostArn();
}
///
/// Check the outpost id against certain criteria.
///
/// The outpost id to be checked
/// A boolean whether or not the string is an outpost Id
public static bool IsValidOutpostId(string outpostId)
{
return outpostId.Length <= 63 && outpostId.Length >= 1
&& outpostId.All(x => char.IsLetterOrDigit(x) || x == '-');
}
///
/// Check the many places that the ARN could be in an S3 Control request
/// for an ARN.
///
/// - CreateAccessPoint can contain the ARN in the payload
/// - ListAccessPoints can contain an ARN in the query params
/// - Most other requests can contain an ARN in the resource path
///
///
/// The request being sent to S3Control
/// Any ARN found in the request
/// A boolean of whether or not the request contains an ARN
internal static bool RequestContainsArn(IRequest request, out Arn arn)
{
var createAccessPointRequest = request.OriginalRequest as CreateAccessPointRequest;
if (createAccessPointRequest != null && createAccessPointRequest.IsSetBucket()
&& Arn.TryParse(createAccessPointRequest.Bucket.Trim().Trim(separators), out arn))
{
return true;
}
return DictionaryContainsArn(request.PathResources, out arn)
|| DictionaryContainsArn(request.Parameters, out arn);
}
///
/// Check if the value of any dictionary item
/// is of type ARN.
/// Note: this assumes that there is just 0-1 ARN in the path resource dictionary
///
/// The dictionary
/// ARN found in dictionary
/// A boolean whether or not the dictionary contains an ARN in the values
internal static bool DictionaryContainsArn(IDictionary dictValues, out Arn arn)
{
Arn outArn = null;
var matchingKvp = dictValues.FirstOrDefault(kvp => Arn.TryParse(kvp.Value, out outArn));
arn = matchingKvp.Value != null ? outArn : null;
return arn != null;
}
///
/// Replace the ARNs in the dictionary
/// with the extracted bucket or access point name.
/// Note: this assumes that there is just 0-1 ARN in the path resource dictionary
///
/// The dictionary of values
/// bucket or access point name
public static void ReplacePathResourceArns(IDictionary dictValues, string bucketOrAccessPointName)
{
var matchingKvp = dictValues.FirstOrDefault(kvp => Arn.TryParse(kvp.Value, out _));
if (!matchingKvp.Equals(default(KeyValuePair)))
{
dictValues[matchingKvp.Key] = bucketOrAccessPointName;
}
}
///
/// Need to hardcode special cases where the outpost ID can be set
/// but there is no ARN in the request.
///
/// An AmazonWebServiceRequest object
/// The outpost id found in the request
/// A boolean of whether or not the request contains a non-null outpost ID
public static bool DoesRequestHaveOutpostId(AmazonWebServiceRequest request, out string outpostId)
{
var createBucketRequest = request as CreateBucketRequest;
var listRegionalBucketsRequest = request as ListRegionalBucketsRequest;
var createAccessPointRequest = request as CreateAccessPointRequest;
outpostId = createBucketRequest?.OutpostId ?? listRegionalBucketsRequest?.OutpostId
?? createAccessPointRequest?.OutpostId;
return outpostId != null;
}
}
}