MojoX-Authentication
view release on metacpan or search on metacpan
lib/MojoX/Authentication.pm view on Meta::CPAN
package MojoX::Authentication;
{ our $VERSION = '0.003' }
use v5.24;
use Moo;
use experimental qw< signatures >;
use Try::Catch;
use Ouch qw< :trytiny_var >;
use MojoX::Authentication::Model;
use Scalar::Util qw< blessed >;
use Data::Dumper;
use constant DEFAULT_ROUTE_FOR => {
ok => 'protected_root',
t_ok => 'public_root',
};
use namespace::clean;
has app => (is => 'rwp', weak_ref => 1, default => sub { ... });
has model => (is => 'rwp', lazy => 1);
has saml2_provider_name => (is => 'ro', default => 'saml2');
has user_stash_key => (is => 'ro', default => 'user');
# if we're building, we assume that it's in the app. Convention over
# configuration. This is most probably never called actually
sub _build_model ($self) { $self->app->model->authentication }
around BUILDARGS => sub ($orig, $class, @args) {
my $hash = { @args == 1 ? $args[0]->%* : @args };
$hash->{model} = MojoX::Authentication::Model->new($hash->{model})
if exists($hash->{model}) && ! blessed($hash->{model});
return $hash;
};
sub BUILD ($self, $hash) {
$self->app_startup if $hash->{'-startup'};
return $self;
};
sub app_startup ($self, $app = undef) {
$self->_set_app($app) if defined($app);
$app = $self->app;
my $authn = $self->model;
# add Mojolicious::Plugin::Authentication with the "right" callbacks
$app->plugin( Authentication => {
load_user => sub (@args) { $authn->load_user(@args) },
validate_user => sub (@args) { $authn->validate_user(@args) },
});
# set a hook to set the user's data in the stash, associated to the
# requested user_stash_key. This stash key is always set anyway, to
# avoid errors in the templates in case it's missing
my $key = $self->user_stash_key;
$app->hook(
before_render => sub ($c, $args) {
my $user = $c->is_user_authenticated ? $c->current_user : undef;
$c->stash($key => $user);
return $c;
}
);
return $self;
}
sub ctr_logout ($self, $c) {
return unless $c->is_user_authenticated;
my $user = $c->current_user;
$c->log->info("logout: ");
my $provider = $self->model->provider_named($user->{provider});
return $provider->logout($c, $user)
if $provider && $provider->can('logout');
$c->logout; # just make Mojolicious::Plugin::Authentication happy
return;
}
# convention over configuration
sub ctr_credentials_login ($self, $c, $route_for = undef) {
my $username = $c->param('username');
try {
my $password = $c->param('password');
$c->authenticate($username, $password, {}) or die 'whatever';
$c->log->info("login successful (credentials): $username");
my $user = $c->current_user;
$c->log->trace(Dumper($user));
$c->flash(message => [ info => "Welcome, $username" ]);
return $c->redirect_to($self->_route_for($route_for, 'ok'));
}
catch {
$username = defined($username) ? qq{'$username'} : '*undef*';
$c->log->error("authentication error (credentials): $username " . bleep());
$c->flash(message => [ error => 'Authentication error' ]);
$c->redirect_to($self->_route_for($route_for, 'not_ok'));
};
return;
}
sub _get_provider ($self, $c) {
my $pname = $c->param('provider') // $self->saml2_provider_name;
return $self->model->provider_named($pname);
}
sub _route_for ($self, $route_for, $outcome) {
$route_for //= {};
return $route_for->{$outcome} // DEFAULT_ROUTE_FOR->{$outcome};
}
sub ctr_saml2_login ($self, $c, $route_for) {
try {
my ($id, $url) = $self->_get_provider($c)->idp_login;
$c->session->{'saml-id'} = $id;
$c->signed_cookie('saml-id' => $id, { path => '/' });
$c->redirect_to($url);
}
catch {
$c->log->error('ctr_saml2_login error: ' . bleep());
$c->flash(message => [ error => 'error... ask the administrator!' ]);
$c->redirect_to($self->_route_for($route_for, 'not_ok'));
};
return;
}
sub ctr_saml2_sso_post ($self, $c, $route_for = undef) {
try {
(my $saml_id = $c->signed_cookie('saml-id'))
or ouch 400, 'Not expecting anything SAML-related', 'no id';
defined(my $saml_response = $c->param('SAMLResponse'))
or ouch 400, 'No SAMLResponse';
my $saml2 = $self->_get_provider($c);
my $user = $saml2->parse_assertion($saml_id, $saml_response);
my $uid = $user->{$saml2->user_key};
# we're not using credentials here, so "username" and "password" do
# not make sense. We use "auto_validate" instead and skip
# validate_user altogether
$c->authenticate(undef, undef, { auto_validate => $uid });
$c->log->info("login successful (SAML2): $uid");
$c->flash(message => [ info => "Welcome, $user->{fullname}" ]);
$c->redirect_to($self->_route_for($route_for, 'ok'));
}
catch {
my $message = bleep() || 'Error: invalid credentials';
$message .= ' ' . Dumper($_->data) if eval { $_->isa('Ouch') };
$c->log->error('authentication error (SAML2): ' . $message);
$c->flash(message => [ error => 'Authentication error' ]);
$c->redirect_to($self->_route_for($route_for, 'not_ok'));
};
return;
}
1;
( run in 2.189 seconds using v1.01-cache-2.11-cpan-0b5f733616e )