Search code examples
c#unity-game-enginefirebase-realtime-databasefirebase-security

Firebase Realtime security rules: unable to compare uid with leaf value


I am trying to configure Firebase Realtime to check if a user should have access to register or not.

To do this I have a value inside each register called "uid" that contains the uid of the user that can access it.

  • The data tree is "(root/)Usuaros/$userid".
  • Each $userid has a leaf called "uid" who contains a string value.

This is how the data tree looks.

Under my logic, this rule should be enough, but it denies access to Usuarios, even if they match uid with auth.uid.

{
  "rules": {     
            "Usuarios" :{      
                "$usuarioPk" : {
                ".read": "data.child('uid').val() == auth.uid"

                }
           }
  }
}

¿What am I doing wrong?

EDIT: Here is the code (C# - Unity Game Engine).

Base Class FirebaseTable.cs

public class FirebaseTable : MonoBehaviour
{
Firebase.Auth.FirebaseAuth auth;
Firebase.FirebaseApp app;

private void Awake()
{


}


protected UnityEvent onDataUpdate = new UnityEvent();

public string dbPath = "";


protected virtual void OnEnable()
{
    IniciarListenerDB();
}

protected virtual void OnDisable()
{
    DetenerListenerDB();
}


#region Iniciar/Detener Listeners


void IniciarListenerDB()
{
    FirebaseDatabase.DefaultInstance
        .GetReference(dbPath)
        .ValueChanged += HandleValueChanged;
        
}

void DetenerListenerDB()
{
    FirebaseDatabase.DefaultInstance
        .GetReference(dbPath)
        .ValueChanged -= HandleValueChanged;

}
#endregion

#region Receptor de actualizaciones

/// <summary>
/// 
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected virtual void HandleValueChanged(object sender, ValueChangedEventArgs args)
{

}
#endregion

Here is the child class

public class TablaUsuarios : FirebaseTable
{

public List<Usuario> usuarios;


protected override void OnEnable()
{
    base.OnEnable();
    dbPath = "Usuarios";
    if(usuarios == null)
    {
        usuarios = new List<Usuario>();
    }

}

protected override void OnDisable()
{
    base.OnDisable();
    usuarios.Clear();

}

protected override void HandleValueChanged(object sender, ValueChangedEventArgs args)
{
    DataSnapshot dataSnapshot = args.Snapshot;

    if(args.DatabaseError != null)
    {
        Debug.LogError(args.DatabaseError.Message);
    }
    

    List<Usuario> entitiesDB = DataSnapshotToList(dataSnapshot);
    usuarios = entitiesDB;
    //UpdatePublicList(entitiesDB, ref usuarios);


}


List<Usuario> DataSnapshotToList(DataSnapshot dataSnapshot)
{
    List<Usuario> result = new List<Usuario>();
    foreach(var children in dataSnapshot.Children)
    {
        if (children.Value is IDictionary<string, object> variables)
        {
            Debug.Log(dataSnapshot.GetRawJsonValue());
            Usuario entity = new Usuario();
            entity.pk = (string)variables["pk"];
            entity.uid = (string)variables["uid"];
            entity.nombre = (string)variables["nombre"];
            entity.apellidos = (string)variables["apellidos"];
            entity.email = (string)variables["email"];
            entity.telefono = (string)variables["telefono"];
            entity.tipoUsuario = (string)variables["tipoUsuario"];

            entity.skContrato = (string)variables["skContrato"];

            result.Add(entity);
        }
    }
    

    return result;
}

void UpdatePublicList(List<Usuario> firebaseEntities, ref List<Usuario> localEntities)
{
    List<Usuario> usuariosToCreate = new List<Usuario>(); // Añadiremos los usuarios que deben ser creados
    List<Usuario> usuariosToUpdate = new List<Usuario>(); // Añadiremos los usuarios que deben ser actualizados
    List<Usuario> usuariosToDelete = new List<Usuario>(); // Añadiremos los usuarios que deben ser eliminados

    foreach (var element in localEntities) // Creamos una lista con los usuarios a eliminar
    {
        usuariosToDelete.Add(element);
    }

    foreach(var firebaseElement in firebaseEntities) // Iteramos por la lista publica de usuarios
    {
        if (!UsuarioExist(firebaseElement.pk, localEntities)) // Si no la encontramos
        {
            usuariosToCreate.Add(firebaseElement);

        }
        else if (UsuarioExist(firebaseElement.pk, localEntities)) // Si la encontramos pero ha cambiado
        {
            usuariosToUpdate.Add(firebaseElement);

        }
    }

    foreach(var localElement in localEntities)
    {
        if(!UsuarioExist(localElement.pk, firebaseEntities))
        {
            usuariosToDelete.Add(localElement);
        }
    }

    for(int i = 0; i < usuariosToDelete.Count; i++)
    {
        usuarios.Remove(FindUsuario(usuariosToDelete[i].pk, usuarios));
    }

    for (int i = 0; i < usuariosToCreate.Count; i++)
    {
        usuarios.Add(usuariosToCreate[i]);
    }

    for (int i = 0; i < usuariosToUpdate.Count; i++)
    {
        FindUsuario(usuariosToUpdate[i].pk, usuarios).CopyDataFrom(usuariosToUpdate[i]);
    }


}

public Usuario FindUsuario(string pk, List<Usuario> data)
{
    

    foreach(var usuario in usuarios)
    {
        if(usuario.Pk == pk)
        {
            return usuario;
        }
    }

    return null;
    
}

public bool UsuarioExist(string pk, List<Usuario> data)
{
    if(FindUsuario(pk, data) != null)
    {
        return true;
    }
    return false;
}



// Start is called before the first frame update
void Start()
{
    
}

// Update is called once per frame
void Update()
{
    
}
}

And the error log:

This client does not have permission to perform this operation. UnityEngine.Debug:LogError (object) TablaUsuarios:HandleValueChanged (object,Firebase.Database.ValueChangedEventArgs) (at Assets/Scripts/Tablas Firebase/NuevoSistema/Tables/TablaUsuarios.cs:36) Firebase.Database.Internal.InternalValueListener/c__AnonStorey1:<>m__0 () (at Z:/tmp/tmp.gpjxyegeCh/firebase/database/client/unity/proxy/InternalValueListener.cs:72) Firebase.ExceptionAggregator:Wrap (System.Action) (at Z:/tmp/tmp.AqqnQIVt80/firebase/app/client/unity/src/Platform/ExceptionAggregator.cs:112) Firebase.Database.Internal.InternalValueListener:OnCancelledHandler (int,Firebase.Database.Internal.Error,string) (at Z:/tmp/tmp.gpjxyegeCh/firebase/database/client/unity/proxy/InternalValueListener.cs:65) Firebase.AppUtil:PollCallbacks () (at Z:/tmp/tmp.RYyft9kBZb/firebase/app/client/unity/proxy/AppUtil.cs:32) Firebase.Platform.FirebaseAppUtils:PollCallbacks () (at Z:/tmp/tmp.RYyft9kBZb/firebase/app/client/unity/proxy/FirebaseAppUtils.cs:33) Firebase.Platform.FirebaseHandler:Update () (at Z:/tmp/tmp.AqqnQIVt80/firebase/app/client/unity/src/Unity/FirebaseHandler.cs:208) Firebase.Platform.FirebaseMonoBehaviour:Update () (at Z:/tmp/tmp.AqqnQIVt80/firebase/app/client/unity/src/Unity/FirebaseMonoBehaviour.cs:45)


Solution

  • Your code is trying to read the /Usarios node. But if we look at the rules, nobody has permission to read the /Usarios node, so that read operation gets (correctly) rejected.

    One common source of this mistake is that rules do not filter data. Instead the rules merely check whether the operation you are trying to perform is allowed.

    If you want to securely filter data, you will have to encode the condition both in your rules and in your code, as shown in the documentation on query based rules. Here that could be:

    "Usarios": {
      ".read": "auth.uid != null &&
                query.orderByChild == 'uid' &&
                query.equalTo == auth.uid" // restrict profile access to owner of the profile
    }