Dancer2-Plugin-SPID
view release on metacpan or search on metacpan
Change: b96056c189625ae29ebf202f4be64f078f1f6607
Author: Alessandro Ranellucci <alessandro@pintle.it>
Date : 2018-08-06 21:22:33 +0000
Apply some nice Bootstrap to the example app
Change: 740adaba378f7ce475bd2e934368821328828f89
Author: Alessandro Ranellucci <alessandro@pintle.it>
Date : 2018-08-06 21:06:58 +0000
Added metadata_endpoint
Change: 7dfe9b4a6cd609cab24513ec2bae1fdc4b810897
Author: Alessandro Ranellucci <alessandro@pintle.it>
Date : 2018-08-06 21:02:10 +0000
Update to the newest Net::SPID API
Change: 486cc691cd3a4125ea061532467baddafeed8608
Author: Alessandro Ranellucci <alessandro@pintle.it>
Date : 2018-08-06 20:27:12 +0000
example/app.pl view on Meta::CPAN
template 'user';
} else {
template 'index';
}
};
# That's it. Seriously, that's all you need.
# Below you'll see how to customize the behavior further by configuring one or
# more hooks that the SPID plugin will call.
# This hook is called when the login endpoint is called and the AuthnRequest
# is about to be crafted.
hook 'plugin.SPID.before_login' => sub {
# ...
};
# This hook is called after the SPID session was successfully initiated.
hook 'plugin.SPID.after_login' => sub {
info "User " . spid_session->nameid . " logged in";
# Here you might want to create the user in your local database or do more
example/app.pl view on Meta::CPAN
# or a dedicated log file.
info "SPID Assertion: " . spid_session->assertion_xml;
};
# This hook is called after a failed SPID login.
hook 'plugin.SPID.after_failed_login' => sub {
my $statuscode = shift;
info "SPID login failed: $statuscode";
};
# This hook is called when the logout endpoint is called and the LogoutRequest
#Â is about to be crafted.
hook 'plugin.SPID.before_logout' => sub {
debug "User " . spid_session->nameid . " is about to logout";
};
# This hook is called when a SPID session is terminated (this might be triggered
# also when user initiated logout from another Service Provider or directly
# within the Identity Provider, thus without calling our logout endpoint and
# the 'before_logout' hook).
hook 'plugin.SPID.after_logout' => sub {
my $success = shift; # 'success' or 'partial'
debug "User " . spid_session->nameid . " logged out";
};
dance;
__END__
example/config.yml view on Meta::CPAN
plugins:
SPID:
sp_entityid: "https://www.prova.it/"
sp_key_file: "sp.key"
sp_cert_file: "sp.pem"
sp_assertionconsumerservice:
- "http://localhost:3000/spid-sso"
sp_singlelogoutservice:
"http://localhost:3000/spid-slo": "HTTP-Redirect"
idp_metadata_dir: "idp_metadata/"
login_endpoint: "/spid-login"
logout_endpoint: "/spid-logout"
sso_endpoint: "/spid-sso"
slo_endpoint: "/spid-slo"
metadata_endpoint: "/spid-metadata.xml"
lib/Dancer2/Plugin/SPID.pm view on Meta::CPAN
# Load Identity Providers from their XML metadata.
$spid->load_idp_metadata($self->config->{idp_metadata_dir});
return $spid;
}
sub _build_spid_button {
my ($self, %args) = @_;
return $self->_spid->get_button($self->config->{login_endpoint} . '?idp=%s');
}
sub spid_session :PluginKeyword {
my ($self) = @_;
return $self->dsl->session('__spid_session');
}
sub BUILD {
my ($self) = @_;
# Check that we have all the required config options.
foreach my $key (qw(sp_entityid sp_key_file sp_cert_file idp_metadata_dir
login_endpoint logout_endpoint)) {
croak "Missing required config option for SPID: '$key'"
if !$self->config->{$key};
}
# Create a hook for populating the spid_* variables in templates.
$self->app->add_hook(Dancer2::Core::Hook->new(
name => 'before_template_render',
code => sub {
my $vars = shift;
lib/Dancer2/Plugin/SPID.pm view on Meta::CPAN
my $jwt = encode_jwt(
payload => {
idp => $idp_id,
level => ($args{level} || 1),
redirect => ($args{redirect} || '/'),
},
alg => 'HS256',
key => $self->config->{jwt_secret} // $DEFAULT_JWT_SECRET,
);
sprintf '%s?t=%s',
$self->config->{login_endpoint},
$jwt;
};
$vars->{spid_button} = sub {
my %args = %{$_[0]};
$self->_spid->get_button($url_cb, %args);
};
$vars->{spid_login} = sub {
my %args = %{$_[0]};
$url_cb->($self->spid_session->idp_id, %args);
};
$vars->{spid_logout} = sub {
my %args = %{$_[0]};
sprintf '%s?redirect=%s',
$self->config->{logout_endpoint},
($args{redirect} || '/');
};
$vars->{spid_session} = sub { $self->spid_session };
}
));
# Create a route for the metadata endpoint.
if ($self->config->{metadata_endpoint}) {
$self->app->add_route(
method => 'get',
regexp => $self->config->{metadata_endpoint},
code => sub {
$self->dsl->content_type('application/xml');
return $self->_spid->metadata;
},
);
}
# Create a route for the login endpoint.
# This endpoint initiates SSO through the user-chosen Identity Provider.
$self->app->add_route(
method => 'get',
regexp => $self->config->{login_endpoint},
code => sub {
$self->execute_plugin_hook('before_login');
my $jwt = decode_jwt(
token => $self->dsl->param('t'),
key => $self->config->{jwt_secret} // $DEFAULT_JWT_SECRET,
);
# Check that we have the mandatory 'idp' parameter and that it matches
# an available Identity Provider.
lib/Dancer2/Plugin/SPID.pm view on Meta::CPAN
$self->dsl->session('__spid_authnreq_id' => $authnreq->ID);
# Save the redirect destination to be used after successful login.
$self->dsl->session('__spid_sso_redirect' => $jwt->{redirect} || '/');
# Redirect user to the IdP using its HTTP-Redirect binding.
$self->dsl->redirect($authnreq->redirect_url, 302);
},
);
# Create a route for the SSO endpoint (AssertionConsumerService).
# During SSO, the Identity Provider will redirect user to this URL POSTing
# the resulting assertion.
$self->app->add_route(
method => 'post',
regexp => $self->config->{sso_endpoint},
code => sub {
# Parse and verify the incoming assertion. This may throw exceptions so we
# enclose it in an eval {} block.
my $response = eval {
$self->_spid->parse_response(
$self->dsl->param('SAMLResponse'),
$self->dsl->session('__spid_authnreq_id'), # Match the ID of our authentication request for increased security.
);
};
lib/Dancer2/Plugin/SPID.pm view on Meta::CPAN
$self->dsl->session('__spid_session' => undef);
$self->execute_plugin_hook('after_failed_login');
}
#Â Regardless of the login result, redirect user to the saved destination
$self->dsl->redirect($self->dsl->session('__spid_sso_redirect'));
$self->dsl->session('__spid_sso_redirect' => undef);
},
);
# Create a route for the logout endpoint.
$self->app->add_route(
method => 'get',
regexp => $self->config->{logout_endpoint},
code => sub {
# If we don't have an open SPID session, do nothing.
return $self->dsl->redirect('/')
if !$self->spid_session;
$self->execute_plugin_hook('before_logout');
# Craft the LogoutRequest.
my $idp = $self->_spid->get_idp($self->spid_session->idp_id);
my $logoutreq = $idp->logoutrequest(session => $self->spid_session);
# Save the ID of the LogoutRequest so that we can check it in the response
# in order to prevent forgery.
$self->dsl->session('__spid_logoutreq_id' => $logoutreq->ID);
# Redirect user to the Identity Provider for logout.
$self->dsl->redirect($logoutreq->redirect_url, 302);
},
);
# Create a route for the SingleLogoutService endpoint.
# This endpoint exposes a SingleLogoutService for our Service Provider, using
# a HTTP-POST or HTTP-Redirect binding (it does not support SOAP).
# Identity Providers can direct both LogoutRequest and LogoutResponse messages
# to this endpoint.
$self->app->add_route(
method => 'post',
regexp => $self->config->{slo_endpoint},
code => sub {
if ($self->dsl->param('SAMLResponse') && $self->dsl->session('__spid_logoutreq_id')) {
my $logoutres = eval {
$self->_spid->parse_logoutresponse(
$self->dsl->param('SAMLResponse'),
$self->dsl->request->uri,
$self->dsl->session('__spid_logoutreq_id'),
)
};
lib/Dancer2/Plugin/SPID.pm view on Meta::CPAN
plugins:
SPID:
sp_entityid: "https://www.prova.it/"
sp_key_file: "sp.key"
sp_cert_file: "sp.pem"
sp_assertionconsumerservice:
- "http://localhost:3000/spid-sso"
sp_singlelogoutservice:
"http://localhost:3000/spid-slo": "HTTP-Redirect"
idp_metadata_dir: "idp_metadata/"
login_endpoint: "/spid-login"
logout_endpoint: "/spid-logout"
sso_endpoint: "/spid-sso"
slo_endpoint: "/spid-slo"
=over
=item I<sp_entityid>
(Required.) The entityID value for this Service Provider. According to SPID regulations, this should be a URI.
=item I<sp_key_file>
(Required.) The absolute or relative file path to our private key file.
=item I<sp_cert_file>
(Required.) The absolute or relative file path to our certificate file.
=item I<sp_assertionconsumerservice>
An arrayref with the URL(s) of our AssertionConsumerService endpoint(s). It is used for metadata generation and for validating the C<Destination> XML attribute of the incoming responses.
=item I<sp_singlelogoutservice>
A hashref with the URL(s) of our SingleLogoutService endpoint(s), along with the specification of the binding. It is used for metadata generation and for validating the C<Destination> XML attribute of the incoming responses.
=item I<sp_attributeconsumingservice>
(Optional.) An arrayref with the AttributeConsumingServices to list in metadata, each one described by a C<servicename> and a list of C<attributes>. This is optional as it's only used for metadata generation.
sp_attributeconsumingservice:
- servicename: "Service 1"
attributes:
- "fiscalNumber"
- "name"
- "familyName"
- "dateOfBirth"
=item I<idp_metadatadir>
(Required.) The absolute or relative path to a directory containing metadata files for Identity Providers in XML format (their file names are expected to end in C<.xml>).
=item I<login_endpoint>
(Required.) The relative HTTP path we want to use for the SPID button login action. A route handler will be created for this path that generates an AuthnRequest and redirects the user to the chosen Identity Provider using the HTTP-Redirect binding.
=item I<logout_endpoint>
(Required.) The relative HTTP path we want to use for the logout action. A route handler will be created for this path that generates a LogoutRequest and redirects the user to the current Identity Provider using the HTTP-Redirect binding.
=item I<sso_endpoint>
(Required.) The relative HTTP path we want to expose as AssertionConsumerService. This must match the URL advertised in the Service Provider metadata.
=item I<slo_endpoint>
(Required.) The relative HTTP path we want to expose as SingleLogoutService. This must match the URL advertised in the Service Provider metadata.
=item I<metadata_endpoint>
(Optional.) The relative HTTP path we want to use for publishing our SP metadata. If omitted, no endpoint will be exposed.
=item I<jwt_secret>
(Optional.) The secret using for encoding relay state data.
=back
=head1 KEYWORDS
The following keywords are available.
lib/Dancer2/Plugin/SPID.pm view on Meta::CPAN
This keyword will return the URL for initiating a Single Logout by directing the user to the current Identity Provider with a LogoutRequest. You must check whether the user has an active SPID session before using it. It accepts an optional C<redirect...
[% IF spid_session %]
<a href="[% spid_logout(redirect => '/') %]">Logout</a>
[% END %]
=head1 HOOKS
=head2 before_login
This hook is called when the login endpoint is called (i.e. the SPID button is clicked or user visited the upgrade URL returned by L<spid_login>) and the AuthnRequest is about to be crafted.
hook 'plugin.SPID.before_login' => sub {
info "User is initiating SSO";
};
=head2 after_login
This hook is called after the user returns to us after a successful SPID login.
hook 'plugin.SPID.after_login' => sub {
lib/Dancer2/Plugin/SPID.pm view on Meta::CPAN
This hook is called after the user returns to us after a failed SPID login. The SAML C<StatusCode> value is supplied as first argument. You can use this hook in order to display the correct error message according the error.
hook 'plugin.SPID.after_failed_login' => sub {
my $statuscode = shift;
info "SPID login failed: $statuscode";
};
=head2 before_logout
This hook is called when the logout endpoint is called and the LogoutRequest
is about to be crafted.
hook 'plugin.SPID.before_logout' => sub {
debug "User " . spid_session->nameid . " is about to logout";
};
=head2 after_logout
This hook is called when a SPID session is terminated. Note that this might be triggered also when user initiated logout from another Service Provider or directly within the Identity Provider, thus without calling our logout endpoint and the L<before...
L<spid_session> will be cleared I<after> this hook is executed, so you can use it.
hook 'plugin.SPID.after_logout' => sub {
my $success = shift; # 'success' or 'partial'
debug "User " . spid_session->nameid . " logged out";
};
=head1 AUTHOR
Alessandro Ranellucci <aar@cpan.org>
use Dancer2;
use Test::More tests => 1;
BEGIN { # would usually be in config.yml
set plugins => {
SPID => {qw(
sp_entityid https://www.prova.it/
sp_key_file sp.key
sp_cert_file sp.pem
idp_metadata_dir idp_metadata/
login_endpoint /spid-login
logout_endpoint /spid-logout
sso_endpoint /spid-sso
slo_endpoint /spid-slo
)},
};
use_ok('Dancer2::Plugin::SPID');
}
__END__
( run in 0.270 second using v1.01-cache-2.11-cpan-b61123c0432 )