I am trying to download a file from AWS S3 to my iOS mobile app from a folder that is specific to the user, using Transfer Manager, like so:
@IBAction func download() {
let transferManager = AWSS3TransferManager.default()!
let downloadingFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("disney1.jpg")
let downloadRequest = AWSS3TransferManagerDownloadRequest()!
downloadRequest.bucket = "sidestreamx"
// user's UUID/disney1
downloadRequest.key = "631d121f-b294-4318-b3cd-36b3b74ebdff/disney1"
downloadRequest.downloadingFileURL = downloadingFileURL
transferManager.download(downloadRequest).continue(with: AWSExecutor.mainThread(), with: {
(task: AWSTask<AnyObject>) -> Any? in
if let error = task.error as? NSError {
// handle error
return nil
}
self.imageView.image = UIImage(contentsOfFile: downloadingFileURL.path)
return nil
})
}
My IAM role permission policy is the following, gotten from this AWS doc:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "GetBucketListIfRequestIsForUser",
"Action": [
"s3:ListBucket"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::sidestreamx"
],
"Condition": {
"StringLike": {
"s3:prefix": [
"${cognito-identity.amazonaws.com:sub}/*"
]
}
}
},
{
"Sid": "S3GetObjects",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::sidestreamx/${cognito-identity.amazonaws.com:sub}/*"
]
}
]
}
Response I get is
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>E1F205B58EF4A670</RequestId>
<HostId>dUWI8PfVZL3mJmykjhXRqvFd1yt/CqDFNlwgwD3kmLk2vrMBP6JvVgezMYSROt3KyE3dx0+3eDE=</HostId>
</Error>
The user is authenticated via AWS Cognito User Pool & Cognito Federated Identities. I've debugged and extracted the JWT token, and seen that the sub = "631d121f-b294-4318-b3cd-36b3b74ebdff"
. I've even used Charles to see the Request/Response.
It does work if I replace ${cognito-identity.amazonaws.com:sub}
in the last statement S3GetObjects
with 631d121f-b294-4318-b3cd-36b3b74ebdff
to get arn:aws:s3:::sidestreamx/631d121f-b294-4318-b3cd-36b3b74ebdff/*
. The first statement can continue having the policy variable and it'll still work. It'll work if I remove the first statement altogether! It's when I add the policy variable to the last statement where it starts to breakdown.
I've checked out this Stack Overflow question and this one, to no avail. So yea, I don't know. I've been at this for almost over 9 man hours, so any help would sincerely be appreciatd.
Problem solved. Turns out, ${cognito-identity.amazonaws.com:sub}
doesn't really refer to the sub
in the JWT token. It refers to IdentityID from the credentialsProvider:
(AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider
as! AWSCognitoCredentialsProvider).getIdentityId()
.continue({task -> Any? in
print("Credentials ID is \(task.result!)")
return nil
})
I manually made a folder in my bucket with the name equal to task.result!
(which is in the format of us-east-1:XXXXXXXXXXXXXXXXXX
fyi), and it worked.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With