Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permission denied on set value #16

Closed
azraelx23 opened this issue Mar 21, 2017 · 21 comments
Closed

Permission denied on set value #16

azraelx23 opened this issue Mar 21, 2017 · 21 comments
Assignees

Comments

@azraelx23
Copy link

I am simply doing this:
admin.database().ref(/chat/last_message/${currentUserId}).set(currentTimestamp);

however I get:

FIREBASE WARNING: set at /chat/last_message/46wyOyreyYSocywqEt1g3943OAH2 failed: permission_denied

even though my database rule is only:

"chat": {
      "last_message": {
        "$uid": {
          ".write": true
        }
      },
}

Strange thing is I do the exact thing on my other firebase account, and it worked perfectly fine.

@jwngr
Copy link

jwngr commented Mar 21, 2017

When you authenticate with the Admin SDK, you should have full read and write access. The permission_denied error you are getting is probably because you are authenticating the Admin SDK with an invalid credential or databaseURL. Can you share the code you are using the initialize the Admin SDK? Are you using admin.initializeApp(functions.config().firebase) or are you passing in your own object into admin.initalizeApp()?

@azraelx23
Copy link
Author

hi @jwngr yes I use the standard admin.initializeApp(functions.config().firebase) to initialize the admin SDK. As I said before, the cloud function works fine on my test project with the same rule set and database structure, yet somehow on my project on production, it failed with this error.

@azraelx23
Copy link
Author

'read' is working fine for me, as in this code:

const lastMessageRef = admin.database().ref(`/chat/last_message/${currentUserId}`);
const lastMessageSnapshot = yield lastMessageRef.once('value'); 
const lastTimestamp = lastMessageSnapshot.val();

@azraelx23
Copy link
Author

apparently my database trigger function also failed with permission denied error, got this error on the log

at Error (native)
    at Mh (/user_code/node_modules/firebase-admin/lib/database/database.js:238:437)
    at Ch (/user_code/node_modules/firebase-admin/lib/database/database.js:236:351)
    at /user_code/node_modules/firebase-admin/lib/database/database.js:236:286
    at /user_code/node_modules/firebase-admin/lib/database/database.js:213:167
    at kh.h.ud (/user_code/node_modules/firebase-admin/lib/database/database.js:214:104)
    at Zg.ud (/user_code/node_modules/firebase-admin/lib/database/database.js:205:364)
    at Qg.Xf (/user_code/node_modules/firebase-admin/lib/database/database.js:203:281)
    at Tg (/user_code/node_modules/firebase-admin/lib/database/database.js:198:109)
    at Client.Ha.onmessage (/user_code/node_modules/firebase-admin/lib/database/database.js:196:467)

my code:

exports.gatheringUserCountChange = functions.database.ref('/gathering/{streamslug}/users/{userid}').onWrite((event) => {
  const collectionRef = event.data.ref.parent;
  const countRef = collectionRef.parent.child('count');

  return countRef.transaction((current) => {
    if (event.data.exists() && !event.data.previous.exists()) {
      return (current || 0) + 1;
    } else if (!event.data.exists() && event.data.previous.exists()) {
      return (current || 0) - 1;
    }
    return (current || 0);
  });
};

@jwngr
Copy link

jwngr commented Mar 22, 2017

Hey @azraelx23, I'm starting to get lost in all the different directions this is heading. Let me give you some general tips about how things are supposed to work and then maybe you can help put together a clear repro of the issue for me.

The event.data available as part of a Database Cloud Function is an instance of DeltaSnapshot. That DeltaSnapshot has both a ref property and an adminRef property. The ref property has the same access to the Realtime Database as the client that triggered the Cloud Function. So, if an unauthenticated client triggered the Cloud Function, then ref would likewise be unauthenticated. If instead an authenticated client triggered the Cloud Function, then ref would likewise be authenticated as that client. The adminRef property has full read and write access to the Realtime Database and should (in theory) never see a permission_denied error.

In your initial post, you are using admin.database().ref(), but instead should probably be using event.data.adminRef which is already authenticated for you. In your last post, you are using event.data.ref which may just be failing because your Security Rules are indeed blocking the write.

In order for me to help you, I'll need to see an mcve of your problem. This would include the full contents of you index.js and the Security Rules you are using for the relevant nodes. Ideally, you'd be able to get rid of any extraneous code that isn't part of the repro.

Hope that helps.

@azraelx23
Copy link
Author

azraelx23 commented Mar 22, 2017

Sorry @jwngr, but let's focus on the last code which is the database trigger because I think the issue is related. The first post was using HTTP trigger, so I cannot access the adminRef through the event object. However, on my last post, it is using database trigger and I changed it to this per your suggestion:

exports.gatheringUserCountChange = functions.database.ref('/gathering/{streamslug}/users/{userid}').onWrite((event) => {
  const collectionRef = event.data.adminRef.parent;
  const countRef = collectionRef.parent.child('count');

  return countRef.transaction((current) => {
    if (event.data.exists() && !event.data.previous.exists()) {
      return (current || 0) + 1;
    } else if (!event.data.exists() && event.data.previous.exists()) {
      return (current || 0) - 1;
    }
    return (current || 0);
  });
};

However, it still throws out the permission denied error.

@jwngr
Copy link

jwngr commented Mar 22, 2017

Ahh okay, now all the different posts are starting to make more sense. Thanks for clarifying.

I am really surprised that the latest code you shared doesn't work and throws a permission denied error. I honestly don't have a suggestion for you at the moment, other than to double check that the deploy actually was successful and the permission denied is coming from the correct Cloud Function. Can you also copy the full error log statement you see in your Functions Console? Other things I can think of are getting rid of all other Functions and even the admin.initializeApp() call and see if you still get this error. I still would really like to see the full file you are using if you are willing to share it. You can also send it to me privately at jacob@firebase.com if you'd prefer some privacy.

It also though has been a long day around here and it's getting late, so I may just be losing my touch 😪 I'll sleep on this and hopefully some of my suggestions above can help you debug this further.

@azraelx23
Copy link
Author

azraelx23 commented Mar 22, 2017

hi @jwngr appreciate that you are still replying late at night, I completely understand 👍

I have updated my index.js to the very barebones, I also include my package.json

'use strict';

const functions = require('firebase-functions');

const admin = require('firebase-admin');

admin.initializeApp(functions.config().firebase);

// GATHERING related
// if there is a new user added/deleted, update the gathering count
exports.gatheringUserCountChange = functions.database.ref('/gathering/{streamslug}/users/{userid}').onWrite((event) => {
  const collectionRef = event.data.adminRef.parent;
  const countRef = collectionRef.parent.child('count');

  return countRef.transaction((current) => {
    if (event.data.exists() && !event.data.previous.exists()) {
      return (current || 0) + 1;
    } else if (!event.data.exists() && event.data.previous.exists()) {
      return (current || 0) - 1;
    }
    return (current || 0);
  });
});

package.json:

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "dependencies": {
    "bad-words": "^1.3.1",
    "capitalize-sentence": "^0.1.2",
    "co": "^4.6.0",
    "cors": "^2.8.1",
    "firebase-admin": "^4.1.2",
    "firebase-functions": "^0.5",
    "moment": "^2.17.1"
  },
  "private": true,
  "devDependencies": {
    "babel-eslint": "^7.1.1",
    "eslint-config-airbnb": "^14.1.0",
    "eslint-plugin-react": "^6.10.0",
    "eslint-plugin-jsx-a11y": "^4.0.0",
    "eslint-plugin-import": "^2.2.0",
    "eslint": "^3.16.1"
  }
}

The error log that I got:

screen shot 2017-03-22 at 2 00 40 pm

Thanks!

@azraelx23
Copy link
Author

FYI, I also tried deleting the admin.initializeApp() call, but still no dice, same error.

@ahaverty
Copy link

The error log is for a transaction at /gathering/livestream-galih-samudra/count
By there's no transaction in that function in your above example? Only a set ()

@azraelx23
Copy link
Author

@ahaverty sorry please ignore the very first post and check my latest sample code, it is a database trigger and it has transaction right on this line

return countRef.transaction((current) => {
    if (event.data.exists() && !event.data.previous.exists()) {
      return (current || 0) + 1;
    } else if (!event.data.exists() && event.data.previous.exists()) {
      return (current || 0) - 1;
    }
    return (current || 0);
  });

@jwngr
Copy link

jwngr commented Mar 22, 2017

This behavior seems very unusual. And the fact that this same code works in a different projects makes me think something is just messed up with this particular project. One thing I would suggest trying is to disable and re-enable the Cloud Functions API entirely and see if that resets things. To do that, you need to follow these steps:

  1. Remove all of your Functions (do a firebase deploy with an empty index.js which exports no Functions).
  2. Disable and re-enable the Functions API (search for "Google Cloud Functions API" from here).
  3. Re-deploy all of your Functions.

Give that a shot and let me know if it solves this problem.

@azraelx23
Copy link
Author

Hi @jwngr , some updates, I did the first step that you outlined , however I noticed that there was 1 function remaining there even though I deployed an empty index.js, looks like my coworker deployed a function from his machine before I deployed my functions. Perhaps it's the source of this whole fiasco? I am trying to contact him first to try having him delete his function before restarting the API.

@azraelx23
Copy link
Author

@jwngr my coworked deleted his function, and I was able to disable and re-enable the functions API, after that I proceeded to upload the non-empty index.js , however I still got the same permission denied error.

@jwngr
Copy link

jwngr commented Mar 23, 2017

Wow, this one is really quite a doozy... I think I'm going to have to hand you off to some other people on the team who can help you debug this. Can you please send me an email at <MY_GITHUB_USERNAME>@google.com and include the project ID of both of your projects, indicating which one the code succeeds for an which one the code fails for. Also, if you don't mind, sending me the full index.js you are using will give ma chance to try to reproduce this issue. Either way, I'll escalate this to appropriate people and report back here once we track down what's up.

@azraelx23
Copy link
Author

@jwngr just sent you that email, thanks for everything!

@jwngr
Copy link

jwngr commented Mar 24, 2017

Mystery solved! After a bunch of back and forth, we finally tracked down the underlying problem. Cloud Functions use your Firebase project's App Engine default service account to authenticate adminRef. This service account is created automatically when your project is created and by default has an IAM permission of Editor. You can see what permission it has by looking here. In this case, the App Engine default service account has somehow gotten set to an IAM role of Viewer, which only granted it read access to the Realtime Database, not write access. The fix was simply to make it an Editor once again.

Thanks to @azraelx23 for helping to debug this and being patient while we tracked it down!

@jwngr jwngr closed this as completed Mar 24, 2017
@azraelx23
Copy link
Author

Thanks @jwngr and firebase team! Great team effort!

@mthompson9
Copy link

Hi @jwngr , we have a similar project that uses transactions and updates. However we checked the App Engine default service account and it was already editor, however we are getting the exact same error?

I know this is an old post but there have been quite a few changes/updates to firebase over the last week or so and we're wondering if the problem lies on our side or the firebase side?

Hoping this doesn't go unseen - Thanks

@justinrosenthal
Copy link
Contributor

Hi @mthompson9, have no fear, we've seen your comment! Since the above solution didn't solve your problem, it sounds like there must be another cause, so could you please go ahead and open a new Issue?

In the Issue it would be helpful to see some code snippets and/or as concise of a repro as possible. Thanks!

@david-shortman
Copy link

In my case, my default service agent was unable to make edits to the Firestore until I added the "Firestore Service Agent" role to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants