Kerberos.pm 4.75 KB
Newer Older
Yadd's avatar
Yadd committed
1 2 3 4 5 6 7
package Lemonldap::NG::Portal::Auth::Kerberos;

use strict;
use Mouse;
use GSSAPI;
use MIME::Base64;
use Lemonldap::NG::Portal::Main::Constants qw(
8
  PE_BADCREDENTIALS
Yadd's avatar
Yadd committed
9
  PE_ERROR
Yadd's avatar
Yadd committed
10
  PE_FIRSTACCESS
Yadd's avatar
Yadd committed
11 12 13 14 15 16 17 18
  PE_OK
  PE_SENDRESPONSE
);

our $VERSION = '2.0.0';

extends 'Lemonldap::NG::Portal::Auth::Base';

Yadd's avatar
Yadd committed
19 20
has keytab => ( is => 'rw' );

Yadd's avatar
Yadd committed
21 22 23 24
# INITIALIZATION

sub init {
    my ($self) = @_;
Yadd's avatar
Yadd committed
25
    my $file;
Yadd's avatar
Yadd committed
26
    unless ( $file = $self->conf->{krbKeytab} ) {
Yadd's avatar
Yadd committed
27 28 29
        $self->error('Keytab not defined');
        return 0;
    }
Yadd's avatar
Yadd committed
30
    $self->keytab("FILE:$file");
Yadd's avatar
Yadd committed
31
    return 1;
Yadd's avatar
Yadd committed
32 33 34 35 36 37
}

sub extractFormInfo {
    my ( $self, $req ) = @_;
    my $auth = $req->env->{HTTP_AUTHORIZATION};
    unless ($auth) {
Yadd's avatar
Yadd committed
38 39 40

        # Case 1: simple usage or first Kerberos Ajax request
        #         => return 401 to initiate Kerberos
Yadd's avatar
Yadd committed
41
        if ( !$self->{conf}->{krbByJs} or $req->param('kerberos') ) {
Yadd's avatar
Yadd committed
42
            $self->logger->debug('Initialize Kerberos dialog');
Yadd's avatar
Yadd committed
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

            # Case 1.1: Ajax request
            if ( $req->wantJSON ) {
                $req->response(
                    [
                        401,
                        [
                            'WWW-Authenticate' => 'Negotiate',
                            'Content-Type'     => 'application/json',
                            'Content-Length'   => 35
                        ],
                        ['{"error":"Authentication required"}']
                    ]
                );
            }

Yadd's avatar
Yadd committed
59 60
            # Case 1.2: HTML request: display error and initiate Kerberos
            #           dialog
Yadd's avatar
Yadd committed
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
            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
Yadd's avatar
Yadd committed
80
        elsif ( defined $req->param('kerberos') ) {
dcoutadeur dcoutadeur's avatar
dcoutadeur dcoutadeur committed
81 82
            $self->userLogger->warn(
                'Kerberos authentication has failed, back to portal');
83
            $self->p->setHiddenFormValue( $req, kerberos => 0, '', 0 );
Yadd's avatar
Yadd committed
84 85 86 87 88
            return PE_BADCREDENTIALS;
        }

        # Case 3: Display kerberos auth page (with javascript)
        else {
Yadd's avatar
Yadd committed
89
            $self->logger->debug('Send Kerberos javascript');
Yadd's avatar
Yadd committed
90 91 92
            $req->datas->{customScript} .=
                '<script type="text/javascript" src="'
              . $self->p->staticPrefix
Yadd's avatar
Yadd committed
93
              . '/common/js/kerberos.js"></script>';
Yadd's avatar
Yadd committed
94 95
            return PE_FIRSTACCESS;
        }
Yadd's avatar
Yadd committed
96
    }
Yadd's avatar
Yadd committed
97 98

    # Case 4: an "Authorization header" has been sent
Yadd's avatar
Yadd committed
99 100
    if ( $auth !~ /^Negotiate (.*)$/ ) {
        $self->userLogger->error('Bad authorization header');
101
        return PE_BADCREDENTIALS;
Yadd's avatar
Yadd committed
102
    }
Yadd's avatar
Yadd committed
103 104

    # Case 5: Kerberos ticket received
Clément OUDOT's avatar
Clément OUDOT committed
105
    $self->logger->debug("Kerberos ticket received: $1");
Yadd's avatar
Yadd committed
106 107 108 109
    my $data;
    eval { $data = MIME::Base64::decode($1) };
    if ($@) {
        $self->userLogger->error( 'Bad authorization header: ' . $@ );
110
        return PE_BADCREDENTIALS;
Yadd's avatar
Yadd committed
111
    }
Yadd's avatar
Yadd committed
112
    $ENV{KRB5_KTNAME} = $self->keytab;
Clément OUDOT's avatar
Clément OUDOT committed
113
    $self->logger->debug( "Set KRB5_KTNAME env to " . $ENV{KRB5_KTNAME} );
Yadd's avatar
Yadd committed
114
    my $status = GSSAPI::Context::accept(
Clément OUDOT's avatar
Clément OUDOT committed
115 116 117 118 119 120 121 122 123 124
        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
Yadd's avatar
Yadd committed
125 126 127 128 129 130 131 132 133
    );
    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');
Yadd's avatar
Yadd committed
134 135 136
        foreach ( $status->generic_message(), $status->specific_message() ) {
            $self->logger->error($_);
        }
Yadd's avatar
Yadd committed
137 138
        return PE_ERROR;
    }
Yadd's avatar
Yadd committed
139
    $self->userLogger->notice("$client_name authentified by Kerberos");
140 141 142 143
    $req->{_krbUser} = $client_name;
    if ( $self->conf->{krbRemoveDomain} ) {
        $client_name =~ s/^(.*)@.*$/$1/;
    }
Yadd's avatar
Yadd committed
144 145 146 147 148 149 150 151
    $req->user($client_name);
    return PE_OK;
}

sub authenticate {
    PE_OK;
}

Yadd's avatar
Yadd committed
152 153 154 155
sub authLogout {
    PE_OK;
}

Yadd's avatar
Yadd committed
156 157
sub setAuthSessionInfo {
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
158
    $req->{sessionInfo}->{authenticationLevel} = $self->conf->{krbAuthnLevel};
159
    $req->{sessionInfo}->{_krbUser}            = $req->{_krbUser};
Yadd's avatar
Yadd committed
160 161 162 163 164 165 166 167
    PE_OK;
}

sub getDisplayType {
    return "logo";
}

1;