Création d’applications Ruby on Rails à l’aide de Microsoft Graph
Ce didacticiel vous apprend à créer une application web Ruby on Rails qui utilise l’API Microsoft Graph pour récupérer les informations de calendrier d’un utilisateur.
Conseil
Si vous préférez simplement télécharger le didacticiel terminé, vous pouvez le télécharger de deux manières.
- Téléchargez le démarrage rapide de Ruby pour obtenir du code de travail en quelques minutes.
- Téléchargez ou clonez le GitHub de données.
Conditions préalables
Avant de commencer ce didacticiel, vous devez avoir installé les outils suivants sur votre ordinateur de développement.
Vous devez également avoir un compte Microsoft personnel avec une boîte aux lettres sur Outlook.com, ou un compte scolaire ou scolaire Microsoft. Si vous n’avez pas de compte Microsoft, deux options s’offrent à vous pour obtenir un compte gratuit :
- Vous pouvez vous inscrire à un nouveau compte Microsoft personnel.
- Vous pouvez vous inscrire au programme Microsoft 365 développeur pour obtenir un abonnement Microsoft 365 gratuit.
Notes
Ce didacticiel a été écrit avec les versions suivantes des outils requis. Les étapes de ce guide peuvent fonctionner avec d’autres versions, mais elles n’ont pas été testées.
- Ruby version 3.0.1
- SQLite3 version 3.35.5
- Node.js version 14.15.0
- La version 1.22.0
Commentaires
N’hésitez pas à nous faire part de vos commentaires sur ce didacticiel dans GitHub référentiel.
Créer une application web Ruby on Rails
Dans cet exercice, vous allez utiliser Ruby on Rails pour créer une application web.
Si Rails n’est pas déjà installé, vous pouvez l’installer à partir de votre interface de ligne de commande (CLI) à l’aide de la commande suivante.
gem install rails -v 6.1.3.1
Ouvrez votre CLI, accédez à un répertoire dans lequel vous avez le droit de créer des fichiers et exécutez la commande suivante pour créer une application Rails.
rails new graph-tutorial
Accédez à ce nouveau répertoire et entrez la commande suivante pour démarrer un serveur web local.
rails server
Ouvrez votre navigateur et accédez à
http://localhost:3000
. Si tout fonctionne, vous verrez un « Yay! Vous êtes sur Rails ! » Message. Si vous ne voyez pas ce message, consultez le guide de mise en route de Rails.
Installer des gems
Avant de passer à autre chose, installez d’autres gems que vous utiliserez ultérieurement :
- omniauth-oauth2 pour la gestion des flux de jeton OAuth et de la signature.
- omniauth-rails_csrf_protection pour ajouter la protection CSRF à OmniAuth.
- httparty pour effectuer des appels à Microsoft Graph.
- activerecord-session_store pour le stockage des sessions dans la base de données.
Ouvrez ./Gemfile et ajoutez les lignes suivantes.
# OAuth gem 'omniauth-oauth2', '~> 1.7.1' # OmniAuth CSRF protection gem 'omniauth-rails_csrf_protection', '~> 1.0.0' # REST calls to Microsoft Graph gem 'httparty', '~> 0.18.1' # Session storage in database gem 'activerecord-session_store', '~> 2.0.0'
Dans votre CLI, exécutez la commande suivante.
bundle install
Dans votre CLI, exécutez les commandes suivantes pour configurer la base de données pour le stockage des sessions.
rails generate active_record:session_migration rake db:migrate
Créez un fichier appelé dans le répertoire
session_store.rb
./config/initializers et ajoutez le code suivant.Rails.application.config.session_store :active_record_store, :key => '_graph_app_session'
Concevoir l’application
Dans cette section, vous allez créer l’interface utilisateur de base de l’application.
Ouvrez ./app/views/layouts/application.html.erb et remplacez son contenu par ce qui suit.
<!DOCTYPE html> <html> <head> <title>Ruby Graph Tutorial</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <link rel="shortcut icon" href="favicon.png"/> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous"> <link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/11.0.0/css/fabric.min.css"/> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> </head> <body> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <div class="container"> <%= link_to "Ruby Graph Tutorial", root_path, class: "navbar-brand" %> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarCollapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <%= link_to "Home", root_path, class: "nav-link#{' active' if controller.controller_name == 'home'}" %> </li> <% if @user_name %> <li class="nav-item" data-turbolinks="false"> <%= link_to "Calendar", "/calendar", class: "nav-link#{' active' if controller.controller_name == 'calendar'}" %> </li> <% end %> </ul> <ul class="navbar-nav justify-content-end"> <li class="nav-item"> <a class="nav-link external-link" href="https://developer.microsoft.com/graph/docs/concepts/overview" target="_blank"> <i class="ms-Icon ms-Icon--NavigateExternalInline mr-1"></i>Docs </a> </li> <% if @user_name %> <li class="nav-item dropdown"> <a class="nav-link avatar-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"> <% if @user_avatar %> <img src=<%= @user_avatar %> class="rounded-circle align-self-center mr-2 profile-photo"> <% else %> <%= image_tag "no-profile-photo.png", class: "rounded-circle align-self-center mr-2 profile-photo" %> <% end %> </a> <div class="dropdown-menu dropdown-menu-right"> <h5 class="dropdown-item-text mb-0"><%= @user_name %></h5> <p class="dropdown-item-text text-muted mb-0"><%= @user_email %></p> <div class="dropdown-divider"></div> <%= link_to "Sign Out", {:controller => :auth, :action => :signout}, :class => "dropdown-item" %> </div> </li> <% else %> <li class="nav-item"> <%= link_to "Sign In", "/auth/microsoft_graph_auth", method: :post, class: "nav-link" %> </li> <% end %> </ul> </div> </div> </nav> <main role="main" class="container"> <% if @errors %> <% @errors.each do |error| %> <div class="alert alert-danger" role="alert"> <p class="mb-3"><%= error[:message] %></p> <%if error[:debug] %> <pre class="alert-pre border bg-light p-2"><code><%= error[:debug] %></code></pre> <% end %> </div> <% end %> <% end %> <%= yield %> </main> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </body> </html>
Ce code ajoute Bootstrap pour un style simple et Fabric Core pour certaines icônes simples. Il définit également une disposition globale avec une barre de navigation.
Ouvrez ./app/assets/stylesheets/application.css et ajoutez ce qui suit à la fin du fichier.
body { padding-top: 4.5rem; } .alert-pre { word-wrap: break-word; word-break: break-all; white-space: pre-wrap; } .external-link { padding-top: 6px; } .avatar-link { padding-top: 4px; padding-bottom: 4px; } .profile-photo { width: 32px; }
Génére un contrôleur de page d’accueil avec la commande suivante.
rails generate controller Home index
Configurez
index
l’action sur leHome
contrôleur comme page par défaut de l’application. Ouvrez ./config/routes.rb et remplacez son contenu par ce qui suitRails.application.routes.draw do get 'home/index' root 'home#index' # Add future routes here end
Ouvrez ./app/view/home/index.html.erb et remplacez son contenu par ce qui suit.
<div class="jumbotron"> <h1>Ruby Graph Tutorial</h1> <p class="lead">This sample app shows how to use the Microsoft Graph API to access a user's data from Ruby</p> <% if @user_name %> <h4>Welcome <%= @user_name %>!</h4> <p>Use the navigation bar at the top of the page to get started.</p> <% else %> <%= link_to "Click here to sign in", "/auth/microsoft_graph_auth", method: :post, class: "btn btn-primary btn-large" %> <% end %> </div>
Ajoutez un fichier PNG nommé no-profile-photo.png dans le répertoire ./app/assets/images.
Enregistrez toutes vos modifications et redémarrez le serveur. L’application doit maintenant avoir une apparence très différente.
Inscrire l’application sur le portail
Dans cet exercice, vous allez créer une inscription d’application web Azure AD à l’aide Azure Active Directory’administration.
Ouvrez un navigateur et accédez au Centre d’administration Azure Active Directory. Connectez-vous à l’aide d’un compte personnel (compte Microsoft) ou d’un compte professionnel ou scolaire.
Sélectionnez Azure Active Directory dans le volet de navigation gauche, puis sélectionnez Inscriptions d’applications sous Gérer.
Sélectionnez Nouvelle inscription. Sur la page Inscrire une application, définissez les valeurs comme suit.
- Définissez le Nom sur
Ruby Graph Tutorial
. - Définissez les Types de comptes pris en charge sur Comptes dans un annuaire organisationnel et comptes personnels Microsoft.
- Sous URI de redirection, définissez la première flèche déroulante sur
Web
, et la valeur surhttp://localhost:3000/auth/microsoft_graph_auth/callback
.
- Définissez le Nom sur
Sélectionner Inscription. Dans la page Du didacticiel Ruby Graph, copiez la valeur de l’ID de l’application (client) et enregistrez-la, vous en aurez besoin à l’étape suivante.
Sélectionnez Certificats et secrets sous Gérer. Sélectionnez le bouton Nouveau secret client. Entrez une valeur dans Description, sélectionnez une des options pour Expire le, puis sélectionnez Ajouter.
Copiez la valeur due la clé secrète client avant de quitter cette page. Vous en aurez besoin à l’étape suivante.
Important
Ce secret client n’apparaîtra plus jamais, aussi veillez à le copier maintenant.
Ajouter une authentification Azure AD
Dans cet exercice, vous allez étendre l’application de l’exercice précédent pour prendre en charge l’authentification avec Azure AD. Cette étape est nécessaire pour obtenir le jeton d’accès OAuth nécessaire pour appeler l’Graph Microsoft. Dans cette étape, vous allez intégrer le gem omniauth-oauth2 dans l’application et créer une stratégie OmniAuth personnalisée.
Créez un fichier distinct pour contenir votre ID d’application et votre secret. Créez un fichier appelé
oauth_environment_variables.rb
dans le dossier ./config et ajoutez le code suivant.ENV['AZURE_APP_ID'] = 'YOUR_APP_ID_HERE' ENV['AZURE_APP_SECRET'] = 'YOUR_APP_SECRET_HERE' ENV['AZURE_SCOPES'] = 'openid profile email offline_access user.read mailboxsettings.read calendars.readwrite'
Remplacez
YOUR_APP_ID_HERE
par l’ID d’application à partir du portail d’inscription des applications et remplacez-le par leYOUR_APP_SECRET_HERE
mot de passe que vous avez généré.Important
Si vous utilisez un contrôle source tel que Git, il est temps d’exclure le fichier du contrôle source afin d’éviter toute fuite accidentelle de votre ID d’application et de votre mot de
oauth_environment_variables.rb
passe.Ouvrez ./config/environment.rb et ajoutez le code suivant avant la
Rails.application.initialize!
ligne.# Load the Rails application. require_relative "application" # Load OAuth settings oauth_environment_variables = File.join(Rails.root, 'config', 'oauth_environment_variables.rb') load(oauth_environment_variables) if File.exist?(oauth_environment_variables) # Initialize the Rails application. Rails.application.initialize!
Configurer OmniAuth
Vous avez déjà installé le gem, mais pour qu’il fonctionne avec les points de terminaison OAuth Azure, vous devez créer une stratégie omniauth-oauth2
OAuth2. Il s’agit d’une classe Ruby qui définit les paramètres pour effectuer des demandes OAuth au fournisseur Azure.
Créez un fichier appelé
microsoft_graph_auth.rb
dans le dossier ./lib'** et ajoutez le code suivant.require 'omniauth-oauth2' module OmniAuth module Strategies # Implements an OmniAuth strategy to get a Microsoft Graph # compatible token from Azure AD class MicrosoftGraphAuth < OmniAuth::Strategies::OAuth2 option :name, :microsoft_graph_auth DEFAULT_SCOPE = 'openid email profile User.Read'.freeze # Configure the Microsoft identity platform endpoints option :client_options, :site => 'https://login.microsoftonline.com', :authorize_url => '/common/oauth2/v2.0/authorize', :token_url => '/common/oauth2/v2.0/token' # Send the scope parameter during authorize option :authorize_options, [:scope] # Unique ID for the user is the id field uid { raw_info['id'] } # Get additional information after token is retrieved extra do { 'raw_info' => raw_info } end def raw_info # Get user profile information from the /me endpoint @raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me?$select=displayName,mail,mailboxSettings,userPrincipalName').parsed end def authorize_params super.tap do |params| params[:scope] = request.params['scope'] if request.params['scope'] params[:scope] ||= DEFAULT_SCOPE end end # Override callback URL # OmniAuth by default passes the entire URL of the callback, including # query parameters. Azure fails validation because that doesn't match the # registered callback. def callback_url options[:redirect_uri] || (full_host + script_name + callback_path) end end end end
Prenez le temps de passer en revue ce que fait ce code.
- Il définit la
client_options
spécification des points Plateforme d’identités Microsoft de terminaison. - Il spécifie que le paramètre
scope
doit être envoyé pendant la phase d’autorisation. - Il mase
id
la propriété de l’utilisateur en tant qu’ID unique pour l’utilisateur. - Il utilise le jeton d’accès pour récupérer le profil de l’utilisateur auprès de Microsoft Graph pour remplir le
raw_info
hachage. - Il remplace l’URL de rappel pour s’assurer qu’elle correspond au rappel inscrit dans le portail d’inscription de l’application.
- Il définit la
Créez un fichier appelé
omniauth_graph.rb
dans le dossier ./config/initializers et ajoutez le code suivant.require 'microsoft_graph_auth' Rails.application.config.middleware.use OmniAuth::Builder do provider :microsoft_graph_auth, ENV['AZURE_APP_ID'], ENV['AZURE_APP_SECRET'], :scope => ENV['AZURE_SCOPES'] end
Ce code s’exécute au démarrage de l’application. Il charge l’intermédiaire OmniAuth avec le fournisseur, configuré avec les variables d’environnement définies dans
microsoft_graph_auth
oauth_environment_variables.rb.
Implémentation de la connexion
Maintenant que l’middleware OmniAuth est configuré, vous pouvez passer à l’ajout de la sign-in à l’application.
Exécutez la commande suivante dans votre CLI pour générer un contrôleur pour la sign-in et la sign-out.
rails generate controller Auth
Ouvrez ./app/controllers/auth_controller.rb. Ajoutez une méthode de rappel à la
AuthController
classe. Cette méthode est appelée par l’intermédiaire OmniAuth une fois le flux OAuth terminé.def callback # Access the authentication hash for omniauth data = request.env['omniauth.auth'] # Temporary for testing! render json: data.to_json end
Pour l’instant, il s’agit du rendu du hachage fourni par OmniAuth. Vous l’utiliserez pour vérifier que la connectez-vous fonctionne avant de passer à autre chose.
Ajoutez les itinéraires à ./config/routes.rb.
# Add route for OmniAuth callback match '/auth/:provider/callback', :to => 'auth#callback', :via => [:get, :post]
Démarrez le serveur et accédez à
https://localhost:3000
. Cliquez sur le bouton de connexion. vous serez redirigé vershttps://login.microsoftonline.com
. Connectez-vous avec votre compte Microsoft et consentez aux autorisations demandées. Le navigateur redirige vers l’application, affichant le hachage généré par OmniAuth.{ "provider": "microsoft_graph_auth", "uid": "eb52b3b2-c4ac-4b4f-bacd-d5f7ece55df0", "info": { "name": null }, "credentials": { "token": "eyJ0eXAi...", "refresh_token": "OAQABAAA...", "expires_at": 1529517383, "expires": true }, "extra": { "raw_info": { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(displayName,mail,mailboxSettings,userPrincipalName)/$entity", "displayName": "Lynne Robbins", "mail": "LynneR@contoso.OnMicrosoft.com", "userPrincipalName": "LynneR@contoso.OnMicrosoft.com", "id": "d294e784-840e-4f9f-bb1e-95c0a75f2f18@2d18179c-4386-4cbd-8891-7fd867c4f62e", "mailboxSettings": { "archiveFolder": "AAMkAGI2...", "timeZone": "Pacific Standard Time", "delegateMeetingMessageDeliveryOptions": "sendToDelegateOnly", "dateFormat": "M/d/yyyy", "timeFormat": "h:mm tt", "automaticRepliesSetting": { "status": "disabled", "externalAudience": "all", "internalReplyMessage": "", "externalReplyMessage": "", "scheduledStartDateTime": { "dateTime": "2020-12-09T17:00:00.0000000", "timeZone": "UTC" }, "scheduledEndDateTime": { "dateTime": "2020-12-10T17:00:00.0000000", "timeZone": "UTC" } }, "language": { "locale": "en-US", "displayName": "English (United States)" }, "workingHours": { "daysOfWeek": [ "monday", "tuesday", "wednesday", "thursday", "friday" ], "startTime": "08:00:00.0000000", "endTime": "17:00:00.0000000", "timeZone": { "name": "Pacific Standard Time" } } } } } }
Stockage des jetons
Maintenant que vous pouvez obtenir des jetons, nous vous conseillons d’implémenter un moyen de les stocker dans l’application. Comme il s’agit d’un exemple d’application, par souci de simplicité, vous les stockerez dans la session. Une application réelle utilise une solution de stockage sécurisé plus fiable, comme une base de données.
Ouvrez ./app/controllers/application_controller.rb. Ajoutez la méthode suivante à la classe
ApplicationController
.def save_in_session(auth_hash) # Save the token info session[:graph_token_hash] = auth_hash[:credentials] # Save the user's display name session[:user_name] = auth_hash.dig(:extra, :raw_info, :displayName) # Save the user's email address # Use the mail field first. If that's empty, fall back on # userPrincipalName session[:user_email] = auth_hash.dig(:extra, :raw_info, :mail) || auth_hash.dig(:extra, :raw_info, :userPrincipalName) # Save the user's time zone session[:user_timezone] = auth_hash.dig(:extra, :raw_info, :mailboxSettings, :timeZone) end
La méthode prend le hachage OmniAuth comme paramètre et extrait les bits d’informations pertinents, puis les stocke dans la session.
Ajoutez des fonctions d’accesseur à la classe pour récupérer le nom d’utilisateur, l’adresse e-mail et le jeton d’accès
ApplicationController
de la session.def user_name session[:user_name] end def user_email session[:user_email] end def user_timezone session[:user_timezone] end def access_token session[:graph_token_hash][:token] end
Ajoutez le code suivant à
ApplicationController
la classe qui s’exécutera avant le traitement d’une action.before_action :set_user def set_user @user_name = user_name @user_email = user_email end
Cette méthode définit les variables que la disposition (application.html.erb) utilise pour afficher les informations de l’utilisateur dans la barre de navigation. En l’ajoutant ici, vous n’avez pas besoin d’ajouter ce code dans chaque action de contrôleur. Toutefois, cela s’exécutera également pour les actions dans
AuthController
le , ce qui n’est pas optimal.Ajoutez le code suivant à la
AuthController
classe dans ./app/controllers/auth_controller.rb pour ignorer l’action précédente.skip_before_action :set_user
Mettez à jour la fonction dans la classe pour stocker les jetons dans la session et
callback
AuthController
rediriger vers la page principale. Remplacez la fonctioncallback
existante par ce qui suit.def callback # Access the authentication hash for omniauth data = request.env['omniauth.auth'] # Save the data in the session save_in_session data redirect_to root_url end
Implémenter la signature
Avant de tester cette nouvelle fonctionnalité, ajoutez un moyen de vous en sortir.
Ajoutez l’action suivante à la
AuthController
classe.def signout reset_session redirect_to root_url end
Ajoutez cette action à ./config/routes.rb.
get 'auth/signout'
Redémarrez le serveur et traversez le processus de sign-in. Vous devez revenir sur la page d’accueil, mais l’interface utilisateur doit changer pour indiquer que vous êtes en cours de signature.
Cliquez sur l’avatar de l’utilisateur dans le coin supérieur droit pour accéder au lien de connexion. Le fait de cliquer sur Se déconnecter réinitialise la session et vous ramène à la page d’accueil.
Actualisation des jetons
Si vous regardez attentivement le hachage généré par OmniAuth, vous remarquerez qu’il y a deux jetons dans le hachage : token
et refresh_token
. La valeur est token
le jeton d’accès, qui est envoyé dans Authorization
l’en-tête des appels d’API. Il s’agit du jeton qui permet à l’application d’accéder au Graph Microsoft au nom de l’utilisateur.
Cependant, ce jeton est de courte durée. Le jeton expire une heure après son émission. C’est là que refresh_token
la valeur devient utile. Le jeton d’actualisation permet à l’application de demander un nouveau jeton d’accès sans obliger l’utilisateur à se reconnecter. Mettez à jour le code de gestion des jetons pour implémenter l’actualisation du jeton.
Ouvrez ./app/controllers/application_controller.rb et ajoutez les
require
instructions suivantes en haut :require 'microsoft_graph_auth' require 'oauth2'
Ajoutez la méthode suivante à la classe
ApplicationController
.def refresh_tokens(token_hash) oauth_strategy = OmniAuth::Strategies::MicrosoftGraphAuth.new( nil, ENV['AZURE_APP_ID'], ENV['AZURE_APP_SECRET'] ) token = OAuth2::AccessToken.new( oauth_strategy.client, token_hash[:token], :refresh_token => token_hash[:refresh_token] ) # Refresh the tokens new_tokens = token.refresh!.to_hash.slice(:access_token, :refresh_token, :expires_at) # Rename token key new_tokens[:token] = new_tokens.delete :access_token # Store the new hash session[:graph_token_hash] = new_tokens end
Cette méthode utilise le gem oauth2 (une dépendance du gem) pour actualiser les jetons et met à jour
omniauth-oauth2
la session.Remplacez la
access_token
méthode actuelle par ce qui suit.def access_token token_hash = session[:graph_token_hash] # Get the expiry time - 5 minutes expiry = Time.at(token_hash[:expires_at] - 300) if Time.now > expiry # Token expired, refresh new_hash = refresh_tokens token_hash new_hash[:token] else token_hash[:token] end end
Au lieu de simplement renvoyer le jeton à partir de la session, il vérifie d’abord s’il est proche de l’expiration. Si c’est le cas, il est actualisé avant de renvoyer le jeton.
Obtenir un affichage Calendrier
Dans cet exercice, vous allez incorporer l’Graph Microsoft dans l’application. Pour cette application, vous allez utiliser le gem httparty pour appeler Microsoft Graph.
Créer un Graph d’aide
Créez un aide pour gérer tous vos appels d’API. Exécutez la commande suivante dans votre CLI pour générer l’aide.
rails generate helper Graph
Ouvrez ./app/helpers/graph_helper.rb et remplacez le contenu par ce qui suit.
require 'httparty' # Graph API helper methods module GraphHelper GRAPH_HOST = 'https://graph.microsoft.com'.freeze def make_api_call(method, endpoint, token, headers = nil, params = nil, payload = nil) headers ||= {} headers[:Authorization] = "Bearer #{token}" headers[:Accept] = 'application/json' params ||= {} case method.upcase when 'GET' HTTParty.get "#{GRAPH_HOST}#{endpoint}", :headers => headers, :query => params when 'POST' headers['Content-Type'] = 'application/json' HTTParty.post "#{GRAPH_HOST}#{endpoint}", :headers => headers, :query => params, :body => payload ? payload.to_json : nil else raise "HTTP method #{method.upcase} not implemented" end end end
Prenez le temps de passer en revue ce que fait ce code. Il effectue une demande GET ou POST simple via le httparty
gem au point de terminaison demandé. Il envoie le jeton d’accès dans l’en-tête et inclut tous les Authorization
paramètres de requête transmis.
Par exemple, pour utiliser la make_api_call
méthode pour faire une get https://graph.microsoft.com/v1.0/me?$select=displayName
to, vous pouvez l’appeler comme ci-après :
make_api_call 'GET', '/v1.0/me', access_token, {}, { '$select': 'displayName' }
Vous pourrez vous appuyer sur cette fonctionnalité plus tard à mesure que vous implémenterez d’autres fonctionnalités Graph Microsoft dans l’application.
Récupérer les événements de calendrier à partir d’Outlook
Dans votre CLI, exécutez la commande suivante pour ajouter un nouveau contrôleur.
rails generate controller Calendar index new
Ajoutez le nouvel itinéraire à ./config/routes.rb.
get 'calendar', :to => 'calendar#index'
Ajoutez une nouvelle méthode à l’Graph’aide pour obtenir un affichage Calendrier. Ouvrez ./app/helpers/graph_helper.rb et ajoutez la méthode suivante au
GraphHelper
module.def get_calendar_view(token, start_datetime, end_datetime, timezone) get_events_url = '/v1.0/me/calendarview' headers = { 'Prefer' => "outlook.timezone=\"#{timezone}\"" } query = { 'startDateTime' => start_datetime.iso8601, 'endDateTime' => end_datetime.iso8601, '$select' => 'subject,organizer,start,end', '$orderby' => 'start/dateTime', '$top' => 50 } response = make_api_call 'GET', get_events_url, token, headers, query raise response.parsed_response.to_s || "Request returned #{response.code}" unless response.code == 200 response.parsed_response['value'] end
Que fait ce code ?
- L’URL qui sera appelée est
/v1.0/me/calendarview
.- L’en-tête entraîne l’ajustement des heures de début et de fin dans les résultats au
Prefer: outlook.timezone
fuseau horaire de l’utilisateur. - Les
startDateTime
endDateTime
paramètres et les paramètres définissent le début et la fin de l’affichage. - Le paramètre limite les champs renvoyés pour chaque événement à ceux que
$select
l’affichage utilisera réellement. - Le
$orderby
paramètre trie les résultats par heure de début. - Le
$top
paramètre limite les résultats à 50 événements.
- L’en-tête entraîne l’ajustement des heures de début et de fin dans les résultats au
- Pour une réponse réussie, elle renvoie le tableau d’éléments contenus dans la
value
clé.
- L’URL qui sera appelée est
Ajoutez une nouvelle méthode à l’Graph pour rechercher un identificateur de fuseau horaire IANA basé sur un Windows de fuseau horaire. Cette étape est nécessaire, car Microsoft Graph des fuseaux horaires sous la Windows des noms de fuseau horaire et la classe Ruby DateTime requiert des identificateurs de fuseau horaire IANA.
TIME_ZONE_MAP = { 'Dateline Standard Time' => 'Etc/GMT+12', 'UTC-11' => 'Etc/GMT+11', 'Aleutian Standard Time' => 'America/Adak', 'Hawaiian Standard Time' => 'Pacific/Honolulu', 'Marquesas Standard Time' => 'Pacific/Marquesas', 'Alaskan Standard Time' => 'America/Anchorage', 'UTC-09' => 'Etc/GMT+9', 'Pacific Standard Time (Mexico)' => 'America/Tijuana', 'UTC-08' => 'Etc/GMT+8', 'Pacific Standard Time' => 'America/Los_Angeles', 'US Mountain Standard Time' => 'America/Phoenix', 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', 'Mountain Standard Time' => 'America/Denver', 'Central America Standard Time' => 'America/Guatemala', 'Central Standard Time' => 'America/Chicago', 'Easter Island Standard Time' => 'Pacific/Easter', 'Central Standard Time (Mexico)' => 'America/Mexico_City', 'Canada Central Standard Time' => 'America/Regina', 'SA Pacific Standard Time' => 'America/Bogota', 'Eastern Standard Time (Mexico)' => 'America/Cancun', 'Eastern Standard Time' => 'America/New_York', 'Haiti Standard Time' => 'America/Port-au-Prince', 'Cuba Standard Time' => 'America/Havana', 'US Eastern Standard Time' => 'America/Indianapolis', 'Turks And Caicos Standard Time' => 'America/Grand_Turk', 'Paraguay Standard Time' => 'America/Asuncion', 'Atlantic Standard Time' => 'America/Halifax', 'Venezuela Standard Time' => 'America/Caracas', 'Central Brazilian Standard Time' => 'America/Cuiaba', 'SA Western Standard Time' => 'America/La_Paz', 'Pacific SA Standard Time' => 'America/Santiago', 'Newfoundland Standard Time' => 'America/St_Johns', 'Tocantins Standard Time' => 'America/Araguaina', 'E. South America Standard Time' => 'America/Sao_Paulo', 'SA Eastern Standard Time' => 'America/Cayenne', 'Argentina Standard Time' => 'America/Buenos_Aires', 'Greenland Standard Time' => 'America/Godthab', 'Montevideo Standard Time' => 'America/Montevideo', 'Magallanes Standard Time' => 'America/Punta_Arenas', 'Saint Pierre Standard Time' => 'America/Miquelon', 'Bahia Standard Time' => 'America/Bahia', 'UTC-02' => 'Etc/GMT+2', 'Azores Standard Time' => 'Atlantic/Azores', 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', 'UTC' => 'Etc/GMT', 'GMT Standard Time' => 'Europe/London', 'Greenwich Standard Time' => 'Atlantic/Reykjavik', 'Sao Tome Standard Time' => 'Africa/Sao_Tome', 'Morocco Standard Time' => 'Africa/Casablanca', 'W. Europe Standard Time' => 'Europe/Berlin', 'Central Europe Standard Time' => 'Europe/Budapest', 'Romance Standard Time' => 'Europe/Paris', 'Central European Standard Time' => 'Europe/Warsaw', 'W. Central Africa Standard Time' => 'Africa/Lagos', 'Jordan Standard Time' => 'Asia/Amman', 'GTB Standard Time' => 'Europe/Bucharest', 'Middle East Standard Time' => 'Asia/Beirut', 'Egypt Standard Time' => 'Africa/Cairo', 'E. Europe Standard Time' => 'Europe/Chisinau', 'Syria Standard Time' => 'Asia/Damascus', 'West Bank Standard Time' => 'Asia/Hebron', 'South Africa Standard Time' => 'Africa/Johannesburg', 'FLE Standard Time' => 'Europe/Kiev', 'Israel Standard Time' => 'Asia/Jerusalem', 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', 'Sudan Standard Time' => 'Africa/Khartoum', 'Libya Standard Time' => 'Africa/Tripoli', 'Namibia Standard Time' => 'Africa/Windhoek', 'Arabic Standard Time' => 'Asia/Baghdad', 'Turkey Standard Time' => 'Europe/Istanbul', 'Arab Standard Time' => 'Asia/Riyadh', 'Belarus Standard Time' => 'Europe/Minsk', 'Russian Standard Time' => 'Europe/Moscow', 'E. Africa Standard Time' => 'Africa/Nairobi', 'Iran Standard Time' => 'Asia/Tehran', 'Arabian Standard Time' => 'Asia/Dubai', 'Astrakhan Standard Time' => 'Europe/Astrakhan', 'Azerbaijan Standard Time' => 'Asia/Baku', 'Russia Time Zone 3' => 'Europe/Samara', 'Mauritius Standard Time' => 'Indian/Mauritius', 'Saratov Standard Time' => 'Europe/Saratov', 'Georgian Standard Time' => 'Asia/Tbilisi', 'Volgograd Standard Time' => 'Europe/Volgograd', 'Caucasus Standard Time' => 'Asia/Yerevan', 'Afghanistan Standard Time' => 'Asia/Kabul', 'West Asia Standard Time' => 'Asia/Tashkent', 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', 'Pakistan Standard Time' => 'Asia/Karachi', 'Qyzylorda Standard Time' => 'Asia/Qyzylorda', 'India Standard Time' => 'Asia/Calcutta', 'Sri Lanka Standard Time' => 'Asia/Colombo', 'Nepal Standard Time' => 'Asia/Katmandu', 'Central Asia Standard Time' => 'Asia/Almaty', 'Bangladesh Standard Time' => 'Asia/Dhaka', 'Omsk Standard Time' => 'Asia/Omsk', 'Myanmar Standard Time' => 'Asia/Rangoon', 'SE Asia Standard Time' => 'Asia/Bangkok', 'Altai Standard Time' => 'Asia/Barnaul', 'W. Mongolia Standard Time' => 'Asia/Hovd', 'North Asia Standard Time' => 'Asia/Krasnoyarsk', 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', 'Tomsk Standard Time' => 'Asia/Tomsk', 'China Standard Time' => 'Asia/Shanghai', 'North Asia East Standard Time' => 'Asia/Irkutsk', 'Singapore Standard Time' => 'Asia/Singapore', 'W. Australia Standard Time' => 'Australia/Perth', 'Taipei Standard Time' => 'Asia/Taipei', 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', 'Aus Central W. Standard Time' => 'Australia/Eucla', 'Transbaikal Standard Time' => 'Asia/Chita', 'Tokyo Standard Time' => 'Asia/Tokyo', 'North Korea Standard Time' => 'Asia/Pyongyang', 'Korea Standard Time' => 'Asia/Seoul', 'Yakutsk Standard Time' => 'Asia/Yakutsk', 'Cen. Australia Standard Time' => 'Australia/Adelaide', 'AUS Central Standard Time' => 'Australia/Darwin', 'E. Australia Standard Time' => 'Australia/Brisbane', 'AUS Eastern Standard Time' => 'Australia/Sydney', 'West Pacific Standard Time' => 'Pacific/Port_Moresby', 'Tasmania Standard Time' => 'Australia/Hobart', 'Vladivostok Standard Time' => 'Asia/Vladivostok', 'Lord Howe Standard Time' => 'Australia/Lord_Howe', 'Bougainville Standard Time' => 'Pacific/Bougainville', 'Russia Time Zone 10' => 'Asia/Srednekolymsk', 'Magadan Standard Time' => 'Asia/Magadan', 'Norfolk Standard Time' => 'Pacific/Norfolk', 'Sakhalin Standard Time' => 'Asia/Sakhalin', 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', 'Russia Time Zone 11' => 'Asia/Kamchatka', 'New Zealand Standard Time' => 'Pacific/Auckland', 'UTC+12' => 'Etc/GMT-12', 'Fiji Standard Time' => 'Pacific/Fiji', 'Chatham Islands Standard Time' => 'Pacific/Chatham', 'UTC+13' => 'Etc/GMT-13', 'Tonga Standard Time' => 'Pacific/Tongatapu', 'Samoa Standard Time' => 'Pacific/Apia', 'Line Islands Standard Time' => 'Pacific/Kiritimati' }.freeze def get_iana_from_windows(windows_tz_name) iana = TIME_ZONE_MAP[windows_tz_name] # If no mapping found, assume the supplied # value was already an IANA identifier iana || windows_tz_name end
Ouvrez ./app/controllers/calendar_controller.rb et remplacez tout son contenu par ce qui suit.
# Calendar controller class CalendarController < ApplicationController include GraphHelper def index # Get the IANA identifier of the user's time zone time_zone = get_iana_from_windows(user_timezone) # Calculate the start and end of week in the user's time zone start_datetime = Date.today.beginning_of_week(:sunday).in_time_zone(time_zone).to_time end_datetime = start_datetime.advance(:days => 7) @events = get_calendar_view access_token, start_datetime, end_datetime, user_timezone || [] render json: @events rescue RuntimeError => e @errors = [ { :message => 'Microsoft Graph returned an error getting events.', :debug => e } ] end end
Redémarrez le serveur. Connectez-vous et cliquez sur le lien Calendrier dans la barre de navigation. Si tout fonctionne, vous devriez voir une image mémoire JSON des événements dans le calendrier de l’utilisateur.
Afficher les résultats
Vous pouvez désormais ajouter du code HTML pour afficher les résultats de manière plus conviviale.
Ouvrez ./app/views/calendar/index.html.erb et remplacez son contenu par ce qui suit.
<h1>Calendar</h1> <a href="/calendar/new" class="btn btn-light btn-sm mb-3">New event</a> <table class="table"> <thead> <tr> <th scope="col">Organizer</th> <th scope="col">Subject</th> <th scope="col">Start</th> <th scope="col">End</th> </tr> </thead> <tbody> <% @events.each do |event| %> <tr> <td><%= event['organizer']['emailAddress']['name'] %></td> <td><%= event['subject'] %></td> <td><%= event['start']['dateTime'].to_time(:utc).strftime('%-m/%-d/%Y %l:%M %p') %></td> <td><%= event['end']['dateTime'].to_time(:utc).strftime('%-m/%-d/%Y %l:%M %p') %></td> </tr> <% end %> </tbody> </table>
Cela permet de parcourir une collection d’événements et d’ajouter une ligne de tableau pour chacun d’eux.
Supprimez
render json: @events
la ligne deindex
l’action dans ./app/controllers/calendar_controller.rb.Actualisez la page et l’application doit maintenant restituer une table des événements.
Créer un événement
Dans cette section, vous allez ajouter la possibilité de créer des événements sur le calendrier de l’utilisateur.
Ouvrez ./app/helpers/graph_helper.rb et ajoutez la méthode suivante à Graph classe.
def create_event(token, timezone, subject, start_datetime, end_datetime, attendees, body) create_event_url = '/v1.0/me/events' # Create an event object # https://docs.microsoft.com/graph/api/resources/event?view=graph-rest-1.0 new_event = { 'subject' => subject, 'start' => { 'dateTime' => start_datetime, 'timeZone' => timezone }, 'end' => { 'dateTime' => end_datetime, 'timeZone' => timezone } } unless attendees.empty? attendee_array = [] # Create an attendee object # https://docs.microsoft.com/graph/api/resources/attendee?view=graph-rest-1.0 attendees.each { |email| attendee_array.push({ 'type' => 'required', 'emailAddress' => { 'address' => email } }) } new_event['attendees'] = attendee_array end unless body.empty? # Create an itemBody object # https://docs.microsoft.com/graph/api/resources/itembody?view=graph-rest-1.0 new_event['body'] = { 'contentType' => 'text', 'content' => body } end response = make_api_call 'POST', create_event_url, token, nil, nil, new_event raise response.parsed_response.to_s || "Request returned #{response.code}" unless response.code == 201 end
Ouvrez ./app/controllers/calendar_controller et ajoutez l’itinéraire suivant à la classe CalendarController.
def create # Semicolon-delimited list, split to an array attendees = params[:ev_attendees].split(';') # Create the event create_event access_token, user_timezone, params[:ev_subject], params[:ev_start], params[:ev_end], attendees, params[:ev_body] # Redirect back to the calendar list redirect_to({ :action => 'index' }) rescue RuntimeError => e @errors = [ { :message => 'Microsoft Graph returned an error creating the event.', :debug => e } ] end
Ouvrez ./config/routes.rb et ajoutez le nouvel itinéraire.
post 'calendar/new', :to => 'calendar#create'
Ouvrez ./app/views/calendar/new.html.erb et remplacez son contenu par ce qui suit.
<h1>New event</h1> <%= form_with url: "/calendar/new" do |form| %> <div class="form-group"> <%= form.label :ev_subject, "Subject" %> <%= form.text_field :ev_subject, class: "form-control", required: true %> </div> <div class="form-group"> <%= form.label :ev_attendees, "Attendees" %> <%= form.text_field :ev_attendees, class: "form-control", placeholder: "Separate multiple email addresses with a semicolon (';')" %> </div> <div class="form-row"> <div class="col"> <div class="form-group"> <%= form.label :ev_start, "Start" %> <%= form.datetime_local_field :ev_start, class: "form-control", required: true %> </div> </div> <div class="col"> <div class="form-group"> <%= form.label :ev_end, "End" %> <%= form.datetime_local_field :ev_end, class: "form-control", required: true %> </div> </div> </div> <div class="form-group mb-3"> <%= form.label :ev_body, "Body" %> <%= form.text_area :ev_body, class: "form-control", rows: "3" %> </div> <%= form.submit "Create", class: "btn btn-primary mr-2" %> <a class="btn btn-secondary" href="/calendar">Cancel</a> <% end %>
Enregistrez vos modifications et actualisez l’application. Dans la page Calendrier, sélectionnez le bouton Nouvel événement. Remplissez le formulaire, puis sélectionnez Créer pour créer un événement.
Félicitations !
Vous avez terminé le didacticiel Graph Ruby Microsoft. Maintenant que vous disposez d’une application de travail qui appelle Microsoft Graph, vous pouvez expérimenter et ajouter de nouvelles fonctionnalités. Consultez la vue d’ensemble de Microsoft Graph pour voir toutes les données accessibles avec Microsoft Graph.
Commentaires
N’hésitez pas à nous faire part de vos commentaires sur ce didacticiel dans GitHub référentiel.
Vous avez un problème avec cette section ? Si c'est le cas, faites-nous part de vos commentaires pour que nous puissions l'améliorer.