Choice.pm 7.98 KB
Newer Older
Xavier Guimard's avatar
Xavier Guimard committed
1 2 3 4
package Lemonldap::NG::Portal::Lib::Choice;

use strict;
use Mouse;
5
use Safe;
Xavier Guimard's avatar
Xavier Guimard committed
6 7

extends 'Lemonldap::NG::Portal::Lib::Wrapper';
Xavier Guimard's avatar
Xavier Guimard committed
8
with 'Lemonldap::NG::Portal::Lib::OverConf';
Xavier Guimard's avatar
Xavier Guimard committed
9 10 11 12 13

our $VERSION = '2.0.0';

has modules => ( is => 'rw', default => sub { {} } );

Xavier Guimard's avatar
Xavier Guimard committed
14 15
has rules => ( is => 'rw', default => sub { {} } );

Xavier Guimard's avatar
Xavier Guimard committed
16 17
has type => ( is => 'rw' );

Xavier Guimard's avatar
Xavier Guimard committed
18 19
has catch => ( is => 'rw', default => sub { {} } );

20 21
has sessionKey => ( is => 'ro', default => '_choice' );

Xavier Guimard's avatar
Xavier Guimard committed
22
my $_choiceRules;
Xavier Guimard's avatar
Xavier Guimard committed
23

Xavier Guimard's avatar
Xavier Guimard committed
24 25 26 27 28 29 30 31
# INITIALIZATION

# init() must be called by module::init() with a number:
#  - 0 for auth
#  - 1 for userDB
#  - 2 for passwordDB ?
sub init {
    my ( $self, $type ) = @_;
Xavier Guimard's avatar
Xavier Guimard committed
32
    $self->type($type);
Xavier Guimard's avatar
Xavier Guimard committed
33

Xavier Guimard's avatar
Xavier Guimard committed
34 35 36
    unless ( $self->conf->{authChoiceModules}
        and %{ $self->conf->{authChoiceModules} } )
    {
Xavier Guimard's avatar
Xavier Guimard committed
37 38 39 40
        $self->error("'authChoiceModules' is empty");
        return 0;
    }

Xavier Guimard's avatar
Xavier Guimard committed
41
    foreach my $name ( keys %{ $self->conf->{authChoiceModules} } ) {
Xavier Guimard's avatar
Xavier Guimard committed
42 43 44 45 46
        my @mods =
          split( /[;\|]/, $self->conf->{authChoiceModules}->{$name} );
        my $module = '::'
          . [ 'Auth', 'UserDB', 'Password' ]->[$type] . '::'
          . $mods[$type];
47 48
        my $over;
        if ( $mods[5] ) {
Xavier Guimard's avatar
Xavier Guimard committed
49
            eval { $over = JSON::from_json( $mods[5] ) };
50 51 52 53 54
            if ($@) {
                $self->logger->error("Bad over value ($@), skipped");
            }
        }
        if ( $module = $self->loadModule( $module, $over ) ) {
Xavier Guimard's avatar
Xavier Guimard committed
55
            $self->modules->{$name} = $module;
56
            $self->logger->debug(
Xavier Guimard's avatar
Xavier Guimard committed
57
                [qw(Authentication User Password)]->[$type]
Xavier Guimard's avatar
Xavier Guimard committed
58
                  . " module $name selected" );
Xavier Guimard's avatar
Xavier Guimard committed
59 60
        }
        else {
61
            $self->logger->error(
Xavier Guimard's avatar
Xavier Guimard committed
62
                "Choice: unable to load $name, disabling it: " . $self->error );
Xavier Guimard's avatar
Xavier Guimard committed
63
            $self->error('');
Xavier Guimard's avatar
Xavier Guimard committed
64
        }
Xavier Guimard's avatar
Xavier Guimard committed
65

Xavier Guimard's avatar
Xavier Guimard committed
66 67 68 69 70 71 72
        # Test if auth module wants to catch some path
        unless ($type) {
            if ( $module->can('catch') ) {
                $self->catch->{$name} = $module->catch;
            }
        }

Xavier Guimard's avatar
Xavier Guimard committed
73 74
        # Display conditions
        my $safe = Safe->new;
Xavier Guimard's avatar
Xavier Guimard committed
75
        my $cond = $mods[4];
76 77
        if ( defined $cond and $cond !~ /^$/ ) {
            $self->logger->debug("Found rule $cond for $name");
Xavier Guimard's avatar
Xavier Guimard committed
78 79
            $_choiceRules->{$name} =
              $safe->reval("sub{my(\$env)=\@_;return ($cond)}");
Xavier Guimard's avatar
Xavier Guimard committed
80 81 82 83 84 85
            if ($@) {
                $self->logger->error("Bad condition $cond: $@");
                return 0;
            }
        }
        else {
Xavier Guimard's avatar
Xavier Guimard committed
86
            $self->logger->debug("No rule for $name");
Xavier Guimard's avatar
Xavier Guimard committed
87
            $_choiceRules->{$name} = sub { 1 };
Xavier Guimard's avatar
Xavier Guimard committed
88
        }
Xavier Guimard's avatar
Xavier Guimard committed
89
    }
Xavier Guimard's avatar
Xavier Guimard committed
90
    unless ( keys %{ $self->modules } ) {
Xavier Guimard's avatar
Xavier Guimard committed
91 92 93
        $self->error('Choice: no available modules found, aborting');
        return 0;
    }
Xavier Guimard's avatar
Xavier Guimard committed
94
    return 1;
Xavier Guimard's avatar
Xavier Guimard committed
95 96
}

97 98
# RUNNING METHODS

Xavier Guimard's avatar
Xavier Guimard committed
99 100
sub checkChoice {
    my ( $self, $req ) = @_;
Xavier Guimard's avatar
Xavier Guimard committed
101
    my $name;
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

    # Check Choice from pdata
    if ( defined $req->pdata->{_choice} ) {

        $name = $req->pdata->{_choice};
        $self->logger->debug("Choice $name selected from pdata");
    }

    unless ($name) {

        # Check with catch method
        foreach ( keys %{ $self->catch } ) {
            if ( $req->path_info =~ $self->catch->{$_} ) {
                $name = $_;
                $self->logger->debug(
                    "Choice $name selected from " . $req->path_info );
                last;
            }
Xavier Guimard's avatar
Xavier Guimard committed
120 121
        }
    }
122 123 124 125

    unless ($name) {

        # Check with other methods
Xavier Guimard's avatar
Xavier Guimard committed
126 127 128 129 130
        $name ||=
             $req->param( $self->conf->{authChoiceParam} )
          || $req->userData->{_choice}
          || $req->sessionInfo->{_choice}
          or return 0;
131 132 133 134

        $self->logger->debug("Choice $name selected");
    }

Xavier Guimard's avatar
Xavier Guimard committed
135
    unless ( defined $self->modules->{$name} ) {
136
        $self->logger->error("Unknown choice '$name'");
Xavier Guimard's avatar
Xavier Guimard committed
137 138
        return 0;
    }
Xavier Guimard's avatar
Xavier Guimard committed
139 140

    # Store choice if module loops
141 142
    $req->pdata->{_choice}    = $name;
    $req->data->{_authChoice} = $name;
Xavier Guimard's avatar
Xavier Guimard committed
143
    return $name if ( $req->data->{ "enabledMods" . $self->type } );
Xavier Guimard's avatar
Xavier Guimard committed
144
    $req->sessionInfo->{_choice} = $name;
Xavier Guimard's avatar
Xavier Guimard committed
145
    $req->data->{ "enabledMods" . $self->type } = [ $self->modules->{$name} ];
Xavier Guimard's avatar
Xavier Guimard committed
146
    $self->p->_authentication->authnLevel("${name}AuthnLevel");
Xavier Guimard's avatar
Xavier Guimard committed
147
    return $name;
Xavier Guimard's avatar
Xavier Guimard committed
148 149
}

Xavier Guimard's avatar
Xavier Guimard committed
150 151
sub name {
    my ( $self, $req, $type ) = @_;
Xavier Guimard's avatar
Xavier Guimard committed
152
    unless ($req) {
Xavier Guimard's avatar
Xavier Guimard committed
153 154
        return 'Choice';
    }
Xavier Guimard's avatar
Xavier Guimard committed
155
    my $n = ref( $req->data->{ "enabledMods" . $self->type }->[0] );
Xavier Guimard's avatar
Xavier Guimard committed
156 157 158 159
    $n =~ s/^Lemonldap::NG::Portal::(?:(?:UserDB|Auth)::)?//;
    return $n;
}

Xavier Guimard's avatar
Xavier Guimard committed
160
package Lemonldap::NG::Portal::Main;
Xavier Guimard's avatar
Xavier Guimard committed
161 162

# Build authentication loop displayed in template
Xavier Guimard's avatar
Xavier Guimard committed
163
# Return authLoop array reference
Xavier Guimard's avatar
Xavier Guimard committed
164 165 166 167 168 169
sub _buildAuthLoop {
    my ( $self, $req ) = @_;
    my @authLoop;

    # Test authentication choices
    unless ( ref $self->conf->{authChoiceModules} eq 'HASH' ) {
170
        $self->logger->warn("No authentication choices defined");
Xavier Guimard's avatar
Xavier Guimard committed
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
        return [];
    }

    foreach ( sort keys %{ $self->conf->{authChoiceModules} } ) {

        my $name = $_;

        # Name can have a digit as first character
        # for sorting purpose
        # Remove it in displayed name
        $name =~ s/^(\d*)?(\s*)?//;

        # Replace also _ by space for a nice display
        $name =~ s/\_/ /g;

        # Find modules associated to authChoice
Xavier Guimard's avatar
Xavier Guimard committed
187 188
        my ( $auth, $userDB, $passwordDB, $url, $condition ) =
          split( /[;\|]/, $self->conf->{authChoiceModules}->{$_} );
Xavier Guimard's avatar
Xavier Guimard committed
189

190 191
        unless ( $_choiceRules->{$_} ) {
            $self->logger->error("$_ has no rule !!!");
Xavier Guimard's avatar
Xavier Guimard committed
192
            $_choiceRules->{$_} = sub { 1 };
Xavier Guimard's avatar
Xavier Guimard committed
193
        }
194
        unless ( $_choiceRules->{$_}->( $req->env ) ) {
195
            $self->logger->debug(
Xavier Guimard's avatar
Xavier Guimard committed
196
"Condition returns false, authentication choice $_ will not be displayed"
197 198 199
            );
        }
        else {
Xavier Guimard's avatar
Xavier Guimard committed
200
            $self->logger->debug("Displaying authentication choice $_");
201 202 203
            if ( $auth and $userDB and $passwordDB ) {

                # Default URL
Christophe Maudoux's avatar
Christophe Maudoux committed
204
                $req->{cspFormAction} ||= '';
Xavier Guimard's avatar
Xavier Guimard committed
205 206 207 208 209 210 211
                if (
                    defined $url
                    and not $self->checkXSSAttack( 'URI',
                        $req->env->{'REQUEST_URI'} )
                    and $url =~
                    q%^(https?://)?[^\s/.?#$].[^\s]+$% # URL must be well formatted
                  )
212
                {
Christophe Maudoux's avatar
Christophe Maudoux committed
213 214 215
                    #$url .= $req->env->{'REQUEST_URI'};

                    # Avoid append same URL
216
                    $req->{cspFormAction} .= " $url"
Xavier Guimard's avatar
Xavier Guimard committed
217
                      unless $req->{cspFormAction} =~ qr%\b$url\b%;
218 219 220 221 222 223 224
                }
                else {
                    $url .= '#';
                }
                $self->logger->debug("Use URL $url");

                # Options to store in the loop
225 226 227 228 229 230
                my $optionsLoop = {
                    name   => $name,
                    key    => $_,
                    module => $auth,
                    url    => $url
                };
231 232 233 234

                # Get displayType for this module
                no strict 'refs';
                my $displayType = "Lemonldap::NG::Portal::Auth::${auth}"
Xavier Guimard's avatar
Xavier Guimard committed
235
                  ->can('getDisplayType')->( $self, $req );
236 237 238 239

                $self->logger->debug(
                    "Display type $displayType for module $auth");
                $optionsLoop->{$displayType} = 1;
Christophe Maudoux's avatar
Christophe Maudoux committed
240 241
                my $logo = $_;
                if ( $auth eq 'Custom' ) {
Xavier Guimard's avatar
Xavier Guimard committed
242 243
                    $logo =
                      ( $self->{conf}->{customAuth} =~ /::(\w+)$/ )[0];
Christophe Maudoux's avatar
Christophe Maudoux committed
244
                }
Xavier Guimard's avatar
Xavier Guimard committed
245

246 247 248
                # If displayType is logo, check if key.png is available
                if (  -e $self->conf->{templateDir}
                    . "/../htdocs/static/common/modules/"
Christophe Maudoux's avatar
Christophe Maudoux committed
249
                    . $logo
250 251
                    . ".png" )
                {
Christophe Maudoux's avatar
Christophe Maudoux committed
252
                    $optionsLoop->{logoFile} = $logo . ".png";
253 254 255 256
                }
                else {
                    $optionsLoop->{logoFile} = $auth . ".png";
                }
Xavier Guimard's avatar
Xavier Guimard committed
257

258 259
                # Register item in loop
                push @authLoop, $optionsLoop;
Xavier Guimard's avatar
Xavier Guimard committed
260

261 262 263 264 265 266 267
                $self->logger->debug(
                    "Authentication choice $name will be displayed");
            }
            else {
                $req->error("Authentication choice $_ value is invalid");
                return 0;
            }
Xavier Guimard's avatar
Xavier Guimard committed
268 269 270 271 272 273 274 275 276 277
        }

    }

    return \@authLoop;

}

1;