Warning: include_once(/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php): failed to open stream: No such file or directory in /var/www/html/fields/dbp13/local/config.php on line 4

Warning: include_once(): Failed opening '/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php' for inclusion (include_path='.:/opt/php/lib/php') in /var/www/html/fields/dbp13/local/config.php on line 4

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/fields/dbp13/local/config.php:4) in /var/www/html/pmwiki-2.2.86/pmwiki.php on line 1250
Datenbankpraktikum SS 2013 - MMOG - Spieler Allianzen Und Nachrichten User

Nutzerverwaltung

René Helmke

Problemstellung

Unsere Primäraufgabe war es, eine Userverwaltung zu implementieren. Spieler sollten sich mit einem Usernamen, einem Passwort und einer E-Mail Adresse registrieren und dann auf einen internen Bereich, nämlich das Spiel aus Sicht des Nutzers, zugreifen können. Auf Basis des Spielverhaltens der Nutzer sollte eine Bestenliste entstehen, die den Spieler zum Weiterspielen animieren sollte.

Planung

Datenbankebene

Da die Gestaltung unseres User-Models bezüglich Attributen und Beziehungen stark von den anderen Gruppen abhing, haben wir eine Vorabversion des ER-Diagrammes, mit der wir vorerst arbeiteten, entwickelt:


Abb.1: Relationales Schema der Benutzer

Die Attribute username, email, password und score ergaben sich aus den Anforderungen der Problemstellung. Wir haben uns mit den anderen Gruppen beraten, welche Ressourcen für den Spieler global erreichbar sein und somit in Form von Attributen an dem User hängen sollten. Hierbei sind wir zu dem Entschluss gekommen, dass wir lediglich money als global zugängliche Ressource implementieren werden. Das Feld last_activity sollte später den Zeitstempel der letzten Aktivität des Nutzers aufzeichnen um in Erfahrung zu bringen, ob der Nutzer gerade online oder offline ist.

Zu diesem Zeitpunkt war uns bereits bewusst, dass wir das Authentifizierungsframework Devise für die Implementierung benutzen werden. Dazu aber erst später mehr. Aufgrund dieser Tatsache ergänzte sich unser ER-Diagramm um einige Attribute, die intern von Devise verwaltet werden:


Abb.1: Relationales Schema der Benutzer mit Devise-Attributen
  • remember_created_at: Beinhaltet den Zeitpunkt, an dem der Nutzer das Feature 'Remember Me' aktiviert hat.
  • sign_in_count: Zählt die Anzahl der Logins des Nutzers.
  • current_sign_in_at: Beinhaltet den Zeitpunkt, an dem sich der Nutzer momentan eingeloggt hat.
  • last_sign_in_at: Beinhaltet den Zeitpunkt, an dem sich der Nutzer das letzte mal eingeloggt hat.
  • current_sign_in_ip: Speichert die IP des Nutzers, unter der er sich momentan eingeloggt hat.
  • last_sign_in_ip: Speichert die IP des Nutzers, unter der er sich das letzte mal eingeloggt hat
  • encrypted_password: Unser Passwort-Attribut musste umbenannt werden, da Devise den Attributnamen encrypted_password verlangt. Hier wird das Passwort in Form eines kryptologischen bcrypt-Hashes hinterlegt.

Applikationsebene

Da ein Nutzerverwaltungssystem unabsehbare Ausmaße annehmen kann, haben wir uns nach einem bereits fertigen Framework umgesehen. Wir haben uns für das Projekt Devise entschieden.


Abb.3: Devise Logo

Devise ist ein Open-Source Authentifizierungsframework für Ruby on Rails, welches von der brasilianischen Softwarefirma Plataformatec koordiniert wird. Es ist zu beinahe 100% in Ruby geschrieben und liegt zu diesem Zeitpunkt als Gem in der Version 3.0.2 vor. Das Framework kommt mit einer Reihe von vordefinierten Views, Controllern und Generatoren, die uns einiges an Arbeit bei der Implementation abnehmen sollten. Devise ist nach dem Baukastenprinzip bestehend aus 10 Modulen aufgebaut. Für uns waren folgende davon relevant:

  • Database Authenticatable: Dieses Modul stellt die entsprechenden Views und Controllermethoden zur Nutzerauthentifizierung zur Verfügung. (Nutzer sollen sich einloggen können)
  • Registerable: Dieses Modul stellt einen Registrierungsprozess bereit. (Nutzer sollen sich registrieren können)
  • Rememberable: Mit diesem Modul wird das allgemein bekannte Feature "Remember Me" implementiert.
  • Trackable: Ergänzt Devise um Logging-Funktionen. (Wir haben es als notwendig empfunden, detaillierte Informationen wie die IP-Adresse über Nutzer zu speichern)
  • Validatable: Kümmert sich um die Validierung von Nutzereingaben wie beispielsweise der E-Mail-Adresse

Mit Hilfe von Devise führt das Hinzufügen der Zeile

  before_filter :authenticate_user!

in einem beliebigen Controller dazu, dass die komplette Controllerfunktionalität nur dem registrierten und authentifizierten Nutzer zugänglich ist. Außerdem können in den Views und den Controllern nützliche Methoden und Helper benutzt werden:

  • current_user: liefert das Objekt des Users zurück, der gerade einen bestimmten Controller oder eine bestimmte View benutzt.
  • user_signed_in?: überprüft, ob der Nutzer eingeloggt ist
  • user_session: gibt die momentane Nutzersession zurück.

Implementierung

Bevor wir mit der Implementierung beginnen konnten, mussten wir uns mit Devise vertraut machen und haben uns mit Hilfe der beiliegenden README-Datei eingearbeitet.

Konfiguration

Als erstes mussten wir den zugehörigen Gem in das Gemfile eintragen

  gem 'devise'

und danach ein

  bundle install

im Projektordner ausführen. Der nächste Schritt war das installieren von Devise in das aktuelle Projekt. Hierzu musste folgendes im Projektordner ausgeführt werden:

  rails generate devise:install

Dieses Kommando erstellt einen Initializer namens devise.rb in /config/initializers. Dieser enthält alle Konfigurationsoptionen für Devise, die vorgenommen werden müssen.

Innerhalb der devise.rb mussten wir einige Änderungen vornehmen, um der Aufgenstellung gerecht zu werden. So war von uns verlangt, dass sich der Nutzer über seinen Usernamen einloggen kann. Standardmäßig benutzt das Framework die E-Mail-Adresse.

  config.authentication_keys = [ :username ]

Nutzernamen und E-Mails sollten also einzigartig sein. Damit sich Nutzer nicht mit dem gleichen Usernamen oder der gleichen E-Mail in unterschiedlicher Schreibweise anmelden konnten, musste folgende Konfiguration vorgenommen werden:

  config.case_insensitive_keys = [ :email, :username ]

Außerdem dürfen weder in E-Mail noch im Username Whitespaces vorhanden sein:

  config.strip_whitespace_keys = [ :email, :username ]

Alle anderen Standardeinstellungen haben wir als akzeptabel eingestuft.

Als nächstes wurde das User-Scaffold mit Hilfe des Devise-Generators erstellt:

  rails generate devise User username:string money:integer score:integer last_activity:date

Model

Im folgenden werde ich nur die Methoden im User-Model beschreiben, die von unserer Seite aus für die Funktionalität des Systemes notwendig sind. Im weiteren Verlauf des Praktikums sind sehr viele Getter- und Setter-Methoden von anderen Gruppen hinzugefügt worden.

Als erstes mussten wir in der user.rb die oben erwähnten Module aktivieren.

  devise :database_authenticatable, :registerable, :rememberable, :trackable, :validatable

Bei Nutzererstellung sollte eine in den Optionen vordefinierte Menge an Geld gesetzt werden. Dieses wurde mit einem after_create-Callback realisiert:

  after_create :set_initial_money
  [...]
  private
    def set_initial_money(initial=GameSettings.get("INITIAL_BUDGET"))
      if self.money==0
        self.money=initial
        self.save
      end
    end

Hierbei ist zu beachten, dass das Geld nur gesetzt wird, wenn andere Entwickler über den create-Befehl in der Railskonsole oder den Seeds keinen initialen Wert für money übergeben haben.

Da wir zu unserer Schande nicht wussten, wie wir den zur Erstellungszeit aktuellen Timestamp automatisch in die Datenbank einpflegen konnten und der Timestamp an unserem last_activity-Attribut auch nicht null sein durfte, haben wir hierfür ebenfalls ein after_create-Callback erstellt:

  after_create :set_initial_activity
  [...]
  def set_initial_activity()
    self.last_activity=Time.now
    self.save
  end

Entsprechend hierzu die Methode, um später den Onlinestatus des spezifischen Nutzers abzufragen:

  
  def online?
    self.last_activity>15.seconds.ago
  end

Außerdem sollten andere Gruppen den Highscore durch von ihnen bestimmte Aktionen erhöhen oder senken können:

  def add_score(value)
    update_attribute(:score, score + value)
  end

Aufgrund eines Konfigurationsfehlers von unserer Seite aus konnten sich Nutzer ohne Usernamen registrieren. Für die Lösung dieses Problemes haben wir eine Validiering eingepflegt:

  validate :is_username_set               
  [...]
  def is_username_set
    if username.blank?
      errors.add :username, "can't be blank"
    end
  end

Controller

Der Devisecontroller kann nicht eingesehen werden, ist zeitgleich aber auch nicht final. Somit kann ein anderer Controller von ihm erben, um Funktionalität zu ergänzen oder Methoden zu überschreiben. Da die Funktionalität des Controllers für unsere Zwecke vollkommen ausreichend war, haben wir auf Controllerebene keine Änderungen vorgenommen.

Lediglich im ApplicationController des Projektes mussten einige Anpassungen gemacht werden. So musste das Attribut username zu den Strong-Parametern hinzugefügt werden:

  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up)<<:username
  end

Außerdem wollten wir, wie oben beschrieben, zuverlässige Aussagen über den Onlinestatus des Nutzers treffen können. Mit der zuvor beschriebenen Methode online()? haben wir entschieden, dass ein Nutzer dann als online zu betrachten ist, wenn er in den letzten 15 Sekunden Aktivität gezeigt hat. Uns war allerdings ein Rätsel, wie wir dieses bewerkstelligen konnten, da ein Nutzer ja auch eingeloggt und anwesend sein kann, ohne Anfragen an den Server zu senden. Dieses Problem haben wir vorerst beiseite gelegt, da es keine so hohe Priorität hatte. Wir brauchten für die Aktualisierung des last_activity-Feldes eine Callback-Methode in dem ApplicationController, die nach jeder Anfrage die entsprechende Operation für eine Aktualisierung ausführt:

  after_filter :user_activity
  [...]
  private
  def user_activity
    if current_user!=nil #only set if user is logged in
      current_user.last_activity=Time.now
      current_user.save
    end
  end

Ironischerweise hat sich das oben geschilderte Problem somit von alleine gelöst, da wir in anderen Bereichen, wie beispielsweise dem Nachrichtensystem, über ein Javascript im fünf-Sekunden-Takt ein JSON-Objekt abrufen, welches Aktualisierungen des Posteinganges beinhält. Somit konnten wir in Zukunft qualifizierte Aussagen über den Onlinestatus des Nutzers treffen. Dies war ein gern gesehenes Feature für unsere Freundesliste.

View

Devise generiert bei der Installation alle Views, die benötigt wurden. Da von uns allerdings erwartet wurde, dass der Nutzer sich mit einem Usernamen registrieren und einloggen können soll, mussten die Login- und die Registrierungsview verändert werden. Als wir diese verändern wollten, stellte sich heraus, dass keine Views für diese in dem Projektordner vorhanden waren. Nach einiger Recherche stellte sich heraus, dass die erforderlichen Views an einem uns unbekannten Ort hinterlegt sind. Man kann sich allerdings den ganzen Bestand an Views von Devise in den Views-Ordner generieren lassen um die von Haus aus mitgelieferten zu überschreiben. Dieses funktioniert mit folgendem Befehl im Projektordner:

  rails generate devise:views

Im Folgenden haben wir die Klassen der HTML-Elemente der Views mit optisch ansprechenderen Klassen aus der Twitter Bootstrap-Bibliothek ausgetauscht. Da, wie bereits erwähnt, Devise standardmäßig die E-Mail-Adresse des Nutzers als Authentifizierungsgrundlage benutzt, mussten wir dieses Element in der Login View austauschen:

  <div>
    <%= f.label :username %><br />
    <%= f.text_field :username, :autofocus => true %>
  </div>

Dieses klappte nicht auf anhieb, da wir zu diesem Zeitpunkt vergessen hatten, username in die Liste der Strong-Parameter aufzunehmen.


Abb.4: Unsere modifizierte Login View

Außerdem mussten wir ein username-Feld in der Registrierungs View hinzufügen:

  <div>
    <%= f.label :username %><br />
    <%= f.text_field :username, :autofocus => true %>
  </div>


Abb.5: Unsere modifizierte Registrierungs View

Wir haben es als lästig empfunden, dass der Nutzer immer erst eine bestimmte View ansteuern muss, um sich zu registrieren oder einzuloggen. Deshalb haben wir uns dazu entschlossen, auf der rechten Seite der Applikationsview ein kompaktes Loginfenster zu implementieren:

application.html.erb:

  <div class="well">
  <% if user_signed_in? %>
    Willkommen zurück, <%= current_user.username %>!
    <%= button_to 'Ausloggen', destroy_user_session_path , :method=>:delete, class: 'btn btn-info' %>
  <% else %>
    <%= form_for("user", :url => user_session_path) do |f| %>
      <%= f.text_field :username, class: 'span2 pagination-centered', placeholder: 'username' %>
      <%= f.password_field :password, class: 'span2 pagination-centered', placeholder: 'password' %>
      <%= f.check_box :remember_me %>
      <div style="font-size:12px;">Eingeloggt bleiben</div>
      <%= f.submit 'Einloggen', class: 'btn' %>
    <% end %>
    <%= button_to 'Registrieren', '/users/sign_up' , :method=>:get, class: 'btn btn-info' %>
  <% end %>
  </div>

Je nachdem, ob der Nutzer momentan eingeloggt ist oder nicht, wird er begrüßt oder dazu aufgefordert, sich zu registrieren oder einzuloggen. Dieses Fenster erscheint in jeder View dieses Projektes.


Abb.6: Neu erstellte, eingebettete Login-View. Nutzer ist nicht eingeloggt.


Abb.7: Neu erstellte, eingebettete Login-View. Nutzer ist eingeloggt

Zum Schluss haben wir den Sichtbarkeitsbereich des Navigationsmenüs auf der linken Seite der Application View eingeschränkt. So sollten nur eingeloggte Nutzer alle Navigationspunkte sehen können:


Abb.8: Navigationsmenü im ausgeloggten Zustand


Abb.9: Navigationsmenü im eingeloggten Zustand

Fazit

Der Einsatz des Authentifizierungsframeworks Devise war unserer Meinung nach die beste Grundlage für die oben erläuterte Problemstellung. Hätten wir mit unserem Erfahrungsgrad dieses Problem ohne die Hilfe eines Frameworks lösen müssen, wären wir niemals in den für das Praktikum vorgesehen 3 Wochen mit der Implementierung der Nutzerverwaltung fertig geworden. Devise hat uns mit hoher Modularität und großem Funktionsumfang alles in die Arme gespielt, was für unseren Bedarf notwendig war. Als problematisch stellte sich die Einarbeitung in das Framework dar. Dadurch, dass wir nicht wirklich mit Rails 4 vertraut waren und einige gewöhnungsbedürftige Änderungen im Vergleich zu Rails 3 vorgenommen wurden (wie z.B. Strong-Parameters), schieben wir die anfänglichen Probleme allerdings auf unser Unwissen.


Page last modified on August 23, 2013, at 04:20 PM