Unverified Commit d6499ab1 authored by robertperez-upc's avatar robertperez-upc Committed by GitHub
Browse files

SSO Auth (#1513)

feat(auth): SSO Auth
parent 3df267ab
......@@ -4,6 +4,7 @@ use warnings;
use strict;
our $LDAP_OK;
our $SSO_OK;
use Ravada::Auth::SQL;
......@@ -32,6 +33,19 @@ sub init {
} else {
$LDAP_OK = 0;
}
if ($config->{sso} && (!defined $SSO_OK || $SSO_OK) ) {
eval {
$SSO_OK = 0;
require Ravada::Auth::SSO;
Ravada::Auth::SSO::init($config);
$SSO_OK = 1;
};
warn $@ if $@;
} else {
$SSO_OK = 0;
}
# Ravada::Auth::SQL::init($config, $db_con);
}
......@@ -64,6 +78,31 @@ sub login {
return $sql_login;
}
=head2 login_external
Tries login_external in all the submodules
my $ok = Ravada::Auth::login_external();
=cut
sub login_external {
my ($ticket, $cookie, $quiet) = @_;
my $login_ok;
if (!defined $SSO_OK || $SSO_OK) {
eval {
$login_ok = Ravada::Auth::SSO::login_external($ticket, $cookie);
};
warn $@ if $@ && $SSO_OK && !$quiet;
if ( $login_ok ) {
$login_ok->{'mode'} = 'external';
return $login_ok;
}
}
return undef;
}
=head2 enable_LDAP
Sets or get LDAP support.
......@@ -81,4 +120,23 @@ sub enable_LDAP {
$LDAP_OK = $value;
return $value;
}
=head2 enable_CAS
Sets or get CAS support.
Ravada::Auth::enable_CAS(0);
print "SSO is supported" if Ravada::Auth::enable_SSO();
=cut
sub enable_SSO {
my $value = shift;
return $SSO_OK if !defined $value;
$SSO_OK = $value;
return $value;
}
1;
package Ravada::Auth::SSO;
use strict;
use warnings;
use Data::Dumper;
use Authen::ModAuthPubTkt;
use URI::Escape;
use LWP::UserAgent;
=head1 NAME
Ravada::Auth::SSO - SSO library for Ravada
=cut
use Moose;
no warnings "experimental::signatures";
use feature qw(signatures);
use Ravada::Auth::SQL;
with 'Ravada::Auth::User';
our $CONFIG = \$Ravada::CONFIG;
sub BUILD {
my $self = shift;
my ($params) = @_;
die 'ERROR: Ticket not found'
if !$params->{ticket};
$self->{ticket} = $params->{ticket};
$self->{logoutURL} = sprintf('%s/logout?service=%s', $$CONFIG->{sso}->{url}, uri_escape($$CONFIG->{sso}->{service})) if ($$CONFIG->{sso}->{logout});
die sprintf('ERROR: Login failed %s', $self->name)
if !$self->login;
return $self;
}
sub add_user($name, $password, $storage='rfc2307', $algorithm=undef) { }
sub remove_user { }
sub search_user { }
sub _check_user_profile {
my $self = shift;
my $user_sql = Ravada::Auth::SQL->new(name => $self->name);
if ( $user_sql->id ) {
if ($user_sql->external_auth ne 'sso') {
$user_sql->external_auth('sso');
}
return;
}
Ravada::Auth::SQL::add_user(name => $self->name, is_external => 1, is_temporary => 0
, external_auth => 'sso');
}
sub _generate_session_ticket
{
my ($name) = @_;
my $cookie;
die 'Can\'t read privkey file (sso->cookie->priv_key value at ravada.conf file)' if (! -r $$CONFIG->{sso}->{cookie}->{priv_key});
eval { $cookie = Authen::ModAuthPubTkt::pubtkt_generate(privatekey => $$CONFIG->{sso}->{cookie}->{priv_key}, keytype => $$CONFIG->{sso}->{cookie}->{type}, userid => $name, validuntil => time() + $$CONFIG->{sso}->{cookie}->{timeout}); };
return $cookie;
}
sub _get_session_userid_by_ticket
{
my ($cookie) = @_;
my $result;
die 'Can\'t read pubkey file (sso->cookie->pub_key value at ravada.conf file)' if (! -r $$CONFIG->{sso}->{cookie}->{pub_key});
eval { $result = Authen::ModAuthPubTkt::pubtkt_verify(publickey => $$CONFIG->{sso}->{cookie}->{pub_key}, keytype => $$CONFIG->{sso}->{cookie}->{type}, ticket => $cookie); };
die $@ ? $@ : 'Cannot validate ticket' if ((! $result) || ($@));
my %data = Authen::ModAuthPubTkt::pubtkt_parse($cookie);
die 'Ticket is expired' if ($data{validuntil} < time());
return $data{uid};
}
sub _validate_ticket
{
my ($ticket) = @_;
my $response = LWP::UserAgent->new->get(sprintf('%s/serviceValidate?service=%s&ticket=%s', $$CONFIG->{sso}->{url}, uri_escape($$CONFIG->{sso}->{service}), uri_escape($ticket)));
return $1 if ($response->content =~ /<cas:user>(.+)<\/cas:user>/);
die sprintf('Ticket validation error: %s', $response->content);
}
sub login($self) {
my $userid = _get_session_userid_by_ticket($self->{ticket});
die 'Ticket user id do not coincides with received user id' if ($self->name ne $userid);
return $self->name;
}
sub login_external($ticket, $cookie) {
if ($cookie) {
my $name = _get_session_userid_by_ticket($cookie);
my $self = Ravada::Auth::SSO->new(name => $name, ticket => $cookie);
$self->_check_user_profile();
return $self;
} elsif ($ticket) {
my $name = _validate_ticket($ticket);
my $self = Ravada::Auth::SSO->new(name => $name, ticket => _generate_session_ticket($name));
$self->_check_user_profile();
return $self;
} else {
return { redirectTo => sprintf('%s/login?service=%s', $$CONFIG->{sso}->{url}, uri_escape($$CONFIG->{sso}->{service})) };
}
}
sub is_admin { }
sub is_external { }
sub init { }
1;
......@@ -258,8 +258,8 @@ any '/test' => sub {
any '/logout' => sub {
my $c = shift;
logout($c);
$c->redirect_to('/');
my $redirect_to = logout($c);
$c->redirect_to($redirect_to ? $redirect_to : '/');
};
get '/anonymous' => sub {
......@@ -2014,19 +2014,30 @@ sub login($c, $status=200) {
my $login = $c->param('login');
my $password = $c->param('password');
my $ticket = $c->param('ticket');
my $url = ($c->param('url') or $c->req->url->to_abs->path);
$url = '/' if $url =~ m{^/login};
my @error =();
if (defined $login || defined $password || $c->param('submit')) {
if (((defined $login) || (defined $password) || ((defined $c->param('submit')) && ($c->param('submit') ne 'sso'))) && (! $ticket)) {
push @error,("Empty login name") if !length $login;
push @error,("Empty password") if !length $password;
}
my $auth_ok;
if (!@error) {
if (defined $login && defined $password) {
eval { $auth_ok = Ravada::Auth::login($login, $password)};
} elsif (($c->param('ticket')) || ($c->param('submit') eq 'sso')) {
eval { $auth_ok = Ravada::Auth::login_external($ticket, $c->session('ticket')) };
if ($auth_ok && !$@) {
return $c->redirect_to($auth_ok->{'redirectTo'}) if ($auth_ok->{'redirectTo'});
$c->session('ticket' => $auth_ok->{'ticket'}) if ($auth_ok->{'ticket'});
$c->session('logoutURL' => $auth_ok->{'logoutURL'}) if ($auth_ok->{'logoutURL'});
$login = $auth_ok->name;
}
}
if ( !@error && defined $login && defined $password) {
my $auth_ok;
eval { $auth_ok = Ravada::Auth::login($login, $password)};
if ( $auth_ok && !$@) {
$c->session('login' => $login);
my $expiration = $SESSION_TIMEOUT;
......@@ -2050,7 +2061,7 @@ sub login($c, $status=200) {
$c->session(auto_view => $auto_view, expiration => $expiration);
app->log->info("Access granted to $login from "._remote_ip($c)) if $CONFIG_FRONT->{log}->{log};
return $c->redirect_to($url);
} else {
} elsif (defined $c->param('submit')) {
app->log->error("Access denied to $login from "._remote_ip($c)) if $CONFIG_FRONT->{log}->{log};
push @error,("Access denied");
}
......@@ -2072,6 +2083,7 @@ sub login($c, $status=200) {
,navbar_custom => 1
,login => $login
,error => \@error
,sso_available => $Ravada::Auth::SSO_OK,
,login_header => $CONFIG_FRONT->{login_header}
,login_message => $CONFIG_FRONT->{login_message}
,guide => $CONFIG_FRONT->{guide}
......@@ -2089,7 +2101,12 @@ sub logout {
sleep 1;
$c->session(expires => 0);
$c->session(login => undef);
$c->session(ticket => undef);
my $logout_url = $c->session('logoutURL');
$c->session(logoutURL => undef);
return $logout_url;
}
sub quick_start {
......@@ -2099,25 +2116,39 @@ sub quick_start {
my $login = $c->param('login');
my $password = $c->param('password');
my $ticket = $c->param('ticket');
my $id_base = $c->param('id_base');
my @error =();
if ($c->param('submit')) {
if (($c->param('submit') ne 'sso') && (! $ticket)) {
push @error,("Empty login name") if !length $login;
push @error,("Empty password") if !length $password;
}
if (defined $ticket) {
push @error, ("Empty ticket value") if !length $ticket;
}
if ( $login && $password ) {
if (!@error) {
my $log_ok;
eval { $log_ok = Ravada::Auth::login($login, $password) };
if ($log_ok) {
if ( $login && $password) {
eval { $log_ok = Ravada::Auth::login($login, $password) };
} elsif (($c->param('ticket')) || ($c->param('submit') eq 'sso')) {
eval { $log_ok = Ravada::Auth::login_external($ticket, $c->session('ticket')) };
if ($log_ok && !$@) {
return $c->redirect_to($log_ok->{'redirectTo'}) if ($log_ok->{'redirectTo'});
$c->session('ticket' => $log_ok->{'ticket'}) if ($log_ok->{'ticket'});
$c->session('logoutURL' => $log_ok->{'logoutURL'}) if ($log_ok->{'logoutURL'});
$login = $log_ok->name;
}
}
if ($log_ok && !$@) {
$c->session('login' => $login);
} else {
push @error,("Access denied");
logout($c);
return login($c);
}
else {
push @error,($@ ? $@ : "Access denied");
}
}
if ( $c->param('submit') && _logged_in($c) && defined $id_base ) {
return quick_start_domain($c, $id_base, ($login or $c->session('login')));
......
......@@ -13,9 +13,9 @@
<input class="form-control" id="user" name="login" value ="<%= $login %>" type="text" placeholder="<%=l 'User' %>" required autofocus>
<input class="form-control" id="pssw" type="password" name="password" value="" placeholder="<%=l 'Password' %>" required>
<input type="hidden" name="url" value="<%= $url %>">
<input type="hidden" name="url" value="<%= $url %>">
<!-- <input type="submit" name="submit" value="launch"> -->
<button id="submit" class="btn btn-success btn-lg btn-block" href="/" type="submit" name="submit" value="launch"><%=l 'Login' %></button>
<button id="submit" class="btn btn-success btn-lg btn-block" href="/" type="submit" name="submit" value="launch"><%=l 'Login with user/password' %></button>
% if (scalar @$error) {
% for my $i (@$error) {
<div class="alert alert-danger">
......@@ -24,6 +24,12 @@
% }
% }
</form>
<form class="form-singin" method="post">
<input type="hidden" name="url" value="<%= $url %>">
% if ($sso_available) {
<button id="submit" class="btn btn-success btn-lg btn-block" href="/" type="submit" name="submit" value="sso"><%=l 'SSO/CAS Login' %></button>
% }
</form>
<div class="alert alert-warning">
<%=l 'A viewer is required to run the virtual machines.' %>
<a href="/requirements"><%=l 'Read more.' %></a>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment