In my iOS app I am making a PFObject and saving it to Parse. Later, a user's account is created (which didn't exist before), and tries to modify it but can't because the PFObjects's ACL wasn't set to allow that user to have permission. How can I modify the ACL of an existing object in Parse to allow this user to have access? I do not want to allow public write access.
The following code prints Success! if given the right code query parameter, but when I check the ACL in Parse it has not been updated at all.
let query = PFQuery(className: "Bike")
query.whereKey("bikeID", equalTo: code)
query.findObjectsInBackground { (objects: [PFObject]?, error: Error?) in
guard let obj = objects?[0], error == nil else {
print("Error")
return
}
obj.acl?.setWriteAccess(true, for: PFUser.current()!)
obj.saveInBackground { (success: Bool, error: Error?) in
if error != nil {
print(error!.localizedDescription)
}
else {
print("Success!")
}
}
}
This post seems to suggest that the ACL cannot be changed through my app's Swift code.
If you know the object you want to grant access to up-front you can change it's ACL in the CloudCode afterSave hook of the User class: in afterSave, test whether the user was just created (to avoid redoing this work for subsequent save requests), then look up the object and set the access rights using the master key.
Related
A custom claim luid has been added to the Firebase Authentication from the backend, I'm looking for a way to access this key from the front end for an iOS application.
First I need to check if the key exists and if it exists then get its value.
What have I tried? Everything under Auth.auth().currentUser
Attaching a picture of the decoded JWT data, which shows the key luid
You can check the custom claims this way:
user.getIDTokenResult(completion: { (result, error) in
guard let luid = result?.claims?["luid"] as? NSNumber else {
// luid absent
return
}
//Check for value here and use if-else
})
Detailed explanation can be found in documentation
This will display a Dictionary with all the keys available for the current user.
Auth.auth().currentUser?.getIDTokenResult(completion: { (authresult, error) in
print("CurrentUser Keys", authresult!.claims.keys)
})
This will return Bool value based on a particular key's availability
Auth.auth().currentUser?.getIDTokenResult(completion: { (authresult, error) in
print("CurrentUser Keys", authresult!.claims.keys.contains("luid"))
})
It was OK in development,
but when I distributed my app by TestFlight, I have had this problem.
While I was checking some failures,
I have guessed that when a user who didn’t create the record try to modify it, can’t do that.
By the way, I can fetch all record values on Public Database. Only modification isn’t performed.
In the picture below, iCloud accounts are written in green areas. I predicted that these have to be same.
image: Metadata - CloudKit Dashboard
Now, users are trying to modify a record by the following code:
func modifyRecord() {
let publicDatabase = CKContainer.default().publicCloudDatabase
let predicate = NSPredicate(format: "accountID == %#", argumentArray: [myID!])
let query = CKQuery(recordType: "Accounts", predicate: predicate)
publicDatabase.perform(query, inZoneWith: nil, completionHandler: {(records, error) in
if let error = error {
print("error1: \(error)")
return
}
for record in records! {
/* ↓ New Value */
record["currentLocation"] = CLLocation(latitude: 40.689283, longitude: -74.044368)
publicDatabase.save(record, completionHandler: {(record, error) in
if let error = error {
print("error2: \(error)")
return
}
print("success!")
})
}
})
}
In development, I created and modified all records by myself, so I was not able to find this problem.
Versions
Xcode 11.6 / Swift 5
Summary
I guessed that it is necessary to create and modify record by same user ( = same iCloud Account ) in this code.
Then, could you please tell me how to modify the record created by other user?
In the first place, can I do that?
Thanks.
Looks like you have a permissions problem. In your cloudkit dashboard, go to: Schema > Security Roles
Then under 'Authenticated' (which means a user logged into Icloud)
you'll need to grant 'Write' permissions to the relevant record type. That should fix it!
I'm wondering if a user can read/write a row in the Parse Installation class that does not belong to their device?
I tried PFInstallation.query() and PFQuery(className: "Installation") to query the installation class to then edit and save the objects. The first method returned the following error: Clients aren't allowed to perform the find operation on the installation collection. The second returned no objects in the query.
Is it impossible to write to an Installation class row other than the PFInstallation.current() of the device? Or does someone know a proper method of how to do so?
Also, I am trying to do this from a client app, WITHOUT the Master Key
First Method:
let query = PFInstallation.query()
query?.findObjectsInBackground(block: { (rows, error) -> Void in
if error == nil {
print(rows)
} else {
print("Error: ", error!)
}
})
Second Method:
let inst = PFQuery(className: "Installation")
inst.whereKey("deviceType", equalTo: "ios")
inst.findObjectsInBackground(block: { (rows, error) -> Void in
if error == nil {
for row in rows! {
print("row: ", row)
}
} else {
print("ERROR: ", error!)
}
})
The class name is _Installation (All reserved table/class names start with an underscore _)
Allowed operations in _Installation (More here):
Get -> ignores CLP, but not ACL
Find -> master key only *
Create -> ignores CLP
Update -> ignores CLP, but not ACL **
Delete -> master key only **
Add Field -> normal behavior
* Get requests on installations follow ACLs normally. Find requests without master key is not allowed unless you supply the installationId as a constraint.
** Update requests on installations do adhere to the ACL defined on the installation, but Delete requests are master-key-only. For more information about how installations work, check out the installations section of the REST guide.
Extra:
It's both safer and simpler to use the class name provided by the class itself rather than entering the string value.
// Unsafe, can lead to typos, etc.
let query = PFQuery(className: "Installation")
// Better
let query = PFQuery(className: PFInstallation.parseClassName)
// Simpler
let query = PFInstallation.query()
In an effort to create the easiest user experience possible, I am on a mission to accept a user as an anonymous user using Parse + Swift. I had thought to use the Anonymous user functions in Parse to accomplish that. As a result, I created the following code:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.setupParse()
// self.setupAppAppearance()
This first section is to create a user and see if at this point in the process - I have a nil objectId (typically true for the user when first they attempt to open the application).
var player = PFUser.currentUser()
if player?.objectId == nil {
}
else
{
println(player!.objectId)
}
If I have an objectId (indicating that I've been down this road before and saved an anonymous user object) - throw that to the console so I can see what it is and check it in the Parse user object. Cool - good so far.
Next - Check to see if the Object is nil again - this time to decide whether or not to attempt to perform an anonymous login - there's not a thing to use to generate an anonymous user other than this anonymous login action.
if player?.objectId == nil {
PFAnonymousUtils.logInWithBlock({
(success, error) -> Void in
if (error != nil)
{
println("Anonymous login failed.")
}
else
{
println("Anonymous login succeeded.")
If anonymous Login succeeded (still considering doing a network available check before trying to run these bits...but assuming network is valid) save a Bool to "isAnonymous" on the server to make sure that we have identified this user as anonymous - I may want that information later, so it seemed worth throwing this action.
Question 1: Do I need to re-query PFUser.currentUser() (known as player) to make sure that I have a valid anon user object that is connected to the server, or will the player object that I allocated earlier recognize that I've logged in and thereby recognize that I can throw other info into the associated record online? I think this is working as is - but I've been getting weird session token errors:
[Error]: invalid session token (Code: 209, Version: 1.7.5)
player!["isAnonymous"] = true as AnyObject
player!.saveInBackgroundWithBlock {
(success, error) -> Void in
if (error != nil)
{
println("error updating user record with isAnonymous true")
}
else
{
println("successfully updated user record with isAnonymous true")
}
}
}
})
}
else
{
}
return true
}
func setupParse()
{
Parse.setApplicationId("dW1UugqmsKkQCoqlKR3hX8dISjvOuApcffGAWR2a", clientKey: "BtXxjTjBRZVnCZbJODhd3UBUU8zuoPU1HBckXh4t")
enableAutomaticUserCreateInParse()
This next bit is just about trying to figure out some way to deal with those token problems. No idea whether it's doing me any good at all or not. It said to turn this on right after instantiating the Parse connection.
PFUser.enableRevocableSessionInBackgroundWithBlock({
(error) -> Void in
if (error != nil) {
println(error?.localizedDescription)
}
})
Next - just throwing around objects because I keep struggling with being connected and storing stuff or not being connected or losing session tokens. So - til I get this worked out - I'm creating more test objects than I can shake a stick at.
var testObject = PFObject(className: "TestObject")
testObject["foo"] = "barnone"
testObject.saveInBackgroundWithBlock { (success: Bool, error: NSError?) -> Void in
println("Object has been saved.")
}
}
Question2: it appears to me that PFUser.enableAutomaticUser() while very handy - causes some headaches when trying to figure out whether I'm logged in/online/whatever. Anyone have any solid experience with this and able to guide me on how you'd check whether you were connected or not - I need to know that later to be able to decide whether to save more things to the user object or not.
func enableAutomaticUserCreateInParse() {
if PFUser.currentUser()?.objectId == nil
{
myHumanGamePlayer.playerDisplayName = "Anonymous Player"
PFUser.enableAutomaticUser()
}
}
Anyone out there who's an expert on using anonymous users in Parse with Swift, let's get in touch and post a tutorial - because this has cost me more hours than I'd like to think about.
Thank you!
Xylus
For player!["isAnonymous"] = true as AnyObject, don't save it as any object. Save it as a bool and look at your parse to see if it's a bool object. Try querying for current user in a different view controller and print to the command line. I hope this helped
My app has a login and signup. Once the user is logged in he can then choose to upgrade the account. When the account is upgraded, a new class in parse is created called "Upgrade". Here it has a bunch of subclasses with stored information. Then once the user is upgraded it brings him to a special page that only upgraded users have access to. But how can I check on login if the user is upgraded, and if he is, automatically bring him to the special page.
In my parse, I have the User information stored with subclasses "Username" and "Password". Then in a separate class I have the upgrade information stores with subclasses "Address", "Phone Number", and I have a linker to link back to the user who created it.
my current code for login is:
#IBAction func loginButton(sender:AnyObject) {
var username = self.usernameTextField.text
var password = self.passwordTextField.text
if(password.utfCount <5) {
var alert = UIAlertView(title:"Invalid", message: "Password must be greater than 5", delegate: self, cancelButtonTitle:"OK")
}
else {
PFUser.logInWithUsernameInBackground(username, password: password, block:{(user, error) -> Void in
if ((user != nil) {
self.performSegueWithIdentifier("LoginSegue", sender: nil)
This is the basic code but it does not check to see if the user is upgraded.
I tried:
if(PFUser.currentUser() == PFQuery(className:"Upgrade")) {
self.performSegueWithIdentifier("UpgradedSegue")
But obivously this didnt work due to the current user not equaling that class.
What kind of code could I user to check if the user made a Upgrade class within parse?
I have tried messing around with fetchinbackground code and enter code hereobjectinbackground but I can't seem to make those work.
I don't know the Swift very well, so sorry if there are errors, but try something like:
var query = PFQuery(className:"Upgrade")
query.whereKey("user", equalTo:currentUser.objectId)
query.getFirstObjectInBackgroundWithBlock {
(object: PFObject!, error: NSError!) -> Void in
if error != nil
println("The getFirstObject request failed.")
} else if object == nil{
//No Upgrade object with that user's ID
} else {
// The find succeeded, user has created an Upgrade object
println("Successfully retrieved the object.")
}
}
You could also set up your code to store the pointer to the Upgrade object on the user, rather than the other way around. You could set a bool value on the user to see if they have upgraded. If it is true, then take them to the upgrade screen, or fetch the Upgrade object, whatever you need first.