Using amazon AWS SNS service IOS - ios

I am developing an app which uses amazon aws service and it is a messenger.
I would like to use IOS Push Notification service and the amazon SNS to achieve the communication between 2 users. I am able to send message from the SNS console by publishing a message to the destination called endpoint.
However, i am not able to send message from one mobile to another mobile by amazon SDK of IOS. Can i do it in that way by the API of Amazon?
I want to send the NSDictionary called messageDict to the destination endPoint. Can i achieve this without the use of server??
NSDictionary *messageDict = [[NSDictionary alloc]init];
messageDict = #{ #"Name" : #"HelloWrold" ,#"Id" :#"GoodBye",};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:messageDict options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"Jsonstring %#",jsonString);
AWSSNS *publishCall = [AWSSNS new];
AWSSNSPublishInput *message = [AWSSNSPublishInput new];
message.subject = #"My First Message";
//This is the ending point
message.topicArn = #"arn:aws:sns:us-east-1:012345678912:endpoint/APNS_SANDBOX/appTesting/201sjad-XXXX-XXXX-XXXX-c34sdfdsf1d9c";
message.messageAttributes = messageDict;
message.messageStructure = jsonString;
[[publishCall publish:message]continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task){
if (task.error != nil) {
NSLog(#"Error %#",task.error);
}
else{
NSLog(#"Successful");
}
return nil;
}];

The hardest thing about sending an APN with SNS is getting the data structure correct. Here is how you you can publish to a topic using swift. Each platform needs to be an encoded string, if you mess this up SNS will only deliver your default message.
func publishPush() {
let sns = AWSSNS.defaultSNS()
let request = AWSSNSPublishInput()
request.messageStructure = "json"
var aps: NSMutableDictionary = NSMutableDictionary()
var dict = ["default": "The default message", "APNS_SANDBOX": "{\"aps\":{\"alert\": \"YOUR_MESSAGE\",\"sound\":\"default\", \"badge\":\"1\"} }"]
let jsonData = NSJSONSerialization.dataWithJSONObject(dict, options: nil, error: nil)
request.message = NSString(data: jsonData!, encoding: NSUTF8StringEncoding) as! String
request.targetArn = "blahblahblah:MyTopic"
sns.publish(request).continueWithBlock { (task) -> AnyObject! in
println("error \(task.error), result:; \(task.result)")
return nil
}
}

You can either send a push notification to a specific device (endpoint) or to a topic (list of multiple subscribers)
The API call is slightly different for both. Either you use message.topicArn or message.targetArn as describe in the API documentation here
http://docs.aws.amazon.com/sns/latest/api/API_Publish.html
(Objective-C class documentation is here : http://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSSNSPublishInput.html)
Your ARN is an Endpoint ARN and your code assigns it to message.topicArn
I would change it to
message.targetArn = #"arn:aws:sns:us-east-1:123456789012:endpoint/APNS_SANDBOX/appTesting/201sjad-XXXX-XXXXXX-XXXX-c34sdfdsf1d9c";
(ARN edited to obfuscate your Account ID)
Also, please read and apply best practice from http://mobile.awsblog.com/post/Tx223MJB0XKV9RU/Mobile-token-management-with-Amazon-SNS to acquire and manage your device token.
Here is a code sample in Javascript, that you easily adapt to Objective-C.
var DEFAULT_SNS_REGION = 'eu-west-1';
var SNS_ENDPOINT_ARN = 'arn:aws:sns:eu-west-1:0123456789012:endpoint/APNS_SANDBOX/AmazonSNSPushDemo/95084b8f-XXXX-XXXX-XXXX-b3429d0fa528';
var SNS_TOPIC_ARN = 'arn:aws:sns:eu-west-1:012345678912:PushNotifications';
function sendNotification(msg, topicARN, endPointARN) {
var sns = new aws.SNS({
apiVersion: '2010-03-31',
region: DEFAULT_SNS_REGION
});
var params = {}
if (topicARN != '') {
params = {
Message: msg,
//MessageStructure: 'json',
TopicArn: topicARN
};
} else {
params = {
Message: msg,
//MessageStructure: 'json',
TargetArn: endPointARN
};
}
console.log(params);
var deferred = Q.defer();
sns.publish(params, function(err,data) {
if (err) {
console.log(err, err.stack); // an error occurred
deferred.reject(err);
} else {
console.log(data); // successful response
deferred.resolve(data);
}
});
return deferred.promise; }

The AWSSNS instance is not correctly instantiated.
AWSSNS *publishCall = [AWSSNS new];
needs to be changed to
AWSSNS *publishCall = [AWSSNS defaultSNS];
In general, however, I do not recommend sending push notifications from the client devices because anyone can grab the temporary credentials from the mobile device and spam your app users. Amazon SNS currently does not support fine-grain access control nor throttling the excessive send requests.
The better solution from the security point of view is to have a push notification server. You can take a look at some of the services that may help:
AWS Lambda
AWS Elastic Beanstalk

Related

iOS test App Receipt Validation

There is a lot of example about how to test in-app purchase receipt validation by using a sandbox tester account.
But how is the Receipt for the paid App itself? How can we get the App Receipt in development environment?
There is two thing I want to do:
To prevent illegal copy of our app running by the user who didn't purchase the app.
As I have seen app that detected the iTune Account was connected doesn't owned the app (it shows warning to the user they didn't own the app, but they fail to stop the user to continue to use the app)
Send the app purchase receipt to our server. We want to know when do they buy our app, what version of app they brought.
Most parts of the answer can be found here in Apple's documentation. But there are gaps and the objective-c code is using deprecated methods.
This Swift 3 code shows how to get the App Receipt and send it to the app store for validation. You should definitely validate the App Receipt with the app store before saving the data you want. The advantage of asking the app store to validate is that it responds with data that you can easily serialize to JSON and from there pull out the values for the keys you want. No cryptography required.
As Apple describes in that documentation the preferred flow is like this...
device -> your trusted server -> app store -> your trusted server -> device
When the app store returns to your server, assuming success, that's where you'll serialize and pull out the data you require and save it as you wish. See the JSON below. And you can send the result and whatever else you want back to the app.
In validateAppReceipt() below, to make it a working example, it simply uses this flow...
device -> app store -> device
To make this work with your server just change validationURLString to point to your server and add whatever else your require to requestDictionary.
To test this in development you need to:
make sure you have a sandbox user set up in itunesconnect
on your test device sign out of iTunes & App Store
during testing, when prompted, use your sandbox user
Here's the code. The happy path flows just fine. Errors and failure points just print or are commented. Deal with those as you require.
This part grabs the app receipt. If it's not there (which will happen when you are testing) it asks the app store to refresh.
let receiptURL = Bundle.main.appStoreReceiptURL
func getAppReceipt() {
guard let receiptURL = receiptURL else { /* receiptURL is nil, it would be very weird to end up here */ return }
do {
let receipt = try Data(contentsOf: receiptURL)
validateAppReceipt(receipt)
} catch {
// there is no app receipt, don't panic, ask apple to refresh it
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
// If all goes well control will land in the requestDidFinish() delegate method.
// If something bad happens control will land in didFailWithError.
}
}
func requestDidFinish(_ request: SKRequest) {
// a fresh receipt should now be present at the url
do {
let receipt = try Data(contentsOf: receiptURL!) //force unwrap is safe here, control can't land here if receiptURL is nil
validateAppReceipt(receipt)
} catch {
// still no receipt, possible but unlikely to occur since this is the "success" delegate method
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("app receipt refresh request did fail with error: \(error)")
// for some clues see here: https://samritchie.net/2015/01/29/the-operation-couldnt-be-completed-sserrordomain-error-100/
}
This part validates the app receipt. This is not local validation. Refer to Note 1 and Note 2 in the comments.
func validateAppReceipt(_ receipt: Data) {
/* Note 1: This is not local validation, the app receipt is sent to the app store for validation as explained here:
https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1
Note 2: Refer to the url above. For good reasons apple recommends receipt validation follow this flow:
device -> your trusted server -> app store -> your trusted server -> device
In order to be a working example the validation url in this code simply points to the app store's sandbox servers.
Depending on how you set up the request on your server you may be able to simply change the
structure of requestDictionary and the contents of validationURLString.
*/
let base64encodedReceipt = receipt.base64EncodedString()
let requestDictionary = ["receipt-data":base64encodedReceipt]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" // this works but as noted above it's best to use your own trusted server
guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
let session = URLSession(configuration: URLSessionConfiguration.default)
var request = URLRequest(url: validationURL)
request.httpMethod = "POST"
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
// if you are using your server this will be a json representation of whatever your server provided
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error)")
}
}
task.resume()
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
You should end up with something like this. In your case this is what you would be working with on your server.
{
environment = Sandbox;
receipt = {
"adam_id" = 0;
"app_item_id" = 0;
"application_version" = "0"; // for me this was showing the build number rather than the app version, at least in testing
"bundle_id" = "com.yourdomain.yourappname"; // your app's actual bundle id
"download_id" = 0;
"in_app" = (
);
"original_application_version" = "1.0"; // this will always return 1.0 when testing, the real thing in production.
"original_purchase_date" = "2013-08-01 07:00:00 Etc/GMT";
"original_purchase_date_ms" = 1375340400000;
"original_purchase_date_pst" = "2013-08-01 00:00:00 America/Los_Angeles";
"receipt_creation_date" = "2016-09-21 18:46:39 Etc/GMT";
"receipt_creation_date_ms" = 1474483599000;
"receipt_creation_date_pst" = "2016-09-21 11:46:39 America/Los_Angeles";
"receipt_type" = ProductionSandbox;
"request_date" = "2016-09-22 18:37:41 Etc/GMT";
"request_date_ms" = 1474569461861;
"request_date_pst" = "2016-09-22 11:37:41 America/Los_Angeles";
"version_external_identifier" = 0;
};
status = 0;
}
I am assuming that you know how to perform InApp purchase.
We are required to validate a receipt, after a transaction is finished.
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"completeTransaction...");
[appDelegate setLoadingText:VALIDATING_RECEIPT_MSG];
[self validateReceiptForTransaction];
}
Once the product has been purchased successfully, it
needs to be validated. Server does this for us, we
just need to pass Receipt data returned by Apple server.
-(void)validateReceiptForTransaction
{
/* Load the receipt from the app bundle. */
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) {
/* No local receipt -- handle the error. */
}
/* ... Send the receipt data to your server ... */
NSData *receipt; // Sent to the server by the device
/* Create the JSON object that describes the request */
NSError *error;
NSDictionary *requestContents = #{ #"receipt-data": [receipt base64EncodedStringWithOptions:0] };
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
if (!requestData) {
/* ... Handle error ... */
}
// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:#"https://buy.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:#"POST"];
[storeRequest setHTTPBody:requestData];
/* Make a connection to the iTunes Store on a background queue. */
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* ... Handle error ... */
}
else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse) {
/* ... Handle error ...*/
}
/* ... Send a response back to the device ... */
}
}];
}
The response’s payload is a JSON object that contains the following keys and values:
status:
Either 0 if the receipt is valid, or one of the error codes mentioned below:
For iOS 6 style transaction receipts, the status code reflects the status of the specific transaction’s receipt.
For iOS 7 style app receipts, the status code is reflects the status of the app receipt as a whole. For example, if you send a valid app receipt that contains an expired subscription, the response is 0 because the receipt as a whole is valid.
receipt:
A JSON representation of the receipt that was sent for verification.
Remember:
We will get staus code 21007 for successful receipt valication,
in Sandbox environment.
In the test environment, use
https://sandbox.itunes.apple.com/verifyReceipt as the URL. In
production, use https://buy.itunes.apple.com/verifyReceipt as the
URL.
You will need to set up an test user account in your iTunes Connect to test purchase in sandbox environment.
EDIT 1
transactionReceipt is deprecated: first deprecated in iOS 7.0
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
// iOS 6.1 or earlier.
// Use SKPaymentTransaction's transactionReceipt.
} else {
// iOS 7 or later.
NSURL *receiptFileURL = nil;
NSBundle *bundle = [NSBundle mainBundle];
if ([bundle respondsToSelector:#selector(appStoreReceiptURL)]) {
// Get the transaction receipt file path location in the app bundle.
receiptFileURL = [bundle appStoreReceiptURL];
// Read in the contents of the transaction file.
} else {
/* Fall back to deprecated transaction receipt,
which is still available in iOS 7.
Use SKPaymentTransaction's transactionReceipt. */
}
}
It works for iOS 13
Here's the steps to verify reciept on device without any server code:
You need password before verifying the receipt. It would be the
shared secret key.
How to generate it:
Go to -> iTunes connect go into "Contracts, Tax, and Banking" and click "Request" on the iOs paid apps contract, then accept the contract.
Visit this link
https://appstoreconnect.apple.com
1:- Click on Features
2:- Click on In-App Purchases and create your subscription package
3:- After creating successfully subscription click on App-Specific Shared Secret
4:- Generate App-Specific Shared Secret
Updated code to verify receipt for subscription in-app:
-(void) verifyReceipt
{
/* Load the receipt from the app bundle. */
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) {
/* No local receipt -- handle the error. */
}
/* Create the JSON object that describes the request */
NSError *error;
/* reciept data and password to be sent, password would be the Shared Secret Key from Apple Developer account for given app. */
NSDictionary *requestContents = #{
#"receipt-data": [receipt base64EncodedStringWithOptions:0]
,#"password": #"2008687bb49145445457ff2b25e9bff3"};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
if (!requestData) {
/* ... Handle error ... */
}
// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:#"https://sandbox.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:#"POST"];
[storeRequest setHTTPBody:requestData];
/* Make a connection to the iTunes Store on a background queue. */
//NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:storeRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// handle request error
if (error) {
//completion(nil, error);
return;
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse) {
/* ... Handle error ...*/
}
/* ... Send a response back to the device ... */
}
}];
[dataTask resume];
}
Hope that helps
Thanks
if you want to test in-app go in the sandbox environment for receipt validation and please take into consideration that in sandbox renewal intervals are
1 week 3 minutes
1 month 5 minutes
2 months 10 minutes
3 months 15 minutes
6 months 30 minutes
1 year 1 hour
The best way is to validate receipt is to communicate your server with the apple server for validation.

PubNub message text returning nil?

When I send a message using iOS to a PubNub channel, I can use the didReceiveMessage function to get that message and put it in my tableView. However, if I send a message via a client in the Dev Dashboard, message.data.message returns nil after I try to cast it as a String. Here's the function in question:
func client(client: PubNub, didReceiveMessage message: PNMessageResult) {
print("Received: %", message.data.message)
let newMessage:String? = message.data.message as? String
print(newMessage) // returns nil
self.messagesArray.append(newMessage!)
dispatch_async(dispatch_get_main_queue()) {
self.messageTableView.reloadData()
}
}
I get the following response in console from print("Received: %", message.data.message):
Received: % Optional({
text = test;
})
However, print(newMessage) is returning nil. What am I doing wrong?
Thanks!
EDIT: I'm getting the same thing when I try to get messages from the historyForChannel function.
//get history
pubNub.historyForChannel("channelname" as String, withCompletion: { (result, status) -> Void in
print(status)
if status == nil {
if result!.data.messages.count > 0 {
let historyMessages = result!.data.messages.description as? [String]
print(result)
for item in historyMessages!{
self.messagesArray.append(item)
}
}
}
})
historyMessages is nil, even though result prints:
Optional({
Operation = History;
Request = {
Authorization = "not set";
Method = GET;
Origin = "pubsub.pubnub.com";
"POST Body size" = 0;
Secure = YES;
URL = "...redacted";
UUID = "...redacted";
};
Response = {
"Processed data" = {
end = 14609023551682481;
messages = (
"technically ",
{
text = "Well..ok then";
},
hi,
"really ",
{
text = "Well..ok then";
},
How do I get the text from these returned messages?
From behaviour and fact what history fetch printed out status object means what you probably configured your client with cipherKey. That status object which you receive probably has category set to decryption error.
If you want to use encryption - you should use same key for all clients, or they won't be able to decrypt sent messages. If cipherKey is set, client automatically try to decrypt data and it will fail if regular text has been received.
Make sure both (console and iOS client) configured with same cipherKey or if you don't need it, make sure what it not set on any of clients.
Best regards,
Sergey.

AMAZON AWS How do i subscribe an endpoint to SNS topic?

I'm implementing push notifications in an iOS app using Amazon SNS and Amazon Cognito services.
Cognito saves tokens successfully, my app gets notified, everything's working well, but there is a thing.
Now, when still in development, I need to manually add endpoints to an SNS topic, so all subscribers can get notifications. When i'll push an update to the App Store, there will be thousands of tokens to add.
I was studying Amazon AWS documentation, but there was no clue whether it's possible to make it happen without that additional effort.
My question: is it possible to automatically subscribe an endpoint to a topic with Amazon services only?
There is no way to automatically subscribe an endpoint to a topic, but you can accomplish all through code.
You can directly call the Subscribe API after you have created your endpoint. Unlike other kinds of subscription, no confirmation is necessary with SNS Mobile Push.
Here is some example Objective-C code that creates an endpoint and subscribes it to a topic:
AWSSNS *sns = [AWSSNS defaultSNS];
AWSSNSCreatePlatformEndpointInput *endpointRequest = [AWSSNSCreatePlatformEndpointInput new];
endpointRequest.platformApplicationArn = MY_PLATFORM_ARN;
endpointRequest.token = MY_TOKEN;
[[[sns createPlatformEndpoint:endpointRequest] continueWithSuccessBlock:^id(AWSTask *task) {
AWSSNSCreateEndpointResponse *response = task.result;
AWSSNSSubscribeInput *subscribeRequest = [AWSSNSSubscribeInput new];
subscribeRequest.endpoint = response.endpointArn;
subscribeRequest.protocols = #"application";
subscribeRequest.topicArn = MY_TOPIC_ARN;
return [sns subscribe:subscribeRequest];
}] continueWithBlock:^id(BFTask *task) {
if (task.cancelled) {
NSLog(#"Task cancelled");
}
else if (task.error) {
NSLog(#"Error occurred: [%#]", task.error);
}
else {
NSLog(#"Success");
}
return nil;
}];
Make sure you have granted access to sns:Subscribe in your Cognito roles to allow your application to make this call.
Update 2015-07-08: Updated to reflect AWS iOS SDK 2.2.0+
This is the original code to subscribe an endpoint to a topic in Swift3
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
//Get Token ENDPOINT
let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
//Create SNS Module
let sns = AWSSNS.default()
let request = AWSSNSCreatePlatformEndpointInput()
request?.token = deviceTokenString
//Send Request
request?.platformApplicationArn = Constants.SNSDEVApplicationARN
sns.createPlatformEndpoint(request!).continue({ (task: AWSTask!) -> AnyObject! in
if task.error != nil {
print("Error: \(task.error)")
} else {
let createEndpointResponse = task.result! as AWSSNSCreateEndpointResponse
print("endpointArn: \(createEndpointResponse.endpointArn)")
let subscription = Constants.SNSEndPoint //Use your own topic endpoint
//Create Subscription request
let subscriptionRequest = AWSSNSSubscribeInput()
subscriptionRequest?.protocols = "application"
subscriptionRequest?.topicArn = subscription
subscriptionRequest?.endpoint = createEndpointResponse.endpointArn
sns.subscribe(subscriptionRequest!).continue ({
(task:AWSTask) -> AnyObject! in
if task.error != nil
{
print("Error subscribing: \(task.error)")
return nil
}
print("Subscribed succesfully")
//Confirm subscription
let subscriptionConfirmInput = AWSSNSConfirmSubscriptionInput()
subscriptionConfirmInput?.token = createEndpointResponse.endpointArn
subscriptionConfirmInput?.topicArn = subscription
sns.confirmSubscription(subscriptionConfirmInput!).continue ({
(task:AWSTask) -> AnyObject! in
if task.error != nil
{
print("Error subscribing: \(task.error)")
}
return nil
})
return nil
})
}
return nil
})
}
If you want to use static credentials instead of using AWSCognito you will need to create those through Amazons IAM console.
Here is the code for initializing the Amazon in your App Delegate
// Sets up the AWS Mobile SDK for iOS
// Initialize the Amazon credentials provider
AWSStaticCredentialsProvider *credentialsProvider =[[AWSStaticCredentialsProvider alloc] initWithAccessKey:AWSAccessID secretKey:AWSSecretKey];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:DefaultServiceRegionType credentialsProvider:credentialsProvider];
[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
Fissh

iOS Upload file to Google Cloud Storage - getting 401: 'Login Required'

I am having trouble figuring out how to upload a file to a Public Access bucket to Cloud Storage.
I have set up a bucket and set it's ACLs to READ and WRITE for all users.
I have enabled the Cloud Storage JSON API and the Cloud Storage API.
I have created an API key for browser applications that allows any referrer.
Here is my code in Swift:
private lazy var googleServiceStorage:GTLServiceStorage = {
var storage = GTLServiceStorage()
storage.APIKey = "AIzaSy**********m8TPCM"
storage.additionalHTTPHeaders = ["x-goog-project-id" : "159*******7"]
return storage
}()
}
public func uploadAssetToGoogle(resourcePath: String?) {
if let _resourcePath = resourcePath {
let fileHandle = NSFileHandle(forReadingAtPath: _resourcePath)
let uploadParams = GTLUploadParameters(fileHandle: fileHandle, MIMEType: "video/mov")
var storageObject = GTLStorageObject.object() as GTLStorageObject
storageObject.name = "12345678"
let query = GTLQueryStorage.queryForObjectsInsertWithObject(storageObject, bucket: "my-bucket", uploadParameters: uploadParams) as GTLQuery
var ticket = googleServiceStorage.executeQuery(query) { ticket, object, error in
if let _error = error {
println("Error upload file: \(error.localizedDescription) : \(error.localizedFailureReason)")
return
}
println("Upload succeeded")
}
ticket.uploadProgressBlock = {ticket, numberOfBytesRead, dataLength in
println("Ticket: \(ticket)")
NSLog("read %llu from %llu bytes", numberOfBytesRead, dataLength)
}
}
When I call this code I get the following output in the console:
mediaURL type: (Metatype) - file:///Users/Michael/Library/Developer/CoreSimulator/Devices/5895B7FA-41E7-4958-84FD-2C1043CA7CD7/data/Containers/Data/Application/2DD62539-E364-4BE0-A89C-E0DD2827D74B/tmp/trim.FCE68DAD-0FC6-4E2D-8C92-37055A02DD12.MOV
Ticket: GTLServiceTicket 0x7feed4860660: {service:<GTLServiceStorage: 0x7feed2cf9d90> devKey:AIzaSyBClcLHWtXzlBHb2VbATA1xIlUO0m8TPCM fetcher:GTMHTTPUploadFetcher 0x7feed4890fb0 (https://www.googleapis.com/upload/rpc?uploadType=resumable&prettyPrint=false) }
2014-09-25 18:25:23.515 Beta[14917:1971326] read 202 from 29512 bytes
Error upload file: The operation couldn’t be completed. (Login Required) : Optional("(Login Required)")
There is something I am missing, but I can't figure it out. If anyone can help me, it will save me a lot more hair.
For Login Required error message try with adding new bucket permission item
ENTITY = User, NAME = allUsers, ACCESS = Writer
Please note: You have to use Server API key not iOS API key
For object upload, you can add GTLStorageObjectAccessControl
GTLStorageObjectAccessControl *objAccessControl = [GTLStorageObjectAccessControl new];
objAccessControl.entity = #"allUsers";
objAccessControl.email = #"xxxxxx-xxxxx#developer.gserviceaccount.com";
objAccessControl.role = #"OWNER";
GTLStorageObject *newObject = [GTLStorageObject object];
newObject.name = #"image.png";
newObject.acl = #[objAccessControl];
Than,
GTLQueryStorage *query = [GTLQueryStorage queryForObjectsInsertWithObject:newObject bucket:kBucketName uploadParameters:uploadParameters];

A complete solution to LOCALLY validate an in-app receipts and bundle receipts on iOS 7

I have read a lot of docs and code that in theory will validate an in-app and/or bundle receipt.
Given that my knowledge of SSL, certificates, encryption, etc., is nearly zero, all of the explanations I have read, like this promising one, I have found difficult to understand.
They say the explanations are incomplete because every person has to figure out how to do it, or the hackers will have an easy job creating a cracker app that can recognize and identify patterns and patch the application. OK, I agree with this up to a certain point. I think they could explain completely how to do it and put a warning saying "modify this method", "modify this other method", "obfuscate this variable", "change the name of this and that", etc.
Can some good soul out there be kind enough to explain how to LOCALLY validate, bundle receipts and in-app purchase receipts on iOS 7 as I am five years old (ok, make it 3), from top to bottom, clearly?
Thanks!!!
If you have a version working on your apps and your concerns are that hackers will see how you did it, simply change your sensitive methods before publishing here. Obfuscate strings, change the order of lines, change the way you do loops (from using for to block enumeration and vice-versa) and things like that. Obviously, every person that uses the code that may be posted here, has to do the same thing, not to risk being easily hacked.
Here's a walkthrough of how I solved this in my in-app purchase library RMStore. I will explain how to verify a transaction, which includes verifying the whole receipt.
At a glance
Get the receipt and verify the transaction. If it fails, refresh the receipt and try again. This makes the verification process asynchronous as refreshing the receipt is asynchronous.
From RMStoreAppReceiptVerifier:
RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
const BOOL verified = [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:nil]; // failureBlock is nil intentionally. See below.
if (verified) return;
// Apple recommends to refresh the receipt if validation fails on iOS
[[RMStore defaultStore] refreshReceiptOnSuccess:^{
RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
[self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:failureBlock];
} failure:^(NSError *error) {
[self failWithBlock:failureBlock error:error];
}];
Getting the receipt data
The receipt is in [[NSBundle mainBundle] appStoreReceiptURL] and is actually a PCKS7 container. I suck at cryptography so I used OpenSSL to open this container. Others apparently have done it purely with system frameworks.
Adding OpenSSL to your project is not trivial. The RMStore wiki should help.
If you opt to use OpenSSL to open the PKCS7 container, your code could look like this. From RMAppReceipt:
+ (NSData*)dataFromPKCS7Path:(NSString*)path
{
const char *cpath = [[path stringByStandardizingPath] fileSystemRepresentation];
FILE *fp = fopen(cpath, "rb");
if (!fp) return nil;
PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL);
fclose(fp);
if (!p7) return nil;
NSData *data;
NSURL *certificateURL = [[NSBundle mainBundle] URLForResource:#"AppleIncRootCertificate" withExtension:#"cer"];
NSData *certificateData = [NSData dataWithContentsOfURL:certificateURL];
if ([self verifyPKCS7:p7 withCertificateData:certificateData])
{
struct pkcs7_st *contents = p7->d.sign->contents;
if (PKCS7_type_is_data(contents))
{
ASN1_OCTET_STRING *octets = contents->d.data;
data = [NSData dataWithBytes:octets->data length:octets->length];
}
}
PKCS7_free(p7);
return data;
}
We'll get into the details of the verification later.
Getting the receipt fields
The receipt is expressed in ASN1 format. It contains general information, some fields for verification purposes (we'll come to that later) and specific information of each applicable in-app purchase.
Again, OpenSSL comes to the rescue when it comes to reading ASN1. From RMAppReceipt, using a few helper methods:
NSMutableArray *purchases = [NSMutableArray array];
[RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) {
const uint8_t *s = data.bytes;
const NSUInteger length = data.length;
switch (type)
{
case RMAppReceiptASN1TypeBundleIdentifier:
_bundleIdentifierData = data;
_bundleIdentifier = RMASN1ReadUTF8String(&s, length);
break;
case RMAppReceiptASN1TypeAppVersion:
_appVersion = RMASN1ReadUTF8String(&s, length);
break;
case RMAppReceiptASN1TypeOpaqueValue:
_opaqueValue = data;
break;
case RMAppReceiptASN1TypeHash:
_hash = data;
break;
case RMAppReceiptASN1TypeInAppPurchaseReceipt:
{
RMAppReceiptIAP *purchase = [[RMAppReceiptIAP alloc] initWithASN1Data:data];
[purchases addObject:purchase];
break;
}
case RMAppReceiptASN1TypeOriginalAppVersion:
_originalAppVersion = RMASN1ReadUTF8String(&s, length);
break;
case RMAppReceiptASN1TypeExpirationDate:
{
NSString *string = RMASN1ReadIA5SString(&s, length);
_expirationDate = [RMAppReceipt formatRFC3339String:string];
break;
}
}
}];
_inAppPurchases = purchases;
Getting the in-app purchases
Each in-app purchase is also in ASN1. Parsing it is very similar than parsing the general receipt information.
From RMAppReceipt, using the same helper methods:
[RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) {
const uint8_t *p = data.bytes;
const NSUInteger length = data.length;
switch (type)
{
case RMAppReceiptASN1TypeQuantity:
_quantity = RMASN1ReadInteger(&p, length);
break;
case RMAppReceiptASN1TypeProductIdentifier:
_productIdentifier = RMASN1ReadUTF8String(&p, length);
break;
case RMAppReceiptASN1TypeTransactionIdentifier:
_transactionIdentifier = RMASN1ReadUTF8String(&p, length);
break;
case RMAppReceiptASN1TypePurchaseDate:
{
NSString *string = RMASN1ReadIA5SString(&p, length);
_purchaseDate = [RMAppReceipt formatRFC3339String:string];
break;
}
case RMAppReceiptASN1TypeOriginalTransactionIdentifier:
_originalTransactionIdentifier = RMASN1ReadUTF8String(&p, length);
break;
case RMAppReceiptASN1TypeOriginalPurchaseDate:
{
NSString *string = RMASN1ReadIA5SString(&p, length);
_originalPurchaseDate = [RMAppReceipt formatRFC3339String:string];
break;
}
case RMAppReceiptASN1TypeSubscriptionExpirationDate:
{
NSString *string = RMASN1ReadIA5SString(&p, length);
_subscriptionExpirationDate = [RMAppReceipt formatRFC3339String:string];
break;
}
case RMAppReceiptASN1TypeWebOrderLineItemID:
_webOrderLineItemID = RMASN1ReadInteger(&p, length);
break;
case RMAppReceiptASN1TypeCancellationDate:
{
NSString *string = RMASN1ReadIA5SString(&p, length);
_cancellationDate = [RMAppReceipt formatRFC3339String:string];
break;
}
}
}];
It should be noted that certain in-app purchases, such as consumables and non-renewable subscriptions, will appear only once in the receipt. You should verify these right after the purchase (again, RMStore helps you with this).
Verification at a glance
Now we got all the fields from the receipt and all its in-app purchases. First we verify the receipt itself, and then we simply check if the receipt contains the product of the transaction.
Below is the method that we called back at the beginning. From RMStoreAppReceiptVerificator:
- (BOOL)verifyTransaction:(SKPaymentTransaction*)transaction
inReceipt:(RMAppReceipt*)receipt
success:(void (^)())successBlock
failure:(void (^)(NSError *error))failureBlock
{
const BOOL receiptVerified = [self verifyAppReceipt:receipt];
if (!receiptVerified)
{
[self failWithBlock:failureBlock message:NSLocalizedString(#"The app receipt failed verification", #"")];
return NO;
}
SKPayment *payment = transaction.payment;
const BOOL transactionVerified = [receipt containsInAppPurchaseOfProductIdentifier:payment.productIdentifier];
if (!transactionVerified)
{
[self failWithBlock:failureBlock message:NSLocalizedString(#"The app receipt doest not contain the given product", #"")];
return NO;
}
if (successBlock)
{
successBlock();
}
return YES;
}
Verifying the receipt
Verifying the receipt itself boils down to:
Checking that the receipt is valid PKCS7 and ASN1. We have done this implicitly already.
Verifying that the receipt is signed by Apple. This was done before parsing the receipt and will be detailed below.
Checking that the bundle identifier included in the receipt corresponds to your bundle identifier. You should hardcode your bundle identifier, as it doesn't seem to be very difficult to modify your app bundle and use some other receipt.
Checking that the app version included in the receipt corresponds to your app version identifier. You should hardcode the app version, for the same reasons indicated above.
Check the receipt hash to make sure the receipt correspond to the current device.
The 5 steps in code at a high-level, from RMStoreAppReceiptVerificator:
- (BOOL)verifyAppReceipt:(RMAppReceipt*)receipt
{
// Steps 1 & 2 were done while parsing the receipt
if (!receipt) return NO;
// Step 3
if (![receipt.bundleIdentifier isEqualToString:self.bundleIdentifier]) return NO;
// Step 4
if (![receipt.appVersion isEqualToString:self.bundleVersion]) return NO;
// Step 5
if (![receipt verifyReceiptHash]) return NO;
return YES;
}
Let's drill-down into steps 2 and 5.
Verifying the receipt signature
Back when we extracted the data we glanced over the receipt signature verification. The receipt is signed with the Apple Inc. Root Certificate, which can be downloaded from Apple Root Certificate Authority. The following code takes the PKCS7 container and the root certificate as data and checks if they match:
+ (BOOL)verifyPKCS7:(PKCS7*)container withCertificateData:(NSData*)certificateData
{ // Based on: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW17
static int verified = 1;
int result = 0;
OpenSSL_add_all_digests(); // Required for PKCS7_verify to work
X509_STORE *store = X509_STORE_new();
if (store)
{
const uint8_t *certificateBytes = (uint8_t *)(certificateData.bytes);
X509 *certificate = d2i_X509(NULL, &certificateBytes, (long)certificateData.length);
if (certificate)
{
X509_STORE_add_cert(store, certificate);
BIO *payload = BIO_new(BIO_s_mem());
result = PKCS7_verify(container, NULL, store, NULL, payload, 0);
BIO_free(payload);
X509_free(certificate);
}
}
X509_STORE_free(store);
EVP_cleanup(); // Balances OpenSSL_add_all_digests (), per http://www.openssl.org/docs/crypto/OpenSSL_add_all_algorithms.html
return result == verified;
}
This was done back at the beginning, before the receipt was parsed.
Verifying the receipt hash
The hash included in the receipt is a SHA1 of the device id, some opaque value included in the receipt and the bundle id.
This is how you would verify the receipt hash on iOS. From RMAppReceipt:
- (BOOL)verifyReceiptHash
{
// TODO: Getting the uuid in Mac is different. See: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5
NSUUID *uuid = [[UIDevice currentDevice] identifierForVendor];
unsigned char uuidBytes[16];
[uuid getUUIDBytes:uuidBytes];
// Order taken from: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5
NSMutableData *data = [NSMutableData data];
[data appendBytes:uuidBytes length:sizeof(uuidBytes)];
[data appendData:self.opaqueValue];
[data appendData:self.bundleIdentifierData];
NSMutableData *expectedHash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH];
SHA1(data.bytes, data.length, expectedHash.mutableBytes);
return [expectedHash isEqualToData:self.hash];
}
And that's the gist of it. I might be missing something here or there, so I might come back to this post later. In any case, I recommend browsing the complete code for more details.
I'm surprised nobody mentioned Receigen here. It's a tool that automatically generates obfuscated receipt validation code, a different one each time; it supports both GUI and command-line operation. Highly recommended.
(Not affiliated with Receigen, just a happy user.)
I use a Rakefile like this to automatically rerun Receigen (because it needs to be done on every version change) when I type rake receigen:
desc "Regenerate App Store Receipt validation code using Receigen app (which must be already installed)"
task :receigen do
# TODO: modify these to match your app
bundle_id = 'com.example.YourBundleIdentifierHere'
output_file = File.join(__dir__, 'SomeDir/ReceiptValidation.h')
version = PList.get(File.join(__dir__, 'YourProjectFolder/Info.plist'), 'CFBundleVersion')
command = %Q</Applications/Receigen.app/Contents/MacOS/Receigen --identifier #{bundle_id} --version #{version} --os ios --prefix ReceiptValidation --success callblock --failure callblock>
puts "#{command} > #{output_file}"
data = `#{command}`
File.open(output_file, 'w') { |f| f.write(data) }
end
module PList
def self.get file_name, key
if File.read(file_name) =~ %r!<key>#{Regexp.escape(key)}</key>\s*<string>(.*?)</string>!
$1.strip
else
nil
end
end
end
Note: It's not recommend to do this type of verification in the client side
This is a Swift 4 version for validation of in-app-purchase receipt...
Lets create an enum to represent the possible errors of the receipt validation
enum ReceiptValidationError: Error {
case receiptNotFound
case jsonResponseIsNotValid(description: String)
case notBought
case expired
}
Then let's create the function that validates the receipt, it will throws an error if it's unable to validate it.
func validateReceipt() throws {
guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) else {
throw ReceiptValidationError.receiptNotFound
}
let receiptData = try! Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString()
let jsonObjectBody = ["receipt-data" : receiptString, "password" : <#String#>]
#if DEBUG
let url = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")!
#else
let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
#endif
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: jsonObjectBody, options: .prettyPrinted)
let semaphore = DispatchSemaphore(value: 0)
var validationError : ReceiptValidationError?
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, let httpResponse = response as? HTTPURLResponse, error == nil, httpResponse.statusCode == 200 else {
validationError = ReceiptValidationError.jsonResponseIsNotValid(description: error?.localizedDescription ?? "")
semaphore.signal()
return
}
guard let jsonResponse = (try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [AnyHashable: Any] else {
validationError = ReceiptValidationError.jsonResponseIsNotValid(description: "Unable to parse json")
semaphore.signal()
return
}
guard let expirationDate = self.expirationDate(jsonResponse: jsonResponse, forProductId: <#String#>) else {
validationError = ReceiptValidationError.notBought
semaphore.signal()
return
}
let currentDate = Date()
if currentDate > expirationDate {
validationError = ReceiptValidationError.expired
}
semaphore.signal()
}
task.resume()
semaphore.wait()
if let validationError = validationError {
throw validationError
}
}
Let's use this helper function, to get the expiration date of a specific product. The function receives a JSON response and a product id. The JSON response can contain multiple receipts info for different products, so it get the last info for the specified parameter.
func expirationDate(jsonResponse: [AnyHashable: Any], forProductId productId :String) -> Date? {
guard let receiptInfo = (jsonResponse["latest_receipt_info"] as? [[AnyHashable: Any]]) else {
return nil
}
let filteredReceipts = receiptInfo.filter{ return ($0["product_id"] as? String) == productId }
guard let lastReceipt = filteredReceipts.last else {
return nil
}
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
if let expiresString = lastReceipt["expires_date"] as? String {
return formatter.date(from: expiresString)
}
return nil
}
Now you can call this function and handle of the possible error cases
do {
try validateReceipt()
// The receipt is valid 😌
print("Receipt is valid")
} catch ReceiptValidationError.receiptNotFound {
// There is no receipt on the device 😱
} catch ReceiptValidationError.jsonResponseIsNotValid(let description) {
// unable to parse the json 🤯
print(description)
} catch ReceiptValidationError.notBought {
// the subscription hasn't being purchased 😒
} catch ReceiptValidationError.expired {
// the subscription is expired 😵
} catch {
print("Unexpected error: \(error).")
}
You can get a Password from the App Store Connect. https://developer.apple.com open this link click on
Account tab
Do Sign in
Open iTune Connect
Open My App
Open Feature Tab
Open In App Purchase
Click at the right side on 'View Shared Secret'
At the bottom you will get a secrete key
Copy that key and paste into the password field.

Resources