AWS Getting Started With Swift (Major Issues) - ios

I am fairly new to Swift programming, and new to developing a mobile application in general. I tried to follow the following getting started guide here: https://github.com/aws/aws-sdk-ios
What I am currently attempting to do is create the component of my application which creates user accounts, and allows users to log in.
I have followed the ReadMe, but when I get to the following portion of code to make a call to AWS:
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: CognitoRegionType,
identityPoolId: CognitoIdentityPoolId)
let configuration = AWSServiceConfiguration(
region: DefaultServiceRegionType,
credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
let dynamoDB = AWSDynamoDB.defaultDynamoDB()
let listTableInput = AWSDynamoDBListTablesInput()
dynamoDB.listTables(listTableInput).continueWithBlock{ (task: AWSTask!) -> AnyObject! in
let listTablesOutput = task.result as AWSDynamoDBListTablesOutput
for tableName : AnyObject in listTablesOutput.tableNames {
println("\(tableName)")
}
return nil
}
I try to run my program, everything compiles, but stops at the line: let listTablesOutput = task.result as AWSDynamoDBListTablesOutput
When I say stops, I mean that I get a
Thread 6:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP,subcode=0x0)
Overall, I have spent hours reading documentation but it has not been able to answer any of my questions. Do we need to use DynamoDB to allow users to create an account and log in? If so, what exactly is Cognito for?
Also for the variables, shown in the code above, what exactly is credentialsProvider? Where do we get that information from?
Thank you in advance to anyone who can help me out in this matter.

you should use
let listTablesOutput = task.result as! AWSDynamoDBListTablesOutput
instead of
let listTablesOutput = task.result as AWSDynamoDBListTablesOutput ,
the compiler should warn you that if you compile it with the latest version of Xcode 6.
There is a DynamoDB Sample Project written in Swift which may be helpful.

Related

Fetching list of things in things group or things from AWS IoT

I need list of things in group or list of things from AWS with that I tried to find solution from AWSIoT Reference So i have used below code to get it. Previously i used to get it using normal API call from our backend service but i need fully use with AWS.
func initializeAWS() {
let credentialsProvider = AWSCognitoCredentialsProvider(regionType:AWS_REGION,
identityPoolId:IDENTITY_POOL_ID)
initializeControlPlane(credentialsProvider: credentialsProvider)
}
func initializeControlPlane(credentialsProvider: AWSCredentialsProvider) {
let controlPlaneServiceConfiguration = AWSServiceConfiguration(region:AWS_REGION, credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = controlPlaneServiceConfiguration
iot = AWSIoT.default()
let request = AWSIoTListThingsInThingGroupRequest()
request?.thingGroupName = "XXXGroupName"
let output = iot.listThings(inThingGroup: request!)
print("output is \(output.result)")
print("error is \(output.error)")
}
I have used here AWSIoT & AWSIoTListThingsInThingGroupRequest object to get list of things may i know is this right way to fetch ? if it is I'm output and error both objects getting nil.
I tried to find solution for the AWS IOT example from Github, I didnt get anything relevant answer to this. Or is there anything in iotDataManager that will give list of things ? Please can you help me on this ? For more info I have raised question on AWS Github Fetching list of things in things group
I've checked log level output was getting, All configurations as well was right only thing i wasn't aware about is how to get response of things, the way to get things is as below.
let credentialsProvider = AWSCognitoCredentialsProvider(regionType:AWS_REGION,
identityPoolId:IDENTITY_POOL_ID)
let controlPlaneServiceConfiguration = AWSServiceConfiguration(region:AWS_REGION, credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = controlPlaneServiceConfiguration
iot = AWSIoT.default()
let request = AWSIoTListThingsInThingGroupRequest()
request?.thingGroupName = "XXXGroupName"
let output = iot.listThings(inThingGroup: request!)
output.continueOnSuccessWith { (response) -> Any? in
if let result = response.result, let things = result.things {
self.awsDevices = things
completionHandler(true)
}
return self.awsDevices
}

Process for uploading image to s3 with AWS Appsync || iOS image uploading with Appsync

I'm working on a new project that requires uploading attachments in the form of images. I'm using DynamoDB and AppSync API's to insert and retrieve data from database. As we are new to the AppSync and all the amazon services and database we are using for the app i'm little bit confused about the authentication process. Right now we are using API key for authentication and I have tried these steps to upload image to s3.
1 Configue the AWSServiceManager with static configuration like :-
let staticCredit = AWSStaticCredentialsProvider(accessKey: kAppSyncAccessKey, secretKey: kAppSyncSecretKey)
let AppSyncRegion: AWSRegionType = .USEast2
let config = AWSServiceConfiguration(region: AppSyncRegion, credentialsProvider: staticCredit)
AWSServiceManager.default().defaultServiceConfiguration = config
2 Uploading picture with this method : -
func updatePictureToServer(url:URL, completion:#escaping (Bool)->Void){
let transferManager = AWSS3TransferManager.default()
let uploadingFileURL = url
let uploadRequest = AWSS3TransferManagerUploadRequest()
let userBucket = String(format: "BUCKET")
uploadRequest?.bucket = userBucket
let fileName = String(format: "%#%#", AppSettings.getUserId(),".jpg")
uploadRequest?.key = fileName
uploadRequest?.body = uploadingFileURL
transferManager.upload(uploadRequest!).continueWith(executor: AWSExecutor.mainThread(), block: { (task:AWSTask<AnyObject>) -> Any? in
if let error = task.error as NSError? {
if error.domain == AWSS3TransferManagerErrorDomain, let code = AWSS3TransferManagerErrorType(rawValue: error.code) {
switch code {
case .cancelled, .paused:
break
default:
print("Error uploading: \(String(describing: uploadRequest!.key)) Error: \(error)")
}
} else {
print("Error uploading: \(String(describing: uploadRequest!.key)) Error: \(error)")
}
completion(false)
return nil
}
_ = task.result
completion(true)
print("Upload complete for: \(String(describing: uploadRequest!.key))")
return nil
})
}
3 And finally i'm able to see the uploaded image on the S3 bucket
But i'm concerned about how to save the url of the image and how to retrieve the image because when i have to make the buket PUBLIC to retrieve the image and i don't think that's a good approach, plus is it necessary to have a Cognito user pool because we aren't using Cognito user pool yet in our app and not have much knowledge about that too and documents are not helping in practical situations because we are implementing ti for the first time so we need some little help.
So two question : -
Proper procedure to use for uploading and retrieving images for S3 and AppSync.
Is it necessary to use Cognito user pool for image uploading and retrieving.
Thanks
Note: Any suggestion or improvement or anything related to the AppSync, S3 or DynamoDB will be truly appreciated and language is not a barrier just looking for directions so swift or objective-c no problem.
You need per-identity security on the bucket using Cognito Federated Identities which gives each user their own secure bucket. You can leverage the AWS Amplify to set this up for your project with $amplify add auth and selecting the default config, then $amplify add storage which configures that bucket and pool with appropriate permissions to use private uploads.
For more info checkout the repo: https://github.com/aws-amplify/amplify-cli

Using DynamoDB With Cognito: Token is not from a supported provider of this identity pool

I am in the process of implementing registration and login for my iOS app, using this project as an example:
https://github.com/awslabs/aws-sdk-ios-samples/tree/75ada5b6283b7c04c1214b2e1e0a6394377e3f2b/CognitoYourUserPools-Sample/Objective-C/CognitoYourUserPoolsSample
Previously, my app was able to access DynamoDB resources by using a credentials provider set up in my AppDelegate's didFinishLaunchingWithOptions method. However, after changing my project to include logging in and function like the example, I see the error:
"__type":"NotAuthorizedException","message":"Token is not from a supported provider of this identity pool."
The code setting the credentialsProviderin AppDelegate currently looks like this:
let serviceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: nil)
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId:APP_CLIENT_ID, clientSecret: APP_CLIENT_SECRET, poolId: USER_POOL_ID)
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: USER_POOL_NAME)
let pool = AWSCognitoIdentityUserPool(forKey:USER_POOL_NAME)
pool.delegate = self
self.storyboard = UIStoryboard(name: "Main", bundle: nil)
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: IDENTITY_POOL_ID, identityProviderManager:pool)
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider:credentialsProvider)
I also cannot access any DynamoDB data through my app.
Based on the console output, the registration process seems to work correctly, although I'm unsure about the sign-in process. It occurred to me that I had changed the region from EU-West-1, where the DynamoDB resources were stored, to US-East-1. In order to account for this change, I repeated the same steps I had intially taken to allow my app to access DynamoDB:
I created Auth and Unauth roles, both with access to the same actions as the role which had previously worked, but for the EU-West-1 resources instead.
I set these roles to the user pool I created when setting up registration under "unauthenticated role" and "authenticated role".
In case it makes a difference, I should note that I did not use the exact same sign-in process outlined in the example project I linked. Instead, I used the explicit sign in process, like so:
let name = usernameField.text!
let user = pool!.getUser(name)
lock()
user.getSession(name, password: passwordField.text!, validationData: nil, scopes: nil).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: {
(task:AWSTask!) -> AnyObject! in
if task.error != nil {
self.sendErrorPopup("ERROR: Unable to sign in. Error description: " + task.error!.description)
} else {
print("Successful Login")
dispatch_async(dispatch_get_main_queue()){
self.performSegueWithIdentifier("mainViewControllerSegue", sender: self)
}
}
self.unlock()
return nil
})
The methods lock(), unlock(), and sendErrorPopup() are strictly UI-related methods that I made so that the beginning and end of the sign-in process would be more visually clear. The console output always reads "successful login", but I am wondering if this code actually signs the user in correctly, since the error message makes it sound like the user might not be properly authorized.
It occurred to me that the US-West tables might not have been set up correctly, but I experience the same problem even when trying to create new tables, so I don't think that's the issue. Are there steps I might have missed as far as giving the user access to DynamoDB? Has the process changed with AWS Cognito's new beta user pool system?
EDIT 2:
I fixed the previous issue, and for a while, my app was working fine. However, it has suddenly stopped loading DynamoDB data when I sign in, and shows the error message: invalid login token. Can't pass in a Cognito token. Currently, my AppData code looks like this:
let serviceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: nil)
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId:APP_CLIENT_ID, clientSecret: APP_CLIENT_SECRET, poolId: USER_POOL_ID)
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: USER_POOL_NAME)
let pool = AWSCognitoIdentityUserPool(forKey:USER_POOL_NAME)
pool.delegate = self
self.storyboard = UIStoryboard(name: "Main", bundle: nil)
self.credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: IDENTITY_POOL_ID, identityProviderManager:pool)
let manager = IdentityProviderManager(tokens: [NSString:NSString]())
self.credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: IDENTITY_POOL_ID, identityProviderManager: manager)
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider:credentialsProvider!)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
...and my sign-in code looks like this:
if locked { return }
trimRegistrationValues()
let name = usernameField.text!
let user = pool!.getUser(name)
lock()
user.getSession(name, password: passwordField.text!, validationData: nil, scopes: nil).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: {
(task:AWSTask!) -> AnyObject! in
if task.error != nil {
self.sendErrorPopup("ERROR: Unable to sign in. Error description: " + task.error!.description)
} else {
print("Successful Login")
let loginKey = "cognito-idp.us-east-1.amazonaws.com/" + USER_POOL_ID
var logins = [NSString : NSString]()
self.credentialsProvider!.identityProvider.logins().continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("ERROR: Unable to get logins. Description: " + task.error!.description)
} else {
if task.result != nil{
let prevLogins = task.result as! [NSString:NSString]
print("Previous logins: " + String(prevLogins))
logins = prevLogins
}
logins[loginKey] = name
let manager = IdentityProviderManager(tokens: logins)
self.credentialsProvider!.setIdentityProviderManagerOnce(manager)
self.credentialsProvider!.getIdentityId().continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("ERROR: Unable to get ID. Error description: " + task.error!.description)
} else {
print("Signed in user with the following ID:")
print(task.result)
dispatch_async(dispatch_get_main_queue()){
self.performSegueWithIdentifier("mainViewControllerSegue", sender: self)
}
}
return nil
}
}
return nil
}
}
self.unlock()
return nil
})
I haven't changed anything between my app working and not working. I did cause a "too many password resets" error while testing the password reset functionality, but the issue persisted even when I created a new user account on my app, so I don't think that's the cause. Am I handling login correctly? If so, where should I look for other possible causes to this issue?
That exception is usually thrown if you've given Cognito a login but have not enabled your identity pool to consume that login provider. If you haven't, go to the Cognito Federated Identities console and turn on whichever provider you are trying to use (looks like User Pools), and this error should go away.
If you're certain you have set that up, can you give a code snippet of how you're setting the logins?
The key that you set the ID token against in logins should be of the format cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID> not your USER_POOL_NAME. This blog along with the link in your post for our dev guide should explain the steps and code you need.
As for the solution to deprecated logins dictionary, you need to use this constructor to create the credentials provider. The identityProviderManager here should be an implementation of AWSIdentityProviderManager Protocol and the logins method should return the dictionary mapping for your provider name to the token. The credentials provider will call this method every time it needs the identity provider token. Check this answer for more details.

Amazon Cognito with iOS: Access to Identity Forbidden

Thanks for the help in advance!
I am having some trouble getting Amazon Cognito to store/synchronize data properly.
On the dataset.synchronize() line (which does not store the data in Cognito), I get a large output error (with ID starred out) such as:
AWSCredentialsProvider.m line:429 | __73-[AWSCognitoCredentialsProvider
getCredentialsWithCognito:authenticated:]_block_invoke | GetCredentialsForIdentity
failed. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain
Code=10 "(null)" UserInfo={__type=NotAuthorizedException, message=Access to
Identity '*****' is forbidden.}]
The cognitoID is not nil, and returns properly (and matches the values I can read online)
For instance, after authenticating with Facebook, I perform the following:
if (FBSDKAccessToken.currentAccessToken() != nil)
{
let fbCognitoToken = FBSDKAccessToken.currentAccessToken().tokenString
credentialsProvider.logins = [AWSCognitoLoginProviderKey.Facebook.rawValue: fbCognitoToken]
// Retrieve your Amazon Cognito ID
credentialsProvider.getIdentityId().continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("Error: " + task.error!.localizedDescription)
}
else {
// the task result will contain the identity id
let cognitoId = task.result
//checking if cognito was successful, if true, sets success condition to true to prepare for segue into app
if cognitoId != nil{
print (cognitoId)
cognitoSuccess = true
let syncClient = AWSCognito.defaultCognito()
let dataset = syncClient.openOrCreateDataset("User_Data")
dataset.setString("test#test.com", forKey:"Email")
// credentialsProvider.refresh()
dataset.synchronize()
} }return nil}}
I can read data from Facebook correctly, and all authentication occurred correctly from what I can tell. I suspect there is something simple that is at the root here, but after spending several days, I cannot figure it out! Using the IAM checker in the AWS portal returns all "green checks" for Cognito functions, so I am sure this not a permissions issue on the server-side, either.
Thanks again for any insight you might have!
Edit:
Before the chunk of code above, I call:
let credentialsProvider = self.initializeCognito()
which runs (identity pool ID starred out):
func initializeCognito () -> AWSCognitoCredentialsProvider
{
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: AWSRegionType.USEast1, identityPoolId: "******")
let defaultServiceConfiguration = AWSServiceConfiguration(
region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
return credentialsProvider
}
That exception can be thrown when you're trying to get credentials for an authenticated id without giving any provider token linked to it. Cognito requires at least one to be given.
Can you check that you're including the facebook token during the GetCredentialsForIdentity call that's failing? If not, I'd guess that's your issue.
Edit:
Since you are using AWSCognito.defaultCognito(), it might help to follow the example on this docs page to make sure the sync client uses the right credentials provider:
let configuration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
Ended up figuring out the answer-- when I first set up AWS and was following some of Amazon's guides, I had placed code to create a new credentialsProvider in the application's App Delegate. I forgot about it, and then was trying later on to initialize another credentialsProvider. This confusion created the issues, and removing the initialization in App Delegate fixed the authentication problems.

Get a reference to the AWSS3 object

I'm uploading a photo from my iOS app to Amazon S3 successfully. I need to get the publicly accessible URL for that photo. Instead of building the URL manually, I use the following way to do that.
let transferManager = AWSS3TransferManager.defaultS3TransferManager()
transferManager.upload(uploadRequest).continueWithBlock { task in
if let error = task.error {
print("Upload failed: \(error.code) - \(error.localizedDescription)")
}
if let exception = task.exception {
print("Upload failed: \(exception)")
}
if task.result != nil {
print("Successfully uploaded!")
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: CognitoRegionType, identityPoolId: CognitoIdentityPoolId)
let configuration = AWSServiceConfiguration(region: DefaultServiceRegionType, credentialsProvider:credentialsProvider)
let aws3 = AWSS3(configuration: configuration)
let publicURL = aws3.configuration.endpoint.URL.URLByAppendingPathComponent(uploadRequest.bucket!).URLByAppendingPathComponent(uploadRequest.key!)
print(publicURL)
}
return nil
}
This works well and I get proper the public URL.
https://s3-ap-northeast-1.amazonaws.com/myapp/DAEF70E9-495A-40B4-B853-3B337486185D-4988-00000E22AB8E25A6.jpg
I have two problems.
1). Initializing it this way AWSS3(configuration: configuration) is deprecated now.
2). This while initializing code already happens inside the App Delegate's didFinishLaunchingWithOptions method.
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: CognitoRegionType, identityPoolId: CognitoIdentityPoolId)
let configuration = AWSServiceConfiguration(region: DefaultServiceRegionType, credentialsProvider:credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
However trying to call the endpoint property from this configuration returns nil.
So what I'm looking to do is this. I don't want to repeat the initializing code in both App Delegate and here. So if there's a way to get a reference to the already initialized object in App Delegate, I'd love to know.
I think you could use the following API: https://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSS3.html#//api/name/registerS3WithConfiguration:forKey:
The SDK would hold the object for you and can always fetch it by using S3ForKey: mentioned here: https://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSS3.html#//api/name/S3ForKey:
There are code snippets in the API reference demonstrating the usage.
-Rohan
I was actually able to get an instance of S3 object with AWSS3.defaultS3(). So I could construct the public URL like this.
let publicURL = AWSS3.defaultS3().configuration.endpoint.URL.URLByAppendingPathComponent(uploadRequest.bucket!).URLByAppendingPathComponent(uploadRequest.key!)

Resources