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.
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.
Voici quelques exemples de mise en œuvre des quatre premiers modèles.
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 }
Ici, "une requête qui spécifie review_id et renvoie le ReviewType correspondant" est implémentée.
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:
Maintenant, ajoutons la restriction que «seul l'utilisateur connecté peut exécuter» au processus implémenté précédemment.
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.
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.
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.
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é?.
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 ...)
Ici, "Mutation qui met à jour le titre et le corps de la revue correspondante en spécifiant review_id" est implémentée.
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.
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é.
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.
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.
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.
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".
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.
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.
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