Conf.pm 17.5 KB
Newer Older
1 2 3 4 5 6 7
##@file
# Base package for Lemonldap::NG configuration system

##@class
# Implements Lemonldap::NG shared configuration system.
# In case of error or warning, the message is stored in the global variable
# $Lemonldap::NG::Common::Conf::msg
8
package Lemonldap::NG::Common::Conf;
9 10

use strict;
Xavier Guimard's avatar
Xavier Guimard committed
11
use utf8;
12
no strict 'refs';
Xavier Guimard's avatar
Xavier Guimard committed
13
use Lemonldap::NG::Common::Conf::Constants;    #inherits
14

Xavier Guimard's avatar
Xavier Guimard committed
15 16 17 18
# Import compacter
use Lemonldap::NG::Common::Conf::Compact;
*compactConf = \&Lemonldap::NG::Common::Conf::Compact::compactConf;

19 20
# TODO: don't import this big file, use a proxy
use Lemonldap::NG::Common::Conf::DefaultValues;    #inherits
21 22
use Lemonldap::NG::Common::Crypto
  ;    #link protected cipher Object "cypher" in configuration hash
23
use Config::IniFiles;
24

25 26 27
#inherits Lemonldap::NG::Common::Conf::File
#inherits Lemonldap::NG::Common::Conf::DBI
#inherits Lemonldap::NG::Common::Conf::SOAP
28
#inherits Lemonldap::NG::Common::Conf::LDAP
29

30
our $VERSION = '2.0.0';
31
our $msg     = '';
32
our $iniObj;
33

34 35 36 37 38 39 40
BEGIN {
    eval {
        require threads::shared;
        threads::shared::share($iniObj);
    };
}

41 42 43 44
## @cmethod Lemonldap::NG::Common::Conf new(hashRef arg)
# Constructor.
# Succeed if it has found a way to access to Lemonldap::NG configuration with
# $arg (or default file). It can be :
Xavier Guimard's avatar
Xavier Guimard committed
45
# - Nothing: default configuration file is tested,
46
# - { confFile => "/path/to/storage.conf" },
47 48 49
# - { Type => "File", dirName => "/path/to/conf/dir/" },
# - { Type => "DBI", dbiChain => "DBI:mysql:database=lemonldap-ng;host=1.2.3.4",
# dbiUser => "user", dbiPassword => "password" },
Xavier Guimard's avatar
Xavier Guimard committed
50
# - { Type => "SOAP", proxy => "https://auth.example.com/config" },
51 52
# - { Type => "LDAP", ldapServer => "ldap://localhost", ldapConfBranch => "ou=conf,ou=applications,dc=example,dc=com",
#  ldapBindDN => "cn=manager,dc=example,dc=com", ldapBindPassword => "secret"},
53 54 55 56 57
#
# $self->{type} contains the type of configuration access system and the
# corresponding package is loaded.
# @param $arg hash reference or hash table
# @return New Lemonldap::NG::Common::Conf object
58 59
sub new {
    my $class = shift;
60
    my $self = bless {}, $class;
61
    if ( ref( $_[0] ) ) {
62
        %$self = %{ $_[0] };
63 64
    }
    else {
65
        if ( (@_) && $#_ % 2 == 1 ) {
66 67
            %$self = @_;
        }
68
    }
69
    unless ( $self->{mdone} ) {
70
        unless ( $self->{type} ) {
Xavier Guimard's avatar
Xavier Guimard committed
71

72
            # Use local conf to get configStorage and localStorage
Xavier Guimard's avatar
Xavier Guimard committed
73 74 75
            my $localconf =
              $self->getLocalConf( CONFSECTION, $self->{confFile}, 0 );
            if ( defined $localconf ) {
76 77 78
                %$self = ( %$self, %$localconf );
            }
        }
79
        unless ( $self->{type} ) {
80
            $msg .= "Error: configStorage: type is not defined.\n";
81
            return 0;
82
        }
83
        unless ( $self->{type} =~ /^[\w:]+$/ ) {
84
            $msg .= "Error: configStorage: type is not well formed.\n";
85
        }
86
        $self->{type} = "Lemonldap::NG::Common::Conf::Backends::$self->{type}"
87
          unless $self->{type} =~ /^Lemonldap::/;
88
        eval "require $self->{type}";
89
        if ($@) {
90
            $msg .= "Error: Unknown package $self->{type}.\n";
91 92
            return 0;
        }
93
        return 0 unless $self->prereq;
94
        $self->{mdone}++;
95
        $msg = "$self->{type} loaded.\n";
96
    }
97 98 99
    if ( $self->{localStorage} and not defined( $self->{refLocalStorage} ) ) {
        eval "use $self->{localStorage};";
        if ($@) {
100
            $msg .= "Unable to load $self->{localStorage}: $@.\n";
101
        }
102 103 104

        # TODO: defer that until $> > 0 (to avoid creating local cache with
        # root privileges
105 106 107 108 109
        else {
            $self->{refLocalStorage} =
              $self->{localStorage}->new( $self->{localStorageOptions} );
        }
    }
110 111 112
    return $self;
}

113
## @method int saveConf(hashRef conf, hash args)
114 115
# Serialize $conf and call store().
# @param $conf Lemonldap::NG configuration hashRef
116 117
# @param %args Parameters
# @return Number of the saved configuration, 0 in case of error.
118
sub saveConf {
119
    my ( $self, $conf, %args ) = @_;
120

121 122
    my $last = $self->lastCfg;

123
    # If configuration was modified, return an error
124
    if ( not $args{force} ) {
125
        return CONFIG_WAS_CHANGED if ( $conf->{cfgNum} != $last );
126
        return DATABASE_LOCKED if ( $self->isLocked() or not $self->lock() );
127
    }
128
    $conf->{cfgNum} = $last + 1 unless ( $args{cfgNumFixed} );
129
    delete $conf->{cipher};
130 131

    # Try to store configuration
132
    my $tmp = $self->store($conf);
133 134 135

    unless ( $tmp > 0 ) {
        $msg .= "Configuration $conf->{cfgNum} not stored.\n";
136
        $self->unlock();
137 138 139 140
        return ( $tmp ? $tmp : UNKNOWN_ERROR );
    }

    $msg .= "Configuration $conf->{cfgNum} stored.\n";
141
    return ( $self->unlock() ? $tmp : UNKNOWN_ERROR );
142 143
}

144 145 146 147 148 149 150 151
## @method hashRef getConf(hashRef args)
# Get configuration from remote configuration storage system or from local
# cache if configuration has not been changed. If $args->{local} is set and if
# a local configuration is available, remote configuration is not tested.
#
# Uses lastCfg to test and getDBConf() to get the remote configuration
# @param $args Optional, contains {local=>1} or nothing
# @return Lemonldap::NG configuration
152
sub getConf {
153
    my ( $self, $args ) = @_;
154

155 156 157
    # Use only cache to get conf if $args->{local} is set
    if (    $>
        and $args->{local}
158 159 160
        and ref( $self->{refLocalStorage} )
        and my $res = $self->{refLocalStorage}->get('conf') )
    {
161
        $msg .= "Get configuration from cache without verification.\n";
162 163
        return $res;
    }
164 165 166

    # Check cfgNum in conf backend
    # Get conf in backend only if a newer configuration is available
167
    else {
168
        $args->{cfgNum} ||= $self->lastCfg;
169
        unless ( $args->{cfgNum} ) {
170
            $msg .= "No configuration available in backend.\n";
171
        }
172
        my $r;
173
        unless ( ref( $self->{refLocalStorage} ) ) {
174 175
            $msg .= "Get remote configuration (localStorage unavailable).\n";
            $r = $self->getDBConf($args);
Xavier Guimard's avatar
Xavier Guimard committed
176 177
            return undef unless ($r->{cfgNum});
            $self->setDefault( $r, $args->{localPrm} );
Xavier Guimard's avatar
Xavier Guimard committed
178
            $self->compactConf($r);
179
        }
180
        else {
181 182
            eval { $r = $self->{refLocalStorage}->get('conf') }
              if ( $> and not $args->{noCache} );
183
            $msg = "Warn: $@" if ($@);
184 185 186 187 188
            if (    ref($r)
                and $r->{cfgNum}
                and $args->{cfgNum}
                and $r->{cfgNum} == $args->{cfgNum} )
            {
189 190
                $msg .=
                  "Configuration unchanged, get configuration from cache.\n";
191
                $args->{noCache} = 1;
Xavier Guimard's avatar
Xavier Guimard committed
192
            }
193 194
            else {
                $r = $self->getDBConf($args);
195
                return undef unless ( $r->{cfgNum} );
196

Xavier Guimard's avatar
Xavier Guimard committed
197
                $self->setDefault( $r, $args->{localPrm} );
Xavier Guimard's avatar
Xavier Guimard committed
198 199
                $self->compactConf($r);

200 201
                # Store modified configuration in cache
                $self->setLocalConf($r)
202 203
                  if ( $self->{refLocalStorage}
                    and not( $args->{noCache} or $args->{raw} ) );
204 205 206 207

            }
        }

208
        # Create cipher object
209
        unless ( $args->{raw} ) {
210

211 212 213 214 215 216
            eval {
                $r->{cipher} = Lemonldap::NG::Common::Crypto->new( $r->{key} );
            };
            if ($@) {
                $msg .= "Bad key: $@. \n";
            }
217
        }
218

219
        # Return configuration hash
220
        return $r;
221 222 223
    }
}

Xavier Guimard's avatar
Xavier Guimard committed
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
# Set default values
sub setDefault {
    my ( $self, $conf, $localPrm ) = @_;
    my $defaultValues =
      Lemonldap::NG::Common::Conf::DefaultValues->defaultValues();
    if ( $localPrm and %$localPrm ) {
        foreach my $k ( keys %$localPrm ) {
            $conf->{$k} = $localPrm->{$k};
        }
    }
    foreach my $k ( keys %$defaultValues ) {
        $conf->{$k} //= $defaultValues->{$k};
    }

    # Convert old option useXForwardedForIP into trustedProxies
    if ( defined $conf->{useXForwardedForIP}
        and $conf->{useXForwardedForIP} == 1 )
    {
        $conf->{trustedProxies} = '*';
        delete $conf->{useXForwardedForIP};
    }

    # Force Choice backend
    if ( $conf->{authentication} eq "Choice" ) {
        $conf->{userDB}     = "Choice";
        $conf->{passwordDB} = "Choice";
    }

    # Some parameters expect key name (example), not variable ($example)
    if ( defined $conf->{whatToTrace} ) {
        $conf->{whatToTrace} =~ s/^\$//;
    }

    return $conf;
}

260
## @method hashRef getLocalConf(string section, string file, int loaddefault)
261 262
# Get configuration from local file
#
263 264 265
# @param $section Optional section name (default DEFAULTSECTION)
# @param $file Optional file name (default DEFAULTCONFFILE)
# @param $loaddefault Optional load default section parameters (default 1)
266 267
# @return Lemonldap::NG configuration
sub getLocalConf {
268
    my ( $self, $section, $file, $loaddefault ) = @_;
269
    my $r = {};
270

Xavier Guimard's avatar
Xavier Guimard committed
271
    $section ||= DEFAULTSECTION;
272 273 274 275
    $file ||=
         $self->{confFile}
      || $ENV{LLNG_DEFAULTCONFFILE}
      || DEFAULTCONFFILE;
276
    $loaddefault = 1 unless ( defined $loaddefault );
277 278 279
    my $cfg;

    # First, search if this file has been parsed
280
    unless ( $cfg = $iniObj->{$file} ) {
281

282 283 284 285 286
        # If default configuration cannot be read
        # - Error if configuration section is requested
        # - Silent exit for other section requests
        unless ( -r $file ) {
            if ( $section eq CONFSECTION ) {
287 288
                $msg .=
                  "Cannot read $file to get configuration access parameters.\n";
289
                return $r;
290
            }
291
            return $r;
292
        }
293

294
        # Parse ini file
295
        $cfg = Config::IniFiles->new( -file => $file, -allowcontinue => 1 );
296

297
        unless ( defined $cfg ) {
298
            $msg .= "Local config error: " . @Config::IniFiles::errors . "\n";
299
            return $r;
300
        }
301

302 303
        # Check if default section exists
        unless ( $cfg->SectionExists(DEFAULTSECTION) ) {
304
            $msg .= "Default section (" . DEFAULTSECTION . ") is missing. \n";
305
            return $r;
306
        }
307

308 309
        # Check if configuration section exists
        if ( $section eq CONFSECTION and !$cfg->SectionExists(CONFSECTION) ) {
310
            $msg .= "Configuration section (" . CONFSECTION . ") is missing.\n";
311
            return $r;
312
        }
313
    }
314

315
    # First load all default section parameters
Xavier Guimard's avatar
Xavier Guimard committed
316 317
    if ($loaddefault) {
        foreach ( $cfg->Parameters(DEFAULTSECTION) ) {
318
            $r->{$_} = $cfg->val( DEFAULTSECTION, $_ );
319
            if ( $r->{$_} =~ /^[{\[].*[}\]]$/ || $r->{$_} =~ /^sub\s*{.*}$/ ) {
320 321
                eval "\$r->{$_} = $r->{$_}";
                if ($@) {
322
                    $msg .= "Warning: error in file $file: $@.\n";
323
                    return $r;
324 325 326
                }
            }
        }
327 328 329
    }

    # Stop if the requested section is the default section
330
    return $r if ( $section eq DEFAULTSECTION );
331 332

    # Check if requested section exists
333
    return $r unless $cfg->SectionExists($section);
334 335

    # Load section parameters
Xavier Guimard's avatar
Xavier Guimard committed
336
    foreach ( $cfg->Parameters($section) ) {
337
        $r->{$_} = $cfg->val( $section, $_ );
338
        if ( $r->{$_} =~ /^[{\[].*[}\]]$/ || $r->{$_} =~ /^sub\s*{.*}$/ ) {
339 340
            eval "\$r->{$_} = $r->{$_}";
            if ($@) {
341
                $msg .= "Warning: error in file $file: $@.\n";
342
                return $r;
343 344
            }
        }
345 346 347 348 349
    }

    return $r;
}

350 351 352
## @method void setLocalConf(hashRef conf)
# Store $conf in the local cache.
# @param $conf Lemonldap::NG configuration hashRef
353 354
sub setLocalConf {
    my ( $self, $conf ) = @_;
355
    return unless ($>);
356
    eval { $self->{refLocalStorage}->set( "conf", $conf ) };
357
    $msg .= "Warn: $@\n" if ($@);
358 359
}

360 361 362 363 364 365
## @method hashRef getDBConf(hashRef args)
# Get configuration from remote storage system.
# @param $args hashRef that must contains a key "cfgNum" (number of the wanted
# configuration) and optionaly a key "fields" that points to an array of wanted
# configuration keys
# @return Lemonldap::NG configuration hashRef
366 367
sub getDBConf {
    my ( $self, $args ) = @_;
368
    return undef unless $args->{cfgNum};
369
    if ( $args->{cfgNum} < 0 ) {
370
        my @a = $self->available();
371 372 373 374
        $args->{cfgNum} =
            ( @a + $args->{cfgNum} > 0 )
          ? ( $a[ $#a + $args->{cfgNum} ] )
          : $a[0];
375
    }
376
    my $conf = $self->load( $args->{cfgNum} );
377 378
    $msg .= "Get configuration $conf->{cfgNum}.\n"
      if ( defined $conf->{cfgNum} );
379
    $self->setLocalConf($conf)
380 381 382
      if (  ref($conf)
        and $self->{refLocalStorage}
        and not( $args->{noCache} ) );
383 384 385
    return $conf;
}

386 387 388
## @method boolean prereq()
# Call prereq() from the $self->{type} package.
# @return True if succeed
389
sub prereq {
390
    return &{ $_[0]->{type} . '::prereq' }(@_);
391 392
}

393 394 395
## @method @ available()
# Call available() from the $self->{type} package.
# @return list of available configuration numbers
396
sub available {
397
    return &{ $_[0]->{type} . '::available' }(@_);
398 399
}

400 401 402
## @method int lastCfg()
# Call lastCfg() from the $self->{type} package.
# @return Number of the last configuration available
403
sub lastCfg {
404 405
    my $result = &{ $_[0]->{type} . '::lastCfg' }(@_) || "0";
    return $result;
406 407
}

408 409 410
## @method boolean lock()
# Call lock() from the $self->{type} package.
# @return True if succeed
411
sub lock {
412
    return &{ $_[0]->{type} . '::lock' }(@_);
413 414
}

415 416 417
## @method boolean isLocked()
# Call isLocked() from the $self->{type} package.
# @return True if database is locked
418
sub isLocked {
419
    return &{ $_[0]->{type} . '::isLocked' }(@_);
420 421
}

422 423 424
## @method boolean unlock()
# Call unlock() from the $self->{type} package.
# @return True if succeed
425
sub unlock {
426
    return &{ $_[0]->{type} . '::unlock' }(@_);
427 428
}

429 430 431 432
## @method int store(hashRef conf)
# Call store() from the $self->{type} package.
# @param $conf Lemondlap configuration serialized
# @return Number of new configuration stored if succeed, 0 else.
433
sub store {
434
    return &{ $_[0]->{type} . '::store' }(@_);
435 436
}

437 438 439
## @method load(int cfgNum, arrayRef fields)
# Call load() from the $self->{type} package.
# @return Lemonldap::NG Configuration hashRef if succeed, 0 else.
440
sub load {
441
    return &{ $_[0]->{type} . '::load' }(@_);
442 443
}

444 445 446 447
## @method boolean delete(int cfgNum)
# Call delete() from the $self->{type} package.
# @param $cfgNum Number of configuration to delete
# @return True if succeed
448
sub delete {
449
    my ( $self, $c ) = @_;
450
    my @a = $self->available();
451 452 453 454 455 456
    if ( grep( /^$c$/, @a ) ) {
        return &{ $self->{type} . '::delete' }( $self, $c );
    }
    else {
        return 0;
    }
457 458
}

459 460 461 462
sub logError {
    return &{ $_[0]->{type} . '::logError' }(@_);
}

463 464
1;
__END__
465 466 467

=head1 NAME

Xavier Guimard's avatar
Xavier Guimard committed
468 469
=encoding utf8

470
Lemonldap::NG::Common::Conf - Perl extension written to manage Lemonldap::NG
471 472 473 474
Web-SSO configuration.

=head1 SYNOPSIS

475
  use Lemonldap::NG::Common::Conf;
Xavier Guimard's avatar
Xavier Guimard committed
476 477
  # Lemonldap::NG::Common::Conf reads loacl configuration from lemonldap-ng.ini.
  # Parameters can be overriden in a hash:
478
  my $confAccess = new Lemonldap::NG::Common::Conf(
479
              {
Xavier Guimard's avatar
Xavier Guimard committed
480 481
                  type=>'File',
                  dirName=>"/tmp/",
482 483 484 485

                  # To use local cache, set :
                  localStorage => "Cache::FileCache",
                  localStorageOptions = {
486
                      'namespace' => 'lemonldap-ng-config',
487 488 489 490
                      'default_expires_in' => 600,
                      'directory_umask' => '007',
                      'cache_root' => '/tmp',
                      'cache_depth' => 5,
Xavier Guimard's avatar
Xavier Guimard committed
491
                  },
492
              },
493
    ) or die "Unable to build Lemonldap::NG::Common::Conf, see Apache logs";
Xavier Guimard's avatar
Xavier Guimard committed
494 495
  # Next, get global configuration. Note that local parameters override global
  # ones
496 497 498 499
  my $config = $confAccess->getConf();

=head1 DESCRIPTION

Xavier Guimard's avatar
Xavier Guimard committed
500 501
Lemonldap::NG::Common::Conf is used by all Lemonldap::NG packages to access to
local/global configuration.
502 503 504 505 506

=head2 SUBROUTINES

=over

Xavier Guimard's avatar
Xavier Guimard committed
507
=item * B<new> (constructor)
508

Xavier Guimard's avatar
Xavier Guimard committed
509 510 511
It can takes any Lemonldap::NG parameter to override configuration. The
'confFile' parameter can be used to override lemonldap-ng.ini path.
Examples:
512

Xavier Guimard's avatar
Xavier Guimard committed
513
=over
514

Xavier Guimard's avatar
Xavier Guimard committed
515
=item * B<Set another lemonldap-ng.ini file>
516
  $confAccess = new Lemonldap::NG::Common::Conf(
Xavier Guimard's avatar
Xavier Guimard committed
517 518
                  { confFile => '/opt/lemonldap-ng.ini' } );
=item * B<Override global storage>:
519
  $confAccess = new Lemonldap::NG::Common::Conf(
Xavier Guimard's avatar
Xavier Guimard committed
520 521 522 523
                  {
                    type    => 'File',
                    dirName => '/var/lib/lemonldap-ng/conf',
                   });
524

525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
=back

=item * B<getConf>: returns a hash reference to the configuration. it takes
a hash reference as first argument containing 2 optional parameters:

=over

=item * C<cfgNum => $number>: the number of the configuration wanted. If this
argument is omitted, the last configuration is returned.

=item * C<fields => [array of names]: the desired fields asked. By default,
getConf returns all (C<select * from lmConfig>).

=back

540
=item * B<saveConf>: stores the Lemonldap::NG configuration passed in argument
541 542 543 544 545 546
(hash reference). it returns the number of the new configuration.

=back

=head1 SEE ALSO

Xavier Guimard's avatar
Xavier Guimard committed
547
L<http://lemonldap-ng.org/>
548

Xavier Guimard's avatar
Xavier Guimard committed
549
=head1 AUTHORS
550

551 552
=over

Xavier Guimard's avatar
Xavier Guimard committed
553
=item LemonLDAP::NG team L<http://lemonldap-ng.org/team>
554 555

=back
556

557 558 559
=head1 BUG REPORT

Use OW2 system to report bug or ask for features:
Xavier Guimard's avatar
Xavier Guimard committed
560
L<http://jira.ow2.org>
561 562 563 564 565 566

=head1 DOWNLOAD

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

567 568
=head1 COPYRIGHT AND LICENSE

Xavier Guimard's avatar
Xavier Guimard committed
569
See COPYING file for details.
570 571

This library is free software; you can redistribute it and/or modify
572 573 574 575 576 577 578 579 580 581 582
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/>.
583 584

=cut