Django-rest-framework: OPTIONS récupère et affiche toutes les clés étrangères possibles dans le champ de choix

Créé le 16 déc. 2015  ·  24Commentaires  ·  Source: encode/django-rest-framework

J'ai un objet avec des clés étrangères et lorsque je charge une requête OPTIONS pour ce type d'objet, cela me montre un objet JSON vraiment insensé avec toutes les valeurs possibles pour la clé étrangère (il y en a des millions!)

Donc, par exemple, j'ai cet objet:

class OpinionCluster(models.Model):
    """A class representing a cluster of court opinions."""
    docket = models.ForeignKey(
        Docket,
        help_text="The docket that the opinion cluster is a part of",
        related_name="clusters",
    )

Qui est sérialisé avec:

class OpinionClusterSerializer(serializers.HyperlinkedModelSerializer):
    docket = serializers.HyperlinkedRelatedField(
        many=False,
        view_name='docket-detail',
        queryset=Docket.objects.all(),
        style={'base_template': 'input.html'},
    )

Quand je charge ceci avec OPTIONS , je récupère quelque chose qui contient:

    "docket": {
        "type": "field",
         "required": true,
         "read_only": false,
         "label": "Docket",
         "choices": [
            {
                "display_name": "4: United States v. Goodwin",
                "value": "http://127.0.0.1:8000/api/rest/v3/dockets/4/"
            },
            {
                "display_name": "5: Quality Cleaning Products v. SCA Tissue of North America",
                "value": "http://127.0.0.1:8000/api/rest/v3/dockets/5/"
            },

....millions more....

Je sais qu'il existe un moyen de désactiver la liste de ces éléments sous la forme de la vue HTML, mais dans la demande OPTIONS, nous avons besoin d'une meilleure fonctionnalité par défaut que d'afficher des millions d'enregistrements.

Bug

Commentaire le plus utile

Maintenant que cela a été corrigé, y a-t-il un moyen d'obtenir les choix si je veux ce comportement (listant tous les choix)? Ou y a-t-il peut-être un moyen d'indiquer à partir de OPTIONS où l'URI listant les choix pourrait être trouvé?

Pour mes retours OPTIONS API:

    "actions": {
        "POST": {
            "date_created": {
                "type": "datetime",
                "required": false,
                "read_only": true,
                "label": "Date created"
            },
            "owner": {
                "type": "field",
                "required": false,
                "read_only": true,
                "label": "Owner"
            },
            "language": {
                "type": "field",
                "required": true,
                "read_only": false,
                "label": "Language"
            },
        }
    }
}

Et je voudrais un moyen pour l'API frontend de savoir où chercher les URI avec lesquels elle peut remplir language

Tous les 24 commentaires

J'ai tenté de résoudre ce problème en écrasant la classe de métadonnées comme mentionné ci-dessus, mais cela ne fonctionne pas car tout ce que vous pouvez faire à ce stade est de supprimer les données qui ont déjà été extraites de la base de données. Signification: Si vous faites cela, vous récupérez toujours des millions d'objets de la base de données, vous ne transmettez tout simplement pas ces valeurs au front-end.

Le correctif pour cela, sauf erreur de ma part, est l'un des suivants:

  • Supprimer la requête OPTIONS de l'API (triste, mais cela fonctionnerait)
  • Rendre les points de terminaison en lecture seule (triste, casse l'API)
  • Une sorte de correctif dans DRF

Merci pour toute aide ou autres idées.

Jalon pour élever la priorité.

Convenu. C'est un comportement étrange, traiter les champs FK comme un champ de choix. Fait d'OPTIONS un piège à ours.

Je pense que le correctif consiste à modifier le conditionnel à https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/metadata.py#L140 pour ne pas faire le choix sur les champs FK, puis éventuellement faire autre chose pour eux.

+1

(Il est préférable de le soumettre dans un numéro distinct. Faites-moi savoir si je devrais.)

Notez également que si l'un de ces champs de choix est un settings.AUTH_USER_MODEL, par exemple un champ appartenant à, il y met les données de compte réelles. Un extrait censuré du JSON renvoyé:

"owned_by":{
  "type":"field",
  "required":true,
  "read_only":false,
  "label":"Owned by",
  "choices":[
        {"display_name":"engineering+admin@<us>.com","value":"18"}, 
        {"display_name":"engineering+notanadmin@<us>.com","value":"19"},...

À mon humble avis, la valeur par défaut ne devrait pas faire cela compte tenu du risque de sécurité.

Existe-t-il des solutions connues pour résoudre ce problème? J'ai le problème de sécurité @fleur décrit avec une clé étrangère à AUTH_USER_MODEL.

J'ai fait ces champs en lecture seule. Fonctionne pour notre implémentation, mais craint autrement. Maintenant, il semble y avoir deux raisons de résoudre ce problème:

  1. Fuite d'informations.
  2. Performance.

Afin de contourner cette limitation, vous devrez implémenter votre propre sous-classe SimpleMetadata :

from rest_framework.metadata import SimpleMetadata
from rest_framework.relations import RelatedField

class NoRelatedFieldChoicesMetadata(SimpleMetadata):
    def get_field_info(self, field):
        related_field = isinstance(field, RelatedField)
        # Remove the related fields choices since they can end up with many values.
        if related_field:
            field.queryset = field.queryset.none()
        field_info = super(NoRelatedFieldChoicesMetaData, self).get_field_info(field)
        # Remove the empty `choices` list.
        if related_field:
            field_info.pop('choices')
        return field_info

Vous pouvez le définir par sérialiseur en utilisant l'attribut metadata_class ou par projet en utilisant le paramètre REST_FRAMEWORK['DEFAULT_METADATA_CLASS'] .

@charettes , cependant,

J'ai tenté de résoudre ce problème en écrasant la classe de métadonnées comme mentionné ci-dessus, mais cela ne fonctionne pas car tout ce que vous pouvez faire à ce stade est de supprimer les données qui ont déjà été extraites de la base de données. Signification: Si vous faites cela, vous récupérez toujours des millions d'objets de la base de données, vous ne transmettez tout simplement pas ces valeurs au front-end.

Il y a certainement un problème de performances ici lorsqu'un champ a un grand nombre d'options possibles. Honnêtement, je n'ai aucune idée de la meilleure façon de résoudre ce problème sans casser la compatibilité ascendante.

...fuite d'informations...

C'est quelque chose que j'entends chaque fois que cela se produit: que DRF divulgue des informations qui ne devraient pas l'être. Et je suppose que c'est vrai, vous n'avez pas l'intention que cette information soit publique. Je veux dire, pourquoi quelqu'un devrait-il être en mesure d'obtenir une liste des utilisateurs qui sont des valeurs possibles pour le champ?

Mais c'est généralement le signe que vous ne limitez pas vos ensembles de requêtes aux seuls objets qui devraient être autorisés. Tous les objets que DRF répertorie dans la requête OPTIONS sont des entrées valides dans le champ, donc s'il y a des objets qui apparaissent dans la liste, vous ne les filtrez probablement pas pour le champ.

@mlissner note que j'utilise queryset.none() pour éviter tout jeu de requête d'être exécuté.

Je veux dire, pourquoi quelqu'un devrait-il être en mesure d'obtenir une liste des utilisateurs qui sont des valeurs possibles pour le champ?

Il s'agit d'une demande courante pour avoir toutes les valeurs associées dans l'option permettant aux applications frontales de remplir les champs de choix.
Notez qu'il n'affiche pas les champs en lecture seule.

@charettes Cela semble en fait prometteur. Vous avez raison, cela peut être une solution de contournement pour l'aspect performances de ce problème.

Salut,
Je suis également affecté par ce bug.
Pendant ce temps, le correctif arrive à PIP, j'ai remplacé la classe SimpleMetadata avec le correctif de @charettes dans a6732e2. Il résout le problème pour les champs associés, mais il manque également pour vérifier les sérialiseurs.

+1

Corrigé via # 4021.

Salut @tomchristie , j'ai vérifié à nouveau # 4021 et il manque toujours le cas avec ManyRelatedField.
Dans rest_framework / metadata.py: 140
if (not field_info.get('read_only') and not isinstance(field, serializers.RelatedField) and hasattr(field, 'choices')):
devrait être
if (not field_info.get('read_only') and not isinstance(field, serializers.RelatedField) and not isinstance(field, serializers.ManyRelatedField) and hasattr(field, 'choices')):

@callmewind , vous avez raison, j'ai supposé que ManyRelatedField était une sous-classe de RelatedField . @tomchristie Je peux soumettre un autre PR si vous n'avez pas le temps de m'en occuper.

Merci pour ça! :)

Beaucoup à plusieurs maintenant également résolus.

Quand pouvons-nous obtenir une version pour cela? Il s'agit d'un problème de sécurité de l'information assez grave.

@unklphil Notez que les utilisateurs ne peuvent voir ces informations que s'ils sont également autorisés à définir ces champs en premier lieu.

La version 3.4.0 sortira dans les prochaines semaines.

Si vous souhaitez désactiver ce comportement avant cet ensemble, REST_FRAMEWORK['DEFAULT_METADATA_CLASS'] = None , ou utilisez une classe de métadonnées personnalisée qui implémente le comportement selon les demandes d'extraction associées.

Maintenant que cela a été corrigé, y a-t-il un moyen d'obtenir les choix si je veux ce comportement (listant tous les choix)? Ou y a-t-il peut-être un moyen d'indiquer à partir de OPTIONS où l'URI listant les choix pourrait être trouvé?

Pour mes retours OPTIONS API:

    "actions": {
        "POST": {
            "date_created": {
                "type": "datetime",
                "required": false,
                "read_only": true,
                "label": "Date created"
            },
            "owner": {
                "type": "field",
                "required": false,
                "read_only": true,
                "label": "Owner"
            },
            "language": {
                "type": "field",
                "required": true,
                "read_only": false,
                "label": "Language"
            },
        }
    }
}

Et je voudrais un moyen pour l'API frontend de savoir où chercher les URI avec lesquels elle peut remplir language

Voici ce que j'essaierais:

Dérivez une classe de SimpleMetadata . Annulez le commit https://github.com/tomchristie/django-rest-framework/commit/05b0c2adff35e136ca2022156534c87f8394f34a dans votre implémentation. Il devrait également vous donner un indice sur la façon de renvoyer un URI. Et n'oubliez pas de modifier la classe de métadonnées dans vos paramètres .

`` python
à partir de rest_framework.metadata import SimpleMetadata
à partir de rest_framework.serializers import ManyRelatedField

classe CustomMetadata (SimpleMetadata):

def get_field_info(self, field):
    field_info = super().get_field_info(field)

    if (not field_info.get('read_only') and
        isinstance(field, ManyRelatedField) and
             hasattr(field, 'choices')):
        field_info['choices'] = [
            {
                'value': choice_value,
                'display_name': force_text(choice_name, strings_only=True)
            }
            for choice_value, choice_name in field.choices.items()
        ]

    return field_info
Cette page vous a été utile?
0 / 5 - 0 notes