[RAILS] Comment autoriser à l'aide de graphql-ruby

Lors de l'utilisation de GraphQL, vous souhaiterez peut-être l'autoriser dans divers processus.

Au début, j'avais peu de connaissances sur graphql-ruby, j'ai donc appelé le processus à autoriser dans le processus d'acquisition et de mise à jour, mais quand j'ai relu le document graphql-ruby, la méthode d'autorisation (autorisée?) J'ai découvert qu'il y avait quelque chose, j'ai donc écrit un article pour vérifier l'opération.

À propos de graphql-ruby

C'est une gemme qui rend GraphQL facile à utiliser dans Ruby (Rails). https://github.com/rmosolgo/graphql-ruby

Souvent, vous ne connaîtrez pas les détails tant que vous ne les aurez pas essayés, mais la documentation est excellente. https://graphql-ruby.org/guides

Au moment d'écrire cet article, j'utilise graphql: 1.11.1. Veuillez noter que le fonctionnement de Gem est toujours mis à jour, donc si la version est différente, l'opération peut avoir changé de manière significative.

Exemple d'implémentation d'autorisation

Voici quelques exemples de mise en œuvre des quatre premiers modèles.

Conditions préalables

On suppose que les informations d'utilisateur de connexion requises pour l'autorisation sont stockées dans le contexte. L'explication de l'authentification est omise car elle s'écarte du point principal de cet article.

app/controllers/graphql_controller.rb


#Les informations de connexion de l'utilisateur sont contextuelles[:current_user]Stocker dans
#Nil si non connecté
context = { current_user: current_user }

Je souhaite que cette requête soit exécutée uniquement par l'utilisateur connecté

Ici, "une requête qui spécifie review_id et renvoie le ReviewType correspondant" est implémentée.

Avant d'obtenir l'approbation

Implémentez une requête qui obtient un ReviewType avant d'implémenter l'autorisation.

app/graphql/types/query_type.rb


module Types
  class QueryType < Types::BaseObject
    field :review, resolver: Resolvers::ReviewResolver
  end
end

app/graphql/resolvers/review_resolver.rb


module Resolvers
  class ReviewResolver < BaseResolver
    type Types::ReviewType, null: true

    argument :review_id, Int, required: true

    def resolve(review_id:)
      Review.find_by(id: review_id)
    end
  end
end

app/graphql/types/review_type.rb


module Types
  class ReviewType < BaseObject
    field :id, ID, null: false
    field :title, String, null: true
    field :body, String, null: true
    field :secret, String, null: true
    field :user, Types::UserType, null: false
  end
end

app/graphql/types/user_type.rb


module Types
  class UserType < BaseObject
    field :id, ID, null: false
    field :name, String, null: false
    field :email, String, null: false
  end
end

Lorsqu'il est exécuté dans GraphiQL, cela ressemble à ceci: スクリーンショット 2020-07-24 13.51.33.png

Mettre en œuvre l'autorisation

Maintenant, ajoutons la restriction que «seul l'utilisateur connecté peut exécuter» au processus implémenté précédemment.

Mise en œuvre sans autorisation?

Auparavant, j'avais une implémentation qui vérifie la connexion avant d'obtenir un examen avec la méthode de résolution.

Tout d'abord, implémentez une méthode de vérification de connexion dans BaseResolver afin qu'elle puisse être utilisée à partir de différents résolveurs. Si le contexte [: utilisateur_actuel] n'est pas inclus, une erreur se produira. En passant, si vous utilisez GraphQL :: ExecutionError, la réponse sera convertie au format d'erreur GraphQL simplement en augmentant.

app/graphql/resolvers/base_resolver.rb


 module Resolvers
   class BaseResolver < GraphQL::Schema::Resolver
     def login_required!
       #Relancez si vous n'êtes pas connecté
       raise GraphQL::ExecutionError, 'login required!!' unless context[:current_user]
     end
   end
 end

Appelez ensuite la vérification de connexion BaseResolver au début du processus.

app/graphql/resolvers/review_resolver.rb


 def resolve(review_id:)
+  #Effectuer une vérification de connexion au début du processus
+  login_required!

   Review.find_by(id: review_id)
 end

Si vous l'exécutez avec GraphiQL sans vous connecter, ce sera comme suit. スクリーンショット 2020-07-25 12.53.22.png

J'ai réalisé ce que je veux faire avec cette méthode, mais les résolveurs qui nécessitent une connexion doivent toujours écrire login_required! Au début du processus. J'ai cherché un moyen d'autoriser automatiquement ce processus avant qu'il ne soit appelé, comme before_action dans controller.

Mise en œuvre en utilisant autorisé?

Quand j'ai relu le guide de graphql-ruby, j'ai remarqué qu'il existe une méthode appelée autorisée?. Il semble que vous puissiez l'utiliser pour effectuer une autorisation avant la méthode de résolution et contrôler si elle peut être exécutée ou non. Vous trouverez ci-dessous un guide à ajouter à la mutation, mais vous pouvez également l'ajouter à Resolver. https://graphql-ruby.org/mutations/mutation_authorization.html

Puisque le résolveur qui nécessite une connexion semble être utilisable à des fins générales, j'ai créé login_required_resolver dont le résolveur qui nécessite une connexion hérite. Les paramètres autorisés (args) contiennent les mêmes paramètres que la résolution.

app/graphql/resolvers/login_required_resolver.rb


module Resolvers
  class LoginRequiredResolver < BaseResolver
    def authorized?(args)
      context[:current_user].present?
    end
  end
end

Modifiez review_resolver pour hériter de login_required_resolver. Les autres implémentations sont les mêmes qu'avant l'ajout de l'autorisation.

app/graphql/resolvers/review_resolver.rb


- class ReviewResolver < BaseResolver
+ class ReviewResolver < LoginRequiredResolver

Si vous l'exécutez avec GraphiQL sans vous connecter, ce sera comme suit. スクリーンショット 2020-07-25 13.01.45.png

Si le résultat de autorisé? Est faux, il n'y a aucune information d'erreur et seul data: null est renvoyé. Comme décrit dans le guide, il semble que le comportement par défaut est de ne renvoyer que «data: null» lorsqu'il est autorisé? S'il n'y a pas de problème avec la spécification de retour de null, vous pouvez le laisser tel quel, mais s'il n'est pas autorisé, essayez de le changer pour que les informations d'erreur soient également renvoyées.

L'ajout d'informations d'erreur est facile et peut être effectué en levant GraphQL :: ExecutionError dans autorisé?. En passant, si vous réussissez, vous devez être prudent car cela ne sera pas reconnu comme un succès à moins que vous ne retourniez explicitement true.

app/graphql/resolvers/login_required_resolver.rb


module Resolvers
  class LoginRequiredResolver < BaseResolver
    def authorized?(args)
      #GraphQL si non autorisé::Lever ExecutionError
      raise GraphQL::ExecutionError, 'login required!!' unless context[:current_user]

      true
    end
  end
end

Si vous l'exécutez avec GraphiQL sans vous connecter, ce sera comme suit. Vous pouvez maintenant renvoyer les informations d'erreur même si vous utilisez autorisé?. スクリーンショット 2020-07-25 13.25.54.png

Si vous utilisez autorisé?, Vous pouvez l'écrire simplement parce que vous n'avez pas besoin d'écrire le processus d'autorisation dans la méthode de résolution. (Cet exemple est une implémentation assez simple, il n'y a donc pas beaucoup de différence ...)

Je souhaite que cette mutation soit exécutée uniquement par l'administrateur

Ici, "Mutation qui met à jour le titre et le corps de la revue correspondante en spécifiant review_id" est implémentée.

Avant d'entrer l'autorisation

Implémentez Mutation pour mettre à jour la révision avant de mettre en œuvre l'autorisation. Les classes qui sont utilisées telles quelles, telles que ReviewType utilisé dans l'exemple précédent, sont omises.

app/graphql/types/mutation_type.rb


module Types
  class MutationType < Types::BaseObject
    field :update_review, mutation: Mutations::UpdateReview
  end
end

app/graphql/mutations/update_review.rb


module Mutations
  class UpdateReview < BaseMutation
    argument :review_id, Int, required: true
    argument :title, String, required: false
    argument :body, String, required: false

    type Types::ReviewType

    def resolve(review_id:, title: nil, body: nil)
      review = Review.find review_id
      review.title = title if title
      review.body = body if body
      review.save!

      review
    end
  end
end

Lorsqu'elles sont exécutées dans GraphiQL, les données de révision sont mises à jour comme suit. スクリーンショット 2020-07-27 11.34.17.png

Mettre en œuvre l'autorisation

Vous pouvez utiliser autorisé? In Mutation comme dans l'exemple précédent. Il est répertorié dans le guide ci-dessous. https://graphql-ruby.org/mutations/mutation_authorization.html

Créez une classe parente dont Mutation hérite, qui n'est disponible que pour les administrateurs, et héritez-en.

app/graphql/mutations/base_admin_mutation.rb


module Mutations
  class BaseAdminMutation < BaseMutation
    def authorized?(args)
      raise GraphQL::ExecutionError, 'login required!!' unless context[:current_user]
      raise GraphQL::ExecutionError, 'permission denied!!' unless context[:current_user].admin?

      super
    end
  end
end

app/graphql/mutations/update_review.rb


- class UpdateReview < BaseMutation
+ class UpdateReview < BaseAdminMutation

Si la mutation est autorisée? Ne renvoie que false, les informations d'erreur ne seront pas renvoyées, les données seront nulles et le traitement de mise à jour ne sera pas exécuté. Le résolveur a toujours l'air bien, mais Mutation ne comprend pas à moins qu'il ne renvoie des informations d'erreur, donc je l'ai implémenté pour élever GraphQL :: ExecutionError également. À propos, si vous lisez le guide, il semble y avoir un moyen de renvoyer des informations d'erreur en renvoyant des erreurs comme valeur de retour, comme indiqué ci-dessous. J'ai essayé, mais la méthode suivante n'a pas renvoyé les emplacements et les chemins sous erreurs, mais j'ai pu renvoyer les messages d'erreur. Si vous avez seulement besoin de renvoyer le message, vous pouvez l'implémenter par l'une ou l'autre des méthodes.

def authorized?(employee:)
  if context[:current_user]&.admin?
    true
  else
    return false, { errors: ["permission denied!!"] }
  end
end

Lorsqu'il est exécuté par un utilisateur qui n'a pas de privilèges d'administrateur dans GraphiQL, ce sera comme suit. Bien entendu, en cas d'erreur, le processus de mise à jour ne sera pas exécuté. スクリーンショット 2020-07-27 11.48.29.png

Je souhaite que cette requête ne soit renvoyée que lorsque les données que je possède

Ici, nous allons le modifier en fonction de la première "requête qui spécifie review_id et renvoie le ReviewType correspondant". La première chose que j'ai faite a été de vérifier uniquement le statut de connexion, mais cette fois, c'est Vérifier ma propriété? Ajoutez un chèque pour.

Essayez de l'implémenter dans la même autorisation? Comme la vérification de connexion

Ce serait bien si je pouvais ajouter une vérification à la même autorisation? Comme la vérification de connexion, mais cette vérification ne peut être effectuée qu'après avoir obtenu Revew. Même avec autorisé?, Review_id est reçu comme argument, il est donc possible d'obtenir un examen, mais cela masque le rôle de la résolution. Je vais effectivement le mettre en œuvre.

app/graphql/resolvers/login_required_resolver.rb


 def authorized?(args)
   raise GraphQL::ExecutionError, 'login required!!' if context[:current_user].blank?

+   #Vous devez obtenir un avis à ce stade
+   review = Review.find_by(id: args[:review_id])
+   return false unless review
+   raise GraphQL::ExecutionError, 'permission denied!!' if context[:current_user].id != review.user_id

   true
 end

Vous devrez obtenir un examen avec autorisé? Il est également acquis par la méthode de résolution, il semble donc inefficace de l'acquérir ici également. Alors, qu'en est-il de la mise en œuvre d'un contrôle du côté de la résolution?

app/graphql/resolvers/review_resolver.rb


 def resolve(review_id:)
-   Review.find_by(id: review_id)
+   review = Review.find_by(id: review_id)
+   raise GraphQL::ExecutionError, 'permission denied!!' if context[:current_user].id != review.user_id

+   review
 end

Cela semble être plus efficace que de l'implémenter avec autorisé?, Mais en supprimant le processus de contrôle dans autorisé?, Le processus de contrôle a été ajouté pour résoudre, qui ne décrit que le processus d'acquisition de données.

Au début, je pensais que la seule chose qui ne pouvait être vérifiée qu'après l'acquisition de données était de vérifier avec la résolution, mais j'ai appris que autorisé? Peut également être défini dans ReviewType, donc je vais le définir dans ReviewType.

Vérifier avec le type d'avis

Que signifie vérifier avec ReviewType? Je vais effectivement le mettre en œuvre.

Je veux rendre ReviewType disponible à tout le monde, donc je vais créer un ReviewType appelé MyReviewType avec des restrictions que je suis le seul à pouvoir afficher.

app/graphql/types/my_review_type.rb


module Types
  class MyReviewType < ReviewType
    def self.authorized?(object, context)
      raise GraphQL::ExecutionError, 'permission denied!!' if context[:current_user].id != object.user_id

      true
    end
  end
end

Comme mentionné dans le guide, autorisé? Utilisé dans Type prend l'objet et le contexte comme arguments. De plus, puisqu'il s'agit d'une méthode de classe, vous devez être prudent. https://graphql-ruby.org/authorization/authorization.html

Tout ce que vous avez à faire est de définir le type de réponse sur MyReviewType. Aucune autre modification n'est requise.

app/graphql/resolvers/review_resolver.rb


- type Types::ReviewType, null: true
+ type Types::MyReviewType, null: true

Si vous spécifiez une révision autre que la vôtre dans GraphiQL, ce sera comme suit. スクリーンショット 2020-07-27 22.40.15.png

Maintenant que vous n'avez pas à écrire le processus d'autorisation dans la méthode de résolution, vous pouvez l'écrire simplement. De plus, en définissant la réponse sur MyReviewType, la simple lecture de la définition de schéma indiquera clairement que cette requête renvoie MyReviewType = "vous seul pouvez l'afficher".

Ce champ est renvoyé uniquement lorsque les propres données de l'utilisateur connecté

Dans l'exemple précédent, j'ai défini MyReviewType afin que je ne puisse voir que la réponse entière pour mes données. Cependant, vous souhaiterez peut-être masquer uniquement des champs spécifiques, pas tous.

Je republierai le type d'examen. Ici, je veux que la colonne secrète ne voie que mes données.

app/graphql/types/review_type.rb


module Types
  class ReviewType < BaseObject
    field :id, ID, null: false
    field :title, String, null: true
    field :body, String, null: true
    field :secret, String, null: true # <-Rendez cela visible uniquement pour vous
    field :user, Types::UserType, null: false
  end
end

Cela semble autorisé? Peut être implémenté sur le terrain également, mais il semble difficile de ne personnaliser qu'un seul champ, j'ai donc décidé de l'implémenter sans utiliser autorisé? Ici. https://graphql-ruby.org/authorization/authorization.html Cliquez ici pour un guide de terrain https://graphql-ruby.org/fields/introduction.html#field-parameter-default-values

Si vous définissez la même méthode que le nom de champ comme indiqué ci-dessous, cette méthode sera appelée. J'ai implémenté l'autorisation dans cette méthode.

app/graphql/types/review_type.rb


module Types
  class ReviewType < BaseObject
    field :id, ID, null: false
    field :title, String, null: true
    field :body, String, null: true
    field :secret, String, null: true
    field :user, Types::UserType, null: false

    #Appelé lors de la définition d'une méthode avec un nom de champ
    def secret
      #Si l'utilisateur connecté et l'utilisateur qui a rédigé l'avis sont différents, renvoyez nil
      return if object.user_id != context[:current_user].id

      object.secret
    end
  end
end

Si vous spécifiez une révision autre que la vôtre dans GraphiQL, ce sera comme suit. Le secret a été renvoyé nul. スクリーンショット 2020-07-28 22.51.28.png

Si vous implémentez cette vérification dans Resolver, tous les résolveurs qui utilisent ReviewType devront considérer le secret, mais en l'implémentant dans ReviewType, les résolveurs individuels n'auront pas à penser au contrôle d'accès du secret.

finalement

Je pensais avoir lu le guide avant de commencer à utiliser graphql-ruby, mais j'ai négligé l'existence d'autorisé? ... Il semble qu'il existe d'autres fonctions utiles autres qu'autorisées? De plus, même si elle n'existe pas maintenant, la version a été mise à jour, et il y a de fortes chances que de nouvelles fonctions soient ajoutées à l'avenir, je voudrais donc continuer à vérifier les tendances de graphql-ruby.

Recommended Posts

Comment autoriser à l'aide de graphql-ruby
Comment créer CloudStack à l'aide de Docker
Comment exécuter un contrat avec web3j
Comment trier une liste à l'aide du comparateur
[Rails] Comment télécharger des images à l'aide de Carrierwave
[Java] Comment calculer l'âge à l'aide de LocalDate
[Swift5] Comment mettre en œuvre une animation à l'aide de "lottie-ios"
Pour implémenter la publication d'images à l'aide de rails
Comment insérer des icônes à l'aide de Font awesome
Comment sortir Excel et PDF avec Excella
[Rails] Comment créer un graphique à l'aide de lazy_high_charts
Comment supprimer un contrôleur, etc. à l'aide d'une commande
Comment jouer de la voix ou de la musique en utilisant javascript
[Ethereum] Comment exécuter un contrat en utilisant web3j-Part 2-
Comment mettre en œuvre la fonction de chapelure avec Gretel
[Rails] Comment télécharger plusieurs images à l'aide de Carrierwave
Comment générer une clé primaire à l'aide de @GeneratedValue
Comment lier des images à l'aide de FactoryBot Active Storage
Comment développer OpenSPIFe
Comment appeler AmazonSQSAsync
Comment utiliser Map
Comment utiliser rbenv
Comment utiliser with_option
Comment utiliser fields_for
Comment utiliser java.util.logging
Comment utiliser collection_select
Comment utiliser Twitter4J
Comment utiliser active_hash! !!
Comment installer Docker
Comment utiliser MapStruct
Comment utiliser TreeSet
Comment désinstaller Rails
Comment installer docker-machine
[Comment utiliser l'étiquette]
Comment faire un pot ombré
Comment écrire docker-compose
Comment utiliser l'identité
Comment utiliser le hachage
Comment écrire Mockito
Comment créer docker-compose
Comment installer MySQL
Comment écrire un fichier de migration
Comment construire android-midi-lib
Comment utiliser Dozer.mapper
Comment utiliser Gradle
Comment utiliser org.immutables
Comment utiliser VisualVM
Comment utiliser Map
Comment repousser la barre oblique \
Comment concaténer des chaînes
Comment déterminer la quantité de disque utilisée par Docker
Comment faire un test unitaire avec JVM sur une source à l'aide de RxAndroid
Comment faire un générateur d'oléore en utilisant Swagger Codegen