firebase - para - Restringir el acceso de niño/campo con reglas de seguridad
guardar datos en firebase android (3)
Estoy escribiendo una aplicación que permite a los usuarios enviar nominaciones moderadas antes de mostrarlas a otros usuarios. Esto requiere una serie de restricciones que hasta ahora no he tenido éxito en implementar con las reglas de seguridad:
- Ocultar cualquier nominación que aún no haya sido aprobada
- Ocultar los campos privados de la presentación (teléfono, estado de aprobación, fecha de creación, etc.)
Mis reglas actuales son las siguientes:
{
"rules": {
"nominations": {
".read": true,
"$nominationId": {
".read": "data.child(''state'').val() == ''approved'' || auth != null", // Only read approved nominations if not authenticated
".write": "!data.exists()", // Only allow new nominations to be created
"phone": {
".read": "auth != null" // Only allow authenticated users to read phone number
},
"state": {
".read": "auth != null", // Only allow authenticated users to read approval state
".write": "auth != null" // Only allow authenticated users to change state
}
}
}
}
}
Las reglas del niño (por ejemplo, $nomination
) no impiden que el padre o la madre lean a todo el niño. Si escucho child_added
en https://my.firebaseio.com/nominations , felizmente devuelve todos los hijos y todos sus datos, incluso con las reglas de seguridad anteriores en su lugar.
Mi idea actual de solución para esto es mantener un nodo separado llamado approved
y simplemente mover los datos entre listas cada vez que alguien aprueba o rechaza una nominación, pero parece un enfoque terriblemente roto.
Actualizar
Siguiendo el excelente comentario de Michael Lehenbauer , he vuelto a implementar la idea inicial con un mínimo esfuerzo.
La nueva estructura de datos es la siguiente:
my-firebase
|
`- nominations
|
`- entries
| |
| `- private
| `- public
|
`- status
|
`- pending
`- approved
`- rejected
Cada nominación se almacena en entries
con información privada, como número de teléfono, correo electrónico, etc. bajo datos public
y private
visibles en public
.
Las reglas actualizadas son las siguientes:
{
"rules": {
"nominations": {
"entries": {
"$id": {
".write": "!data.exists()",
"public": {
".read": true,
},
"private": {
".read": "auth != null"
}
}
},
"status": {
"pending": {
".read": "auth != null",
"$id": {
".write": "root.child(''nominations/entries'').child($id).exists() && (auth != null || newData.val() == true)"
}
},
"approved": {
".read": true,
"$id": {
".write": "root.child(''nominations/entries'').child($id).exists() && auth != null"
}
},
"rejected": {
".read": "auth != null",
"$id": {
".write": "root.child(''nominations/entries'').child($id).exists() && auth != null"
}
}
}
}
}
}
Y la implementación de JavaScript:
var db = new Firebase(''https://my.firebaseio.com'')
var nominations = db.child(''nominations'')
var entries = nominations.child(''entries'')
var status = nominations.child(''status'')
var pending = status.child(''pending'')
var approved = status.child(''approved'')
var rejected = status.child(''rejected'')
// Create nomination via form input (not shown)
var createNomination = function() {
var data = {
public: {
name: ''Foo'',
age: 20
},
private: {
createdAt: new Date().getTime(),
phone: 123456
}
}
var nomination = entries.push()
nomination.setWithPriority(data, data.private.createdAt)
pending.child(nomination.name()).set(true)
}
// Retrieve current nomination status
var getStatus = function(id, callback) {
approved.child(id).once(''value'', function(snapshot) {
if (snapshot.val()) {
callback(id, ''approved'')
} else {
rejected.child(id).once(''value'', function(snapshot) {
callback(id, snapshot.val() ? ''rejected'' : ''pending'')
})
}
})
}
// Change status of nomination
var changeStatus = function(id, from, to) {
status.child(from).child(id).remove()
status.child(to).child(id).set(true)
}
La única parte de la implementación con la que estoy luchando es la manipulación de los cambios de estado, mi enfoque actual seguramente puede mejorarse:
_.each([pending, approved, rejected], function(status) {
status.on(''child_added'', function(snapshot) {
$(''#'' + snapshot.name()).removeClass(''pending approved rejected'').addClass(status.name())
})
})
Estaba planeando usar child_changed
en las nominations/status
pero no he podido hacer que funcione de manera confiable.
Kato tiene razón. Es importante entender que las reglas de seguridad nunca filtran datos. Para cualquier ubicación, podrá leer todos los datos (incluidos sus hijos) o ninguno. Entonces, en el caso de sus reglas, tener un ".read": verdadero bajo "nominaciones" niega todas sus otras reglas.
Entonces, el enfoque que recomendaría aquí es tener 3 listas. Uno que contiene datos de nominación, uno para contener la lista de nominaciones aprobadas, y uno para contener la lista de nominaciones pendientes.
Tus reglas podrían ser así:
{
"rules": {
// The actual nominations. Each will be stored with a unique ID.
"nominations": {
"$id": {
".write": "!data.exists()", // anybody can create new nominations, but not overwrite existing ones.
"public_data": {
".read": true // everybody can read the public data.
},
"phone": {
".read": "auth != null", // only authenticated users can read the phone number.
}
}
},
"approved_list": {
".read": true, // everybody can read the approved nominations list.
"$id": {
// Authenticated users can add the id of a nomination to the approved list
// by creating a child with the nomination id as the name and true as the value.
".write": "auth != null && root.child(''nominations'').child($id).exists() && newData.val() == true"
}
},
"pending_list": {
".read": "auth != null", // Only authenticated users can read the pending list.
"$id": {
// Any user can add a nomination to the pending list, to be moderated by
// an authenticated user (who can then delete it from this list).
".write": "root.child(''nominations'').child($id).exists() && (newData.val() == true || auth != null)"
}
}
}
}
Un usuario no autenticado podría agregar una nueva nominación con:
var id = ref.child(''nominations'').push({ public_data: "whatever", phone: "555-1234" });
ref.child(''pending_list'').child(id).set(true);
Un usuario autenticado podría aprobar un mensaje con:
ref.child(''pending_list'').child(id).remove();
ref.child(''approved_list'').child(id).set(true);
Y para representar las listas aprobadas y pendientes, usaría un código como:
ref.child(''approved_list'').on(''child_added'', function(childSnapshot) {
var nominationId = childSnapshot.name();
ref.child(''nominations'').child(nominationId).child(''public_data'').on(''value'', function(nominationDataSnap) {
console.log(nominationDataSnap.val());
});
});
De esta forma, utiliza la lista_de_prueba y la lista_de_entrada como listas livianas que pueden enumerarse (por usuarios no autenticados y autenticados, respectivamente) y almacenar todos los datos nominales reales en la lista de nominaciones (que nadie puede enumerar directamente).
Si entiendo completamente el funcionamiento de las reglas de seguridad (solo las estoy aprendiendo), cuando una regla permite el acceso, se otorga el acceso. Por lo tanto, se leen de la siguiente manera:
- nominaciones ".read": verdadero, ACCESO OTORGADO
- otras reglas: no leer
Además, si se elimina esa regla, $nominationId
".read" otorga acceso si se aprueba el registro; por lo tanto, el .read
en el phone
y el state
vuelven superfluos cuando se aprueba.
Probablemente sería más simple dividir esto en public/
y private/
niños, así:
nominations/unapproved/ # only visible to logged in users
nominations/approved/ # visible to anyone (move record here after approval)
nominations/approved/public/ # things everyone can see
nominations/approved/restricted/ # things like phone number, which are restricted
ACTUALIZAR
Pensando en esto aún más, creo que todavía tendrá un problema con la approved/
público, lo que le permitirá listar los registros y tener approved/restricted/
privado. Los datos restringidos también pueden necesitar su propia ruta en este caso de uso.
este hilo está un poco desactualizado y puede tener una solución a través de reglas, pero como dice el video, es un truco perfecto: https://youtu.be/5hYMDfDoHpI?t=8m50s
Puede que esta no sea una buena práctica ya que los usuarios de FireBase dicen que las reglas no son filtros: https://firebase.google.com/docs/database/security/securing-data
No soy especialista en seguridad pero probé el truco y funcionó bien para mí. :)
Así que espero una mejor comprensión de los problemas de seguridad en torno a esta implementación.