# Referenzimplementierung für die Nutzung des OpenID-Servers des Fachbereich 3 der Universität Bremen
#
# Dieses Beispiel beruht auf dem Einsatz von Ruby on Rails ab Version 1.2 und folgenden Ruby Gems:
# * ruby-openid (Version 2.0.4, http://www.openidenabled.com/ruby-openid/)
# * ruby-yadis (Version 0.3.4)
#
# Neben der folgenden Implementierung eines Controllers, welcher die Authentisierung und anschließende
# Verarbeitung der übergebenen Daten übernimmt, müssen noch weitere Teile dem Projekt hinzugefügt werden.
#
# Folgende Routes müssen in der Datei config/routes.rb ergänzt werden:
#
# map.with_options :controller => 'openid' do |openid|
# openid.openid_start 'openid/start', :action => 'start'
# openid.openid_complete 'openid/complete', :action => 'complete'
# end
#
# Um das Formular darzustellen, welches für die Anmeldung mit OpenID benötigt wird, kann folgendes
# HTML auf der Loginseite eingefügt werden.
#
# <% form_tag openid_start_path do %>
#
# <%= image_submit_tag 'https://openid.tzi.de/images/login_button.png' %>
#
# <% end %>
#
# Das Formular enthält lediglich den Login-Buttons des OpenID-Servers vom Fachbereich 3 und startet
# beim Abschicken den Authentisierungsvorgang in der start-Action des folgenden OpenidControllers.
#
# ------------------------------------------------------------------------------------------------------------
require 'openid' # Einbinden der benötigten Bibliotheken aus den Gems
require 'openid/store/filesystem' # Store fürs Speichern der OpenID-Transaktionsdaten
require 'openid/consumer/discovery' # Yadis Discovery Funktionalitäten
require 'openid/extensions/sreg' # Simple Registration Funktionalitäten
require 'openid/extensions/ax' # Attribute Exchange Funktionalitäten
class OpenidController < ApplicationController
skip_before_filter :verify_authenticity_token
# Die start-Action initialisiert die Anfrage, dabei werden auch die angeforderten Attribute
# angegeben, welche in diesem Fall mittels Attribute Exchange angefordert werden.
def start
begin
# In diesem Fall wird der Identity Provider des Fachbereich 3 direkt angesprochen. Wenn es
# möglich sein soll, dass der Benutzer einen Identity Provider seiner Wahl angeben kann,
# dann muss der begin-Methode die vom Benutzer angegebene OpenID-URL übergeben werden.
oidreq = openid_consumer.begin(identity_provider)
rescue OpenID::OpenIDError => e
failed_login("Der OpenID-Server #{identity_provider} konnte nicht kontaktiert werden.
#{e}")
return
end
# Anfrage von Benutzerdaten mittels Attribute Exchange. In diesem Fall werden relativ
# viele Attribute angefragt, es empfiehlt sich, jedoch nur die Daten anzufordern,
# welche auch für die Anwendung benötigt werden.
#
# Erster Parameter bei der Attributanfrage ist der Type Identifier, des gewünschten
# Attributs. Eine Liste aller verfügbaren Attribute findet sich unter:
# https://openid.tzi.de/spec/schema
#
# Zweiter Parameter ist der Bezeichner des Attributs, welcher dem Benutzer dargestellt wird.
# Der dritte Parameter ist ein Boolean und kennzeichnet Attribute als erforderlich (true) oder
# optional (false). Als vierter Parameter kann zusätzlich die Anzahl der Werte für das Attribut
# angegeben werden - standardmäßig ist dies 1.
axreq = OpenID::AX::FetchRequest.new
requested_attrs = [
['http://openid.tzi.de/spec/schema/uid', 'Kennung', true],
['http://openid.tzi.de/spec/schema/mail', 'E-Mail', true],
['http://openid.tzi.de/spec/schema/affiliation', 'Zugehörigkeit', true],
['http://openid.tzi.de/spec/schema/degreeCourse', 'Studiengang', true],
['http://openid.tzi.de/spec/schema/givenName', 'Vorname'],
['http://openid.tzi.de/spec/schema/surName', 'Nachname'],
['http://openid.tzi.de/spec/schema/displayName', 'Voller Name'],
['http://openid.tzi.de/spec/schema/principalName', 'Netz-ID'],
['http://openid.tzi.de/spec/schema/telephoneNumber', 'Telefon'],
['http://openid.tzi.de/spec/schema/postalAddress', 'Adresse'],
['http://openid.tzi.de/spec/schema/matriculationNumber', 'Matrikelnummer']]
requested_attrs.each { |a| axreq.add(OpenID::AX::AttrInfo.new(a[0], a[1], a[2] || false, a[3] || 1)) }
oidreq.add_extension(axreq)
# Anfrage von Attributen mittels Simple Registration. SReg sollte nur in Ausnahmefällen
# genutzt werden, beispielsweise wenn Daten von Benutzern außerhalb der Uni angefragt
# werden sollen. Wenn möglich bitte die obige Anfrage per Attribute Exhcange verwenden!
sregreq = OpenID::SReg::Request.new
sregreq.request_fields(['nickname', 'email'], true) # erforderliche Attribute
sregreq.request_fields(['fullname'], false) # optionale Attribute
oidreq.add_extension(sregreq)
# Anfrage absenden: Der erste Parameter der Redirects ist das "Trust Root",
# die Root-URL der Anwendung, für welche der Benutzer sich authentisieren soll.
# Zweiter Parameter ist die Adresse der complete Action (siehe Routes oben)
if oidreq.send_redirect?(home_url, openid_complete_url)
redirect_to oidreq.redirect_url(home_url, openid_complete_url)
else
@form_text = oidreq.form_markup(home_url, openid_complete_url, false, { 'id' => 'checkid_form' })
end
end
# Die complete-Action nimmt die Antwort des OpenID-Servers entgegen
# und verarbeitet die übergebenen Attribute
def complete
oidparams = params.reject{ |k,v| request.path_parameters[k] }
oidresp = openid_consumer.complete(oidparams, url_for({}))
# Überprüfung ob die Antwort vom OpenID-Server des Fachbereich 3 kommt. Wenn auch andere
# Identity Provider zulässig sein sollen, dann muss diese Abfrage entfernt werden.
if oidresp && oidresp.endpoint && oidresp.endpoint.server_url.match(identity_provider)
# Wenn die Anfrage bestätigt wurde, wird der Benutzer anhand der OpenID identifiziert
if oidresp.status == OpenID::Consumer::SUCCESS
@user, data = User.find_or_initialize_by_identity_url(oidresp.display_identifier), {}
# Wenn Attribute übergeben wurden, werden diese dem lokalen Benutzerkonto zugewiesen.
# An dieser Stelle ist es relativ anwendungsspezifisch, welche Daten angefordert wurden
# und wie diese auf die Attribute des Benutzerkontos mappen, daher ist an dieser Stelle
# Anpassungsarbeit erforderlich.
if ax_resp = OpenID::AX::FetchResponse.from_success_response(oidresp)
data[:login] = ax_resp.data['http://openid.tzi.de/spec/schema/uid'][0] unless ax_resp.data['http://openid.tzi.de/spec/schema/uid'][0].blank?
data[:email] = ax_resp.data['http://openid.tzi.de/spec/schema/mail'][0] unless ax_resp.data['http://openid.tzi.de/spec/schema/mail'][0].blank?
data[:firstname] = ax_resp.data['http://openid.tzi.de/spec/schema/givenName'][0] unless ax_resp.data['http://openid.tzi.de/spec/schema/givenName'][0].blank?
data[:lastname] = ax_resp.data['http://openid.tzi.de/spec/schema/surName'][0] unless ax_resp.data['http://openid.tzi.de/spec/schema/surName'][0].blank?
data[:phone] = ax_resp.data['http://openid.tzi.de/spec/schema/telephoneNumber'][0] unless ax_resp.data['http://openid.tzi.de/spec/schema/telephoneNumber'][0].blank?
data[:homepage] = ax_resp.data['http://axschema.org/contact/web/default'][0] unless ax_resp.data['http://axschema.org/contact/web/default'][0].blank?
elsif sreg_resp = OpenID::SReg::Response.from_success_response(oidresp)
data[:login] = sreg_resp.data['nickname'] unless sreg_resp.data['nickname'].blank?
data[:email] = sreg_resp.data['email'] unless sreg_resp.data['email'].blank?
data[:fullname] = sreg_resp.data['fullname'] unless sreg_resp.data['fullname'].blank?
end
# Lokales Benutzerkonto ggf. mit den Daten vo Identity Provider abgleichen
@user.update_attributes(data) unless data.empty?
# Den Benutzer einloggen, sofern ein lokales Benutzerkonto eingerichtet werden konnte.
# Die Funktionen dazu (logged_in?, successful_login, etc.) sind eher als Pseudocode zu
# sehen, da diese anwendungsspezifisch implementiert werden müssen.
self.current_user = @user unless @user.new_record?
logged_in? ? successful_login : failed_login('Das Login mit OpenID ist fehlgeschlagen, da erforderliche Daten nicht freigegeben wurden.')
else
failed_login('Das Login mit OpenID ist fehlgeschlagen.')
end
else
failed_login("Es werden nur OpenIDs von #{identity_provider} akzeptiert.")
end
end
private
# Die Daten der OpenID-Transaktionen werden in diesem Fall im Dateisystem abgelegt.
# Sollte dies nicht erwünscht sein, kann auch ein anderer Store-Mechanismus angegeben
# werden - näheres dazu findet sich in der Dokumentation des ruby-openid Gems unter:
# http://www.openidenabled.com/ruby-openid/
def openid_consumer
store = OpenID::Store::Filesystem.new("#{RAILS_ROOT}/tmp/openid")
@openid_consumer ||= OpenID::Consumer.new(session, store)
end
# Die URL des Identity Providers - in diesem Fall die URL
# des OpenID-Servers des Fachbereich 3 der Uni Bremen
def identity_provider
'https://openid.tzi.de/'
end
end