Mojo-SAML
view release on metacpan or search on metacpan
lib/Mojolicious/Plugin/SAML.pm view on Meta::CPAN
my $login = $conf->{handle_login} // Carp::croak 'handle_login is required';
my $key = Crypt::OpenSSL::RSA->new_private_key(path($conf->{key})->slurp);
my $cert = Crypt::OpenSSL::X509->new_from_string(path($conf->{cert})->slurp);
my $idp = Mojo::SAML::IdP->new->from($conf->{idp});
my $location = $conf->{location};
my $entity_id = $conf->{entity_id} // $location;
my $key_info = KeyInfo->new(cert => $cert);
my $key_desc = KeyDescriptor->new(
key_info => $key_info,
use => 'signing',
);
my $post = AssertionConsumerService->new(
index => 0,
binding => 'HTTP-POST',
location => $location,
);
my $redir = AssertionConsumerService->new(
index => 1,
binding => 'HTTP-Redirect',
location => $location,
);
my $sp = SPSSODescriptor->new(
key_descriptors => [$key_desc],
assertion_consumer_services => [$post, $redir],
nameid_format => [qw/unspecified/],
);
my $metadata = EntityDescriptor->new(
id => 'MOJOSAML_METADATA',
entity_id => $entity_id,
descriptors => [$sp],
insert_signature => Signature->new(key_info => $key_info),
insert_xml_declaration => 1,
sign_with_key => $key,
);
$plugin->metadata($metadata);
$app->helper('saml.authn_request' => sub {
my ($c, %opt) = @_;
my $binding = $opt{binding} // 'HTTP-Redirect';
my $passive = $opt{passive} // 0;
# TODO get "sign" default from idp
my $sign = $opt{sign} // 1;
my $url = $idp->location_for(SingleSignOnService => $binding);
my $req = AuthnRequest->new(
issuer => $entity_id,
assertion_consumer_service_index => 0,
is_passive => $passive,
nameid_policy => NameIDPolicy->new(format => 'unspecified'),
destination => "$url",
);
if ($binding eq 'HTTP-Redirect') {
$url->query(SAMLRequest => $req->to_string_deflate);
if ($sign) {
$url->query({SigAlg => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'});
$key->use_sha256_hash;
my $val = $url->query->to_string;
my $sig = $key->sign($val);
$url->query({Signature => Mojo::Util::b64_encode($sig, '')});
}
$c->redirect_to($url);
} else {
Carp::croak "Binding '$binding' is not implemented by saml.authn_request helper";
}
});
$app->helper('saml.response' => sub {
my $c = shift;
Carp::croak 'saml response methods must be called during a saml response'
unless my $dom = $c->stash->{'saml.response'};
return $dom;
});
my @ns = (samlp => 'urn:oasis:names:tc:SAML:2.0:protocol');
$app->helper('saml.response_success' => sub {
my $c = shift;
my $dom = $c->saml->response;
return !!$dom->at('samlp|Response > samlp|Status > samlp|StatusCode[Value="urn:oasis:names:tc:SAML:2.0:status:Success"]', @ns);
});
$app->helper('saml.response_status' => sub {
my $c = shift;
my $dom = $c->saml->response;
return +($dom->at('samlp|Response > samlp|Status > samlp|StatusCode[Value]', @ns) || {})->{Value};
});
my $path = Mojo::URL->new($location)->path->to_string;
my $r = $app->routes->any($path);
$plugin->route($r);
Scalar::Util::weaken $plugin->{route};
$r->get('/descriptor' => sub {
state $my_meta = $plugin->metadata->to_string; # only render once
my $c = shift;
return $c->reply->not_found unless $my_meta;
$c->render(text => $my_meta, format => 'xml');
}, 'saml_descriptor');
$r->get('/authn_request' => sub { shift->saml->authn_request }, 'saml_authn_request');
$r->any('/' => sub {
my $c = shift;
my $pub = $idp->public_key_for('signing');
my $text = Mojo::Util::b64_decode($c->param('SAMLResponse'));
my $dom = Mojo::DOM->new->xml(1)->parse($text);
return $c->reply->exception('Login failed from SAML provider: Response failed to verify')
unless Mojo::XMLSig::verify($dom, $pub);
$c->stash->{'saml.response'} = $dom;
$c->$login;
}, 'saml_endpoint');
return $plugin;
( run in 0.724 second using v1.01-cache-2.11-cpan-71847e10f99 )