miguel@debian:~/rails_app$ su -
Password:
debian:~# gem install -y ruby-openid
Bulk updating Gem source index for: http://gems.rubyforge.org
Successfully installed ruby-openid-1.1.4
Installing ri documentation for ruby-openid-1.1.4...
Installing RDoc documentation for ruby-openid-1.1.4...
Esta gem es una implementación de la especificación de OpenID en Ruby. Además de esto necesitaremos también el plugin de rails que nos evita el tener que escribir código de autenticación que haga uso del gem directamente. En este punto tenemos varias opciones:
- openid_login_generator de JanRain (gem install openid_login_generator)
- openid_consumer de Eastmedia. Este fue el primer plugin creado para RoR y está basado en LoginGenerator.
- restful_open_id_authentication también de Eastmedia. Este es un rewrite de openid_consumer que además integra restful_authentication. Si no existiera el siguiente plugin, este sería el recomendado
- open_id_authentication del core team de RoR. Este plugin puede usarse en conjunto con Acts_as_authenticated o con restful_authentication. Al estar creado por los mismos individuos de RoR hay muchas posibilidades de que tenga mejor soporte y mayor cantidad de usuarios. Este es el plugin que usaremos en nuestra aplicación
miguel@debian:~/rails_app$ ./script/plugin install
http://svn.rubyonrails.org/rails/plugins/open_id_authentication/
Este plugin crea un par de migraciones que debemos crear y aplicar:
miguel@debian:~/rails_app$ rake open_id_authentication:db:create
miguel@debian:~/rails_app$ rake db:migrate
Las tablas creadas por la migración son necesarias para la autenticación por medio de OpenID.
Debemos modificar nuestra tabla de usuarios para que se almacenen la información manejada por OpenID.
Creamos una migración:
miguel@debian:~/rails_app$ ./script/generate migration add_openid_fields
exists db/migrate
create db/migrate/003_add_openid_fields.rb
y ponemos el siguiente código:
class AddOpenidFields < ActiveRecord::Migration
def self.up
add_column "usuarios", "nombre", :string
add_column "usuarios", "identity_url", :string
end
def self.down
remove_column "usuarios", "nombre"
remove_column "usuarios", "identity_url"
end
end
Ejecutamos la migración para que se agreguen las columnas:
miguel@debian:~/rails_app$ rake db:migrate
Este plugin necesita que la acción de autenticación permita operaciones POST y GET. Debido a que estamos usando restful_authentication necesitamos permitir esto explícitamente en config/routes.rb. Agregamos el siguiente map justo antes de los demás maps que insertamos antes:
map.open_id_complete 'sesion', :controller => "sesion",
:action => "create", :requirements => { :method => :get }
Después de esto tenemos que hacer varias modificaciones a nuestro código. No son complicadas una vez que las entiendes. Estas instrucciones están basadas en las señaladas en el README que viene con open_id_authentication, junto con las instrucciones de Ben Curtis.
Copiaré aquí las versiones finales de los archivos que hay que modificar. El código es claro con un poco de estudio:
app/controllers/sesion_controller.rb
class SesionController < ApplicationController
skip_before_filter :login_required
def new
end
def create
if using_open_id?
open_id_authentication(params[:openid_url])
else
password_authentication(params[:login], params[:password])
end
end
def destroy
self.current_usuario.forget_me if logged_in?
cookies.delete :auth_token
reset_session
flash[:notice] = "Ha salido del sistema."
redirect_back_or_default('/')
end
protected
def password_authentication(name, password)
if self.current_usuario = Usuario.authenticate(params[:login], params[:password])
successful_login
else
failed_login "Lo sentimos, usuario o contraseña incorrectas"
end
end
def open_id_authentication(identity_url)
authenticate_with_open_id(identity_url,
:required => [:nickname, :email],
:optional => :fullname) do |result, identity_url, registration|
if result.successful?
if self.current_usuario = Usuario.find_or_create_by_identity_url(identity_url)
assign_registration_attributes!(registration)
if current_usuario.save
successful_login
else
failed_login "Su registro de su cuenta con OpenID falló: " +
self.current_usuario.errors.full_messages.to_sentence
end
else
failed_login "Lo sentimos, no existe usuario con esa URL de identidad (#{identity_url})"
end
else
failed_login "Lo sentimos, la verificación usando OpenID falló"
end
end
end
private
def successful_login
if params[:remember_me] == "1"
self.current_usuario.remember_me
cookies[:auth_token] = { :value => self.current_usuario.remember_token ,
:expires => self.current_usuario.remember_token_expires_at }
end
session[:usuario_id] = self.current_usuario.id
flash[:notice] = "Ingreso exitoso"
redirect_back_or_default(home_url)
end
def failed_login(message)
flash[:warning] = message
redirect_to(new_sesion_url)
end
# registration is a hash containing the valid sreg keys given above
# use this to map them to fields of your user model
def assign_registration_attributes!(registration)
model_to_registration_mapping.each do |model_attribute, registration_attribute|
unless registration[registration_attribute].blank?
self.current_usuario.send("#{model_attribute}=", registration[registration_attribute])
end
end
end
def model_to_registration_mapping
{ :login => 'nickname', :email => 'email', :nombre => 'fullname' }
end
end
app/models/usuario.rb
require 'digest/sha1'
class Usuario < ActiveRecord::Base
# Virtual attribute for the unencrypted password
attr_accessor :password
validates_presence_of :login, :email, :if => :not_openid?
validates_presence_of :password, :if => :password_required?
validates_presence_of :password_confirmation, :if => :password_required?
validates_length_of :password, :within => 4..40, :if => :password_required?
validates_confirmation_of :password, :if => :password_required?
validates_length_of :login, :within => 3..40, :if => :not_openid?
validates_length_of :email, :within => 3..100, :if => :not_openid?
validates_uniqueness_of :login, :email, :salt, :case_sensitive => false, :allow_nil => true
before_save :encrypt_password
before_create :make_activation_code
# Activates the user in the database.
def activate
@activated = true
self.attributes = {:activated_at => Time.now.utc, :activation_code => nil}
save(false)
end
def activated?
!! activation_code.nil?
end
# Returns true if the user has just been activated.
def recently_activated?
@activated
end
def not_openid?
identity_url.blank?
end
# Authenticates a user by their login name and unencrypted password. Returns the user or nil.
def self.authenticate(login, password)
u = find :first, :conditions => ['login = ? and activated_at IS NOT NULL', login] # need to get the salt
u && u.authenticated?(password) ? u : nil
end
# Encrypts some data with the salt.
def self.encrypt(password, salt)
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
end
# Encrypts the password with the user salt
def encrypt(password)
self.class.encrypt(password, salt)
end
def authenticated?(password)
crypted_password == encrypt(password)
end
def remember_token?
remember_token_expires_at && Time.now.utc < remember_token_expires_at
end
# These create and unset the fields required for remembering users between browser closes
def remember_me
remember_me_for 2.weeks
end
def remember_me_for(time)
remember_me_until time.from_now.utc
end
def remember_me_until(time)
self.remember_token_expires_at = time
self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
save(false)
end
def forget_me
self.remember_token_expires_at = nil
self.remember_token = nil
save(false)
end
protected
# before filter
def encrypt_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
self.crypted_password = encrypt(password)
end
def password_required?
not_openid? && (crypted_password.blank? || !password.blank?)
end
def make_activation_code
self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
end
end
app/views/usuarios/new.rhtml
<%= error_messages_for :usuario %>
Puedes crear una cuenta usando el formulario que esta debajo.
Si tienes una URL OpenID puedes
ingresar con OpenID en lugar de crear una cuenta.
<% form_for :usuario, :url => usuarios_path do |f| -%>
<%= f.text_field :login %>
<%= f.text_field :email %>
<%= f.password_field :password %>
<%= f.password_field :password_confirmation %>
<%= submit_tag 'Registrarse' %>
<% end -%>
app/views/sesion/new.rhtml
<% form_tag sesion_path do -%>
<%= text_field_tag 'login' %>
<%= password_field_tag 'password' %>
...o use:
<%= text_field_tag "openid_url" %>
<%= check_box_tag 'remember_me' %>
<%= submit_tag 'Ingresar', :disable_with => 'Ingresando…' %>
<% end -%>
Uno de los requisitos de este plugin es usar Rails Edge rev 6317 o posterior. Convertimos nuestra aplicación a Rails Edge:
rake rails:freeze:edge REVISION=6317
Eliminamos la siguiente línea de app/controllers/application.rb
# Pick a unique cookie name to distinguish our session data from others'
session :session_key => "_rails_app_session_id"
y agregamos la siguiente línea a config/environment.rb, después de:
config.action_controller.session = { :session_key => "_rails_app_sesion", :secret => "secreto" }
donde secreto es una palabra o frase secreta que será usada por el plugin de openid durante el intercambio con el server de OpenID encargado de autenticar a los usuarios que ingresen a nuestra aplicación.
Ahora solamente tienes que crear una cuenta en OpenID y usarla para probar que la aplicación te permita autenticarte usando tu OpenID URL.
Suerte!