Erstellen von Ruby on Rails-Apps mit Microsoft Graph
In diesem Lernprogramm erfahren Sie, wie Sie eine Ruby on Rails-Web-App erstellen, die die Microsoft Graph-API zum Abrufen von Kalenderinformationen für einen Benutzer verwendet.
Tipp
Wenn Sie es vorziehen, nur das abgeschlossene Lernprogramm herunterzuladen, können Sie es auf zwei Arten herunterladen.
- Laden Sie den Ruby-Schnellstart herunter, um den Arbeitscode in Minuten zu erhalten.
- Laden Sie das GitHub Repository herunter, oder klonen Sie es.
Voraussetzungen
Bevor Sie mit diesem Lernprogramm beginnen, sollten Sie die folgenden Tools auf Ihrem Entwicklungscomputer installiert haben.
Sie sollten auch über ein persönliches Microsoft-Konto mit einem Postfach auf Outlook.com oder ein Microsoft-Geschäfts-, Schul- oder Unikonto verfügen. Wenn Sie kein Microsoft-Konto haben, gibt es einige Optionen, um ein kostenloses Konto zu erhalten:
- Sie können sich für ein neues persönliches Microsoft-Konto registrieren.
- Sie können sich für das Microsoft 365 Entwicklerprogramm registrieren, um ein kostenloses Microsoft 365 Abonnement zu erhalten.
Hinweis
Dieses Lernprogramm wurde mit den folgenden Versionen der erforderlichen Tools geschrieben. Die Schritte in diesem Handbuch funktionieren möglicherweise mit anderen Versionen, die jedoch nicht getestet wurden.
- Ruby Version 3.0.1
- SQLite3, Version 3.35.5
- Node.js Version 14.15.0
- Yarn Version 1.22.0
Feedback
Bitte geben Sie Feedback zu diesem Lernprogramm im GitHub Repository.
Erstellen einer Ruby on Rails-Web-App
In dieser Übung verwenden Sie Ruby on Rails zum Erstellen einer Web-App.
Wenn Rails noch nicht installiert ist, können Sie es über die Befehlszeilenschnittstelle (CLI) mit dem folgenden Befehl installieren.
gem install rails -v 6.1.3.1
Öffnen Sie Ihre CLI, navigieren Sie zu einem Verzeichnis, in dem Sie die Berechtigung zum Erstellen von Dateien haben, und führen Sie den folgenden Befehl aus, um eine neue Rails-App zu erstellen.
rails new graph-tutorial
Navigieren Sie zu diesem neuen Verzeichnis, und geben Sie den folgenden Befehl ein, um einen lokalen Webserver zu starten.
rails server
Öffnen Sie Ihren Browser und navigieren Sie zu
http://localhost:3000
. Wenn alles funktioniert, sehen Sie eine "Yay! You're on Rails!" Nachricht. Wenn diese Meldung nicht angezeigt wird, überprüfen Sie den Leitfaden für die ersten Schritte von Rails.
Installieren von Gems
Bevor Sie fortfahren, installieren Sie einige zusätzliche Gems, die Sie später verwenden werden:
- omniauth-oauth2 für die Verarbeitung von Anmelde- und OAuth-Tokenflüssen.
- Omniauth-rails_csrf_protection zum Hinzufügen von CSRF-Schutz zu OmniAuth.
- httparty for making calls to Microsoft Graph.
- activerecord-session_store zum Speichern von Sitzungen in der Datenbank.
Öffnen Sie ./Gemfile, und fügen Sie die folgenden Zeilen hinzu.
# 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'
Führen Sie in Der CLI den folgenden Befehl aus.
bundle install
Führen Sie in Der CLI die folgenden Befehle aus, um die Datenbank zum Speichern von Sitzungen zu konfigurieren.
rails generate active_record:session_migration rake db:migrate
Erstellen Sie eine neue
session_store.rb
Datei, die im Verzeichnis ./config/initializers aufgerufen wird, und fügen Sie den folgenden Code hinzu.Rails.application.config.session_store :active_record_store, :key => '_graph_app_session'
Entwerfen der App
In diesem Abschnitt erstellen Sie die grundlegende Benutzeroberfläche für die App.
Öffnen Sie ./app/views/layouts/application.html.erb, und ersetzen Sie den Inhalt durch Folgendes.
<!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>
Dieser Code fügt Bootstrap für einfache Formatierung und Fabric Core für einige einfache Symbole hinzu. Außerdem wird ein globales Layout mit einer Navigationsleiste definiert.
Öffnen Sie ./app/assets/stylesheets/application.css, und fügen Sie am Ende der Datei Folgendes hinzu.
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; }
Generieren Sie mit dem folgenden Befehl einen Homepagecontroller.
rails generate controller Home index
Konfigurieren Sie die
index
Aktion auf dem Controller alsHome
Standardseite für die App. Öffnen Sie ./config/routes.rb, und ersetzen Sie den Inhalt durch Folgendes:Rails.application.routes.draw do get 'home/index' root 'home#index' # Add future routes here end
Öffnen Sie ./app/view/home/index.html.erb, und ersetzen Sie den Inhalt durch Folgendes.
<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>
Fügen Sie eine PNG-Datei mit dem Namen no-profile-photo.png im Verzeichnis ./app/assets/images hinzu.
Speichern Sie alle Änderungen, und starten Sie den Server neu. Jetzt sollte die App ganz anders aussehen.
Registrieren der App im Portal
In dieser Übung erstellen Sie eine neue Azure AD-Webanwendungsregistrierung über das Azure Active Directory Admin Center.
Öffnen Sie einen Browser, und navigieren Sie zum Azure Active Directory Admin Center. Melden Sie sich mit einem persönlichen Konto (auch: Microsoft-Konto) oder einem Geschäfts- oder Schulkonto an.
Wählen Sie in der linken Navigationsleiste Azure Active Directory aus, und wählen Sie dann App-Registrierungen unter Verwalten aus.
Wählen Sie Neue Registrierung aus. Legen Sie auf der Seite Anwendung registrieren die Werte wie folgt fest.
- Legen Sie Name auf
Ruby Graph Tutorial
fest. - Legen Sie Unterstützte Kontotypen auf Konten in allen Organisationsverzeichnissen und persönliche Microsoft-Konten fest.
- Legen Sie unter Umleitungs-URI die erste Dropdownoption auf
Web
fest, und legen Sie den Wert aufhttp://localhost:3000/auth/microsoft_graph_auth/callback
fest.
- Legen Sie Name auf
Wählen Sie Registrieren aus. Kopieren Sie auf der Seite "Ruby Graph Tutorial" den Wert der Anwendungs-ID (Client-ID), und speichern Sie ihn. Sie benötigen ihn im nächsten Schritt.
Wählen Sie unter Verwalten die Option Zertifikate und Geheime Clientschlüssel aus. Wählen Sie die Schaltfläche Neuen geheimen Clientschlüssel aus. Geben Sie einen Wert in Beschreibung ein, wählen Sie eine der Optionen für Gilt bis aus, und wählen Sie dann Hinzufügen aus.
Kopieren Sie den Wert des geheimen Clientschlüssels, bevor Sie diese Seite verlassen. Sie benötigen ihn im nächsten Schritt.
Wichtig
Dieser geheime Clientschlüssel wird nicht noch einmal angezeigt, stellen Sie daher sicher, dass Sie ihn jetzt kopieren.
Hinzufügen der Azure AD-Authentifizierung
In dieser Übung erweitern Sie die Anwendung aus der vorherigen Übung, um die Authentifizierung mit Azure AD zu unterstützen. Dies ist erforderlich, um das erforderliche OAuth-Zugriffstoken zum Aufrufen der Microsoft Graph abzurufen. In diesem Schritt integrieren Sie das Omniauth-oauth2-Gem in die Anwendung und erstellen eine benutzerdefinierte OmniAuth-Strategie.
Erstellen Sie eine separate Datei, um Ihre App-ID und ihren geheimen Schlüssel zu speichern. Erstellen Sie eine neue
oauth_environment_variables.rb
Datei, die im Ordner "./config" aufgerufen wird, und fügen Sie den folgenden Code hinzu.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'
Ersetzen Sie
YOUR_APP_ID_HERE
dies durch die Anwendungs-ID aus dem Anwendungsregistrierungsportal, und ersetzenYOUR_APP_SECRET_HERE
Sie sie durch das von Ihnen generierte Kennwort.Wichtig
Wenn Sie die Quellcodeverwaltung wie Git verwenden, wäre jetzt ein guter Zeitpunkt, um die
oauth_environment_variables.rb
Datei aus der Quellcodeverwaltung auszuschließen, um zu vermeiden, dass Versehentlich Ihre App-ID und Ihr Kennwort offengelegt wird.Öffnen Sie ./config/environment.rb, und fügen Sie den folgenden Code vor der
Rails.application.initialize!
Zeile hinzu.# 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!
Einrichten von OmniAuth
Sie haben das Gem bereits omniauth-oauth2
installiert, aber damit es mit den Azure OAuth-Endpunkten funktioniert, müssen Sie eine OAuth2-Strategie erstellen. Dies ist eine Ruby-Klasse, die die Parameter für OAuth-Anforderungen an den Azure-Anbieter definiert.
Erstellen Sie eine neue
microsoft_graph_auth.rb
Datei, die im Ordner ./lib'** aufgerufen wird, und fügen Sie den folgenden Code hinzu.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
Nehmen Sie sich einen Moment Zeit, um zu überprüfen, was dieser Code bewirkt.
- Sie legt
client_options
fest, dass die Microsoft Identity Platform Endpunkte angegeben werden. - Es gibt an, dass der
scope
Parameter während der Autorisierungsphase gesendet werden soll. - Sie ordnet die
id
Eigenschaft des Benutzers als eindeutige ID für den Benutzer zu. - Es verwendet das Zugriffstoken, um das Profil des Benutzers von Microsoft Graph abzurufen, um den
raw_info
Hash auszufüllen. - Sie überschreibt die Rückruf-URL, um sicherzustellen, dass sie dem registrierten Rückruf im App-Registrierungsportal entspricht.
- Sie legt
Erstellen Sie eine neue
omniauth_graph.rb
Datei, die im Ordner "./config/initializers" aufgerufen wird, und fügen Sie den folgenden Code hinzu.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
Dieser Code wird ausgeführt, wenn die App gestartet wird. Sie lädt die OmniAuth-Middleware mit dem
microsoft_graph_auth
Anbieter, konfiguriert mit den in oauth_environment_variables.rb festgelegten Umgebungsvariablen.
Implementieren der Anmeldung
Nachdem die OmniAuth-Middleware konfiguriert wurde, können Sie mit dem Hinzufügen der Anmeldung zur App fortgehen.
Führen Sie den folgenden Befehl in Ihrer CLI aus, um einen Controller für die Anmeldung und Abmeldung zu generieren.
rails generate controller Auth
Öffnen Sie ./app/controllers/auth_controller.rb. Fügen Sie der Klasse eine Rückrufmethode
AuthController
hinzu. Diese Methode wird von der OmniAuth-Middleware aufgerufen, sobald der OAuth-Fluss abgeschlossen ist.def callback # Access the authentication hash for omniauth data = request.env['omniauth.auth'] # Temporary for testing! render json: data.to_json end
Derzeit wird lediglich der von OmniAuth bereitgestellte Hash gerendert. Damit überprüfen Sie, ob die Anmeldung funktioniert, bevor Sie fortfahren.
Fügen Sie die Routen zu ./config/routes.rb hinzu.
# Add route for OmniAuth callback match '/auth/:provider/callback', :to => 'auth#callback', :via => [:get, :post]
Starten Sie den Server, und navigieren Sie zu
https://localhost:3000
. Klicken Sie auf die Schaltfläche zum Anmelden, um zuhttps://login.microsoftonline.com
weitergeleitet zu werden. Melden Sie sich mit Ihrem Microsoft-Konto an, und stimmen Sie den angeforderten Berechtigungen zu. Der Browser leitet zur App um, wobei der von OmniAuth generierte Hash angezeigt wird.{ "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" } } } } } }
Speichern des Tokens
Nun, da Sie Token abrufen können, ist es an der Zeit, eine Möglichkeit einzurichten, diese in der App zu speichern. Da es sich um eine Beispiel-App handelt, speichern Sie sie der Einfachheit halber in der Sitzung. Eine echte App würde eine zuverlässigere sichere Speicherlösung wie eine Datenbank verwenden.
Öffnen Sie ./app/controllers/application_controller.rb. Fügen Sie der Klasse
ApplicationController
die folgende Methode hinzu.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
Die Methode verwendet den OmniAuth-Hash als Parameter und extrahiert die relevanten Informationsbits und speichert diese dann in der Sitzung.
Fügen Sie der Klasse Accessorfunktionen
ApplicationController
hinzu, um den Benutzernamen, die E-Mail-Adresse und das Zugriffstoken aus der Sitzung abzurufen.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
Fügen Sie der Klasse, die vor der Verarbeitung einer Aktion ausgeführt wird, den folgenden Code
ApplicationController
hinzu.before_action :set_user def set_user @user_name = user_name @user_email = user_email end
Diese Methode legt die Variablen fest, die das Layout (in application.html.erb) verwendet, um die Benutzerinformationen in der Navigationsleiste anzuzeigen. Wenn Sie ihn hier hinzufügen, müssen Sie diesen Code nicht in jeder einzelnen Controlleraktion hinzufügen. Dies wird jedoch auch für Aktionen in der
AuthController
ausgeführt, was nicht optimal ist.Fügen Sie der
AuthController
Klasse in ./app/controllers/auth_controller.rb den folgenden Code hinzu, um die Vorabaktion zu überspringen.skip_before_action :set_user
Aktualisieren Sie die
callback
Funktion in derAuthController
Klasse, um die Token in der Sitzung zu speichern und zurück zur Hauptseite umzuleiten. Ersetzen Sie die vorhandenecallback
-Funktion durch Folgendes.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
Implementieren der Abmeldung
Bevor Sie dieses neue Feature testen, fügen Sie eine Möglichkeit zum Abmelden hinzu.
Fügen Sie der Klasse die folgende Aktion
AuthController
hinzu.def signout reset_session redirect_to root_url end
Fügen Sie diese Aktion zu ./config/routes.rb hinzu.
get 'auth/signout'
Starten Sie den Server neu, und durchlaufen Sie den Anmeldevorgang. Sie sollten wieder auf der Startseite angezeigt werden, aber die Benutzeroberfläche sollte geändert werden, um anzugeben, dass Sie angemeldet sind.
Klicken Sie auf den Benutzer-Avatar in der oberen rechten Ecke, um auf den Abmeldelink zuzugreifen. Wenn Sie auf Abmelden klicken, wird die Sitzung zurückgesetzt und Sie kehren zur Startseite zurück.
Aktualisieren von Token
Wenn Sie sich den von OmniAuth generierten Hash genauer ansehen, werden Sie feststellen, dass zwei Token im Hash vorhanden sind: token
und refresh_token
. Der Wert in token
ist das Zugriffstoken, das im Authorization
Header von API-Aufrufen gesendet wird. Dies ist das Token, mit dem die App im Namen des Benutzers auf die Microsoft-Graph zugreifen kann.
Dieses Token ist jedoch nur kurzzeitig verfügbar. Das Token läuft eine Stunde nach der Ausstellung ab. Hier wird der refresh_token
Wert nützlich. Anhand des Aktualisierungstoken ist die App in der Lage, ein neues Zugriffstoken anzufordern, ohne dass der Benutzer sich erneut anmelden muss. Aktualisieren Sie den Tokenverwaltungscode, um die Tokenaktualisierung zu implementieren.
Öffnen Sie ./app/controllers/application_controller.rb, und fügen Sie die folgenden
require
Anweisungen oben hinzu:require 'microsoft_graph_auth' require 'oauth2'
Fügen Sie der Klasse
ApplicationController
die folgende Methode hinzu.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
Diese Methode verwendet das oauth2-Gem (eine Abhängigkeit des
omniauth-oauth2
Gems), um die Token zu aktualisieren und die Sitzung zu aktualisieren.Ersetzen Sie die aktuelle
access_token
Methode durch Folgendes.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
Anstatt nur das Token aus der Sitzung zurückzugeben, wird zuerst überprüft, ob es kurz vor dem Ablauf steht. Wenn dies der Typ ist, wird er aktualisiert, bevor das Token zurückgegeben wird.
Abrufen einer Kalenderansicht
In dieser Übung integrieren Sie die Microsoft Graph in die Anwendung. Für diese Anwendung verwenden Sie den httparty-Gem, um Aufrufe an Microsoft Graph zu tätigen.
Erstellen eines Graph-Hilfsprogramm
Erstellen Sie ein Hilfsprogramm, um alle Ihre API-Aufrufe zu verwalten. Führen Sie den folgenden Befehl in Der CLI aus, um das Hilfsprogramm zu generieren.
rails generate helper Graph
Öffnen Sie ./app/helpers/graph_helper.rb, und ersetzen Sie den Inhalt durch Folgendes.
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
Nehmen Sie sich einen Moment Zeit, um zu überprüfen, was dieser Code bewirkt. Es wird eine einfache GET- oder POST-Anforderung über das httparty
Gem an den angeforderten Endpunkt gesendet. Es sendet das Zugriffstoken in der Authorization
Kopfzeile und enthält alle übergebenen Abfrageparameter.
Wenn Sie beispielsweise die Methode verwenden möchten, make_api_call
um eine GET-Funktion zu https://graph.microsoft.com/v1.0/me?$select=displayName
verwenden, können Sie sie wie folgt aufrufen:
make_api_call 'GET', '/v1.0/me', access_token, {}, { '$select': 'displayName' }
Darauf bauen Sie später auf, wenn Sie weitere Microsoft Graph-Features in die App implementieren.
Abrufen von Kalenderereignissen von Outlook
Führen Sie in Der CLI den folgenden Befehl aus, um einen neuen Controller hinzuzufügen.
rails generate controller Calendar index new
Fügen Sie die neue Route zu ./config/routes.rb hinzu.
get 'calendar', :to => 'calendar#index'
Fügen Sie dem Graph Hilfsprogramm eine neue Methode hinzu, um eine Kalenderansicht abzurufen. Öffnen Sie ./app/helpers/graph_helper.rb, und fügen Sie dem Modul die folgende Methode
GraphHelper
hinzu.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
Überlegen Sie sich, was dieser Code macht.
- Die URL, die aufgerufen wird, lautet
/v1.0/me/calendarview
.- Die
Prefer: outlook.timezone
Kopfzeile bewirkt, dass die Start- und Endzeiten in den Ergebnissen an die Zeitzone des Benutzers angepasst werden. - Mit den
startDateTime
endDateTime
Parametern wird der Start und das Ende der Ansicht festgelegt. - Der
$select
Parameter beschränkt die für jedes Ereignis zurückgegebenen Felder auf die Felder, die tatsächlich von der Ansicht verwendet werden. - Der
$orderby
Parameter sortiert die Ergebnisse nach Startzeit. - Der
$top
Parameter beschränkt die Ergebnisse auf 50 Ereignisse.
- Die
- Für eine erfolgreiche Antwort gibt sie das Array von Elementen zurück, die im Schlüssel enthalten
value
sind.
- Die URL, die aufgerufen wird, lautet
Fügen Sie dem Graph Hilfsprogramm eine neue Methode hinzu, um einen IANA-Zeitzonenbezeichner basierend auf einem Windows Zeitzonennamen zu suchen. Dies ist erforderlich, da Microsoft Graph Zeitzonen als Windows Zeitzonennamen zurückgeben können und die Ruby DateTime-Klasse IANA-Zeitzonenbezeichner erfordert.
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
Öffnen Sie ./app/controllers/calendar_controller.rb, und ersetzen Sie den gesamten Inhalt durch Folgendes.
# 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
Starten Sie den Server neu. Melden Sie sich an, und klicken Sie auf den Kalenderlink in der Navigationsleiste. Wenn alles funktioniert, sollte ein JSON-Abbild von Ereignissen im Kalender des Benutzers angezeigt werden.
Anzeigen der Ergebnisse
Jetzt können Sie HTML hinzufügen, um die Ergebnisse benutzerfreundlicher anzuzeigen.
Öffnen Sie ./app/views/calendar/index.html.erb, und ersetzen Sie den Inhalt durch Folgendes.
<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>
Dadurch wird eine Ereignissammlung durchlaufen und jedem Ereignis wird jeweils eine Tabellenzeile hinzugefügt.
Entfernen Sie die
render json: @events
Zeile aus der Aktion inindex
./app/controllers/calendar_controller.rb.Aktualisieren Sie die Seite, und die App sollte nun eine Tabelle mit Ereignissen rendern.
Erstellen eines neuen Ereignisses
In diesem Abschnitt fügen Sie die Möglichkeit hinzu, Ereignisse im Kalender des Benutzers zu erstellen.
Öffnen Sie ./app/helpers/graph_helper.rb, und fügen Sie der Graph Klasse die folgende Methode hinzu.
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
Öffnen Sie ./app/controllers/calendar_controller, und fügen Sie die folgende Route zur CalendarController-Klasse hinzu.
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
Öffnen Sie ./config/routes.rb, und fügen Sie die neue Route hinzu.
post 'calendar/new', :to => 'calendar#create'
Öffnen Sie ./app/views/calendar/new.html.erb, und ersetzen Sie den Inhalt durch Folgendes.
<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 %>
Speichern Sie Ihre Änderungen, und aktualisieren Sie die App. Wählen Sie auf der Kalenderseite die Schaltfläche "Neues Ereignis" aus. Füllen Sie das Formular aus, und wählen Sie "Erstellen" aus, um ein neues Ereignis zu erstellen.
Herzlichen Glückwunsch!
Sie haben das Ruby Microsoft Graph Lernprogramm abgeschlossen. Da Sie nun über eine funktionierende App verfügen, die Microsoft Graph aufruft, können Sie experimentieren und neue Features hinzufügen. Besuchen Sie die Übersicht über Microsoft Graph, um alle Daten anzuzeigen, auf die Sie mit Microsoft Graph zugreifen können.
Feedback
Bitte geben Sie Feedback zu diesem Lernprogramm im GitHub Repository.
Liegt ein Problem mit diesem Abschnitt vor? Wenn ja, senden Sie uns Feedback, damit wir den Abschnitt verbessern können.