Tomas Mota
Tomas Mota

Reputation: 679

Why am I getting access denied on Firebase Database

I am getting Permission Denied whenever I try to read from my database (I can write)

  /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
  "rules": {
    "Users": {
      "$user_id":{
        ".read": "$user_id == auth.uid",
        ".write": "$user_id == auth.uid"
      }
    }
  }
}

This is an activity that is supposed to retrieve every user in the database (I have 20)


import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.example.shrinkio.Adapter.UserAdapter;
import com.example.shrinkio.R;
import com.example.shrinkio.model.User;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;

import java.util.ArrayList;
import java.util.List;

public class PeopleFragment extends Fragment {

    private RecyclerView recyclerView;
    private UserAdapter userAdapter;
    private List<User> mUsers;

    EditText search_bar;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_people, container, false);

        recyclerView = view.findViewById(R.id.recycler_view);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

        search_bar = view.findViewById(R.id.search_bar);
        mUsers = new ArrayList<>();
        userAdapter = new UserAdapter(getContext(), mUsers);
        recyclerView.setAdapter(userAdapter);

        readUsers();
        search_bar.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
             searchUsers(charSequence.toString().toLowerCase());
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });

        return view;
    }

    private void searchUsers(String s) {
        Query query = FirebaseDatabase.getInstance().getReference("Users").child("user_Id").orderByChild("Name").startAt(s).endAt(s+"\uf8ff");

        query.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                mUsers.clear();
                for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
                    User user = snapshot.getValue(User.class);
                    mUsers.add(user);
                }
                userAdapter.notifyDataSetChanged();
            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });
    }

    public void readUsers(){
        DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Users");
        reference.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                if ( search_bar.getText().toString().equals("")) {
                    mUsers.clear();
                    for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()){
                        User user = dataSnapshot.getValue(User.class);
                        mUsers.add(user);
                    }

                }            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });
    }
}

As previously mentioned, I am supposed to get 20 users, on a recycler view, however, no error occurs, but, the activity remains blank and once I check logcat I get the following error.

It does show as an error, but I can't see the posts that are on the db or any user info:

W/SyncTree: Listen at /Users failed: DatabaseError: Permission denied

Upvotes: 0

Views: 805

Answers (2)

samthecodingman
samthecodingman

Reputation: 26171

If you are looking to be able to search users, you would need to grant read access at the /Users level. But because security rules cascade in the Realtime Database, if you granted such a permission, the deeper restrictive ".read" rules would get ignored allowing any user to read private user data.

"rules": {
  "Users": {
    "$user_id": {
      ".read": "$user_id == auth.uid", // ignored, superseded by /Users/.read rule
      ".write": "$user_id == auth.uid"
    },
    ".read": "auth != null" // logged in users can read everything under /Users
  }
}

Instead, I recommend splitting the user data into private user data and public profile data. The public profile would contain non-sensitive information like display name, profile picture, username, hashed email addresses (for "search by email"/Gravatar), etc.

"rules": {
  "Users": {
    "$user_id": {
      ".read": "$user_id == auth.uid", // only the owner can read/write their private data
      ".write": "$user_id == auth.uid"
    }
  },
  "Profiles": {
    "$user_id": {
      ".write": "$user_id == auth.uid" // only the profile owner can update their profile
    },
    ".read": "auth != null" // logged in users can read everything under /Profiles
  }
}

On your front-end, you would change your RecyclerView to use /Profiles instead.

Upvotes: 2

Frank van Puffelen
Frank van Puffelen

Reputation: 598668

Your security rules require that a user is signed in before they can read/write to the database. Even then, a user can only read or write their own node under /Users/theirUid, where theirUid is the user ID that Firebase Authentication assigns to them when they first sign in.

Your code makes no use of Firebase Authentication whatsoever. This means that no user is signed in, and thus the read operation on /Users gets rejected.

You'll need to make two changes:

  1. Use Firebase Authentication to sign the user in. For more on this, see the documentation for Firebase Authentication.
  2. Either widen what a signed-in user is allowed to read to /Users or narrow the code to only read /Users/theirUid. The latter essentially makes searching impossible.

If you want to allow the user to search the data, but not to read all that data, consider implementing the search functionality in Cloud Functions. Since Cloud Functions run in a trusted environment, they have full access to the data in the database. But since your code runs on a server, you can be sure that users can only access what your code allows.

Upvotes: 0

Related Questions