lemonldap-ng/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm

585 lines
18 KiB
Perl

##@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
package Lemonldap::NG::Common::Conf;
use strict;
use utf8;
no strict 'refs';
use Lemonldap::NG::Common::Conf::Constants; #inherits
# Import compacter
use Lemonldap::NG::Common::Conf::Compact;
*compactConf = \&Lemonldap::NG::Common::Conf::Compact::compactConf;
# TODO: don't import this big file, use a proxy
use Lemonldap::NG::Common::Conf::DefaultValues; #inherits
use Lemonldap::NG::Common::Crypto
; #link protected cipher Object "cypher" in configuration hash
use Config::IniFiles;
#inherits Lemonldap::NG::Common::Conf::File
#inherits Lemonldap::NG::Common::Conf::DBI
#inherits Lemonldap::NG::Common::Conf::SOAP
#inherits Lemonldap::NG::Common::Conf::LDAP
our $VERSION = '2.0.0';
our $msg = '';
our $iniObj;
BEGIN {
eval {
require threads::shared;
threads::shared::share($iniObj);
};
}
## @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 :
# - Nothing: default configuration file is tested,
# - { confFile => "/path/to/storage.conf" },
# - { Type => "File", dirName => "/path/to/conf/dir/" },
# - { Type => "DBI", dbiChain => "DBI:mysql:database=lemonldap-ng;host=1.2.3.4",
# dbiUser => "user", dbiPassword => "password" },
# - { Type => "SOAP", proxy => "https://auth.example.com/config" },
# - { Type => "LDAP", ldapServer => "ldap://localhost", ldapConfBranch => "ou=conf,ou=applications,dc=example,dc=com",
# ldapBindDN => "cn=manager,dc=example,dc=com", ldapBindPassword => "secret"},
#
# $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
sub new {
my $class = shift;
my $self = bless {}, $class;
if ( ref( $_[0] ) ) {
%$self = %{ $_[0] };
}
else {
if ( (@_) && $#_ % 2 == 1 ) {
%$self = @_;
}
}
unless ( $self->{mdone} ) {
unless ( $self->{type} ) {
# Use local conf to get configStorage and localStorage
my $localconf =
$self->getLocalConf( CONFSECTION, $self->{confFile}, 0 );
if ( defined $localconf ) {
%$self = ( %$self, %$localconf );
}
}
unless ( $self->{type} ) {
$msg .= "Error: configStorage: type is not defined.\n";
return 0;
}
unless ( $self->{type} =~ /^[\w:]+$/ ) {
$msg .= "Error: configStorage: type is not well formed.\n";
}
$self->{type} = "Lemonldap::NG::Common::Conf::Backends::$self->{type}"
unless $self->{type} =~ /^Lemonldap::/;
eval "require $self->{type}";
if ($@) {
$msg .= "Error: Unknown package $self->{type}.\n";
return 0;
}
return 0 unless $self->prereq;
$self->{mdone}++;
$msg = "$self->{type} loaded.\n";
}
if ( $self->{localStorage} and not defined( $self->{refLocalStorage} ) ) {
eval "use $self->{localStorage};";
if ($@) {
$msg .= "Unable to load $self->{localStorage}: $@.\n";
}
# TODO: defer that until $> > 0 (to avoid creating local cache with
# root privileges
else {
$self->{refLocalStorage} =
$self->{localStorage}->new( $self->{localStorageOptions} );
}
}
return $self;
}
## @method int saveConf(hashRef conf, hash args)
# Serialize $conf and call store().
# @param $conf Lemonldap::NG configuration hashRef
# @param %args Parameters
# @return Number of the saved configuration, 0 in case of error.
sub saveConf {
my ( $self, $conf, %args ) = @_;
my $last = $self->lastCfg;
# If configuration was modified, return an error
if ( not $args{force} ) {
return CONFIG_WAS_CHANGED if ( $conf->{cfgNum} != $last );
return DATABASE_LOCKED if ( $self->isLocked() or not $self->lock() );
}
$conf->{cfgNum} = $last + 1 unless ( $args{cfgNumFixed} );
delete $conf->{cipher};
# Try to store configuration
my $tmp = $self->store($conf);
unless ( $tmp > 0 ) {
$msg .= "Configuration $conf->{cfgNum} not stored.\n";
$self->unlock();
return ( $tmp ? $tmp : UNKNOWN_ERROR );
}
$msg .= "Configuration $conf->{cfgNum} stored.\n";
return ( $self->unlock() ? $tmp : UNKNOWN_ERROR );
}
## @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
sub getConf {
my ( $self, $args ) = @_;
# Use only cache to get conf if $args->{local} is set
if ( $>
and $args->{local}
and ref( $self->{refLocalStorage} )
and my $res = $self->{refLocalStorage}->get('conf') )
{
$msg .= "Get configuration from cache without verification.\n";
return $res;
}
# Check cfgNum in conf backend
# Get conf in backend only if a newer configuration is available
else {
$args->{cfgNum} ||= $self->lastCfg;
unless ( $args->{cfgNum} ) {
$msg .= "No configuration available in backend.\n";
}
my $r;
unless ( ref( $self->{refLocalStorage} ) ) {
$msg .= "Get remote configuration (localStorage unavailable).\n";
$r = $self->getDBConf($args);
return undef unless ($r->{cfgNum});
$self->setDefault( $r, $args->{localPrm} );
$self->compactConf($r);
}
else {
eval { $r = $self->{refLocalStorage}->get('conf') }
if ( $> and not $args->{noCache} );
$msg = "Warn: $@" if ($@);
if ( ref($r)
and $r->{cfgNum}
and $args->{cfgNum}
and $r->{cfgNum} == $args->{cfgNum} )
{
$msg .=
"Configuration unchanged, get configuration from cache.\n";
$args->{noCache} = 1;
}
else {
$r = $self->getDBConf($args);
return undef unless ( $r->{cfgNum} );
$self->setDefault( $r, $args->{localPrm} );
$self->compactConf($r);
# Store modified configuration in cache
$self->setLocalConf($r)
if ( $self->{refLocalStorage}
and not( $args->{noCache} or $args->{raw} ) );
}
}
# Create cipher object
unless ( $args->{raw} ) {
eval {
$r->{cipher} = Lemonldap::NG::Common::Crypto->new( $r->{key} );
};
if ($@) {
$msg .= "Bad key: $@. \n";
}
}
# Return configuration hash
return $r;
}
}
# 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;
}
## @method hashRef getLocalConf(string section, string file, int loaddefault)
# Get configuration from local file
#
# @param $section Optional section name (default DEFAULTSECTION)
# @param $file Optional file name (default DEFAULTCONFFILE)
# @param $loaddefault Optional load default section parameters (default 1)
# @return Lemonldap::NG configuration
sub getLocalConf {
my ( $self, $section, $file, $loaddefault ) = @_;
my $r = {};
$section ||= DEFAULTSECTION;
$file ||=
$self->{confFile}
|| $ENV{LLNG_DEFAULTCONFFILE}
|| DEFAULTCONFFILE;
$loaddefault = 1 unless ( defined $loaddefault );
my $cfg;
# First, search if this file has been parsed
unless ( $cfg = $iniObj->{$file} ) {
# 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 ) {
$msg .=
"Cannot read $file to get configuration access parameters.\n";
return $r;
}
return $r;
}
# Parse ini file
$cfg = Config::IniFiles->new( -file => $file, -allowcontinue => 1 );
unless ( defined $cfg ) {
$msg .= "Local config error: " . @Config::IniFiles::errors . "\n";
return $r;
}
# Check if default section exists
unless ( $cfg->SectionExists(DEFAULTSECTION) ) {
$msg .= "Default section (" . DEFAULTSECTION . ") is missing. \n";
return $r;
}
# Check if configuration section exists
if ( $section eq CONFSECTION and !$cfg->SectionExists(CONFSECTION) ) {
$msg .= "Configuration section (" . CONFSECTION . ") is missing.\n";
return $r;
}
}
# First load all default section parameters
if ($loaddefault) {
foreach ( $cfg->Parameters(DEFAULTSECTION) ) {
$r->{$_} = $cfg->val( DEFAULTSECTION, $_ );
if ( $r->{$_} =~ /^[{\[].*[}\]]$/ || $r->{$_} =~ /^sub\s*{.*}$/ ) {
eval "\$r->{$_} = $r->{$_}";
if ($@) {
$msg .= "Warning: error in file $file: $@.\n";
return $r;
}
}
}
}
# Stop if the requested section is the default section
return $r if ( $section eq DEFAULTSECTION );
# Check if requested section exists
return $r unless $cfg->SectionExists($section);
# Load section parameters
foreach ( $cfg->Parameters($section) ) {
$r->{$_} = $cfg->val( $section, $_ );
if ( $r->{$_} =~ /^[{\[].*[}\]]$/ || $r->{$_} =~ /^sub\s*{.*}$/ ) {
eval "\$r->{$_} = $r->{$_}";
if ($@) {
$msg .= "Warning: error in file $file: $@.\n";
return $r;
}
}
}
return $r;
}
## @method void setLocalConf(hashRef conf)
# Store $conf in the local cache.
# @param $conf Lemonldap::NG configuration hashRef
sub setLocalConf {
my ( $self, $conf ) = @_;
return unless ($>);
eval { $self->{refLocalStorage}->set( "conf", $conf ) };
$msg .= "Warn: $@\n" if ($@);
}
## @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
sub getDBConf {
my ( $self, $args ) = @_;
return undef unless $args->{cfgNum};
if ( $args->{cfgNum} < 0 ) {
my @a = $self->available();
$args->{cfgNum} =
( @a + $args->{cfgNum} > 0 )
? ( $a[ $#a + $args->{cfgNum} ] )
: $a[0];
}
my $conf = $self->load( $args->{cfgNum} );
$msg .= "Get configuration $conf->{cfgNum}.\n"
if ( defined $conf->{cfgNum} );
$self->setLocalConf($conf)
if ( ref($conf)
and $self->{refLocalStorage}
and not( $args->{noCache} ) );
return $conf;
}
## @method boolean prereq()
# Call prereq() from the $self->{type} package.
# @return True if succeed
sub prereq {
return &{ $_[0]->{type} . '::prereq' }(@_);
}
## @method @ available()
# Call available() from the $self->{type} package.
# @return list of available configuration numbers
sub available {
return &{ $_[0]->{type} . '::available' }(@_);
}
## @method int lastCfg()
# Call lastCfg() from the $self->{type} package.
# @return Number of the last configuration available
sub lastCfg {
my $result = &{ $_[0]->{type} . '::lastCfg' }(@_) || "0";
return $result;
}
## @method boolean lock()
# Call lock() from the $self->{type} package.
# @return True if succeed
sub lock {
return &{ $_[0]->{type} . '::lock' }(@_);
}
## @method boolean isLocked()
# Call isLocked() from the $self->{type} package.
# @return True if database is locked
sub isLocked {
return &{ $_[0]->{type} . '::isLocked' }(@_);
}
## @method boolean unlock()
# Call unlock() from the $self->{type} package.
# @return True if succeed
sub unlock {
return &{ $_[0]->{type} . '::unlock' }(@_);
}
## @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.
sub store {
return &{ $_[0]->{type} . '::store' }(@_);
}
## @method load(int cfgNum, arrayRef fields)
# Call load() from the $self->{type} package.
# @return Lemonldap::NG Configuration hashRef if succeed, 0 else.
sub load {
return &{ $_[0]->{type} . '::load' }(@_);
}
## @method boolean delete(int cfgNum)
# Call delete() from the $self->{type} package.
# @param $cfgNum Number of configuration to delete
# @return True if succeed
sub delete {
my ( $self, $c ) = @_;
my @a = $self->available();
if ( grep( /^$c$/, @a ) ) {
return &{ $self->{type} . '::delete' }( $self, $c );
}
else {
return 0;
}
}
sub logError {
return &{ $_[0]->{type} . '::logError' }(@_);
}
1;
__END__
=head1 NAME
=encoding utf8
Lemonldap::NG::Common::Conf - Perl extension written to manage Lemonldap::NG
Web-SSO configuration.
=head1 SYNOPSIS
use Lemonldap::NG::Common::Conf;
# Lemonldap::NG::Common::Conf reads loacl configuration from lemonldap-ng.ini.
# Parameters can be overriden in a hash:
my $confAccess = new Lemonldap::NG::Common::Conf(
{
type=>'File',
dirName=>"/tmp/",
# To use local cache, set :
localStorage => "Cache::FileCache",
localStorageOptions = {
'namespace' => 'lemonldap-ng-config',
'default_expires_in' => 600,
'directory_umask' => '007',
'cache_root' => '/tmp',
'cache_depth' => 5,
},
},
) or die "Unable to build Lemonldap::NG::Common::Conf, see Apache logs";
# Next, get global configuration. Note that local parameters override global
# ones
my $config = $confAccess->getConf();
=head1 DESCRIPTION
Lemonldap::NG::Common::Conf is used by all Lemonldap::NG packages to access to
local/global configuration.
=head2 SUBROUTINES
=over
=item * B<new> (constructor)
It can takes any Lemonldap::NG parameter to override configuration. The
'confFile' parameter can be used to override lemonldap-ng.ini path.
Examples:
=over
=item * B<Set another lemonldap-ng.ini file>
$confAccess = new Lemonldap::NG::Common::Conf(
{ confFile => '/opt/lemonldap-ng.ini' } );
=item * B<Override global storage>:
$confAccess = new Lemonldap::NG::Common::Conf(
{
type => 'File',
dirName => '/var/lib/lemonldap-ng/conf',
});
=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
=item * B<saveConf>: stores the Lemonldap::NG configuration passed in argument
(hash reference). it returns the number of the new configuration.
=back
=head1 SEE ALSO
L<http://lemonldap-ng.org/>
=head1 AUTHORS
=over
=item LemonLDAP::NG team L<http://lemonldap-ng.org/team>
=back
=head1 BUG REPORT
Use OW2 system to report bug or ask for features:
L<http://jira.ow2.org>
=head1 DOWNLOAD
Lemonldap::NG is available at
L<http://forge.objectweb.org/project/showfiles.php?group_id=274>
=head1 COPYRIGHT AND LICENSE
See COPYING file for details.
This library is free software; you can redistribute it and/or modify
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/>.
=cut