C'est parti pour une partie de ping-pong entre listes.example.com
, le serveur de listes de diffusion, et sso.example.com
, le serveur CAS.
Il s'agit de se connecter une première fois au serveur de listes, pour initier le processus d'authentification.
LWP::UserAgent
et HTML::Form
(cf man LWP
, man LWP::UserAgent
, man HTML::Form
)
LWP::UserAgent
)HTTP::Request
)sympa_session
, et le sauvegarder pour plus tardHTTP::Form
)Connexion
)
LWP::UserAgent
suit (partiellement) les redirections HTTP. Comme nous voulons les gérer nous-mêmes (pour injecter les bons coockies au bon moment), nous allons désactiver cette fonctionnalité avec l'option max_redirect⇒0
.
1: use LWP::UserAgent; 2: use HTML::Form; 3: use strict ; 4: 5: my $base = 'https://listes.example.com'; 6: my $url = "$base/sympa"; 7: 8: # on initialise le client HTTP 9: my $ua = new LWP::UserAgent( 10: agent => 'Mon client web en Perl a moi' 11: , ssl_opts => { verify_hostname => 0 } 12: , max_redirect=> 0 13: ) ; 14: 15: # on prépare la requête 16: my $req = HTTP::Request->new( GET => $url ) ; 17: 18: # en envoie la requête et récupère la réponse 19: my $rep = $ua->request($req) ; 20: die $rep->status_line unless $rep->is_success() ; 21: 22: # on récupère le cookie sympa_session 23: my $sympa_cookie = $rep->header("Set-Cookie") ; 24: # on enlève ses attributs 25: $sympa_cookie =~ s/;.*// ; 26: 27: # on récupère les formulaires 28: my @form = HTML::Form->parse($rep, base=>$base, verbose => 1 ) ; 29: 30: # on garde le premier formulaire 31: my $form = shift(@form) ; # ou bien : = $form[0] ; 32: 33: # on soumet ce formulaire 34: $rep = $ua->request( $form->click() ) ; 35: 36: # on regarde la réponse 37: printf("%s\n",$rep->as_string()) ;
On constate que l'on obtient un code de retour 302 Found
, ce qui signifie que l'on est redirigé vers une nouvelle URL. Cette redirection a pour objectif de nous emmener sur le serveur CAS.
L'URL de redirection exacte se trouve dans le corps de la réponse (pour que l'internaute puisse cliquer) et aussi dans le champ d'en-tête Location
.
Il s'agit ici d'envoyer une première requête au serveur CAS, en suivant simplement la redirection. Pour cela, nous allons construire de toute pièce une requête à l'aide de l'URL de redirection obtenue à l'étape précédente. On la soumet, et on regarde ce que l'on obtient en retour.
1: # la nouvelle requête 2: $req = HTTP::Request->new( GET => $rep->header("Location") ) ; 3: $rep = $ua->request($req) ; 4: 5: # la réponse 6: printf("%s\n",$rep->as_string()) ;
On constate que l'on obtient à nouveau un 302
. Suivons cette redirection (quel suspens…), en dupliquant simplement le code précédent.
Cette fois-ci, nous obtenons un 200 OK
, avec la page d'authentification du serveur CAS.
La page obtenue précédemment contient plusieurs formulaires.
Nous allons soumettre le formulaire fm1
contenu dans cette page, après avoir rempli les champs de type text
, password
et checkbox
. On affichera au passage l'URL destination du post.
1: # on récupère les formulaires 2: my @form = HTML::Form->parse($rep) ; 3: 4: # on récupère le formulaire fm1 5: my $form ; 6: for my $f (@form) 7: { 8: next unless $f->attr("id") eq "fm1" ; 9: $form = $f ; 10: last ; 11: } 12: 13: # on remplit (partiellement) le formulaire 14: for my $i ($form->inputs()) 15: { 16: next unless "|text|password|checkbox|" =~ $i->type() ; 17: printf("%s (type: '%s', defaut: '%s') : ",$i->name(),$i->type(),$i->value()) ; 18: my $v = <stdin> ; chomp($v) ; 19: next unless $v=~/\S/ ; 20: $i->value($v) ; 21: } 22: 23: # on regarde où le formulaire va être posté 24: printf("action = %s\n",$form->action()) ; 25: 26: # on soumet le formulaire 27: $rep = $ua->request( $form->click() ) ; 28: 29: # et on voit 30: printf("%s\n",$rep->as_string()) ;
L'attribut action
du formulaire nous indique que le formulaire est soumis au serveur CAS.
Après soumission, nous obtenons en retour un 302 Moved Temporarily
. L'en-tête Location
contient une URL qui va nous ramener sur le serveur de listes.
On remarquera que l'on récupère également 2 cookies : CASPRIVACY
et CASTGC
.
On va construire de toute pièce une requête vers le serveur de listes, à l'aide de l'URL de redirection obtenue à l'étape précédente. Il s'agit simplement de fournir les 2 cookies.
On soumet la requête et on voit ce que ça donne.
1: # on construit l'en-tête contenant les cookies 2: my $header = new HTTP::Headers ; 3: for my $c ($rep->header("Set-Cookie")) 4: { 5: $header->push_header("Cookie",$c) ; 6: } 7: 8: # on construit la requête 9: $req = new HTTP::Request("get",$rep->header("Location"),$header) ; 10: 11: # on soumet 12: $rep = $ua->request($req) ; 13: 14: # et on voit 15: printf("%s\n",$rep->as_string()) ;
On récupère en retour un 302 Found
. L'URL de redirection nous laisse sur le serveur de listes.
On récupère au passage un nouveau cookie : MOD_AUTH_CAS_S
.
Encore une fois, nous allons suivre la redirection renvoyée à l'étape précédente. Mais ici, on va envoyer le cookie MOD_AUTH_CAS_S
obtenu à l'étape précédente, ainsi que le ce cookie sympa_session
sauvegardé à l'étape 1.
1: $header = new HTTP::Headers ; 2: for my $c ($rep->header("Set-Cookie")) 3: { 4: $header->push_header("Cookie",$c) ; 5: } 6: 7: $req = new HTTP::Request("get",$rep->header("Location"),$header) ; 8: $rep = $ua->request($req) ; 9: 10: # et on voit 11: printf("%s\n",substr($rep->as_string(),0,2000)) ;
On récupère encore un 302
, et aussi le cookie sympa_session
, qu'il faudra envoyer désormais à chaque fois.
Pour la dernière fois, nous allons suivre la redirection proposée, tout en passant le cookie sympa_session
. On va obtenir en retour la page du serveur de listes sous l'identité fournie initialement au serveur CAS.
Pour le vérifier, on va extraire de la page retournée le contenu de la division id=Identity
, qui doit contenir l'identité du visiteur.
HTML::TreeBuilder::XPath
permet d'extraire des portions de fichiers HTML à l'aide d'une expression Xpath
1: header = new HTTP::Headers ; 2: @h = () ; 3: for my $c ($rep->header("Set-Cookie")) 4: { 5: $c =~ s/;.*$// ; 6: push(@h,$c) ; 7: } 8: $header->push_header("Cookie",join("; ",@h)) ; 9: print Dumper $header ; 10: 11: $req = HTTP::Request->new("GET",$url,$header ) ; 12: $rep = $ua->request($req) ; 13: 14: { 15: use FileHandle ; 16: 17: my $fh = new FileHandle(">out.html") or die ; 18: $fh->print($rep->content()) ; 19: $fh->close() ; 20: } 21: 22: use HTML::TreeBuilder::XPath; 23: 24: my $tree= new HTML::TreeBuilder::XPath->new; 25: $tree->parse($rep->content()); 26: print $tree->findvalue( '/html/body//div[@id="Identity"]');
Pour finir, reprendre tout ce qui a été développé dans ce TP pour obtenir un programme qui met bien en évidence l'enchaînement des opérations, avec tous les détails intervenants dans le dialogue :
Pour cela, nous allons définir une classe MonClient
qui hérite de LWP::UserAgent
. MonClient
aura une méthode request
qui surchage la méthode de même nom de LWP::UserAgent
et qui :
HTTP::Response
.
Cette classe sera définie dans le fichier MonCLient.pm
1: package MonClient ; 2: require Exporter ; 3: our @ISA = qw(Exporter LWP::UserAgent); 4: our @EXPORT=qw() ; 5: 6: use LWP::UserAgent; 7: use HTTP::Request ; 8: use HTTP::Response ; 9: use FileHandle ; 10: use strict ; 11: 12: my $iresponse = 0 ; 13: 14: #-------------------- 15: sub new 16: { 17: my ($type) = @_ ; 18: my $this ; 19: 20: shift(@_) ; 21: $this = $type->SUPER::new(@_) ; 22: bless $this,$type ; 23: 24: return $this ; 25: } 26: 27: #-------------------- 28: sub request 29: { 30: my ($this,$requete) = @_ ; 31: shift(@_) ; 32: 33: printf("\n\n%s\nMonClient: requete:\n\t%s %s\n\t%s = %s\n" 34: , "-"x40 35: , $requete->method(), $requete->uri() 36: , "header('Cookie')", $requete->header('Cookie') 37: ) ; 38: 39: print "\npress a key to continue..." ; 40: <STDIN> ; 41: 42: my $response = $this->SUPER::request(@_) ; 43: 44: my $filename = sprintf("content#%d.html",++$iresponse) ; 45: 46: printf("\nMonClient: reponse:\n\tcode() = %s\n\tmessage() = %s\n" 47: , $response->code(), $response->message() 48: ) ; 49: 50: printf("\theader('Set-Cookie') = %s\n", scalar($response->header('Set-Cookie'))) 51: if $response->header('Set-Cookie') ; 52: 53: printf("\theader('Location') = %s\n", $response->header('Location')) 54: if $response->header('Location') ; 55: 56: printf("\tcontent() : voir le fichier '%s'\n", $filename) ; 57: 58: my $fh = new FileHandle(">$filename") || die $! ; 59: $fh->print($response->content()) ; 60: $fh->close() ; 61: 62: return $response ; 63: } 64: 65: 1 ;
Le programme n'a plus alors qu'à utiliser MonCLient
à la place de LWP::UserAgent
.
1: #!/usr/bin/perl 2: 3: use HTML::Form; 4: use HTML::TreeBuilder::XPath; 5: use MonClient ; 6: use strict ; 7: 8: # Q1 9: 10: my $base = 'https://listes.example.com'; 11: my $url = "$base/sympa"; 12: 13: # on initialise le client HTTP 14: my $ua = new MonClient( 15: agent => 'Mon client web en Perl a moi' 16: , ssl_opts => { verify_hostname => 0 } 17: , max_redirect=> 0 18: ) ; 19: 20: # on prépare la requête 21: my $req = HTTP::Request->new( GET => $url ) ; 22: 23: # en envoie la requête et récupère la réponse 24: my $rep = $ua->request($req) ; 25: die $rep->status_line if not $rep->is_success() ; 26: 27: # on récupère le cookie sympa_session 28: my $sympa_cookie = $rep->header("Set-Cookie") ; 29: # on enlève ses attributs 30: $sympa_cookie =~ s/;.*// ; 31: 32: # on récupère les formulaires 33: my @form = HTML::Form->parse($rep, base=>$base, verbose => 1 ) ; 34: 35: # on garde le premier formulaire 36: my $form = shift(@form) ; # ou bien : = $form[0] ; 37: 38: # on soumet ce formulaire 39: $rep = $ua->request( $form->click() ) ; 40: 41: # Q2 42: 43: # on suit 2 redirections 44: 45: $req = HTTP::Request->new( GET => $rep->header("Location") ) ; 46: $rep = $ua->request($req) ; 47: 48: $req = HTTP::Request->new( GET => $rep->header("Location") ) ; 49: $rep = $ua->request($req) ; 50: 51: # Q3 52: 53: # on récupère les formulaires 54: my @form = HTML::Form->parse($rep) ; 55: 56: # on récupère le formulaire fm1 57: my $form ; 58: for my $f (@form) 59: { 60: next unless $f->attr("id") eq "fm1" ; 61: $form = $f ; 62: last ; 63: } 64: 65: # on remplit (partiellement) le formulaire 66: for my $i ($form->inputs()) 67: { 68: next unless "|text|password|checkbox|" =~ $i->type() ; 69: printf("%s (type: '%s', defaut: '%s') : ",$i->name(),$i->type(),$i->value()) ; 70: my $v = <stdin> ; chomp($v) ; 71: next unless $v=~/\S/ ; 72: $i->value($v) ; 73: } 74: 75: # on soumet le formulaire 76: $rep = $ua->request( $form->click() ) ; 77: 78: # Q4 79: 80: $req = new HTTP::Request("GET",$rep->header("Location")) ; 81: $rep = $ua->request($req) ; 82: 83: # Q5 84: 85: my $header = new HTTP::Headers ; 86: my @h = () ; 87: for my $c ($rep->header("Set-Cookie")) 88: { 89: $c =~ s/;.*$// ; 90: push(@h,$c) ; 91: } 92: push(@h,$sympa_cookie) ; 93: $header->push_header("Cookie",join("; ",@h)) ; 94: 95: $req = new HTTP::Request("GET",$rep->header("Location"),$header) ; 96: $rep = $ua->request($req) ; 97: 98: # Q6 99: 100: $header = new HTTP::Headers ; 101: @h = () ; 102: for my $c ($rep->header("Set-Cookie")) 103: { 104: $c =~ s/;.*$// ; 105: push(@h,$c) ; 106: } 107: $header->push_header("Cookie",join("; ",@h)) ; 108: 109: $req = HTTP::Request->new("GET",$url,$header ) ; 110: $rep = $ua->request($req) ; 111: 112: my $tree= new HTML::TreeBuilder::XPath->new; 113: $tree->parse($rep->content()); 114: printf("\nidentite de la connexion: %s\n", $tree->findvalue( '/html/body//div[@id="Identity"]')) ;