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

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];

Related

Error 13010 "Object does not exist" while downloading jpeg image from Firebase storage using getData()

Language : Swift 5
iOS: 13.2
macOS: Catalina 10.15.4
Firebase Storage Rules:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth!=null;
}
}
}
The code to upload image and save download URL: (Which works fine, because I can see images uploaded to storage and their respective download URLs stored to real-time database.)
let storageRef = Storage.storage().reference()
//Let's upload all workout pictures
let uploadPicsRef =
storageRef.child("WORKOUTDATA/USERS/"+self.UID!).child("WHITEBOARDWORKOUTS")
let uploadNumberRef = uploadPicsRef.child("\(String(describing: workoutNum))")
let workoutPicturesRef = uploadNumberRef.child("WORKOUTPICTURES")
let workoutPicURLRef = workoutRef.child("WORKOUTPICTURESURL")
var count = 0
var picNumber = 0
//workoutPictures list/array contains images selected from iPhone Gallery, using
//UIImagePickerController
for workoutPic in self.workoutPictures
{
let workoutPicData = workoutPic.jpegData(compressionQuality: 1.0)!
count = count + 1
let pictureName = "Picture\(count).jpg"
// Upload the file to the path in pictureRef
let pictureRef = workoutPicturesRef.child("\(pictureName)")
let metaData = StorageMetadata()
metaData.contentType = "image/jpg"
pictureRef.putData(workoutPicData, metadata: metaData) { (metadata, error) in
if error != nil {
print("Error while uploading image")
}
else
{
pictureRef.downloadURL { (url, err) in
picNumber = picNumber + 1
workoutPicURLRef.child("Picture\(picNumber)").setValue(url?.absoluteString)
}
}
}
}
The code to download image:
let myGroup = DispatchGroup()
let workoutPicUrls = snapshot.childSnapshot(forPath: "WORKOUTPICTURESURL")
for url in workoutPicUrls.children
{
myGroup.enter()
let snap = url as! DataSnapshot
let link = snap.value as? String
let storageRef = Storage.storage().reference()
let pictureRef = storageRef.root().child(link!)
DispatchQueue.main.async {
pictureRef.getData(maxSize: 1*2000000*2000000) { (data, err) in
if (err != nil) {
print(err!)
print(err!.localizedDescription)
} else {
let pic = UIImage(data: data!)
workoutPicsArray.append(pic!)
myGroup.leave()
}
}
}
}
Error:
Error Domain=FIRStorageErrorDomain Code=-13010 "Object https:/firebasestorage.googleapis.com/v0/b/trainer-8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547 does not exist." UserInfo={object=https:/firebasestorage.googleapis.com/v0/b/trainer-8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547, ResponseBody={
"error": {
"code": 404,
"message": "Not Found. Could not get object",
"status": "GET_OBJECT"
}
}, bucket=trainer-8cb52.appspot.com, data={length = 115, bytes = 0x7b0a2020 22657272 6f72223a 207b0a20 ... 54220a20 207d0a7d }, data_content_type=application/json; charset=UTF-8, NSLocalizedDescription=Object https:/firebasestorage.googleapis.com/v0/b/trainer-8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547 does not exist., ResponseErrorDomain=com.google.HTTPStatus, ResponseErrorCode=404}
What I have tried so far:
Checked firebase storage rules.
When I paste the path https:/firebasestorage.googleapis.com/v0/b/trainer8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547 in chrome browser window, the expected image opens.
Set the maxSize to a ridiculously high number 1*2000000*2000000.
Thank you!
Is it possible that you are storing the full https URL in the database and are trying to create a reference by adding the full https url as a child to the storage reference?
I think you should try to either store just the path and name in your database or you change your download code to use the https URL.
// Create a reference from an HTTPS URL
// Note that in the URL, characters are URL escaped!
let httpsReference = storage.reference(forURL: "https://firebasestorage.googleapis.com/b/bucket/o/images%20stars.jpg")
httpsReference.getData(maxSize: ...
Also you're running your getData method inside DispatchQueue.main.async. getData has itself a completion handler and might take some time, when you run that inside of DispatchQueue.main.async it will block your code until the download is done. Only put code that update the UI inside DispatchQueue.main.async. In your case as soon as you do something with your workoutPicsArray or the UIImage to update your view.
Have a look here to see if you can figure out how you are actually trying to get the data. It might be helpful to put a print() after each line to see what you are creating and using at what point.
Download Files on iOS

How do I configure identityData of NEVPNProtocolIKEv2 with String certificate?

I using NetworkExtension framework to creating an application, it connect to VPN server via NEVPNProtocolIKEv2.
After research, I found an tutorial about working with NetworkExtension framework, and I try to follow it.
(http://ramezanpour.net/post/2014/08/03/configure-and-manage-vpn-connections-programmatically-in-ios-8/)
But, I stuck when I configure identityData of this protocol.
Here is m code:
self.vpnManager.loadFromPreferencesWithCompletionHandler { [unowned self] (error) in
if error != nil {
printError("\(error?.errorDescription)")
return
}
let p = NEVPNProtocolIKEv2()
p.username = server.userName
p.serverAddress = server.serverUrl
// Get password persistent reference from keychain
self.createKeychainValue(server.password, forIdentifier: KeychainId_Password)
p.passwordReference = self.searchKeychainCopyMatching(KeychainId_Password)
p.authenticationMethod = NEVPNIKEAuthenticationMethod.None
self.createKeychainValue(kVPNsecret, forIdentifier: KeychainId_PSK)
p.sharedSecretReference = self.searchKeychainCopyMatching(KeychainId_PSK)
// certificate
p.identityData = ??????
p.useExtendedAuthentication = true
p.disconnectOnSleep = false
self.vpnManager.`protocol` = p
self.vpnManager.localizedDescription = server.serverName
self.vpnManager.saveToPreferencesWithCompletionHandler({ [unowned self] (error) in
if error != nil {
printError("Save config failed " + error!.localizedDescription)
}
})
}
In tutorial, p.identityData is NSData, that was loading from a P12 file.
But I have only a string that call: server.certificate
This server.certificate has a value like this
"-----BEGIN CERTIFICATE-----\nMIIEdDCCA1ygAwIBAgIBADANBgkqhki......1iEtCZg7SAlsBiaxpJzpZm5C6OifUCkUfZNdPQ==\n-----END CERTIFICATE-----\n"
This is a very very long string, that call x509Certificate... or something like that, I do not remember exactly.
I found an library support write an String to file p12, It is "openssl".
But demo code is Objective-C. I keep trying port this code to Swift, but it is so hard.
(democode: iOS: How to create PKCS12 (P12) keystore from private key and x509certificate in application programmatically?)
Finally, I have only a String certificate, and I want to configure p.identityData for my application.
How I do it?

Error when I'm using MailCore2 in Swift 2

I'm using the MailCore2 at Swift in my iOS application to send message to email in the background without open built-in mail application, But when I'm running app on my device ( real device ) I have this problem:
My complete code for send button:
#IBAction func sendButton(sender: AnyObject) {
var smtpSession = MCOSMTPSession()
smtpSession.hostname = "smtp.gmail.com"
smtpSession.username = "from-email"
smtpSession.password = "password"
smtpSession.port = 465
smtpSession.authType = MCOAuthType.SASLPlain
smtpSession.connectionType = MCOConnectionType.TLS
smtpSession.connectionLogger = {(connectionID, type, data) in
if data != nil {
if let string = NSString(data: data, encoding: NSUTF8StringEncoding){
NSLog("Connectionlogger: \(string)")
}
}
}
var builder = MCOMessageBuilder()
builder.header.to = [MCOAddress(displayName: "AAA", mailbox: "to-email")]
builder.header.from = MCOAddress(displayName: "RRR", mailbox: "from-email")
builder.header.subject = messageTitleTextField.text
var message = messageTextView.text
builder.htmlBody = message
let rfc822Data = builder.data()
let sendOperation = smtpSession.sendOperationWithData(rfc822Data)
sendOperation.start { (error) -> Void in
if (error != nil) {
NSLog("Error sending email: \(error)")
} else {
NSLog("Sent successfully, Thanks!")
}
}
}
Error:
Optional(Error Domain=MCOErrorDomain Code=5 "Unable to authenticate with the current session's credentials." UserInfo={NSLocalizedDescription=Unable to authenticate with the current session's credentials.})
Already I change setting of unsecure app from gmail account but I solved the credentials problem by unlocking gmail account from this link
https://accounts.google.com/DisplayUnlockCaptcha
In my case I had to "Change account access for less secure apps"
https://support.google.com/accounts/answer/6010255?hl=en
If somebody has this problem its quite helpful to know that it is actually problem with security on google side and there it has to be handled...I will leave it because when this happened to me the error didn't say anything I had to search for problem to find out that you have to go to that link on google and find out how to setup it correctly, I wasted time , it will be helpful for others who wants to send mail by smtp with google account

Using amazon AWS SNS service 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

SSKeychain: Accounts not stored in iCloud?

I'm using sskeychain (https://github.com/soffes/sskeychain) to store my accounts and passwords in the IOS keychain. I assume, that if I store an account, it should be available on my other device. But it doesn't appear there.
I read my accounts with this code:
NSArray *arr=[SSKeychain accountsForService:#"Login"];
for (NSString *s in arr) {
NSLog(#"Account: %#",s);
}
and get this (only shown one entry, the others are similar):
Account: {
acct = "friXXXXXter#XXXX.com";
agrp = "3B4384Z34A.de.gondomir.LocalButler";
cdat = "2014-05-09 22:55:08 +0000";
mdat = "2014-05-09 22:55:08 +0000";
pdmn = ak;
svce = Login;
sync = 0;
tomb = 0;
}
But this doesn't appear on the other device. Both devices have IOS 7.1.1.
I store the password with this line:
[SSKeychain setPassword:self.passwortField.text forService:#"Login" account:self.userField.text];
I have switched on keychain sharing in Xcode and have a keychain group "de.gondomir.LocalButler" listed there.
Am I missing something? Must the service name something special?
Thanks!
In case this is still relevant for you I managed to find the solution. (works for >=iOS7)
Don't use the static methods of SSKeychain to write your credentials. Instead use SSKeychainQuery and set the synchronizationMode to SSKeychainQuerySynchronizationModeYes like this
NSError *error;
[SSKeychain setAccessibilityType:self.keychainAccessibilityType];
SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
query.service = service;
query.account = account;
query.password = password;
query.synchronizationMode = SSKeychainQuerySynchronizationModeYes;
[query save:&error];
if (error) {
NSLog(#"Error writing credentials %#", [error description]);
}
The static convenience methods on SSKeychain use the default synchronization mode SSKeychainQuerySynchronizationModeAny causing credentials not to be synchronized with the iCloud keychain.
Additionally, make sure your devices have Keychain via iCloud enabled (Settings>iCloud>Keychain). You might also want to enable Keychain Sharing in your targets Capabilities.
After I have a new project I tried the answer of MarkHim, it works.
I used swift now, so here is my working code:
let account = defaults.objectForKey("Sync_toPhoneNumber") as? String
SSKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlock)
var error:NSError?
let lookupQuery = SSKeychainQuery()
lookupQuery.synchronizationMode = .Yes
lookupQuery.service = "DasDing"
lookupQuery.account = account
let password = SSKeychain.passwordForService("DasDing", account: account, error: &error)
if error == nil {
commandKey = password!
} else {
print("Error für \(account): \(error!.localizedDescription)")
commandKey = ""
}
// query all accounts for later use
let allQuery = SSKeychainQuery()
allQuery.service = "DasDing"
do {
let dict = try allQuery.fetchAll()
print("Accounts:")
for acc in dict {
print(acc["acct"]!)
}
} catch let error as NSError {
print("keine Accounts")
print("Error: \(error.localizedDescription)")
}
That's for reading, for writing you must delete the account first (if you want to change the password):
let account = defaults.objectForKey("Sync_toPhoneNumber") as? String
SSKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlock)
SSKeychain.deletePasswordForService("DasDing", account: account)
let newQuery = SSKeychainQuery()
newQuery.service = "DasDing"
newQuery.account = account
newQuery.password = str?.uppercaseString
newQuery.synchronizationMode = .Yes
try! newQuery.save()

Resources