2017-04-05 13:54:44 +02:00
|
|
|
package Lemonldap::NG::Portal::Auth::Kerberos;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
|
|
|
use GSSAPI;
|
|
|
|
use MIME::Base64;
|
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
2017-04-05 14:02:02 +02:00
|
|
|
PE_BADCREDENTIALS
|
2017-04-05 13:54:44 +02:00
|
|
|
PE_ERROR
|
2017-04-06 13:33:12 +02:00
|
|
|
PE_FIRSTACCESS
|
2017-04-05 13:54:44 +02:00
|
|
|
PE_OK
|
|
|
|
PE_SENDRESPONSE
|
|
|
|
);
|
|
|
|
|
|
|
|
our $VERSION = '2.0.0';
|
|
|
|
|
2018-02-19 22:11:43 +01:00
|
|
|
extends 'Lemonldap::NG::Portal::Main::Auth';
|
2017-04-05 13:54:44 +02:00
|
|
|
|
2017-04-06 22:37:38 +02:00
|
|
|
has keytab => ( is => 'rw' );
|
|
|
|
|
2017-04-05 13:54:44 +02:00
|
|
|
# INITIALIZATION
|
|
|
|
|
|
|
|
sub init {
|
|
|
|
my ($self) = @_;
|
2017-04-05 14:21:21 +02:00
|
|
|
my $file;
|
2017-04-06 23:25:41 +02:00
|
|
|
unless ( $file = $self->conf->{krbKeytab} ) {
|
2017-04-05 14:09:58 +02:00
|
|
|
$self->error('Keytab not defined');
|
|
|
|
return 0;
|
|
|
|
}
|
2017-04-05 14:21:21 +02:00
|
|
|
$self->keytab("FILE:$file");
|
2017-04-05 14:09:58 +02:00
|
|
|
return 1;
|
2017-04-05 13:54:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub extractFormInfo {
|
|
|
|
my ( $self, $req ) = @_;
|
2018-03-13 15:04:19 +01:00
|
|
|
|
2018-07-05 22:56:16 +02:00
|
|
|
if ( $req->data->{_krbUser} ) {
|
2018-03-13 15:04:19 +01:00
|
|
|
$self->logger->debug( 'Kerberos ticket already validated for '
|
2018-07-05 22:56:16 +02:00
|
|
|
. $req->data->{_krbUser} );
|
2018-03-13 15:04:19 +01:00
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
|
2017-04-05 13:54:44 +02:00
|
|
|
my $auth = $req->env->{HTTP_AUTHORIZATION};
|
|
|
|
unless ($auth) {
|
2017-04-06 13:33:12 +02:00
|
|
|
|
|
|
|
# Case 1: simple usage or first Kerberos Ajax request
|
|
|
|
# => return 401 to initiate Kerberos
|
2017-04-06 22:37:38 +02:00
|
|
|
if ( !$self->{conf}->{krbByJs} or $req->param('kerberos') ) {
|
2017-04-06 13:44:29 +02:00
|
|
|
$self->logger->debug('Initialize Kerberos dialog');
|
2017-04-06 13:33:12 +02:00
|
|
|
|
|
|
|
# Case 1.1: Ajax request
|
|
|
|
if ( $req->wantJSON ) {
|
|
|
|
$req->response(
|
|
|
|
[
|
|
|
|
401,
|
|
|
|
[
|
|
|
|
'WWW-Authenticate' => 'Negotiate',
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
'Content-Length' => 35
|
|
|
|
],
|
|
|
|
['{"error":"Authentication required"}']
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-04-06 13:44:29 +02:00
|
|
|
# Case 1.2: HTML request: display error and initiate Kerberos
|
|
|
|
# dialog
|
2017-04-06 13:33:12 +02:00
|
|
|
else {
|
|
|
|
$req->error(PE_BADCREDENTIALS);
|
|
|
|
push @{ $req->respHeaders }, 'WWW-Authenticate' => 'Negotiate';
|
|
|
|
my ( $tpl, $prms ) = $self->p->display($req);
|
|
|
|
$req->response(
|
|
|
|
$self->p->sendHtml(
|
|
|
|
$req, $tpl,
|
|
|
|
params => $prms,
|
|
|
|
code => 401
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return PE_SENDRESPONSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Case 2: Ajax Kerberos request has failed, and javascript has reloaded
|
|
|
|
# page with "kerberos=0". Return an error to be able to switch to
|
|
|
|
# another backend (Combination)
|
|
|
|
# switch to another backend
|
2017-04-06 22:37:38 +02:00
|
|
|
elsif ( defined $req->param('kerberos') ) {
|
2017-06-23 11:57:07 +02:00
|
|
|
$self->userLogger->warn(
|
|
|
|
'Kerberos authentication has failed, back to portal');
|
2017-11-06 12:25:55 +01:00
|
|
|
$self->p->setHiddenFormValue( $req, kerberos => 0, '', 0 );
|
2017-04-06 13:33:12 +02:00
|
|
|
return PE_BADCREDENTIALS;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Case 3: Display kerberos auth page (with javascript)
|
|
|
|
else {
|
2017-04-06 13:44:29 +02:00
|
|
|
$self->logger->debug('Send Kerberos javascript');
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{customScript} .=
|
2017-04-06 13:33:12 +02:00
|
|
|
'<script type="text/javascript" src="'
|
|
|
|
. $self->p->staticPrefix
|
2017-04-06 22:37:38 +02:00
|
|
|
. '/common/js/kerberos.js"></script>';
|
2018-02-08 22:31:41 +01:00
|
|
|
$self->p->setHiddenFormValue( $req, kerberos => 0, '', 0 );
|
2017-04-06 13:33:12 +02:00
|
|
|
return PE_FIRSTACCESS;
|
|
|
|
}
|
2017-04-05 13:54:44 +02:00
|
|
|
}
|
2017-04-06 13:33:12 +02:00
|
|
|
|
|
|
|
# Case 4: an "Authorization header" has been sent
|
2017-04-05 13:54:44 +02:00
|
|
|
if ( $auth !~ /^Negotiate (.*)$/ ) {
|
|
|
|
$self->userLogger->error('Bad authorization header');
|
2017-04-05 14:02:02 +02:00
|
|
|
return PE_BADCREDENTIALS;
|
2017-04-05 13:54:44 +02:00
|
|
|
}
|
2017-04-06 13:33:12 +02:00
|
|
|
|
|
|
|
# Case 5: Kerberos ticket received
|
2017-11-06 16:19:08 +01:00
|
|
|
$self->logger->debug("Kerberos ticket received: $1");
|
2017-04-05 13:54:44 +02:00
|
|
|
my $data;
|
|
|
|
eval { $data = MIME::Base64::decode($1) };
|
|
|
|
if ($@) {
|
|
|
|
$self->userLogger->error( 'Bad authorization header: ' . $@ );
|
2017-04-05 14:02:02 +02:00
|
|
|
return PE_BADCREDENTIALS;
|
2017-04-05 13:54:44 +02:00
|
|
|
}
|
2017-04-05 14:09:58 +02:00
|
|
|
$ENV{KRB5_KTNAME} = $self->keytab;
|
2017-11-06 16:19:08 +01:00
|
|
|
$self->logger->debug( "Set KRB5_KTNAME env to " . $ENV{KRB5_KTNAME} );
|
2017-04-05 13:54:44 +02:00
|
|
|
my $status = GSSAPI::Context::accept(
|
2017-11-06 16:19:08 +01:00
|
|
|
my $server_context,
|
|
|
|
GSS_C_NO_CREDENTIAL,
|
|
|
|
$data,
|
|
|
|
GSS_C_NO_CHANNEL_BINDINGS,
|
|
|
|
my $gss_client_name,
|
|
|
|
undef,
|
|
|
|
my $gss_output_token,
|
|
|
|
my $out_flags,
|
|
|
|
my $out_time,
|
|
|
|
my $gss_delegated_cred
|
2017-04-05 13:54:44 +02:00
|
|
|
);
|
|
|
|
unless ($status) {
|
|
|
|
$self->logger->error('Unable to accept security context');
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
my $client_name;
|
|
|
|
$status = $gss_client_name->display($client_name);
|
|
|
|
unless ($status) {
|
|
|
|
$self->logger->error('Unable to display KRB client name');
|
2017-04-05 13:59:29 +02:00
|
|
|
foreach ( $status->generic_message(), $status->specific_message() ) {
|
|
|
|
$self->logger->error($_);
|
|
|
|
}
|
2017-04-05 13:54:44 +02:00
|
|
|
return PE_ERROR;
|
|
|
|
}
|
2017-04-05 14:21:21 +02:00
|
|
|
$self->userLogger->notice("$client_name authentified by Kerberos");
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{_krbUser} = $client_name;
|
2017-11-06 16:19:06 +01:00
|
|
|
if ( $self->conf->{krbRemoveDomain} ) {
|
|
|
|
$client_name =~ s/^(.*)@.*$/$1/;
|
|
|
|
}
|
2017-04-05 13:54:44 +02:00
|
|
|
$req->user($client_name);
|
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub authenticate {
|
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
2017-04-11 14:57:52 +02:00
|
|
|
sub authLogout {
|
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
2017-04-05 13:54:44 +02:00
|
|
|
sub setAuthSessionInfo {
|
|
|
|
my ( $self, $req ) = @_;
|
2017-04-05 14:09:58 +02:00
|
|
|
$req->{sessionInfo}->{authenticationLevel} = $self->conf->{krbAuthnLevel};
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->{sessionInfo}->{_krbUser} = $req->data->{_krbUser};
|
2017-04-05 13:54:44 +02:00
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub getDisplayType {
|
|
|
|
return "logo";
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|