SAML in progress (#595)

This commit is contained in:
Xavier Guimard 2016-11-28 21:15:57 +00:00
parent 9b065f2e3c
commit e5fdcbc3fd
5 changed files with 100 additions and 48 deletions

View File

@ -84,7 +84,8 @@ sub extractFormInfo {
# Check SAML Message
my ( $request, $response, $method, $relaystate, $artifact ) =
$self->checkMessage( $url, $request_method, $content_type, "login" );
$self->checkMessage( $req, $url, $request_method, $content_type,
"login" );
# Create Login object
my $login = $self->createLogin( $self->lassoServer );
@ -392,7 +393,8 @@ sub extractFormInfo {
# Check SAML Message
my ( $request, $response, $method, $relaystate, $artifact ) =
$self->checkMessage( $url, $request_method, $content_type, "logout" );
$self->checkMessage( $req, $url, $request_method, $content_type,
"logout" );
# Create Logout object
my $logout = $self->createLogout( $self->lassoServer );

View File

@ -47,7 +47,7 @@ sub init {
my $saml_sso_art_url_ret = $self->getMetaDataURL(
"samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact", 2 );
$self->ssoUrlRe(
qr/^\Q($saml_sso_soap_url|$saml_sso_soap_url_ret|$saml_sso_get_url|$saml_sso_get_url_ret|$saml_sso_post_url|$saml_sso_post_url_ret|$saml_sso_art_url|$saml_sso_art_url_ret)\E$/i
qr/^($saml_sso_soap_url|$saml_sso_soap_url_ret|$saml_sso_get_url|$saml_sso_get_url_ret|$saml_sso_post_url|$saml_sso_post_url_ret|$saml_sso_art_url|$saml_sso_art_url_ret)$/i
);
my $saml_slo_soap_url =
@ -65,7 +65,7 @@ qr/^\Q($saml_sso_soap_url|$saml_sso_soap_url_ret|$saml_sso_get_url|$saml_sso_get
$self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceHTTPPost",
2 );
$self->sloRe(
qr/^(\Q$saml_slo_soap_url\E|\Q$saml_slo_soap_url_ret\E|\Q$saml_slo_get_url\E|\Q$saml_slo_get_url_ret\E|\Q$saml_slo_post_url\E|\Q$saml_slo_post_url_ret\E)$/i
qr/^($saml_slo_soap_url|$saml_slo_soap_url_ret|$saml_slo_get_url|$saml_slo_get_url_ret|$saml_slo_post_url|$saml_slo_post_url_ret)$/i
);
return (
@ -101,12 +101,12 @@ sub run {
# Get HTTP request informations to know
# if we are receving SAML request or response
my $url = $self->url( -absolute => 1 );
my $request_method = $self->request_method();
my $content_type = $self->content_type();
my $idp_initiated = $self->param('IDPInitiated');
my $idp_initiated_sp = $self->param('sp');
my $idp_initiated_spConfKey = $self->param('spConfKey');
my $url = $req->uri;
my $request_method = $req->method();
my $content_type = $req->contentType();
my $idp_initiated = $req->param('IDPInitiated');
my $idp_initiated_sp = $req->param('sp');
my $idp_initiated_spConfKey = $req->param('spConfKey');
# 1.1. SSO (SSO URL or Proxy Mode)
if ( $url =~ $self->ssoUrlRe or $req->datas->{_proxiedRequest} ) {
@ -114,11 +114,12 @@ sub run {
$self->lmLog( "URL $url detected as an SSO request URL", 'debug' );
# Get hidden params for IDP initiated if needed
$idp_initiated = $self->getHiddenFormValue('IDPInitiated')
$idp_initiated = $self->p->getHiddenFormValue( $req, 'IDPInitiated' )
unless defined $idp_initiated;
$idp_initiated_sp = $self->getHiddenFormValue('sp')
$idp_initiated_sp = $self->p->getHiddenFormValue( $req, 'sp' )
unless defined $idp_initiated_sp;
$idp_initiated_spConfKey = $self->getHiddenFormValue('spConfKey')
$idp_initiated_spConfKey =
$self->p->getHiddenFormValue( $req, 'spConfKey' )
unless defined $idp_initiated_spConfKey;
# Check message
@ -132,7 +133,7 @@ sub run {
}
else {
( $request, $response, $method, $relaystate, $artifact ) =
$self->checkMessage( $url, $request_method, $content_type );
$self->checkMessage( $req, $url, $request_method, $content_type );
}
# Create Login object
@ -444,7 +445,7 @@ sub run {
# Build Assertion
unless (
$self->buildAssertion(
$login, $authn_context, $notOnOrAfterTimeout
$req, $login, $authn_context, $notOnOrAfterTimeout
)
)
{
@ -501,7 +502,7 @@ sub run {
my $nameIDContent;
if ( defined $req->{sessionInfo}->{$nameIDSessionKey} ) {
$nameIDContent =
$self->getFirstValue(
$self->p->getFirstValue(
$req->{sessionInfo}->{$nameIDSessionKey} );
}
@ -730,7 +731,7 @@ sub run {
last;
}
}
$self->_sub( 'userNotice',
$self->p->userNotice(
"SAML authentication response sent to SAML SP $spConfKey for $user$nameIDLog"
);
@ -795,7 +796,7 @@ sub run {
if ( $login->is_session_dirty ) {
$self->lmLog( "Save Lasso session in session", 'debug' );
$self->updateSession(
$self->p->updateSession(
{ _lassoSessionDump => $login->get_session->dump },
$session_id );
}
@ -903,7 +904,8 @@ sub run {
$req->{postFields}->{'RelayState'} = $relaystate
if ($relaystate);
return $self->_subProcess(qw(autoPost));
$req->steps( ['autoPost'] );
return PE_OK;
}
}
@ -931,7 +933,8 @@ sub run {
# Check SAML Message
my ( $request, $response, $method, $relaystate, $artifact ) =
$self->checkMessage( $url, $request_method, $content_type, "logout" );
$self->checkMessage( $req, $url, $request_method, $content_type,
"logout" );
# Create Logout object
my $logout = $self->createLogout($server);

View File

@ -49,6 +49,10 @@ sub init {
sub _redirect {
my ( $self, $req ) = @_;
my $prms = $req->params;
foreach my $k ( keys %$prms ) {
$self->p->setHiddenFormValue( $req, $k, $prms->{$k}, '', 0 );
}
$req->{urldc} =
$self->conf->{portal}
. $req->path
@ -74,9 +78,6 @@ sub _redirect {
sub _pRedirect {
my ( $self, $req ) = @_;
$req->parseBody;
# TODO
die("TODO: store datas");
return $self->_redirect($req);
}

View File

@ -518,7 +518,7 @@ sub setHiddenFormValue {
# Store value
if ($val) {
$key = $prefix . $key;
$val = encode_base64($val) if $base64;
$val = encode_base64($val,'') if $base64;
$req->{portalHiddenFormValues}->{$key} = $val;
$self->lmLog( "Store $val in hidden key $key", 'debug' );
}
@ -572,4 +572,13 @@ sub clearHiddenFormValue {
return;
}
# Get the first value of a multivaluated session value
sub getFirstValue {
my ( $self, $value ) = @_;
my @values = split /$self->{conf}->{multiValuesSeparator}/, $value;
return $values[0];
}
1;

View File

@ -4,7 +4,7 @@ use IO::String;
require 't/test-lib.pm';
my $maintests = 10;
my $maintests = 14;
my $debug = 'debug';
my $res;
my %handlerOR = ( issuer => [], sp => [] );
@ -60,9 +60,9 @@ SKIP: {
$res = $sp->_post(
'/',
IO::String->new(
"confirm=$confirm&idp=https://auth.idp.com/saml/metadata"),
"confirm=$confirm&idp=http://auth.idp.com/saml/metadata"),
accept => 'text/html',
length => length($confirm) + 47,
length => length($confirm) + 46,
),
'Select IDP'
);
@ -72,13 +72,13 @@ SKIP: {
(
defined( $cookies->{lemonldapidp} )
and $cookies->{lemonldapidp} eq
'https://auth.idp.com/saml/metadata'
'http://auth.idp.com/saml/metadata'
),
'IDP cookie defined'
)
or explain(
$res->[1],
'Set-Cookie => lemonldapidp=https://auth.idp.com/saml/metadata; domain=.sp.com; path=/'
'Set-Cookie => lemonldapidp=http://auth.idp.com/saml/metadata; domain=.sp.com; path=/'
);
ok(
$res->[2]->[0] =~
@ -90,6 +90,42 @@ SKIP: {
' <input type="hidden" name="SAMLRequest" id="SAMLRequest" value="...'
);
my $samlReq = $1;
ok(
$res->[2]->[0] =~ m#<form id="form" action="http://auth.idp.com(.*?)"#s,
'Found IdP URL'
);
my $url = $1;
switch ('issuer');
my $s = "SAMLRequest=$samlReq";
ok(
$res = $issuer->_post(
$url,
IO::String->new($s),
accept => 'text/html',
length => length($s)
),
'Post SAML request to IdP'
);
ok( $res->[0] == 200, 'Return code is 200' );
my $body = $res->[2]->[0];
$body =~ s/^.*?<form.*?>//s;
$body =~ s#</form>.*$##s;
my %fields =
( $body =~ /<input type="hidden".+?name="(.+?)".+?value="(.*?)"/sg );
$fields{user} = $fields{password} = 'dwho';
use URI::Escape;
$s = join( '&', map { "$_=" . uri_escape( $fields{$_} ) } keys %fields );
ok(
$res = $issuer->_post(
$url,
IO::String->new($s),
accept => 'text/html',
length => length($s)
),
'Post authentication'
);
#print STDERR Dumper($res);
}
count($maintests);
@ -109,7 +145,7 @@ sub issuer {
ini => {
logLevel => $debug,
domain => 'idp.com',
portal => 'auth.idp.com',
portal => 'http://auth.idp.com',
authentication => 'Demo',
userDB => 'Demo',
issuerDBSAMLActivation => 1,
@ -120,7 +156,7 @@ sub issuer {
},
samlOrganizationDisplayName => "IDP",
samlOrganizationName => "IDP",
samlOrganizationURL => "https://www.idp.com/",
samlOrganizationURL => "http://www.idp.com/",
samlServicePrivateKeyEnc => "-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAnfKBDG/K0TnGT7Xu8q1N45sNWvIK91SqNg8nvN2uVeKoHADT
csus5Xn3id5+8Q9TuMFsW9kIEeXiaPKXQa9ryfSNDhWDWloNkpGEeWif2BnHUu46
@ -357,6 +393,7 @@ sub sp {
ini => {
logLevel => $debug,
domain => 'sp.com',
portal => 'http://auth.sp.com',
authentication => 'SAML',
userDB => 'SAML',
issuerDBSAMLActivation => 0,
@ -379,7 +416,7 @@ sub sp {
<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\"
xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\"
xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"
entityID=\"https://auth.idp.com/saml/metadata\">
entityID=\"http://auth.idp.com/saml/metadata\">
<IDPSSODescriptor WantAuthnRequestsSigned=\"true\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">
<KeyDescriptor use=\"signing\">
@ -412,20 +449,20 @@ g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService isDefault=\"true\" index=\"0\" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"https://auth.idp.com/saml/artifact\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"https://auth.idp.com/saml/singleLogoutSOAP\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"https://auth.idp.com/saml/singleLogout\" ResponseLocation=\"https://auth.idp.com/saml/singleLogoutReturn\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"https://auth.idp.com/saml/singleLogout\" ResponseLocation=\"https://auth.idp.com/saml/singleLogoutReturn\" />
<ArtifactResolutionService isDefault=\"true\" index=\"0\" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://auth.idp.com/saml/artifact\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://auth.idp.com/saml/singleLogoutSOAP\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://auth.idp.com/saml/singleLogout\" ResponseLocation=\"http://auth.idp.com/saml/singleLogoutReturn\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://auth.idp.com/saml/singleLogout\" ResponseLocation=\"http://auth.idp.com/saml/singleLogoutReturn\" />
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"https://auth.idp.com/saml/singleSignOn\" />
<SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"https://auth.idp.com/saml/singleSignOn\" />
<SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\" Location=\"https://auth.idp.com/saml/singleSignOnArtifact\" />
<SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"https://auth.idp.com/saml/singleSignOnSOAP\" />
<SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://auth.idp.com/saml/singleSignOn\" />
<SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://auth.idp.com/saml/singleSignOn\" />
<SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\" Location=\"http://auth.idp.com/saml/singleSignOnArtifact\" />
<SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://auth.idp.com/saml/singleSignOnSOAP\" />
</IDPSSODescriptor>
<SPSSODescriptor AuthnRequestsSigned=\"true\" WantAssertionsSigned=\"true\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">
@ -459,18 +496,18 @@ g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService isDefault=\"true\" index=\"0\" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"https://auth.idp.com/saml/artifact\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"https://auth.idp.com/saml/proxySingleLogoutSOAP\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"https://auth.idp.com/saml/proxySingleLogout\" ResponseLocation=\"https://auth.idp.com/saml/proxySingleLogoutReturn\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"https://auth.idp.com/saml/proxySingleLogout\" ResponseLocation=\"https://auth.idp.com/saml/proxySingleLogoutReturn\" />
<ArtifactResolutionService isDefault=\"true\" index=\"0\" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://auth.idp.com/saml/artifact\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://auth.idp.com/saml/proxySingleLogoutSOAP\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://auth.idp.com/saml/proxySingleLogout\" ResponseLocation=\"http://auth.idp.com/saml/proxySingleLogoutReturn\" />
<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://auth.idp.com/saml/proxySingleLogout\" ResponseLocation=\"http://auth.idp.com/saml/proxySingleLogoutReturn\" />
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<AssertionConsumerService isDefault=\"true\" index=\"0\" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\" Location=\"https://auth.idp.com/saml/proxySingleSignOnArtifact\" />
<AssertionConsumerService isDefault=\"false\" index=\"1\" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"https://auth.idp.com/saml/proxySingleSignOnPost\" />
<AssertionConsumerService isDefault=\"true\" index=\"0\" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\" Location=\"http://auth.idp.com/saml/proxySingleSignOnArtifact\" />
<AssertionConsumerService isDefault=\"false\" index=\"1\" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://auth.idp.com/saml/proxySingleSignOnPost\" />
</SPSSODescriptor>
<AttributeAuthorityDescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">
@ -504,7 +541,7 @@ g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<AttributeService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"https://auth.idp.com/saml/AA/SOAP\"/>
<AttributeService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://auth.idp.com/saml/AA/SOAP\"/>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
@ -516,7 +553,7 @@ g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==
<Organization>
<OrganizationName xml:lang=\"en\">IDP</OrganizationName>
<OrganizationDisplayName xml:lang=\"en\">IDP</OrganizationDisplayName>
<OrganizationURL xml:lang=\"en\">https://www.idp.fr/</OrganizationURL>
<OrganizationURL xml:lang=\"en\">http://www.idp.fr/</OrganizationURL>
</Organization>
</EntityDescriptor>