CGI.pm 12.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
## @file
# Auto-protected CGI machanism

## @class
# Base class for auto-protected CGI
package Lemonldap::NG::Handler::CGI;

use strict;

use Lemonldap::NG::Common::CGI;
11
use Lemonldap::NG::Common::Session;
12 13 14 15 16
use CGI::Cookie;
use MIME::Base64;

use base qw(Lemonldap::NG::Common::CGI);

dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
17
use Lemonldap::NG::Handler::SharedConf qw(:all);
18 19 20

#link Lemonldap::NG::Handler::_CGI protected _handler

21
our $VERSION = '1.4.1';
22 23 24 25 26 27 28 29 30

## @cmethod Lemonldap::NG::Handler::CGI new(hashRef args)
# Constructor.
# @param $args hash passed to Lemonldap::NG::Handler::_CGI object
# @return new object
sub new {
    my $class = shift;
    my $self = $class->SUPER::new() or $class->abort("Unable to build CGI");
    $Lemonldap::NG::Handler::_CGI::_cgi = $self;
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
31
    unless ( $Lemonldap::NG::Handler::_CGI::tsv->{cookieName} ) {
32
        Lemonldap::NG::Handler::_CGI->init(@_);
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
33 34

#Lemonldap::NG::Handler::_CGI->initLocalStorage(@_); # already called by _CGI->init()
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    }
    unless ( eval { Lemonldap::NG::Handler::_CGI->testConf() } == OK ) {
        if ( $_[0]->{noAbort} ) {
            $self->{_noConf} = $@;
        }
        else {
            $class->abort( "Unable to get configuration", $@ );
        }
    }

    # Arguments
    my @args = splice @_;
    if ( ref( $args[0] ) ) {
        %$self = ( %$self, %{ $args[0] } );
    }
    else {
        %$self = ( %$self, @args );
    }

    # Protection
    if ( $self->{protection} and $self->{protection} ne 'none' ) {
        $self->authenticate();

        # ACCOUNTING
        if ( $self->{protection} =~ /^manager$/i ) {
            $self->authorize()
              or $self->abort( 'Forbidden',
                "You don't have rights to access this page" );
        }
        elsif ( $self->{protection} =~ /rule\s*:\s*(.*)\s*$/i ) {
            my $rule = $1;
            $rule =~ s/\$date/&POSIX::strftime("%Y%m%d%H%M%S",localtime())/e;
            $rule =~ s/\$(\w+)/\$datas->{$1}/g;
            $rule = 0 if ( $rule eq 'deny' );
            my $r;
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
70

71
            unless ( $rule eq 'accept'
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
72
                or Lemonldap::NG::Handler::_CGI->safe_reval($rule) )
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
            {
                $self->abort( 'Forbidden',
                    "You don't have rights to access this page" );
            }
        }
        elsif ( $self->{protection} !~ /^authenticate$/i ) {
            $self->abort(
                'Bad configuration',
                "The rule <code>" . $self->{protection} . "</code> is not known"
            );
        }
    }
    return $self;
}

## @method boolean authenticate()
# Checks if user session is valid.
# Checks Lemonldap::NG cookie and search session in sessions database.
# If nothing is found, redirects the user to the Lemonldap::NG portal.
# @return boolean : true if authentication is good. Exit before else
sub authenticate {
    my $self = shift;
    $self->abort(
        "Can't authenticate because configuration has not been loaded",
        $self->{_noConf} )
      if ( $self->{_noConf} );
    my %cookies = fetch CGI::Cookie;
    my $id;
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
101 102 103
    unless ($cookies{ $tsv->{cookieName} }
        and $id = $cookies{ $tsv->{cookieName} }->value )
    {
104 105 106
        return $self->goToPortal();
    }
    unless ( $datas and $id eq $datas->{_session_id} ) {
107 108 109 110 111 112 113 114 115

        my $apacheSession = Lemonldap::NG::Common::Session->new(
            {
                storageModule        => $tsv->{globalStorage},
                storageModuleOptions => $tsv->{globalStorageOptions},
                cacheModule          => $tsv->{localSessionStorage},
                cacheModuleOptions   => $tsv->{localSessionStorageOptions},
                id                   => $id,
                kind                 => "SSO",
116
            }
117 118
        );

119
        if ( $apacheSession->error ) {
120 121
            Lemonldap::NG::Handler::Main::Logger->lmLog(
                "Session $id can't be retrieved", 'info' );
122 123
            Lemonldap::NG::Handler::Main::Logger->lmLog( $apacheSession->error,
                'info' );
124
            return $self->goToPortal();
125
        }
126 127 128

        $datas->{$_} = $apacheSession->data->{$_}
          foreach ( keys %{ $apacheSession->data } );
129 130 131
    }

    # Accounting : set user in apache logs
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
132 133
    $self->setApacheUser( $datas->{ $tsv->{whatToTrace} } );
    $ENV{REMOTE_USER} = $datas->{ $tsv->{whatToTrace} };
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194

    return 1;
}

## @method boolean authorize()
# Checks if user is authorized to access to the current request.
# Call Lemonldap::NG::Handler::_CGI::grant() function.
# @return boolean : true if user is granted
sub authorize {
    my $self = shift;
    return Lemonldap::NG::Handler::_CGI->grant( $ENV{REQUEST_URI} );
}

## @method int testUri(string uri)
# Checks if user is authorized to access to $uri.
# Call Lemonldap::NG::Handler::_CGI::grant() function.
# @param $uri URI or URL to test
# @return int : 1 if user is granted, -1 if virtual host has no configuration,
# 0 if user isn't granted
sub testUri {
    my $self = shift;
    $self->abort( "Can't test URI because configuration has not been loaded",
        $self->{_noConf} )
      if ( $self->{_noConf} );
    my $uri = shift;
    my $host =
      ( $uri =~ s#^(?:https?://)?([^/]*)/#/# ) ? $1 : $ENV{SERVER_NAME};
    return -1 unless ( Lemonldap::NG::Handler::_CGI->vhostAvailable($host) );
    return Lemonldap::NG::Handler::_CGI->grant( $uri, $host );
}

## @method hashRef user()
# @return hash of user datas
sub user {
    return $datas;
}

## @method boolean group(string group)
# @param $group name of the Lemonldap::NG group to test
# @return boolean : true if user is in this group
sub group {
    my ( $self, $group ) = splice @_;
    return ( $datas->{groups} =~ /\b$group\b/ );
}

## @method void goToPortal()
# Redirects the user to the portal and exit.
sub goToPortal {
    my $self = shift;
    my $tmp = encode_base64( $self->_uri, '' );
    print CGI::redirect(
        -uri => Lemonldap::NG::Handler::_CGI->portal() . "?url=$tmp" );
    exit;
}

## @fn private string _uri()
# Builds current URL including "http://" and server name.
# @return URL_string
sub _uri {
    my $vhost = $ENV{SERVER_NAME};
    my $portString =
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
195 196
         $tsv->{port}->{$vhost}
      || $tsv->{port}->{_}
197
      || $ENV{SERVER_PORT};
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
198 199 200 201 202
    my $_https = (
        defined( $tsv->{https}->{$vhost} )
        ? $tsv->{https}->{$vhost}
        : $tsv->{https}->{_}
    );
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
    $portString =
        ( $_https  && $portString == 443 ) ? ''
      : ( !$_https && $portString == 80 )  ? ''
      :                                      ':' . $portString;
    my $url = "http"
      . ( $_https ? "s" : "" ) . "://"
      . $vhost
      . $portString
      . $ENV{REQUEST_URI};
    return $url;
}

## @class
# Private class used by Lemonldap::NG::Handler::CGI for his internal handler.
package Lemonldap::NG::Handler::_CGI;

use strict;

dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
221 222
#use Lemonldap::NG::Handler::SharedConf qw(:locationRules :localStorage :traces);
use Lemonldap::NG::Handler::SharedConf qw(:tsv :ntsv :jailSharedVars);
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
223 224
use Lemonldap::NG::Handler::Main::Jail;

dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
225
use base qw(Lemonldap::NG::Handler::SharedConf);
226 227 228

our $_cgi;

dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242
sub safe_reval {
    my $class = shift;
    my $rule  = shift;

    my $jail = Lemonldap::NG::Handler::Main::Jail->new(
        'safe'            => $ntsv->{safe},
        'useSafeJail'     => $tsv->{useSafeJail},
        'customFunctions' => $tsv->{customFunctions}
    );
    $ntsv->{safe} = $jail->build_safe();

    return $ntsv->{safe}->reval($rule);
}

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
## @method boolean childInit()
# Since this is not a real Apache handler, childs have not to be initialized.
# @return true
sub childInit { 1 }

## @method boolean purgeCache()
# Since this is not a real Apache handler, it must not purge the cache at starting.
# @return true
sub purgeCache { 1 }

## @method void lmLog(string message,string level)
# Replace lmLog by "print STDERR $message".
# @param $message Message to log
# @param $level error level (debug, info, warning or error)
sub lmLog {
    my $class = shift;
    $_cgi->lmLog(@_);
}

## @method boolean vhostAvailable(string vhost)
# Checks if $vhost has been declared in configuration
# @param $vhost Virtual Host to test
# @return boolean : true if $vhost is available
sub vhostAvailable {
    my ( $self, $vhost ) = splice @_;
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
268
    return defined( $tsv->{defaultCondition}->{$vhost} );
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
}

## @method boolean grant(string uri, string vhost)
# Return true if user is granted to access.
# @param $uri URI string
# @param $vhost Optional virtual host (default current virtual host)
sub grant {
    my ( $self, $uri, $vhost ) = splice @_;
    $vhost ||= $ENV{SERVER_NAME};
    $apacheRequest = Lemonldap::NG::Apache::Request->new(
        {
            uri      => $uri,
            hostname => $vhost,
            args     => '',
        }
    );
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
285 286 287
    for ( my $i = 0 ; $i < $tsv->{locationCount}->{$vhost} ; $i++ ) {
        if ( $uri =~ $tsv->{locationRegexp}->{$vhost}->[$i] ) {
            return &{ $tsv->{locationCondition}->{$vhost}->[$i] }($datas);
288 289
        }
    }
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
290
    unless ( $tsv->{defaultCondition}->{$vhost} ) {
291 292 293 294 295 296
        $self->lmLog(
            "User rejected because VirtualHost \"$vhost\" has no configuration",
            'warn'
        );
        return 0;
    }
dcoutadeur dcoutadeur's avatar
 
dcoutadeur dcoutadeur committed
297
    return &{ $tsv->{defaultCondition}->{$vhost} }($datas);
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
}

package Lemonldap::NG::Apache::Request;

sub new {
    my $class = shift;
    my $self  = shift;
    return bless $self, $class;
}

sub hostname {
    return $_[0]->{hostname};
}

sub uri {
    return $_[0]->{uri};
}

sub args {
    return $_[0]->{args};
}

1;
__END__

Yadd's avatar
Yadd committed
323 324
=head1 NAME

Yadd's avatar
Yadd committed
325 326
=encoding utf8

Yadd's avatar
Yadd committed
327 328 329 330 331 332 333 334
Lemonldap::NG::Handler::CGI - Perl extension for using Lemonldap::NG
authentication in Perl CGI without using Lemonldap::NG::Handler

=head1 SYNOPSIS

  use Lemonldap::NG::Handler::CGI;
  my $cgi = Lemonldap::NG::Handler::CGI->new ( {
      # Local storage used for sessions and configuration
335
      localStorage        => "Cache::FileCache",
Yadd's avatar
Yadd committed
336 337 338 339 340 341 342 343 344
      localStorageOptions => {...},
      # How to get my configuration
      configStorage       => {
          type                => "DBI",
          dbiChain            => "DBI:mysql:database=lemondb;host=$hostname",
          dbiUser             => "lemonldap",
          dbiPassword          => "password",
      },
      https               => 0,
Yadd's avatar
Yadd committed
345
      # Optional
346 347 348 349 350
      protection    => 'rule: $uid eq "admin"',
      # Or to use rules from manager
      protection    => 'manager',
      # Or just to authenticate without managing authorization
      protection    => 'authenticate',
Yadd's avatar
Yadd committed
351 352 353
    }
  );
  
354
  # Lemonldap::NG cookie validation (done if you set "protection")
Yadd's avatar
Yadd committed
355 356
  $cgi->authenticate();
  
Yadd's avatar
Yadd committed
357
  # Optional Lemonldap::NG authorization (done if you set "protection")
Yadd's avatar
Yadd committed
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
  $cgi->authorize();
  
  # See CGI(3) for more about writing HTML pages
  print $cgi->header;
  print $cgi->start_html;
  
  # Since authentication phase, you can use user attributes and macros
  my $name = $cgi->user->{cn};
  
  # Instead of using "$cgi->user->{groups} =~ /\badmin\b/", you can use
  if( $cgi->group('admin') ) {
    # special html code for admins
  }
  else {
    # another HTML code
  }

=head1 DESCRIPTION

Lemonldap::NG::Handler provides the protection part of Lemonldap::NG web-SSO
system. It can be used with any system used with Apache (PHP or JSP pages for
example). If you need to protect only few Perl CGI, you can use this library
instead.

Warning, this module must not be used in a Lemonldap::NG::Handler protected
area because it hides Lemonldap::NG cookies. 

=head1 SEE ALSO

Yadd's avatar
Yadd committed
387
L<http://lemonldap-ng.org/>
Yadd's avatar
Yadd committed
388 389 390 391 392
L<CGI>, L<Lemonldap::NG::Handler>, L<Lemonldap::NG::Manager>,
L<Lemonldap::NG::Portal>

=head1 AUTHOR

393 394 395 396 397 398 399 400 401
=over

=item Clement Oudot, E<lt>clem.oudot@gmail.comE<gt>

=item Xavier Guimard, E<lt>x.guimard@free.frE<gt>

=item Sandro Cazzaniga, E<lt>cazzaniga.sandro@gmail.comE<gt>

=back
Yadd's avatar
Yadd committed
402 403 404 405

=head1 BUG REPORT

Use OW2 system to report bug or ask for features:
Yadd's avatar
Yadd committed
406
L<http://jira.ow2.org>
Yadd's avatar
Yadd committed
407 408 409 410 411 412 413 414

=head1 DOWNLOAD

Lemonldap::NG is available at
L<http://forge.objectweb.org/project/showfiles.php?group_id=274>

=head1 COPYRIGHT AND LICENSE

415 416 417 418 419 420 421 422 423
=over

=item Copyright (C) 2007, 2008, 2009, 2010, 2012 by Xavier Guimard, E<lt>x.guimard@free.frE<gt>

=item Copyright (C) 2012 by Sandro Cazzaniga, E<lt>cazzaniga.sandro@gmail.comE<gt>

=item Copyright (C) 2010, 2011, 2012 by Clement Oudot, E<lt>clem.oudot@gmail.comE<gt>

=back
Yadd's avatar
Yadd committed
424 425

This library is free software; you can redistribute it and/or modify
426 427 428 429 430 431 432 433 434 435 436
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see L<http://www.gnu.org/licenses/>.
Yadd's avatar
Yadd committed
437 438

=cut