Failed to get downloadURL when uploading file in FirebaseStorage - swift

I'm using FirebaseStorage in my Swift project for uploading images in a non public bucket.
Here are my rules:
In GoogleCloud Storage console:
allUser access has been removed
In FirebaseStorage console:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read: if request.auth != nil
allow write: if true;
}
}
}
With these rules, upload failed when trying to downloadURL:
let uploadTask = fileRef.putFile(from: url, metadata: metadata, completion: { (metadata, error) in
guard let _ = metadata else {
completion(nil,error)
return
}
fileRef.downloadURL { (url, error) in
completion(url,error) // <--- url is nil and error is set
return
}
})
Error is:
▿ Optional<Error>
- some : Error Domain=FIRStorageErrorDomain Code=-13021 "User does not have permission to access gs://utw6xcl26d6ywvtosast/6309669a88262d10cea863e6/35B8D02C-476E-4B6D-A51D-501CC061F047.jpg." UserInfo={ResponseErrorDomain=com.google.HTTPStatus, data={length = 73, bytes = 0x7b0a2020 22657272 6f72223a 207b0a20 ... 2e220a20 207d0a7d }, object=6309669a88262d10cea863e6/35B8D02C-476E-4B6D-A51D-501CC061F047.jpg, NSLocalizedDescription=User does not have permission to access gs://utw6xcl26d6ywvtosast/6309669a88262d10cea863e6/35B8D02C-476E-4B6D-A51D-501CC061F047.jpg., bucket=utw6xcl26d6ywvtosast, data_content_type=application/json; charset=UTF-8, ResponseErrorCode=403, ResponseBody={
"error": {
"code": 403,
"message": "Permission denied."
}
}}
If I change rules in Firebase Storage to read,write: if true this is working but resource is accessible even without access token. Which is not I want.
Do you have an idea?
Thanks!

The user who uploads the file will have to have read access to that file in order to generate a download URL for it. Given your allow read: if request.auth != nil rule, it seems like the user is not authenticated.
You might want to authenticate the user (even if just with anonymous sign-in, which doesn't require them to enter credentials) and then for example to allow them read/write access to files that are written under their own UID.

Related

Trying to access https://api.github.com/user but it is returning status code of 401

I am working on creating Github third-party OAuth application. I get an email like this, which shows that my app is authorized:
A third-party OAuth application (Dev Snapshot) with read:user,
repo:status, user:email, and user:follow scopes was recently
authorized to access your account.
I am also signed in under my Github account via Firebase. However, I cannot access this endpoint: https://api.github.com/user
I get 401 response, meaning I am not authorized.
Here is the code that signs me in my app:
provider.customParameters = [
"allow_signup": "true"
]
provider.scopes = ["read:user","user:email","user:follow","repo:status"]
provider.getCredentialWith(nil) { credential, error in
if error != nil {
print("error: \(error!.localizedDescription)")
}
if credential == nil {
print("no credential")
}
if credential != nil {
Auth.auth().signIn(with: credential!) { [self] authResult, error in
if error != nil {
// Handle error.
print("error sign in: \(error?.localizedDescription)")
}
// User is signed in.
// IdP data available in authResult.additionalUserInfo.profile.
guard let oauthCredential = authResult?.credential as? OAuthCredential else {
print("oauth error")
return
}
print("signed in")
// GitHub OAuth access token can also be retrieved by:
// oauthCredential.accessToken
// GitHub OAuth ID token can be retrieved by calling:
// oauthCredential.idToken
configure()
tableView.reloadData()
}
}
}

How to download images from AWS S3 in 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.

Firestore Insufficient Permissions

I keep receiving this error:
Adding Post Error: Missing or insufficient permissions.
These are my current permissions, which lets anyone do anything (which isn't ideal, but I'm just testing).
service cloud.firestore {
match /databases/{database}/documents {
match /Posts {
allow read, write;
}
}
}
And the code I am trying to run is:
func addPost (postID: String, date: NSDate, link: String, profileID: String, text: String) {
let db = Firestore.firestore()
let settings = db.settings
settings.areTimestampsInSnapshotsEnabled = true
db.settings = settings
db.collection("Posts").document(postID).setData(["Date": date, "Link": link, "ProfileID": profileID, "Text": text]) { (error) in
if (error != nil) {
print ("Adding Post Error: " + error!.localizedDescription)
} else {
print("Post added sucessfully")
}
}
}
Why am I getting this error message? I am running the latest version of FirebaseFirestore as of June 27, 2018.
I'm pretty sure you need to specify that the user is allowed to access documents in the collection as shown in the documentation on basic read/write rules:
service cloud.firestore {
match /databases/{database}/documents {
match /Posts/{post} {
allow read, write;
}
}
}
Difference above is the {post} in match /Posts/{post}.

Accessing some file via Cloud Speech in Google cloud storage throws error 7

here is the Context : My iOS swift app
records a sound,
creates a firebase object,
renames the file with the key of the object
uploads on firebase cloud the wav file.
A firebase cloud function is triggered that sends the audio file to google speech .recognize
My problem :
When I upload manually a sound file to the cloud storage, it works fine, but when the file is uploaded by the app automatically, I get the following error message as a return form the speech API :
{ Error: The caller does not have permission
at /user_code/node_modules/#google-cloud/speech/node_modules/grpc/src/node/src/client.js:554:15
code: 7, metadata: Metadata { _internal_repr: {} }, note:
'Exception occurred in retry method that was not classified as
transient' }
Here is the swift part :
func uploadFile(fileName:String){
// File located on disk
let localFileURL = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask)[0]
let fileURL = localFileURL.appendingPathComponent(fileName)
if FileManager.default.fileExists(atPath: fileURL.path) {
print("FilePath", fileURL.path)
// Create a reference to the file you want to upload
let newMnemoRef = MnemoDatabase.shared.createNew()
let newMnemoId = newMnemoRef.key
let filename=newMnemoId+".wav"
//let filename=fileName
let audioStorageRef = storage.reference().child(filename)
let storagePath = "gs://\(audioStorageRef.bucket)/\(audioStorageRef.fullPath)"
print(storagePath)
// Upload the file to the path "audio"
let uploadTask = audioStorageRef.putFile(from: fileURL, metadata: nil) { metadata, error in
if let error = error {
print("Upload error : ", error.localizedDescription)
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print ("OK")
}
}
// Add a progress observer to an upload task
let observer = uploadTask.observe(.success) { snapshot in
print("uploaded!")
newMnemoRef.child("audio").setValue([
"encoding_converted":"LINEAR16",
"sampleRate_converted":"44100",
"path_converted":storagePath])
}
} else {
print ("Non existent file", fileURL.path)
}
}
The cloud function calling the the speech API is fine with manually uploaded files.
here is the extract
const request = {
encoding: encoding,
sampleRateHertz: sampleRateHertz,
languageCode: language,
speechContexts: context
};
speech.recognize(uri, request)
The cloud storage bucket and cloud function all share the same project credentials.
I removed all authentification from the bucket
// Anyone can read or write to the bucket, even non-users of your app.
// Because it is shared with Google App Engine, this will also make
// files uploaded via GAE public.
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write;
}
}
}
I even tried hard coding the path into the cloud function, but to no avail.
I will appreciate any help
Well it seems that I was working on two different projects, and the one I was calling functions from didn't have the speech API activated, and I was passing credentials of the other project.
I should really stop working too late...
I re-engineered my project to work with a file trigger now, this is how I found the bug...

Requesting user's email from Twitter

I am trying to request a user's email address from Twitter and put it into Firebase. I got my app whitelisted by Twitter and activated it in the apps.twitter.com portal. As per the Fabric documentation here, I wrote this code out:
let twitterEmailClient = TWTRAPIClient.clientWithCurrentUser()
let twitterEmailRequest = twitterEmailClient.URLRequestWithMethod("GET", URL: "https://api.twitter.com/1.1/account/verify_credentials.json", parameters: ["include_email": "true", "skip_status": "true"], error: nil)
twitterEmailClient.sendTwitterRequest(twitterEmailRequest, completion: { (TWTREmailClientResponse: NSURLResponse?, TWTREmailClientEmail: NSData?, TWTREmailClientError: NSError?) in
if TWTREmailClientError != nil {
print("Twitter Email Client Error - \(TWTREmailClientError!.code): \(TWTREmailClientError!.localizedDescription)")
} else if TWTREmailClientResponse == nil {
print("Twitter Email Client Error - valid connection not available")
} else if TWTREmailClientEmail != nil {
print("Twitter Client Email - \(String(data: TWTREmailClientEmail!, encoding: NSUTF8StringEncoding))")
FIRAuth.auth()?.currentUser?.updateEmail("\(TWTREmailClientEmail)", completion: { (updateEmailError: NSError?) in
if updateEmailError != nil {
print("Set Email from Twitter Error - \(updateEmailError)")
}
})
}
})
Again, as per Fabric's docs, I should get a JSON result including the 'email'. Here is the result:
Twitter Client Email -
Optional("{\"id\":560366005,\"id_str\":\"560366005\",\"name\":\"Dan
Levy\",\"screen_name\":\"DanLevy114\",\"location\":\"Buffalo,
NY\",\"description\":\"Florida Tech \'20, Amherst \'16, iOS
Developer\",\"url\":\"https:\/\/t.co\/KOtATAEV3X\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"https:\/\/t.co\/KOtATAEV3X\",\"expanded_url\":\"http:\/\/Instagr.am\/danlevy114\",\"display_url\":\"Instagr.am\/danlevy114\",\"indices\":[0,23]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":292,\"friends_count\":196,\"listed_count\":4,\"created_at\":\"Sun
Apr 22 15:20:46 +0000
2012\",\"favourites_count\":1151,\"utc_offset\":-10800,\"time_zone\":\"Atlantic
Time
(Canada)\",\"geo_enabled\":true,\"verified\":false,\"statuses_count\":1305,\"lang\":\"en\",\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\/\/pbs.twimg.com\/profile_background_images\/743634202\/69dd45bc569542274b017cc25c1e464d.png\",\"profile_background_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_background_images\/743634202\/69dd45bc569542274b017cc25c1e464d.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\/\/pbs.twimg.com\/profile_images\/745047852796289024\/BWFfrEoI_normal.jpg\",\"profile_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_images\/745047852796289024\/BWFfrEoI_normal.jpg\",\"profile_banner_url\":\"https:\/\/pbs.twimg.com\/profile_banners\/560366005\/1466468226\",\"profile_link_color\":\"0084B4\",\"profile_sidebar_border_color\":\"FFFFFF\",\"profile_sidebar_fill_color\":\"33FF33\",\"profile_text_color\":\"E05151\",\"profile_use_background_image\":false,\"has_extended_profile\":true,\"default_profile\":false,\"default_profile_image\":false,\"following\":false,\"follow_request_sent\":false,\"notifications\":false}")
I logged in with my Twitter account and an email did not appear. I know I have an email associated with my Twitter account. Any ideas?
I had the same problem with Twitter. First of all change Permissions in dev.twitter to "Read only".
Next, use this code to login with Twitter:
Twitter.sharedInstance().logInWithCompletion() { session, error in
if let session = session {
let credential = FIRTwitterAuthProvider.credentialWithToken(session.authToken, secret: session.authTokenSecret)
FIRAuth.auth()?.signInWithCredential(credential) { (user, error) in
if let error = error {
Alert.sharedInstance.showAlert("Error", message: error.localizedDescription)
return
}
//Logged!
}
} else {
Alert.sharedInstance.showAlert("Error", message: error!.localizedDescription)
}
}
This work without request to Twitter.
API Key and API Secret in Firebase Console get from dev.twitter.