2
votes

Règles de stockage Firebase avec revendications personnalisées

Je ne parviens pas à faire fonctionner Firebase Storage avec des règles personnalisées et des revendications personnalisées.

Dans mon panneau d'administration Python, je fais ce qui suit pour créer l'utilisateur et attribuer une revendication client_id:

service firebase.storage {
  match /b/{bucket}/o {
    match /{environment}/{client_id}/{allPaths=**} {
      allow read: if request.auth.token.client_id == client_id
    }
  }
}

Ensuite, pour les règles Firebase, j'essaie d'autoriser l'utilisateur à lire (ou télécharger) uniquement les fichiers lorsque le fichier est dans un sous-dossier avec le client_id:

Structure des fichiers sur le stockage:

/{environment}/{client_id}/other_folders_and_files

J'ai défini les règles de stockage suivantes:

# Standard Auth
import firebase_admin
from firebase_admin import db, storage, auth
cred   = firebase_admin.credentials.Certificate('path_to_cert_json')
app    = firebase_admin.initialize_app(cred, 'config')
bucket = storage.bucket(app=app)

# Create User
auth.create_user(email=email) 

# Create custom claims
auth.set_custom_user_claims(uid, {'client_id': client_id})

Mais cela me donne une erreur selon laquelle l'autorisation est refusée.

Qu'est-ce que je fais mal?

Remarque:

  • client_id est correct et la structure du dossier est correcte, je l'ai vérifié un million de fois.


7 commentaires

Je vais essayer ça aussi. Cela semble être la seule solution possible!


L'avez-vous résolu?


Pouvez-vous s'il vous plaît regarder ce git , peut-être que cela peut résoudre votre question


J'en ai résolu une partie: en créant des revendications personnalisées à l'aide du SDK Admin (CLI sur mon système), je peux les vérifier dans les règles de stockage. Cette partie fonctionne. Je n'ai pas encore écrit la partie pour créer une fonction côté serveur qui se déclenche sur les mises à jour de l'enregistrement utilisateur dans Firestore et crée / met à jour les revendications d'authentification personnalisées, mais je le ferai aujourd'hui. En supposant que cela fonctionne, cela semble être la seule route (quoique très détournée, longue et difficile à tester). Ce serait tellement plus facile si les règles de stockage pouvaient simplement lire les enregistrements Firestore. Maintenant, je dois tout garder à deux endroits en utilisant des fonctions côté serveur.


Après quelques heures de travail, je peux dire oui, cela fonctionne vraiment. J'ai écrit une fonction cloud déclenchée par une modification de n'importe quel document dans users/{userId} qui synchronise les customClaims à partir de la base de données.


Pourquoi avez-vous besoin d'une fonction pour synchroniser les revendications à tout moment, ne pouvez-vous pas simplement définir la revendication une fois lors de la création de l'utilisateur comme je le fais dans ma question?


Êtes-vous sûr que la revendication a été appliquée à l'utilisateur? Vous pouvez vérifier les réclamations côté client , ce que je suggérerais simplement pour vérifier que la réclamation a été correctement appliquée. De plus, dans la ligne auth.set_custom_user_claims(uid, {'client_id': client_id}) , d'où vient la valeur de uid ? Pouvez-vous mettre à jour votre code pour l'inclure?


3 Réponses :


2
votes

Si je ne me trompe pas, vous l'utilisez mal. Devrait être:

service firebase.storage {
  match /b/{bucket}/o {
    match /{environment}/{client_id}/{allPaths=**} {
      allow read: if request.auth.token.client_id == client_id
    }
  }
}

Le jeton renvoie d'autres objets, comme:

  • email
  • Email verifié
  • numéro de téléphone
  • Nom
  • sous

Donc, pour pouvoir comparer l'ID utilisateur, vous devez utiliser request.auth.uid . De cette façon, vous comparerez l'ID client du client. Si vous voulez jeter un oeil à la documentation , est-ce que tout request.auth le request.auth .

Éditer

Si vous voulez votre propre jeton personnalisé, comme: request.auth.token.client_id , vous devez le faire avec ce code en Python:

uid = 'some-uid'
additional_claims = {
    'client_id': your_custom_client_id
}

custom_token = auth.create_custom_token(uid, additional_claims)

Ensuite, vous pouvez utiliser dans vos règles de stockage:

service firebase.storage {
  match /b/{bucket}/o {
    match /{environment}/{client_id}/{allPaths=**} {
      allow read: if request.auth.uid == client_id
    }
  }
}

Voir la documentation


6 commentaires

Ça n'est pas correct. L'UID est un ID unique attribué par défaut par Firebase. Client_ID est une revendication personnalisée que j'ai attribuée à l'utilisateur avec un UID. Je ne veux pas définir UID = Client_ID, ce sont deux choses différentes.


Je vois ... Mais request.auth.token n'a pas de propriété client_id . Vous devez créer votre propre jeton personnalisé. Jetez un œil à la documentation . Vous pouvez voir comment faire cela. Si vous avez déjà fait que votre code est correct.


Ok .. mais il doit y avoir un moyen d'obtenir la revendication personnalisée dans le request.auth, ce que je recherche. Voir la partie: If the custom token contains additional claims, they can be referenced off of the auth.token (Firebase Realtime Database) or request.auth.token (Cloud Storage) object in your rules... et sélectionnez "Règles de stockage"


Et votre réponse est exactement ce que j'ai dans ma question, non? Où est la différence?


C'est vrai haha ... Mon mauvais je n'ai pas vu la partie custom, là-bas ....


Les documents sont terribles - ils n'abordent pas du tout comment s'authentifier par rapport à n'importe quel groupe. Uniquement par utilisateur individuel. La réponse semble être des fonctions côté serveur pour définir des revendications d'authentification personnalisées, mais le document n'entre jamais dans cela.



1
votes

Les revendications personnalisées sont le seul moyen de le faire pour le moment. Les règles devraient ressembler à ceci:

exports.updateUser = functions.firestore
  .document('users/{userId}')
  .onWrite( async (change, context) => {
    // change.before and change.after are DocumentSnapshot objects
    const userid=context.params.userId // (from {userId} above)
    const isDeleted = !change.after.exists
    const isNew = !change.before.exists
    let customClaims = {}
    if (!isDeleted) {
      let newData = change.after.data()
      let oldData = change.before.data()
      // do we need to update anything?
      if (isNew ||
          newData.admin !== oldData.admin ||
          newData.client !== oldData.client) {
        customClaims.admin = Boolean(newData.admin)
        customClaims.clientId = newData.client
      }
    }
    else {
      let oldData = change.before.data()
      customClaims.admin = false
      customClaims.clientId = null
    }
    // now do the update if needed
    if (customClaims !== {}) {
      // See https://firebase.google.com/docs/reference/admin/node/admin.auth.Auth
      await admin.auth().setCustomUserClaims(userid, customClaims)
      console.log(`Updating client for ${isNew?"new":"existing"} user ${userid}: ` +
                  `${JSON.stringify(customClaims)}`)
    }
  })

où nous utilisons deux champs personnalisés sur le jeton d'authentification: admin et clientId . La fonction cloud à synchroniser avec la base de données peut ressembler à ceci:

service firebase.storage {
  match /b/{bucket}/o {
    function isAuth() {
      return request.auth != null && request.auth.uid != null
    }
    function isAdmin() {
      return isAuth() &&
      request.auth.token.admin == true;
    }
    function clientMatch(clientId) { // expects user's "client" field to be ID of client
      return isAuth() &&
      clientId == request.auth.token.clientId;
    }
    match /storage/path/{clientId}/{allPaths=**} {
        allow read, write: if isAdmin() || clientMatch(clientId)
    }

Cela s'exécute sur toute modification du document utilisateur et le synchronise avec les revendications personnalisées de l'authentification.


0 commentaires

0
votes

Par souci d'exhaustivité, il faut créer un jeton avant de définir le jeton pour l'utilisateur.

Ainsi code complet:

uid = 'some-uid'
additional_claims = {
    'client_id': your_custom_client_id
}

# First time, only once needed
custom_token = auth.create_custom_token(uid, additional_claims)

# Then
auth.set_custom_user_claims(uid, additional_claims )


0 commentaires