Table of Contents

14h00 - Fil rouge « Parcours XPath et Manipulation du DOM »

Navigation rapide : Lundi / Mardi / Mercredi / Jeudi / Vendredi Mémos : Perl / Python / Ruby

Finalité

De plus en plus de services WEB universitaires sont aujourd'hui authentifiés via des mécanismes de Single-Sign-On tel que CAS (CAS Protocol).
Vous allez voir comment :

  1. récupérer dans un formulaire HTML l'adresse du serveur CAS auquel un serveur Sympa délègue les authentifications
  2. vous authentifier sur ce serveur CAS, en remplissant les valeurs du formulaire d'authentifiaction, pour ensuite pouvoir intéragir avec le serveur SOAP de Sympa

Enoncé

  1. Dans le fichier « sympa/a_completer.(pl|py|rb) », compléter la fonction get_sso_uri()
    1. Extraire de la variable d'instance doc, l'objet DOM correspondant au formulaire ayant la valeur « Identity_not_connect » pour son attribut « id »; Le mettre dans la variable locale form
    2. Extraire la valeur de l'attribut « action » dans la variable locale form_action
    3. Extraire tous les éléments « input » du formulaire dans la variable locale form_inputs
    4. A partir de la variable form_inputs, construire une table de hashage contenant uniquement les champs à poster. La mettre dans la variable locale post_fields.
  2. Dans le fichier « cas/a_completer.(pl|py|rb) », compléter la fonction extract_login_fields()
    1. Extraire de la variable d'instance doc, l'objet DOM correspondant au formulaire ayant la valeur « fm1 » pour son attribut « id »; Le mettre dans la variable locale form
    2. Extraire la valeur de l'attribut « action » dans la variable d'instance form_action
    3. Extraire tous les éléments « input » du formulaire dans la variable locale form_inputs
    4. A partir de la variable form_inputs, construire une table de hashage contenant uniquement les champs à poster. La mettre dans la variable d'instance post_fields.

nokogiri-xml-node.pdf

main.rb

 1: require 'rubygems' #
 2: require 'nokogiri' # pour manipuler les arbres XML
 3: require 'patron'   # client web s'appuyant sur la libcurl
 4: require 'yaml'     # pour charger le fichier d'identifiant
 5: require 'pp'       # pour le pretty-printing
 6: require 'uri'      # pour le parsing des URI
 7:
 8: require 'sympa/client'
 9:
10: Sympa::Client.new(
11:   :service_uri     => 'https://listes.example.com/sympa',
12:   :credential_file => 'cas_credential.yaml'
13: )

cas_credential.yaml

1: username: leonardo.davinci
2: password: joconde

sympa/client.rb

 1: require 'sso/cas/client'
 2: %w(a_completer get_cas_uri authenticate_session).each do |lib| require "sympa/client/#{lib}" end
 3:
 4: module Sympa; class Client; def initialize(params={})
 5:   @cookies                       = {}
 6:   @service_uri                   = params[:service_uri]
 7:   @credential_file               = params[:credential_file]
 8:   uri                            = URI(@service_uri)
 9:   @base_uri, @service_path       = "#{uri.scheme}://#{uri.host}", uri.path
10:   @session                       = Patron::Session.new
11:   @session.base_url              = @base_uri
12:   @session.headers['User-Agent'] = 'Ruby/Script'
13:   @session.enable_debug('debug/sympa_client.log')
14:   @session.insecure              = true
15:   @session.max_redirects         = 0
16: #######################################################################
17: # 4 phases:
18: #  - get_sso_uri : obtention du chemin, sur le serveur sympa, a authentifier par CAS
19: #  - get_cas_uri : interrogation de ce chemin pour obtenir l'URI du serveur CAS
20: #  - SSO::Cas::Client.new : on passe cette URI au client CAS pour l'authentification; le serveur CAS retourne un Login-Ticket
21: #    on s'authentifie aupres du serveur cas avec le Login-Ticket; le serveur CAS retourne un service-ticket
22: #  - on passe le service-ticket a apache qui va valider la session aupres du serveur CAS
23: #######################################################################
24:   get_sso_uri     # dans le fichier 'sympa/a_completer.rb'
25:   get_cas_uri
26:   cas_client = SSO::CAS::Client.new( :cas_uri => @cas_uri, :credential_file => @credential_file )
27:   lt_uri = URI(cas_client.return_uri)
28:   authenticate_session(lt_uri)
29: end; end;  end

sympa/client/a_completer.rb

 1: module Sympa; class Client; def get_sso_uri()
 2:   puts "==================================================================="
 3:   puts "= Sympa::Client.get_sso_uri()"
 4:   puts "==================================================================="
 5:   response = @session.get(@service_path)
 6:   doc      = Nokogiri::HTML(response.body)
 7: #######################################################################
 8: #
 9: # DEBUT A COMPLETER
10: #
11: #######################################################################
12:
13:
14:
15:
16:
17:
18:
19:
20:
21: #######################################################################
22: #
23: # FIN A COMPLETER
24: #
25: #######################################################################
26:   response = @session.post(form_action,post_fields)
27:   @sso_uri = response.headers['Location']
28:   puts @sso_uri
29: end; end; end

sympa/client/get_cas_uri.rb

 1: module Sympa; class Client; def get_cas_uri()
 2:   puts "==================================================================="
 3:   puts "= Sympa::Client.get_cas_uri()"
 4:   puts "==================================================================="
 5:   uri = URI(@sso_uri)
 6:   base_uri,sso_path = "#{uri.scheme}://#{uri.host}", uri.path
 7:   response = @session.get(sso_path)
 8:   @cas_uri = response.headers['Location']
 9:   puts @cas_uri
10: end; end; end

sympa/client/authenticate_session.rb

1: module Sympa; class Client; def authenticate_session(lt_uri)
2:   last_hop = [lt_uri.path,lt_uri.query].join('?')
3:   response = @session.get(last_hop)
4:   @cookies['MOD_AUTH_CAS_S'] = $1 unless "#{response.headers['Set-Cookie']}".match(/MOD_AUTH_CAS_S=([^;]*)/).nil?
5:   @session.headers['Cookie'] = @cookies.collect{|k,v| "#{k}=#{v}" }.join(';')
6:   response = @session.get(lt_uri.path)
7:   pp response.headers
8: end; end; end

sso/cas/client.rb

 1: %w(get_login_form authenticate_user a_completer).each do |lib| require "sso/cas/client/#{lib}" end
 2:
 3: module SSO; module CAS; class Client
 4:   attr_reader :return_uri
 5:   def initialize(params={})
 6:     @cas_uri                       = params[:cas_uri]
 7:     @credential_file               = params[:credential_file]
 8:     @session                       = Patron::Session.new
 9:     uri                            = URI(@cas_uri)
10:     @base_uri,@service_path        = "#{uri.scheme}://#{uri.host}:#{uri.port}", "#{uri.path}?#{uri.query}"
11:     @session.base_url              = @base_uri
12:     @session.headers['User-Agent'] = 'Ruby/Script'
13:     @session.enable_debug('debug/sso_cas_client.log')
14:     @session.insecure              = true
15:     @session.max_redirects         = 0
16: #######################################################################
17: #
18: # 3 Phases :
19: #  - récupération du formulaire de login
20: #  - extraction des champs a renvoyer
21: #  - post du formulaire avec les identifiants utilisateurs
22: #
23: #######################################################################
24:     get_login_form
25:     extract_login_fields # dans le fichier 'sso/cas/client/a_completer.rb'
26:     authenticate_user
27:   end
28: end; end; end

sso/cas/client/get_login_form.rb

1: module SSO; module CAS; class Client; def get_login_form()
2:   puts "==================================================================="
3:   puts "= SSO::CAS::Client.get_login_form()"
4:   puts "==================================================================="
5:   response = @session.get(@service_path)
6:   @doc     = Nokogiri::HTML(response.body)
7: end; end; end; end

sso/cas/client/authenticate_user.rb

 1: module SSO; module CAS; class Client; def authenticate_user(params={})
 2:   puts "==================================================================="
 3:   puts "= SSO::CAS::Client.authenticate_user(params={})"
 4:   puts "==================================================================="
 5:   credential               = YAML::load(File.open(@credential_file))
 6:   @post_fields['username'] = credential['username']
 7:   @post_fields['password'] = credential['password']
 8:   pp @post_fields
 9:   response                 = @session.post(@form_action,@post_fields)
10:   @return_uri=response.headers['Location']
11: end; end; end; end

sso/cas/client/a_completer.rb

 1: module SSO; module CAS; class Client; def extract_login_fields()
 2:   puts "==================================================================="
 3:   puts "= SSO::CAS::Client.extract_login_fields()"
 4:   puts "==================================================================="
 5: #######################################################################
 6: #
 7: # DEBUT A COMPLETER
 8: #
 9: #######################################################################
10:
11:
12:
13:
14:
15:
16:
17:
18:
19: #######################################################################
20: #
21: # FIN A COMPLETER
22: #
23: #######################################################################
24:   output = {
25:     '@form_action' => @form_action,
26:     '@form_fields' => @post_fields
27:   }
28:   pp output
29: end; end; end; end

Corrigé

sympa/client/a_completer.rb

 1: module Sympa; class Client; def get_sso_uri()
 2:   puts "==================================================================="
 3:   puts "= Sympa::Client.get_sso_uri()"
 4:   puts "==================================================================="
 5:   response = @session.get(@service_path)
 6:   doc      = Nokogiri::HTML(response.body)
 7: #######################################################################
 8: #
 9: # DEBUT A COMPLETER
10: #
11: #######################################################################
12:   form        = doc.xpath('//div[@id="Identity_not_connect"]/form')
13:   form_action = form.attribute('action').text
14:   form_inputs = form.xpath('.//input')
15:   post_fields = {}
16:   form_inputs.each{|input|
17:     name  = input.attribute('name').text
18:     value = input.attribute('value').text
19:     post_fields[name]=value
20:   }
21: #######################################################################
22: #
23: # FIN A COMPLETER
24: #
25: #######################################################################
26:   response = @session.post(form_action,post_fields)
27:   @sso_uri = response.headers['Location']
28:   puts @sso_uri
29: end; end; end

sso/cas/client/a_completer.rb

 1: module SSO; module CAS; class Client; def extract_login_fields()
 2:   puts "==================================================================="
 3:   puts "= SSO::CAS::Client.extract_login_fields()"
 4:   puts "==================================================================="
 5: #######################################################################
 6: #
 7: # DEBUT A COMPLETER
 8: #
 9: #######################################################################
10:   form         = @doc.xpath('//form[@id="fm1"]')
11:   @form_action = form.attribute('action').text
12:   form_inputs  = form.xpath('.//input')
13:   @post_fields = {}
14:   form_inputs.each do |input|
15:     name  = input.attribute('name').text
16:     value = input.attribute('value').text
17:     @form_fields[name]=value unless (name == 'reset')
18:   end
19: #######################################################################
20: #
21: # FIN A COMPLETER
22: #
23: #######################################################################
24:   output = {
25:     '@form_action' => @form_action,
26:     '@form_fields' => @post_fields
27:   }
28:   pp output
29: end; end; end; end

Version

$Id: jeudi_corrige_ruby_1.txt 567 2012-05-21 07:29:58Z aicardi $