You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.8 KiB
207 lines
5.8 KiB
package Lemonldap::NG::Common::Combination::Parser;
|
|
|
|
use strict;
|
|
use Mouse;
|
|
use Safe;
|
|
use constant PE_OK => 0;
|
|
|
|
our $VERSION = '2.0.0';
|
|
|
|
# Handle "if then else" (used during init)
|
|
# return a sub that can be called with ($req) to get a [array] of combination
|
|
#
|
|
# During auth, these combinations represents "or" (like Multi)
|
|
# Each combination is a [authSub,userSub] called like this:
|
|
# $authSub->('authenticate',$req)
|
|
# This means that the 'authenticate' method of the real auth module will be
|
|
# called with $req
|
|
|
|
sub parse {
|
|
my ( $self, $moduleList, $expr ) = @_;
|
|
|
|
my $sub = '';
|
|
my $rest = $expr;
|
|
if ( $rest =~ s/^\s*if\s*\(// ) {
|
|
my ( $cond, $then, $else );
|
|
( $cond, $rest ) = $self->findB( $rest, ')' );
|
|
unless ( length $cond ) {
|
|
die('Bad combination: unmatched bracket');
|
|
}
|
|
unless ( $rest =~ s/^\s*\bthen\b\s*// ) {
|
|
die('Bad combination: missing "then"');
|
|
}
|
|
unless ( $rest =~ /(.*?)\s*\belse\b\s*(.*)$/ ) {
|
|
die('Bad combination: missing "else"');
|
|
}
|
|
( $then, $else ) = ( $1, $2 );
|
|
unless ($then) {
|
|
die('Bad combination: missing "then" content');
|
|
}
|
|
unless ($else) {
|
|
die('Bad combination: missing "else" content');
|
|
}
|
|
|
|
$cond = $self->buildSub($cond);
|
|
$then = $self->parseOr( $moduleList, $then );
|
|
$else = $self->parse( $moduleList, $else );
|
|
unless ( $then and $else ) {
|
|
die('Bad combination: bad then or else');
|
|
}
|
|
return sub {
|
|
my ($env) = @_;
|
|
return ( $cond->($env) ? $then : $else->($env) );
|
|
};
|
|
}
|
|
else {
|
|
my $res = $self->parseOr( $moduleList, $rest );
|
|
return sub { $res };
|
|
}
|
|
}
|
|
|
|
# Internal request to manage "or" boolean expr.
|
|
# Returns [ [authSub,userSub], [authSub,userSub] ] array
|
|
sub parseOr {
|
|
my ( $self, $moduleList, $expr ) = @_;
|
|
my @res;
|
|
foreach my $part ( split /\s+or\s+/, $expr ) {
|
|
push @res, $self->parseAnd( $moduleList, $part );
|
|
}
|
|
return \@res;
|
|
}
|
|
|
|
# Internal request to manage "and" boolean expr
|
|
# Returns [authSub,userSub] array
|
|
sub parseAnd {
|
|
my ( $self, $moduleList, $expr ) = @_;
|
|
if ( $expr =~ /\]\s*and\s*\[/ ) {
|
|
my @mod = ( [], [] );
|
|
foreach my $part ( split /\s*and\s*/, $expr ) {
|
|
my $tmp = $self->parseBlock( $moduleList, $part );
|
|
push @{ $mod[0] }, $tmp->[0];
|
|
push @{ $mod[1] }, $tmp->[1];
|
|
}
|
|
my @res;
|
|
foreach my $type (@mod) {
|
|
push @res, sub {
|
|
my %str;
|
|
foreach my $obj (@$type) {
|
|
my ( $r, $name ) = $obj->(@_);
|
|
|
|
# Case "string" (form type)
|
|
if ( $r & ~$r ) {
|
|
$str{$r}++;
|
|
}
|
|
else {
|
|
return ( $r, $name ) unless ( $r == PE_OK );
|
|
}
|
|
}
|
|
return ( ( %str ? join( ',', keys %str ) : PE_OK ), $expr );
|
|
};
|
|
}
|
|
return \@res;
|
|
}
|
|
else {
|
|
return $self->parseBlock( $moduleList, $expr );
|
|
}
|
|
}
|
|
|
|
# Internal method to parse [AuthModule,UserModule] expr
|
|
# Returns [authSub,userSub] array
|
|
sub parseBlock {
|
|
my ( $self, $moduleList, $expr ) = @_;
|
|
unless ( $expr =~ /^\s*\[(.*?)\s*(?:,\s*(.*?))?\s*\]\s*$/ ) {
|
|
die "Bad expression: $expr";
|
|
}
|
|
my @res = ( $1, $2 || $1 );
|
|
@res = (
|
|
$self->parseMod( $moduleList, 0, $res[0] ),
|
|
$self->parseMod( $moduleList, 1, $res[1] )
|
|
);
|
|
return \@res;
|
|
}
|
|
|
|
# Internal method to parse auth or userDB expr
|
|
# These expressions can be "LDAP" or "LDAP and DBI"
|
|
# Return sub
|
|
sub parseMod {
|
|
my ( $self, $moduleList, $type, $expr ) = @_;
|
|
my @list = split( /\s+and\s+/, $expr );
|
|
my @mods = map {
|
|
die "Undeclared module $_"
|
|
unless ( $moduleList->{$_}->[$type] );
|
|
$moduleList->{$_}->[$type]
|
|
} @list;
|
|
if ( @mods == 1 ) {
|
|
my ($m) = @mods;
|
|
return sub {
|
|
my ( $sub, $req ) = @_;
|
|
return ( $m->$sub($req), $expr );
|
|
};
|
|
}
|
|
return sub {
|
|
my ( $sub, $req ) = @_;
|
|
my %str;
|
|
for ( my $i = 0 ; $i < @list ; $i++ ) {
|
|
my $res = $mods[$i]->$sub($req);
|
|
|
|
# Case "string" (form type)
|
|
if ( $res & ~$res ) {
|
|
$str{$res}++;
|
|
}
|
|
else {
|
|
return ( $res, $list[$i] ) unless ( $res == PE_OK );
|
|
}
|
|
}
|
|
return ( ( %str ? join( ',', keys %str ) : PE_OK ), $expr );
|
|
};
|
|
}
|
|
|
|
# Internal request to find brackets
|
|
sub findB {
|
|
my ( $self, $expr, $char ) = @_;
|
|
my $res;
|
|
my @chars = split //, $expr;
|
|
while (@chars) {
|
|
my $c = shift @chars;
|
|
if ( $c eq "\\" ) {
|
|
$res .= $c . shift(@chars);
|
|
next;
|
|
}
|
|
if ( $c eq $char ) {
|
|
my $rest = join( '', @chars );
|
|
$res =~ s/^\s*(.*?)\s*/$1/;
|
|
$rest =~ s/^\s*(.*?)\s*/$1/;
|
|
return ( $res, $rest );
|
|
}
|
|
if ( $c =~ /^(?:\(|\{|\[|'|")$/ ) {
|
|
my $wanted = {
|
|
'(' => ')',
|
|
'{' => '}',
|
|
'[' => ']',
|
|
"'" => "'",
|
|
'"' => '"'
|
|
}->{$c};
|
|
my ( $m, $rest ) =
|
|
$self->findB( join( '', @chars ), $wanted );
|
|
unless ( length $m ) {
|
|
die("Bad combination: unmatched $c");
|
|
}
|
|
$res .= "$c$m$wanted";
|
|
@chars = split //, $rest;
|
|
next;
|
|
}
|
|
$res .= $c;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
# Compiles condition into sub
|
|
sub buildSub {
|
|
my ( $self, $cond ) = @_;
|
|
my $safe = Safe->new;
|
|
my $res = $safe->reval("sub{my(\$env)=@\_;return ($cond)}");
|
|
die "Bad condition $cond: $@" if ($@);
|
|
return $res;
|
|
}
|
|
|
|
1;
|
|
|