How to download images from AWS S3 in swift? - swift

is there a good function to download images from AWS S3 bucket? I have an access key and a secret key for permisson. The URL is thru a different database accessible. I also already imported AWSS3 and AWSCore.
I have already found a upload function:
func uploadFile(withImage image: UIImage) {
let access = "access_key"
let secret = "secret_key"
let credentials = AWSStaticCredentialsProvider(accessKey: access, secretKey: secret)
let configuration = AWSServiceConfiguration(region: AWSRegionType.EUCentral1, credentialsProvider: credentials)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let s3BucketName = "bucket_name"
let compressedImage = image.resizedImage(newSize: CGSize(width: 80, height: 80))
let data: Data = compressedImage.pngData()!
let remoteName = generateRandomStringWithLength(length: 12)+"."+data.format
print("REMOTE NAME : ",remoteName)
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = { (task, progress) in
DispatchQueue.main.async(execute: {
// Update a progress bar
})
}
var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?
completionHandler = { (task, error) -> Void in
DispatchQueue.main.async(execute: {
// Do something e.g. Alert a user for transfer completion.
// On failed uploads, `error` contains the error object.
})
}
let transferUtility = AWSS3TransferUtility.default()
transferUtility.uploadData(data, bucket: s3BucketName, key: remoteName, contentType: "image/"+data.format, expression: expression, completionHandler: completionHandler).continueWith { (task) -> Any? in
if let error = task.error {
print("Error : \(error.localizedDescription)")
}
if task.result != nil {
let url = AWSS3.default().configuration.endpoint.url
let publicURL = url?.appendingPathComponent(s3BucketName).appendingPathComponent(remoteName)
if let absoluteString = publicURL?.absoluteString {
// Set image with URL
print("Image URL : ",absoluteString)
}
}
return nil
}
}

I would not recommend to download files directly from S3 using an access and secret key.
I'd propose you do the following:
Make sure the bucket is as "private" as can be.
Have an API with authentication and authorisation (AWS API Gateway) that checks if the user is authenticated and permitted to download the S3 object.
Generate a pre-signed download URL with that is only valid for a short period of time (15-60 minutes).
Return that pre-signed download URL to your app through the API.
Use the URL within your app to download the S3 object.
This way you don't have to ship username and password in your app and the bucket is closed off to the "outside" reducing the risk of accidental information leakage.
Why I wouldn't recommend using the access key and secret key:
This is a potential security issue. People that reverse engineer the app could gain access to those "static" keys and depending on the underlying IAM role do all sorts of harm. But even if you have proper IAM roles with very limited access, essentially shipping a username and password with your app is not a good idea under any circumstance. How would you "rotate" the secret if something bad happens etc.

Related

Log into a site, and then download a file, in Swift

I'm downloading a CSV file from a website. I need to download this file while being logged in. The CSV file gives player projections for fantasy sports. When you download the file it will give you five players. However, if you purchase the premium service you get all player projections. I purchased the premium service, so, I'm trying to download this file while being signed into my account.
The code below downloads the CSV file with only five players. How do I sign into my account and then download this file?
guard let url = URL(string: "https://rotogrinders.com/projected-stats/nba-player.csv?site=fanduel") else { return }
let config = URLSessionConfiguration.default
// I don't know what I'm doing here. Also, the user name and password is not correct
let credential = URLCredential(user: "joe", password: "12345", persistence: .forSession)
let protectionSpace = URLProtectionSpace(host: "rotogrinders.com", port: 443, protocol: "https", realm: "Restricted", authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
// I don't know what I'm doing here either.
let credentialStorage = URLCredentialStorage()
credentialStorage.set(credential, for: protectionSpace)
config.urlCredentialStorage = credentialStorage
let task = URLSession(configuration: config).dataTask(with: url) { data, response, error in
guard data != nil else { return }
guard let rows = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)?.components(separatedBy: "\n") else { return }
print(rows)
}
task.resume()
The problem is that this site doesn't have an API. They specifically don't allow the use case you are trying to do.
https://rotogrinders.com/threads/site-with-api-597932
However, there are great tools in Python that may let you do what you are trying to do. Take a look at scrapy:
https://www.edureka.co/blog/web-scraping-with-python/

Downloading a JSON file as an Unauthenticated user AWS/S3/Cognito

I am trying to download a JSON file which is in my bucket in my S3 on my AWS account. I created an Unauthenticated cognito pool and copied this into my app delegate from the sample code:
let credentialsProvider = AWSCognitoCredentialsProvider(regionType:.USWest2,
identityPoolId:"us-west-2:59a31a8f-ee6a-45fe-adaa-fa3eff871c80")
let configuration = AWSServiceConfiguration(region:.USWest2, credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
In my view controller I have this code:
let transferManager = AWSS3TransferManager.default()
let downloadingFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("db_storage/costdb_latest.json ")
if let downloadRequest = AWSS3TransferManagerDownloadRequest(){
downloadRequest.bucket = "coast-s3-bucket"
downloadRequest.key = "db_storage/costdb_latest.json "
downloadRequest.downloadingFileURL = downloadingFileURL
transferManager.download(downloadRequest).continueWith(executor: AWSExecutor.default(), block: { (task: AWSTask<AnyObject>) -> Any? in
if( task.error != nil){
print(task.error!.localizedDescription)
return nil
}
print(task.result!)
if let data = NSData(contentsOf: downloadingFileURL){
DispatchQueue.main.async(execute: { () -> Void in
print(data)
})
}
return nil
})
}
Both the bucket and the pool are in the same region. USWest2 (Oregon). My bucket is public, and I've added AmazonS3FullAccess and AmazonS3ReadOnlyAccess to my policies. And I'm getting this error:
The operation couldn’t be completed. (com.amazonaws.AWSS3ErrorDomain error 4.)
Its not an authentication/authorization error (I think). From the looks of it, there is some issue with the configuration (region,endpoint etc.). Generally, these errors contain a full description of what's wrong. Try logging the full error message.
Also, see this example app for S3 TransferManager. Use your S3 & Cognito details. If this works, there is some code issue and you can find out by comparing both of these. If this does not work, then this could be a service issue.

Attempting to save username from twitter user to Firebase database iOS app

I'm attempting to save a twitter users username into the database for later reference my code below is executing but doesn't seem to be accessing the database or saving the username into the database and I'm really lost as to why. I'm attempting to have the username and userID so I can retrieve information about the user for a profile page in the app. So if I can avoid saving this data to the database all together that works too but I don't think it can be done that way.
fileprivate func setupTwitterButton() {
let twitterButton = TWTRLogInButton { (session, error) in
if let err = error {
print("Failed to login via Twitter: ", err)
return
}
// debug statement
//print("Successfully logged in using Twitter")
HUD.show(.labeledProgress(title: nil, subtitle: "Signing In"))
//we've authenticated twitter, time to log into firebase
guard let token = session?.authToken else { return }
guard let secret = session?.authTokenSecret else { return }
let creds = FIRTwitterAuthProvider.credential(withToken: token, secret: secret)
let dbref = FIRDatabase.database().reference()
let usersref = dbref.child("users")
let uid = session?.userID
//let user = FIRAuth.auth?.signIn
print("Creating user")
let newUserReference = usersref.child(uid!)
newUserReference.setValue(["username": session?.userName])
Okay so after some debugging it was pretty simple where I went wrong. I was trying to write to the database before I'd authenticated with the database. Once I had put my code for writing to the database after I'd authenticated it all worked correctly.

How to invoke an AWS Lambda function in Swift

I can't find any documentation or examples on how to invoke a Lambda function in Swift but I've tried to extrapolate from the documentation using Objective-C and I'm still getting errors:
"Error in myFunction: ValidationException: Supplied AttributeValue is empty, must contain exactly one of the supported datatypes"
It appears that I'm not passing in the parameters to the function correctly when I invoke the lambda function from swift because the script tries to write to DynamoDB but one of the parameters is empty (this lambda script works when I invoke it in javascript/node).
let lambda = AWSLambda.defaultLambda()
let request = AWSLambdaInvocationRequest()
var context = [String: String]()
let jsonString = "{\"email\":\"example#example.com\",\"name\":\"example\"}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
request.clientContext = jsonData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
request.functionName = "myFunction"
lambda.invoke(request).continueWithBlock( {
(currentTask: AWSTask!) -> AWSTask in
if (currentTask.error != nil) {
// failed to execute.
print("Error executing: ", currentTask.error)
task.setError(currentTask.error)
} else {
print("token: ", currentTask.result)
task.setResult(currentTask.result)
}
return currentTask
})
You need to set the payload parameter to a map containing the data you want to pass.
let invocationRequest = AWSLambdaInvokerInvocationRequest()
invocationRequest.functionName = "myFunction"
invocationRequest.invocationType = AWSLambdaInvocationType.RequestResponse
invocationRequest.payload = ["email" : "example#example.com", "name" : "example"]
let lambdaInvoker = AWSLambdaInvoker.defaultLambdaInvoker()
let task = lambdaInvoker.invoke(invocationRequest).continueWithSuccessBlock() { (task) -> AWSTask! in
print("response: ", task.result)
}
Ryan Fitzgerald's answer gives me multiple compile-time errors, but I've had success with this version:
First, I have an initialization function with access credentials. Note that this is not the recommended secure access method for production code, but it is fine for testing and other purposes. It also assumes you have a Constants.swift file where you define the listed constants:
func initializeLambda() {
let credentialsProvider = AWSStaticCredentialsProvider.init(accessKey:Constants.AWS_ACCESS_KEY, secretKey: Constants.AWS_SECRET_KEY)
let defaultServiceConfiguration = AWSServiceConfiguration(region: Constants.AWS_REGION, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
}
For the remainder we can provide a version similar to the previous version. I removed the 'let task' because 'task' is not used in his example. Additionally, I've included the logical outline of some JSON parsing that you are likely to be doing inside the invoke task. Finally, I've changed to a continueWithBlock(). If you use a continueWithSuccessBlock() you will not enter this block when Amazon Lambda reaches its timeout window or if something else goes wrong with the request and typically you do want these situations to be handled here.
self.initializeLambda() //Call our previously written initialization function
let invocationRequest = AWSLambdaInvokerInvocationRequest()
invocationRequest.functionName = "functionName"
invocationRequest.invocationType = AWSLambdaInvocationType.RequestResponse
invocationRequest.payload = ["key1" : "value1", "key2" : "value2"]
let lambdaInvoker = AWSLambdaInvoker.defaultLambdaInvoker()
lambdaInvoker.invoke(invocationRequest).continueWithBlock() { (task: AWSTask) -> AWSTask in
print("response: ", task.result)
//In here you'll likely be parsing a JSON payload
if let payload: AnyObject = task.result?.payload {
if let error: AnyObject = payload.objectForKey("error") {
//If there is an error key in the JSON dictionary...
} else {
//If the JSON dictionary has no error key...
}
return task;
}
}
Tested and verified as functional on Swift 2.2 in Xcode 7.3.
The answers from both the Ryan's were great and useful and I just want to add a couple of additional thoughts.
In most cases, before you can invoke a Lambda, you might need to authenticate, so the errors you get might not necessarily be because of your Lambda calls but due to failing authentication. With AWS, however, there are several different ways to authenticate and this will change based on the credentials you have.
Ryan Davis shows you one way where your backend team has set up an AWS Access Key and an AWS Secret Key.
In my case, I had to authenticate using AWS Cognito Identity Pools and there are also User Pool authentication so you need to figure out what credentials your team has given you and read the appropriate authentication documentation.
Since I needed to use using AWS Cognito Identity Pools, all I had was the region and the identity pool id so in Swift 5 authentication for AWS Cognito Identity Pools
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: Constants.AWS_REGION,
identityPoolId: Constants.AWS_REGION.AWS_IDENTITY_POOL_ID)
let serviceConfiguration = AWSServiceConfiguration(region: Constants.AWS_REGION,
credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
And then the Lambda invocation more or less stays the same but just with slightly updated Swift 5 syntax:
if let invocationRequest = AWSLambdaInvokerInvocationRequest() {
invocationRequest.functionName = "function_name"
invocationRequest.invocationType = AWSLambdaInvocationType.requestResponse
invocationRequest.payload = ["key_1": "value_1"]
let lambdaInvoker = AWSLambdaInvoker.default()
lambdaInvoker.invoke(invocationRequest) { (awsLambdaInvokerInvocationResponse, error) in
guard let payload = awsLambdaInvokerInvocationResponse?.payload as? [String: String] else {
// Handle error here
return
}
if let userId = payload["message"] {
print("USR Id: \(userId)")
}
}
}
You will need to adjust your handling based on the structure of your payload returned to you by the Lambda, in my case it was:
{
"message": "user-id-8868-8475-8757"
}
Finally, remember to import the required libraries for your use case, for my above case I needed:
import AWSCore
import AWSLambda

iOS7 Access user Facebook Profile Picture

So i'm trying to get a users Facebook profile image using SLRequest. I feel like I've scoured the entire internet to no avail and am at my wits end. Here's the dilemma...
Version 1 of the code:
let store = ACAccountStore()
let type = store.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierFacebook)
store.requestAccessToAccountsWithType(type, options: [ ACFacebookAppIdKey: "1437725166510606", ACFacebookPermissionsKey: ["email"] ]) { (granted: Bool, error: NSError!) -> Void in
if granted {
let accounts = store.accountsWithAccountType(type)
if let account = accounts.last as? ACAccount {
let pictureURLString = "https://graph.facebook.com/v2.1/me/picture"
let request = SLRequest(forServiceType: SLServiceTypeFacebook, requestMethod: SLRequestMethod.GET, URL: NSURL(string: pictureURLString), parameters: nil)
request.account = account
request.performRequestWithHandler() { (data: NSData!, response: NSHTTPURLResponse!, error: NSError!) -> Void in
if let imageData = data {
// Save the image
// println("Data size: \(imageData.length)\ndata: \(imageData.description)\nAs string: \(NSString(data: imageData, encoding: NSUTF8StringEncoding))")
data.writeToFile(NSFileManager.defaultManager().profileImagePath(), atomically: true)
}
}
}
}
}
Ok, so this versions works, but returns a really, really small version of the profile image. I want a larger image! According to the Facebook docs, and lot's of others on SO the way to do this is to specify parameters such as: type=large or width=120&height=120 but as soon as I do this I get the following error:
{"error":{"message":"An active access token must be used to query information about the current user.","type":"OAuthException","code":2500}}
When the Facebook docs for getting the profile image (at https://developers.facebook.com/docs/graph-api/reference/v2.1/user/picture) explicitly state:
Because profile pictures are always public on Facebook, this call does
not require any access token.
Many suggestions, such as this answer https://stackoverflow.com/a/7882628/1175289, suggest using the Facebook id rather than "me" in the request, but this does not seem to work at all now that we get an app_scoped_user_id rather than the canonical fbId.
EDIT: This works fine, I was just being a plank! :)
For the sake of sanity, here is the code that causes the error:
let store = ACAccountStore()
let type = store.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierFacebook)
store.requestAccessToAccountsWithType(type, options: [ ACFacebookAppIdKey: "1437725166510606", ACFacebookPermissionsKey: ["email"] ]) { (granted: Bool, error: NSError!) -> Void in
if granted {
let accounts = store.accountsWithAccountType(type)
if let account = accounts.last as? ACAccount {
let pictureURLString = "https://graph.facebook.com/v2.1/me/picture?type=large"
let request = SLRequest(forServiceType: SLServiceTypeFacebook, requestMethod: SLRequestMethod.GET, URL: NSURL(string: pictureURLString), parameters: nil)
request.account = account
request.performRequestWithHandler() { (data: NSData!, response: NSHTTPURLResponse!, error: NSError!) -> Void in
if let imageData = data {
// Save the image
// println("Data size: \(imageData.length)\ndata: \(imageData.description)\nAs string: \(NSString(data: imageData, encoding: NSUTF8StringEncoding))")
data.writeToFile(NSFileManager.defaultManager().profileImagePath(), atomically: true)
}
}
}
}
}
as you can see, the only thing that has changed is the addition of ?type=large to the url string.
If anyone has faced a similar issue, or has any idea what I'm doing wrong, help would be very much appreciated! :)
Because you are using /me/ in your API call, an access_token is required because the API doesn't know who me is. If you replace this with a User ID, e.g.
https://graph.facebook.com/v2.1/4/picture?type=large
It should work fine.
If you want to continue using /me/ in the URL, just append the user's access_token to the URL too, e.g.:
https://graph.facebook.com/v2.1/4/picture?type=large&access_token=abcdef