Skip to content

Commit

Permalink
Opera cambio de clave
Browse files Browse the repository at this point in the history
  • Loading branch information
vtamara committed Mar 29, 2017
1 parent 49ea065 commit 72e6795
Show file tree
Hide file tree
Showing 13 changed files with 232 additions and 76 deletions.
8 changes: 4 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ GEM
tzinfo (~> 1.1)
arel (7.1.4)
bcrypt (3.1.11)
bindex (0.5.0)
bootstrap-datepicker-rails (1.6.4.1)
railties (>= 3.0)
builder (3.2.3)
Expand Down Expand Up @@ -108,7 +109,6 @@ GEM
sass-rails (< 5.1)
sprockets (< 4.0)
concurrent-ruby (1.0.5)
debug_inspector (0.0.2)
devise (4.2.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
Expand Down Expand Up @@ -243,14 +243,14 @@ GEM
tzinfo
tzinfo (1.2.3)
thread_safe (~> 0.1)
uglifier (3.1.10)
uglifier (3.1.11)
execjs (>= 0.3.0, < 3)
warden (1.2.7)
rack (>= 1.0)
web-console (3.4.0)
web-console (3.5.0)
actionview (>= 5.0)
activemodel (>= 5.0)
debug_inspector
bindex (>= 0.4.0)
railties (>= 5.0)
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
Expand Down
63 changes: 45 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,58 @@
Este es un motor para autenticar con directorio LDAP y realizar operaciones
básicas de adminsitración de usuarios y grupos

# Invariatnes

* El directorio LDAP es autoridad respecto a identificación y autenticacion
- Se usa prioritariamente
- No puede escribirse aunque un usuario puede cambiar su clave
- Quien registra un usuario debe emplear el nombre preciso de nuevas
personas como aparece en el documento de identificación principal
* RFC2307 https://www.ietf.org/rfc/rfc2307.txt

# Características

* Esta gema emplea Devise y la tabla usuario de sip, pone la estrategía
```ldap_authenticable``` después de la estrategía que usa cookies
```rememberable``` y antes de la estrategía que usa los datos de la base
(```database_authenticable```),
de forma que primero se usan las credenciales almacenadas en cookies,
después las del directorio LDAP y si el usuario es nuevo o no se puede
establecer la conexión con el directorio LDAP se usan las que estén
almacenadas en la base de datos (más parecido a la forma como hace un
cliente en un dominio Windows que a la forma que usa GLPI al usar un
directorio LDAP).
* Tras cada conexión exitosa autenticado por LDAP se actualizan datos del
usuario de la base de datos a partir de los del directorio LDAP (si el
usuario no existía se crea en la base de datos incluyendo la clave
--condensado criptográfico-- para permitir ingresos futuros aún con
disrupción del LDAP).
* Dado que se almacenan condensados en LDAP y en base no es posible
sincronizar estas. Pero sin sincronización no es posible determinar
si un usuario ha sido deshabilitado (la convención en LDAP es dejar la
clave en blanco para borrar el atributo userpassword).
Tarea: asegurar:
si el ldap opera pero el usuario no existe
es diferente a existe pero la clave es errada?

después las del directorio LDAP y si el usuario no está en el LDAP o si
no se puede establecer la conexión con el directorio LDAP se usan las
que estén almacenadas en la base de datos (más parecido a la forma como
hace un cliente en un dominio Windows que a la forma que usa GLPI al usar un
directorio LDAP). Así que la prioridad la tiene el directorio LDAP
mientras esté disponible, pero se usa base de datos local para respaldar
el LDAP y para permitir usuarios que no estén en el LDAP.

* Tras cada conexión con el directorio LDAP para autenticar un usuario
se actualizan datos del usuario de la base de datos (siempre y cuando
el usuario esté en el directorio LDAP). Esto se hace bien si la clave
es correcta como si no. Cuando es exitosa el condensando bcrypt de la
clave también se almacena en la base de datos (para permitir ingresos
futuros si hay disrupción del LDAP). Este tipo de operación (búsqueda
y extración de datos de un usuario) requiere privilegios especiales
en LDAP por lo que la aplicación debe tener configurado un usuario con
estos privilegios

* Sería bueno tener bitácora de conexiones e intentos. Junto con cada
usuario mantener fecha de la última sincronización exitosa desde el
LDAP.

* La forma de deshabilitar usuarios es desde el directorio LDAP
dejando la clave en blanco (es decir eliminando el atributo userPassword).
Cuando se sincroniza LDAP en base de un usuario, a los deshabilitados
se les pone fecha de deshabilitación.

* La aplicación permite cambiar clave a un usuario, este cambio se intenta
primero en el directorio LDAP mientras el usuario esté activo (si no
está activo debe sincronizar y sacar de la cuenta al usuario). Si el
usuario está en el directorio LDAP se pone la nueva clave y en
base de datos. Si el usuario no está en el directorio LDAP sólo se
actualiza clave en base de datos.


* Grupos ....

# Configuración

Expand Down
2 changes: 2 additions & 0 deletions TAREAS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Implementar políticas de claves. Ver por ejemplo
https://www.isode.com/whitepapers/password-policy.html
29 changes: 29 additions & 0 deletions app/controllers/jn316_gen/registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# encoding: utf-8

require 'devise/registrations_controller'

class Jn316Gen::RegistrationsController < ::Devise::RegistrationsController
include Jn316Gen::LdapHelper

def update
if params[:usuario] && params[:usuario][:password] &&
params[:usuario][:password_confirmation] &&
params[:usuario][:current_password] &&
params[:usuario][:password] != '' &&
params[:usuario][:password_confirmation] == params[:usuario][:password]
prob = ''
if ldap_cambia_clave(current_usuario.nusuario,
params[:usuario][:current_password],
params[:usuario][:password], prob)
flash[:notice] = 'Clave cambiada en directorio LDAP'
else
flash[:error] = 'No pudo cambiar clave en directorio LDAP: ' + prob
redirect_to Rails.configuration.relative_url_root
end
end
super
rescue Exception => exception
redirect_to Rails.configuration.relative_url_root
end

end
49 changes: 49 additions & 0 deletions app/helpers/jn316_gen/ldap_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#encoding: UTF-8

require 'digest/sha1'
require 'base64'

module Jn316Gen
module LdapHelper

# https://www.ietf.org/rfc/rfc4514.txt
def escapa_ldap

end

def ldap_cambia_clave(nusuario, claveactual, nuevaclave, prob)
if !ENV['JN316_CLAVE']
prob='Falta clave LDAP para cambiar clave'
return nil
end
opcon = {
host: Rails.application.config.x.jn316_servidor,
port: Rails.application.config.x.jn316_puerto,
auth: {
method: :simple,
username: "cn=#{nusuario},#{Rails.application.config.x.jn316_base}",
password: claveactual
}
}.merge(Rails.application.config.x.jn316_opcon)
ldap_con = Net::LDAP.new( opcon )
if !ldap_con.bind
prob = 'Clave actual errada'
return false
end
ldap_con.open do |ldap|
dn="cn=#{nusuario},#{Rails.application.config.x.jn316_base}"
hash = "{SHA}" + Base64.encode64(
Digest::SHA1.digest(nuevaclave)
).chomp!
puts 'userPassword: '+hash+"\n"
ldap.replace_attribute dn, :userPassword, hash
end
return true
rescue Exception => exception
prob = 'Problema conectando a servidor LDAP. Excepción: ' +
exception.to_s
return false
end

end
end
22 changes: 1 addition & 21 deletions app/models/jn316_gen/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,7 @@ def tablasbasicas
# Hereda tablasbasicas_prio de sip

def acciones_plantillas
{'Listado de casos' =>
{
campos: [
'ultimaatencion_mes', 'ultimaatencion_fecha',
'contacto_nombres', 'contacto_apellidos',
'contacto_identificacion',
'contacto_genero', 'contacto_edad', 'contacto_etnia',
'beneficiarios_0_5', 'beneficiarios_6_12',
'beneficiarios_13_17', 'beneficiarios_18_26',
'beneficiarios_27_59', 'beneficiarios_60_',
'beneficiarias_0_5', 'beneficiarias_6_12',
'beneficiarias_13_17', 'beneficiarias_18_26',
'beneficiarias_27_59', 'beneficiarias_60_',
'ultimaatencion_derechos', 'ultimaatencion_as_humanitaria',
'ultimaatencion_as_juridica', 'ultimaatencion_otros_ser_as',
'oficina'
],
controlador: 'Sivel2Sjr::CasosController#index',
unregistro: false # Son muchos registros debe iterarse
}
}
{}
end

end
Expand Down
2 changes: 1 addition & 1 deletion app/views/devise/registrations/edit.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h2>Editar <%= resource_name.to_s.humanize %> <%= @usuario.nusuario %></h2>
<h2>Cambiar clave de <%= @usuario.nusuario %></h2>

<%= simple_form_for(resource, :as => resource_name,
:url => registro_usuario_path(resource_name),
Expand Down
112 changes: 83 additions & 29 deletions config/initializers/ldap_authenticatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class LdapAuthenticatable < Authenticatable

# Busca usuario nusuario como admin y retorna toda la información
def ldap_busca_como_admin(nusuario, prob)
prob = ""
if !ENV['JN316_CLAVE']
prob='Falta clave LDAP para buscar'
return nil
Expand Down Expand Up @@ -45,13 +44,69 @@ def ldap_busca_como_admin(nusuario, prob)
end


def actualiza_usuario(usuario, ldapus, clave=nil)
usuario.nombres=ldapus.givenname if ldapus.givenname
usuario.apellidos=ldapus.sn if ldapus.sn
usuario.email=ldapus.mail if ldapus.mail
usuario.email=ldapus.mail[0] if is_array?(ldapus.mail)
def actualizar_usuario(usuario, ldapus, prob, clave=nil)
usuario.nombres = ldapus.givenname if ldapus.givenname
usuario.apellidos = ldapus.sn if ldapus.sn
usuario.email = ldapus.mail if ldapus.mail
usuario.email = ldapus.mail[0] if ldapus.mail.kind_of?(Array)
if (ldapus.userPassword.nil? && usuario.fechadeshbilitacion.nil?)
# deshabilitar
usuario.fechadeshabilitacion = Date.today
else
# habilitado, guardar clave si hay
unless clave.nil?
usuario.encrypted_password = BCrypt::Password.create(
clave,
{:cost => Rails.application.config.devise.stretches}
)
end
end
usuario.ultimasincldap = Date.today
usuario.save
if (usuario.errors.messages.length > 0)
prob = usuario.errors.messages.to_s
return nil
end
return usuario
end


def crear_usuario_min(nusuario, ldapus, prob, clave)
email = nusuario + '@porcompletar.org'
email = ldapus.mail if ldapus.mail
email = ldapus.mail[0] if ldapus.mail.kind_of?(Array)
ep = 'x'
ep = BCrypt::Password.create( clave, {
cost: Rails.application.config.devise.stretches}) if !clave.nil?
usuario = Usuario.create(
nusuario: nusuario,
email: email,
encrypted_password: ep,
fechacreacion: Date.today
)
if (usuario.errors.messages.length > 0)
prob = usuario.errors.messages.to_s
return nil
end

return usuario
end


# crea un usuario y/o actualizarlo si ya existe
def crear_actualizar_usuario(nusuario, ldapus, prob, clave = nil)
usuario = Usuario.where(nusuario: nusuario).take
if usuario.nil?
usuario = crear_usuario_min(nusuario, ldapus, prob, clave)
if usuario.nil?
prob = 'No pudo crear usuario: ' + prob
return nil
end
end
return actualizar_usuario(usuario, ldapus, prob, clave)
end


def authenticate!
if params[:usuario]
nusuario = limpia_usuario(params[:usuario][:nusuario])
Expand All @@ -66,47 +121,46 @@ def authenticate!
password: password
}
}.merge(Rails.application.config.x.jn316_opcon)
prob = ""
ldap_con = Net::LDAP.new( opcon )

if ldap_con.bind
usuario = Usuario.find_or_create_by(nusuario: nusuario)
ldapus = ldap_busca_como_admin(nusuario, prob)
actualiza_usuario(usuario, ldapus, password)

if (!usuario.email) then
usuario.email = 'x' # aqui completar registro de usuario con información de LDAP considerar tambien el tema de sincronizar
usuario = crear_actualizar_usuario(
nusuario, ldapus, prob, password)
if usuario.nil?
prob = "No pudo crear/actualizar usuario en base de datos" + prob
puts prob
self.errors.add :nusuario, prob
fail(prob)
@halted = true
return nil
end
success!(usuario)
success!(usuario)
else
# No se logró autenticar, bien porque el usuario no existe
# No se logró autenticar, bien porque el usuario no existe en LDAP
# o bien porque la clave es errada
#byebug
prob = ""
ldapus = ldap_busca_como_admin(nusuario, prob)
unless ldapus.nil?
usuario = Usuario.find_or_create_by(nusuario: nusuario)
actualiza_usuario(usuario, ldapus)
if ldapus.userpassword == '' &&
usuario.fechadeshabiltiacion == ''
# si está deshabilitado en LDAP pero no en base
# también deshabilita en base
usuario.fechadeshabilitacion = Date.today
end
# el usuario existe en el LDAP, crear o actualizar en base
# pero sin clave porque fue errada
usuario = crear_actualizar_usuario(nusuario, ldapus, prob)
prob = "No pudo autenticar: " + prob
puts prob
self.errors.add :nusuario, prob
fail(:invalid_login)
fail(prob)
@halted = true
return nil
end

# No se encontró usuario en LDAP pasar a usuarios locales en base
return pass
end
end
rescue Exception => exception
puts "No pudo conectarse a servido LDAP"
fail('No pudo conectarse a servidor LDAP')
self.errors.add :nusuario, "Servidor LDAP no opera?"
rescue Exception => exception
prob = "¿Opera el servidor LDAP? " + prob + ' Excepción: ' +
exception.to_s
puts prob
self.errors.add :nusuario, prob
return pass
end

Expand Down
5 changes: 5 additions & 0 deletions config/locales/devise.es.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
es:
devise:
failure:
usuario:
invalid_login: 'Autenticación falló'
5 changes: 5 additions & 0 deletions db/migrate/20170328172001_agrega_fecha_sinc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AgregaFechaSinc < ActiveRecord::Migration[5.0]
def change
add_column :usuario, :ultimasincldap, :date
end
end
Loading

0 comments on commit 72e6795

Please sign in to comment.