Init.pm 9.7 KB
Newer Older
Xavier Guimard's avatar
Xavier Guimard committed
1
##@class Lemonldap::NG::Portal::Main::Init
2
# Initialization part of Lemonldap::NG portal
3
#
Xavier Guimard's avatar
Xavier Guimard committed
4
# 2 public methods:
5 6 7 8
#  - init():       launch at startup. Load 'portal' section of lemonldap-ng.ini,
#                  initialize default route and launch reloadConf()
#  - reloadConf(): (re)load configuration using localConf (ie 'portal' section
#                  of lemonldap-ng.ini) and underlying handler configuration
Xavier Guimard's avatar
Xavier Guimard committed
9
package Lemonldap::NG::Portal::Main::Init;
Xavier Guimard's avatar
Xavier Guimard committed
10

Xavier Guimard's avatar
Xavier Guimard committed
11 12 13 14
our $VERSION = '2.0.0';

package Lemonldap::NG::Portal::Main;

Xavier Guimard's avatar
Xavier Guimard committed
15 16
use strict;
use Mouse;
Xavier Guimard's avatar
Xavier Guimard committed
17
use Regexp::Assemble;
Xavier Guimard's avatar
Xavier Guimard committed
18

Xavier Guimard's avatar
Xavier Guimard committed
19 20
# PROPERTIES

Xavier Guimard's avatar
Xavier Guimard committed
21
# Configuration storage
Xavier Guimard's avatar
Xavier Guimard committed
22 23
has localConfig => ( is => 'rw', default => sub { {} } );
has conf        => ( is => 'rw', default => sub { {} } );
Xavier Guimard's avatar
Xavier Guimard committed
24
has menu        => ( is => 'rw', default => sub { {} } );
Xavier Guimard's avatar
Xavier Guimard committed
25

Xavier Guimard's avatar
Xavier Guimard committed
26 27 28
# Sub modules
has _authentication => ( is => 'rw' );
has _userDB         => ( is => 'rw' );
Xavier Guimard's avatar
Xavier Guimard committed
29
has _passwordDB     => ( is => 'rw' );
Xavier Guimard's avatar
Xavier Guimard committed
30

31 32
has loadedModules => ( is => 'rw' );

33
# Macros and groups
Xavier Guimard's avatar
Xavier Guimard committed
34 35 36
has _macros     => ( is => 'rw' );
has _groups     => ( is => 'rw' );
has _jsRedirect => ( is => 'rw' );
37

Xavier Guimard's avatar
Xavier Guimard committed
38
# TrustedDomain regexp
39
has trustedDomainsRe => ( is => 'rw' );
Xavier Guimard's avatar
Xavier Guimard committed
40

41
# Lists to store plugins entry-points
Xavier Guimard's avatar
Xavier Guimard committed
42
has beforeAuth => (
43 44 45 46
    is      => 'rw',
    isa     => 'ArrayRef',
    default => sub { [] }
);
Xavier Guimard's avatar
Xavier Guimard committed
47
has betweenAuthAndDatas => (
48 49 50 51
    is      => 'rw',
    isa     => 'ArrayRef',
    default => sub { [] }
);
Xavier Guimard's avatar
Xavier Guimard committed
52
has afterDatas => (
53 54 55 56 57 58 59 60 61
    is      => 'rw',
    isa     => 'ArrayRef',
    default => sub { [] }
);
has forAuthUser => (
    is      => 'rw',
    isa     => 'ArrayRef',
    default => sub { [] }
);
62 63 64 65 66
has beforeLogout => (
    is      => 'rw',
    isa     => 'ArrayRef',
    default => sub { [] }
);
Xavier Guimard's avatar
Xavier Guimard committed
67

68 69 70
# Custom template parameters
has customParameters => ( is => 'rw', default => sub { {} } );

71 72 73
# Content-Security-Policy header
has csp => ( is => 'rw' );

Xavier Guimard's avatar
Xavier Guimard committed
74 75
# INITIALIZATION

Xavier Guimard's avatar
Xavier Guimard committed
76 77 78
sub init {
    my ( $self, $args ) = @_;
    $args ||= {};
79 80 81 82 83 84 85 86
    $self->localConfig(
        {
            %{ Lemonldap::NG::Common::Conf->new( $args->{configStorage} )
                  ->getLocalConf('portal')
            },
            %$args
        }
    );
Xavier Guimard's avatar
Xavier Guimard committed
87
    foreach my $k ( keys %{ $self->localConfig } ) {
88 89 90 91
        if ( $k =~ /tpl_(.*)/ ) {
            $self->customParameters->{$1} = $self->localConfig->{$k};
        }
    }
92 93 94

    # Purge loaded module list
    $self->loadedModules( {} );
95
    Lemonldap::NG::Handler::Main->onReload( $self, 'reloadConf' );
Xavier Guimard's avatar
Xavier Guimard committed
96
    return 0 unless ( $self->SUPER::init($args) );
97
    return 0 if ( $self->error );
Xavier Guimard's avatar
Xavier Guimard committed
98

99 100
    # Handle requests (other path may be declared in enabled plugins)
    $self
101

102
      # "/"
103 104 105 106
      ->addUnauthRoute( '*' => 'login',     ['GET'] )
      ->addUnauthRoute( '*' => 'postLogin', ['POST'] )
      ->addAuthRoute( '*' => 'authenticatedRequest',     ['GET'] )
      ->addAuthRoute( '*' => 'postAuthenticatedRequest', ['POST'] )
107 108

      # Core REST API
109 110 111 112 113
      ->addUnauthRoute( ping => 'pleaseAuth', ['GET'] )
      ->addAuthRoute( ping => 'authenticated', ['GET'] )

      # Logout
      ->addAuthRoute( logout => 'logout', ['GET'] );
114

115 116
    # Default routes must point to routines declared above
    $self->defaultAuthRoute('');
117
    $self->defaultUnauthRoute('');
118

119
    return 1;
120 121 122
}

sub reloadConf {
123
    my ( $self, $conf ) = @_;
124

Xavier Guimard's avatar
Xavier Guimard committed
125 126
    # Reinitialize $self->conf
    %{ $self->{conf} } = %{ $self->localConfig };
Xavier Guimard's avatar
Xavier Guimard committed
127

128
    # Reinitialize arrays
Xavier Guimard's avatar
Xavier Guimard committed
129
    foreach (
130
        qw(_macros _groups beforeAuth betweenAuthAndDatas afterDatas forAuthUser beforeLogout)
Xavier Guimard's avatar
Xavier Guimard committed
131 132
      )
    {
133 134 135
        $self->{$_} = [];
    }

136 137
    # Load conf in portal object
    foreach my $key ( keys %$conf ) {
Xavier Guimard's avatar
Xavier Guimard committed
138
        $self->{conf}->{$key} ||= $conf->{$key};
139
    }
Xavier Guimard's avatar
Xavier Guimard committed
140

141 142 143 144
    # Initialize content-security-policy header
    my $csp = '';
    foreach (qw(default img src style font connect)) {
        my $prm = $self->conf->{ 'csp' . ucfirst($_) };
145
        $csp .= "$_-src $prm;" if ($prm);
146 147 148
    }
    $self->csp($csp);

Xavier Guimard's avatar
Xavier Guimard committed
149 150 151 152
    # Initialize templateDir
    $self->{templateDir} =
      $self->conf->{templateDir} . '/' . $self->conf->{portalSkin};

Xavier Guimard's avatar
Xavier Guimard committed
153
    $self->{staticPrefix} = $self->conf->{staticPrefix} || '/static';
154
    $self->{languages}    = $self->conf->{languages}    || '/';
Xavier Guimard's avatar
Xavier Guimard committed
155

156 157 158 159 160
    # Initialize session DBs
    unless ( $self->conf->{globalStorage} ) {
        $self->error(
            'globalStorage not defined (perhaps configuration can not be read)'
        );
Xavier Guimard's avatar
Xavier Guimard committed
161
        return $self->fail;
162
    }
Xavier Guimard's avatar
Xavier Guimard committed
163

164 165 166
    # Initialize persistent session DB
    unless ( $self->conf->{persistentStorage} ) {
        $self->conf->{persistentStorage} = $self->conf->{globalStorage};
Xavier Guimard's avatar
Xavier Guimard committed
167 168
        $self->conf->{persistentStorageOptions} =
          $self->conf->{globalStorageOptions};
169 170
    }

171 172 173
    # Initialize cookie domain
    unless ( $self->conf->{domain} ) {
        $self->error('Configuration error: no domain');
Xavier Guimard's avatar
Xavier Guimard committed
174
        return $self->fail;
175 176
    }
    $self->conf->{domain} =~ s/^([^\.])/.$1/;
Xavier Guimard's avatar
Xavier Guimard committed
177

178 179
    # Load authentication/userDB
    # --------------------------
180
    my $mod;
181 182 183
    for my $type (qw(authentication userDB)) {
        unless ( $self->conf->{$type} ) {
            $self->error("$type is not set");
Xavier Guimard's avatar
Xavier Guimard committed
184
            return $self->fail;
Xavier Guimard's avatar
Xavier Guimard committed
185
        }
186
        $mod = $self->conf->{$type} unless ( $self->conf->{$type} eq 'Same' );
187
        my $module = '::' . ucfirst($type) . '::' . $mod;
Xavier Guimard's avatar
Xavier Guimard committed
188
        $module =~ s/Authentication/Auth/;
189 190

        # Launch and initialize module
Xavier Guimard's avatar
Xavier Guimard committed
191
        return $self->fail
192
          unless ( $self->{"_$type"} = $self->loadPlugin($module) );
193
    }
194

Xavier Guimard's avatar
Xavier Guimard committed
195
    # Initialize trusted domain regexp
Xavier Guimard's avatar
Xavier Guimard committed
196 197 198
    if (    $self->conf->{trustedDomains}
        and $self->conf->{trustedDomains} =~ /^\s*\*\s*$/ )
    {
199
        $self->trustedDomainsRe(qr#^https?://#);
Xavier Guimard's avatar
Xavier Guimard committed
200 201 202 203 204 205
    }
    else {
        my $re = Regexp::Assemble->new();
        if ( my $td = $self->conf->{trustedDomains} ) {
            $td =~ s/^\s*(.*?)\s*/$1/;
            foreach ( split( /\s+/, $td ) ) {
206
                next unless ($td);
Xavier Guimard's avatar
Xavier Guimard committed
207
                s#^\.#([^/]+\.)?#;
208 209 210 211 212 213 214 215
                $self->lmLog( "Domain $_ added in trusted domains", 'debug' );
                s/\./\\./g;

                # This regexp is valid for the followings hosts:
                #  - $td
                #  - $domainlabel.$td
                # $domainlabel is build looking RFC2396
                # (see Regexp::Common::URI::RFC2396)
216 217
                $_ =~
                  s/\*\\\./(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9]\\.)*/g;
218
                $re->add("$_");
Xavier Guimard's avatar
Xavier Guimard committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
            }
        }
        foreach my $vhost ( keys %{ $self->conf->{locationRules} } ) {
            $self->lmLog( "Vhost $vhost added in trusted domains", 'debug' );
            $re->add( quotemeta($vhost) );
            if ( my $tmp =
                $self->conf->{vhostOptions}->{$vhost}->{vhostAliases} )
            {
                foreach my $alias ( split /\s+/, $tmp ) {
                    $self->lmLog( "Alias $alias added in trusted domains",
                        'debug' );
                    $re->add( quotemeta($alias) );
                }
            }
        }
234 235
        my $tmp = 'https?://' . $re->as_string . '(?::\d+)?(?:/|$)';
        $self->trustedDomainsRe(qr/$tmp/);
236
    }
237

238 239 240 241 242
    # Compile macros in _macros, groups in _groups
    foreach my $type (qw(macros groups)) {
        $self->{"_$type"} = {};
        if ( $self->conf->{$type} ) {
            for my $name ( sort keys %{ $self->conf->{$type} } ) {
243
                my $sub =
Xavier Guimard's avatar
Xavier Guimard committed
244 245
                  HANDLER->buildSub(
                    HANDLER->substitute( $self->conf->{$type}->{$name} ) );
246 247 248 249 250 251 252 253 254 255 256 257 258
                if ($sub) {
                    $self->{"_$type"}->{$name} = $sub;
                }
                else {
                    $self->lmLog(
                        "$type $name returns an error: "
                          . HANDLER->tsv->{jail}->error,
                        'error'
                    );
                }
            }
        }
    }
Xavier Guimard's avatar
Xavier Guimard committed
259
    $self->{_jsRedirect} =
Xavier Guimard's avatar
Xavier Guimard committed
260
      HANDLER->buildSub( HANDLER->substitute( $self->conf->{jsRedirect} ) )
Xavier Guimard's avatar
Xavier Guimard committed
261 262 263
      or $self->lmLog(
        'jsRedirect returns an error: ' . HANDLER->tsv->{jail}->error,
        'error' );
264

Xavier Guimard's avatar
Xavier Guimard committed
265 266 267
    $self->menu( $self->loadPlugin('::Main::Menu') );
    $self->displayInit;

268 269
    # Load plugins
    foreach my $plugin ( $self->enabledPlugins ) {
Xavier Guimard's avatar
Xavier Guimard committed
270
        $self->loadPlugin($plugin) or return $self->fail;
Xavier Guimard's avatar
Xavier Guimard committed
271
    }
Xavier Guimard's avatar
Xavier Guimard committed
272

Xavier Guimard's avatar
Xavier Guimard committed
273 274 275
    1;
}

276 277 278 279
sub loadPlugin {
    my ( $self, $plugin ) = @_;
    my $obj;
    return 0
Xavier Guimard's avatar
Xavier Guimard committed
280
      unless ( $obj = $self->loadModule("$plugin") );
281 282 283 284 285
    return $self->findEP( $plugin, $obj );
}

sub findEP {
    my ( $self, $plugin, $obj ) = @_;
286
    foreach my $sub (
287
        qw(beforeAuth betweenAuthAndDatas afterDatas forAuthUser beforeLogout))
288 289
    {
        if ( $obj->can($sub) ) {
Xavier Guimard's avatar
Xavier Guimard committed
290
            $self->lmLog( " Found $sub entry point:", 'debug' );
291
            if ( my $callback = $obj->$sub ) {
292
                push @{ $self->{$sub} }, sub { $obj->$callback( $_[0] ) };
Xavier Guimard's avatar
Xavier Guimard committed
293
                $self->lmLog( "  -> $callback", 'debug' );
294
            }
295 296
        }
    }
297
    ( $obj and $obj->init ) or return 0;
Xavier Guimard's avatar
Xavier Guimard committed
298
    $self->lmLog( "Plugin $plugin initializated", 'debug' );
299
    return $obj;
300 301
}

Xavier Guimard's avatar
Xavier Guimard committed
302
sub loadModule {
303 304
    my ( $self, $module, $conf ) = @_;
    $conf //= $self->conf;
305
    my $obj;
Xavier Guimard's avatar
Xavier Guimard committed
306
    $module = "Lemonldap::NG::Portal$module" if ( $module =~ /^::/ );
Xavier Guimard's avatar
Xavier Guimard committed
307 308 309

    eval "require $module";
    if ($@) {
Xavier Guimard's avatar
Xavier Guimard committed
310
        $self->lmLog( "$module load error: $@", 'error' );
Xavier Guimard's avatar
Xavier Guimard committed
311 312
        return 0;
    }
Xavier Guimard's avatar
Xavier Guimard committed
313
    eval {
314
        $obj = $module->new( { p => $self, conf => $conf } );
315
        $self->lmLog( "Module $module loaded", 'debug' );
Xavier Guimard's avatar
Xavier Guimard committed
316 317 318 319 320
    };
    if ($@) {
        $self->error("Unable to build $module object: $@");
        return 0;
    }
321
    $self->loadedModules->{$module} = $obj;
322
    return $obj;
Xavier Guimard's avatar
Xavier Guimard committed
323 324
}

Xavier Guimard's avatar
Xavier Guimard committed
325
sub fail {
Xavier Guimard's avatar
Xavier Guimard committed
326
    $_[0]->lmLog( $_[0]->error, 'error' );
327 328
    $_[0]->addUnauthRoute( '*' => 'displayError' );
    $_[0]->addAuthRoute( '*' => 'displayError' );
Xavier Guimard's avatar
Xavier Guimard committed
329 330 331
    return 0;
}

332 333 334 335 336 337
sub displayError {
    my ( $self, $req ) = @_;
    return $self->sendError( $req, 'Portal error, contact your administrator',
        500 );
}

Xavier Guimard's avatar
Xavier Guimard committed
338
1;