LDAP extensible match support (EJAB-722)

This commit is contained in:
Evgeniy Khramtsov 2010-04-19 14:08:00 +10:00
parent 84c4d75735
commit 1405e9d375
5 changed files with 182 additions and 195 deletions

View File

@ -1,4 +1,4 @@
# $Id$
# $Id: Makefile.in 2842 2009-12-29 19:10:52Z badlop $
CC = @CC@
CFLAGS = @CFLAGS@
@ -20,18 +20,23 @@ ifdef debug
endif
OUTDIR = ..
SOURCES = $(wildcard *.erl) ELDAPv3.erl
SOURCES = $(wildcard *.erl) ELDAPv3.erl eldap_filter_yecc.erl
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
all: $(BEAMS) ELDAPv3.beam
all: $(BEAMS) ELDAPv3.beam eldap_filter_yecc.beam
ELDAPv3.beam: ELDAPv3.erl
ELDAPv3.erl: ELDAPv3.asn
@ERLC@ $(ASN_FLAGS) -W $(EFLAGS) $<
$(OUTDIR)/%.beam: %.erl ELDAPv3.erl
eldap_filter_yecc.beam: eldap_filter_yecc.erl
eldap_filter_yecc.erl: eldap_filter_yecc.yrl
@ERLC@ -W $<
$(OUTDIR)/%.beam: %.erl ELDAPv3.erl eldap_filter_yecc.erl
@ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
clean:
@ -39,6 +44,8 @@ clean:
rm -f ELDAPv3.erl
rm -f ELDAPv3.hrl
rm -f ELDAPv3.beam
rm -f eldap_filter_yecc.erl
rm -f eldap_filter_yecc.beam
rm -f $(BEAMS)
distclean: clean

View File

@ -4,7 +4,7 @@ include ..\Makefile.inc
EFLAGS = -I .. -pz ..
OUTDIR = ..
BEAMS = ..\eldap.beam ..\eldap_filter.beam ..\eldap_pool.beam ..\eldap_utils.beam
BEAMS = ..\eldap.beam ..\eldap_filter.beam ..\eldap_pool.beam ..\eldap_utils.beam ..\eldap_filter_yecc.beam
ASN_FLAGS = -bber_bin +optimize +driver
@ -15,11 +15,16 @@ Clean :
-@erase ELDAPv3.erl
-@erase ELDAPv3.hrl
-@erase ELDAPv3.beam
-@erase eldap_filter_yecc.erl
-@erase eldap_filter_yecc.beam
-@erase $(BEAMS)
ELDAPv3.erl : ELDAPv3.asn
erlc $(ASN_FLAGS) -W $(EFLAGS) ELDAPv3.asn
eldap_filter_yecc.erl: eldap_filter_yecc.yrl
erlc -W eldap_filter_yecc.yrl
$(OUTDIR)\eldap.beam : eldap.erl ELDAPv3.erl
erlc -W $(EFLAGS) -o $(OUTDIR) eldap.erl
@ -34,3 +39,6 @@ $(OUTDIR)\eldap_utils.beam : eldap_utils.erl
$(OUTDIR)\eldap_pool.beam : eldap_pool.erl
erlc -W $(EFLAGS) -o $(OUTDIR) eldap_pool.erl
$(OUTDIR)\eldap_filter_yecc.beam : eldap_filter_yecc.erl
erlc -W $(EFLAGS) -o $(OUTDIR) eldap_filter_yecc.erl

View File

@ -35,6 +35,7 @@
%%% Modified by Evgeniy Khramtsov <xram@jabber.ru>
%%% Implemented queue for bind() requests to prevent pending binds.
%%% Implemented extensibleMatch/2 function.
%%% Modified by Christophe Romain <christophe.romain@process-one.net>
%%% Improve error case handling
@ -71,7 +72,7 @@
-export([baseObject/0,singleLevel/0,wholeSubtree/0,close/1,
equalityMatch/2,greaterOrEqual/2,lessOrEqual/2,
approxMatch/2,search/2,substrings/2,present/1,
approxMatch/2,search/2,substrings/2,present/1,extensibleMatch/2,
'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2,
mod_replace/2, add/3, delete/2, modify_dn/5, bind/3]).
-export([get_status/1]).
@ -374,6 +375,29 @@ substrings(Type, SubStr) when is_list(Type), is_list(SubStr) ->
{substrings,#'SubstringFilter'{type = Type,
substrings = Ss}}.
%%%
%%% extensibleMatch filter.
%%% FIXME: Describe the purpose of this filter.
%%%
%%% Value ::= string( <attribute> )
%%% Opts ::= listof( {matchingRule, Str} | {type, Str} | {dnAttributes, true} )
%%%
%%% Example: extensibleMatch("Fred", [{matchingRule, "1.2.3.4.5"}, {type, "cn"}]).
%%%
extensibleMatch(Value, Opts) when is_list(Value), is_list(Opts) ->
MRA = #'MatchingRuleAssertion'{matchValue=Value},
{extensibleMatch, extensibleMatch_opts(Opts, MRA)}.
extensibleMatch_opts([{matchingRule, Rule} | Opts], MRA) when is_list(Rule) ->
extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{matchingRule=Rule});
extensibleMatch_opts([{type, Desc} | Opts], MRA) when is_list(Desc) ->
extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{type=Desc});
extensibleMatch_opts([{dnAttributes, true} | Opts], MRA) ->
extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{dnAttributes=true});
extensibleMatch_opts([_ | Opts], MRA) ->
extensibleMatch_opts(Opts, MRA);
extensibleMatch_opts([], MRA) ->
MRA.
get_handle(Pid) when is_pid(Pid) -> Pid;
get_handle(Atom) when is_atom(Atom) -> Atom;
@ -973,6 +997,8 @@ v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV};
v_filter({approxMatch,AV}) -> {approxMatch,AV};
v_filter({present,A}) -> {present,A};
v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S};
v_filter({extensibleMatch, S}) when is_record(S, 'MatchingRuleAssertion') ->
{extensibleMatch, S};
v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}).
v_modifications(Mods) ->

View File

@ -3,7 +3,7 @@
%%% Purpose: Converts String Representation of
%%% LDAP Search Filter (RFC 2254)
%%% to eldap's representation of filter
%%% Author: Evgeniy Khramtsov <xramtsov@gmail.com>
%%% Author: Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
@ -24,19 +24,17 @@
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(eldap_filter).
%%%======================
%%% Export functions
%%%======================
%% TODO: remove this when new regexp module will be used
-compile({nowarn_deprecated_function, {regexp, sub, 3}}).
-export([parse/1,
parse/2,
do_sub/2
]).
-export([parse/1, parse/2, do_sub/2]).
%%%-------------------------------------------------------------------------
%%====================================================================
%% API
%%====================================================================
%%%-------------------------------------------------------------------
%%% Arity: parse/1
%%% Function: parse(RFC2254_Filter) -> {ok, EldapFilter} |
%%% {error, bad_filter}
@ -47,15 +45,15 @@
%%% to eldap's representation of filter.
%%%
%%% Example:
%%% > eldap_filter:parse("(&(!(uid<=100))(mail=*))").
%%% > eldap_filter:parse("(&(!(uid<=100))(mail=*))").
%%%
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
%%% {present,"mail"}]}}
%%%-------------------------------------------------------------------------
parse(RFC2254_Filter) ->
parse(RFC2254_Filter, []).
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
%%% {present,"mail"}]}}
%%%-------------------------------------------------------------------
parse(L) when is_list(L) ->
parse(L, []).
%%%-------------------------------------------------------------------------
%%%-------------------------------------------------------------------
%%% Arity: parse/2
%%% Function: parse(RFC2254_Filter, [SubstValue |...]) ->
%%% {ok, EldapFilter} |
@ -81,135 +79,53 @@ parse(RFC2254_Filter) ->
%%% {equalityMatch,{'AttributeValueAssertion',
%%% "jid",
%%% "xramtsov@gmail.com"}}]}}
%%%--------------------------------------------------------------------------
parse(RFC2254_Filter, ListOfSubValues) ->
case catch convert_filter(parse_filter(RFC2254_Filter), ListOfSubValues) of
[EldapFilter] when is_tuple(EldapFilter) ->
{ok, EldapFilter};
{regexp, Error} ->
{error, Error};
_ ->
{error, bad_filter}
%%%-------------------------------------------------------------------
parse(L, SList) when is_list(L), is_list(SList) ->
case catch eldap_filter_yecc:parse(scan(L, SList)) of
{error, {_, _, Msg}} ->
{error, Msg};
{ok, Result} ->
{ok, Result};
{regexp, Err} ->
{error, Err}
end.
%%%==========================
%%% Internal functions
%%%==========================
%%====================================================================
%% Internal functions
%%====================================================================
-define(do_scan(L), scan(Rest, [], [{L, 1} | check(Buf, S) ++ Result], L, S)).
%%%----------------------
%%% split/1,4
%%%----------------------
split(Filter) ->
split(Filter, 0, [], []).
scan(L, SList) ->
scan(L, "", [], undefined, SList).
split([], _, _, Result) ->
Result;
scan("=*)" ++ Rest, Buf, Result, '(', S) ->
scan(Rest, [], [{')', 1}, {'=*', 1} | check(Buf, S) ++ Result], ')', S);
scan(":dn" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':dn');
scan(":=" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':=');
scan(":=" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':=');
scan(":=" ++ Rest, Buf, Result, ':', S) -> ?do_scan(':=');
scan("~=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('~=');
scan(">=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('>=');
scan("<=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('<=');
scan("=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('=');
scan(":" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':');
scan(":" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':');
scan("&" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('&');
scan("|" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('|');
scan("!" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('!');
scan("*" ++ Rest, Buf, Result, '*', S) -> ?do_scan('*');
scan("*" ++ Rest, Buf, Result, '=', S) -> ?do_scan('*');
scan("(" ++ Rest, Buf, Result, _, S) -> ?do_scan('(');
scan(")" ++ Rest, Buf, Result, _, S) -> ?do_scan(')');
scan([Letter | Rest], Buf, Result, PreviosAtom, S) ->
scan(Rest, [Letter|Buf], Result, PreviosAtom, S);
scan([], Buf, Result, _, S) ->
lists:reverse(check(Buf, S) ++ Result).
split([H|T], Num, Rest, Result) ->
NewNum = case H of
$( -> Num + 1;
$) -> Num - 1;
_ -> Num
end,
if
NewNum == 0 ->
X = Rest++[H],
LenX = length(X),
if
LenX > 2 ->
split(T, 0, [], Result ++ [lists:sublist(X, 2, LenX-2)]);
true ->
split(T, 0, Rest, Result)
end;
true ->
split(T, NewNum, Rest++[H], Result)
end.
%%%-----------------------
%%% parse_filter/1
%%%-----------------------
parse_filter(Filter) ->
case Filter of
[$! | T] ->
{'not', parse_filter(T)};
[$| | T] ->
{'or', parse_filter(T)};
[$& | T] ->
{'and', parse_filter(T)};
[$( | _] ->
parse_filter(split(Filter));
[List | _] when is_list(List) ->
[parse_filter(X) || X <- Filter];
_ ->
Filter
end.
%%%--------------------
%%% convert_filter/2
%%%--------------------
convert_filter({'not', [Val | _]}, Replace) ->
eldap:'not'(convert_filter(Val, Replace));
convert_filter({'or', Vals}, Replace) ->
eldap:'or'([convert_filter(X, Replace) || X <- Vals]);
convert_filter({'and', Vals}, Replace) ->
eldap:'and'([convert_filter(X, Replace) || X <- Vals]);
convert_filter([H|_] = Filter, Replace) when is_integer(H) ->
parse_attr(Filter, Replace);
convert_filter(Filter, Replace) when is_list(Filter) ->
[convert_filter(X, Replace) || X <- Filter].
%%%-----------------
%%% parse_attr/2,3
%%%-----------------
parse_attr(Attr, ListOfSubValues) ->
{Action, [_|_] = Name, [_|_] = Value} = split_attribute(Attr),
parse_attr(Action, {Name, Value}, ListOfSubValues).
parse_attr(approx, {Name, Value}, ListOfSubValues) ->
NewValue = do_sub(Value, ListOfSubValues),
eldap:approxMatch(Name, NewValue);
parse_attr(greater, {Name, Value}, ListOfSubValues) ->
NewValue = do_sub(Value, ListOfSubValues),
eldap:greaterOrEqual(Name, NewValue);
parse_attr(less, {Name, Value}, ListOfSubValues) ->
NewValue = do_sub(Value, ListOfSubValues),
eldap:lessOrEqual(Name, NewValue);
parse_attr(equal, {Name, Value}, ListOfSubValues) ->
{ok, RegSList} = regexp:split(remove_extra_asterisks(Value), "[*]"),
Pattern = case [do_sub(X, ListOfSubValues) || X <- RegSList] of
[Head | Tail] when Tail /= [] ->
{Head, lists:sublist(Tail, length(Tail)-1), lists:last(Tail)};
R ->
R
end,
case Pattern of
[V] ->
eldap:equalityMatch(Name, V);
{[], [], []} ->
eldap:present(Name);
{"", Any, ""} ->
eldap:substrings(Name, [{any, X} || X<-Any]);
{H, Any, ""} ->
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]);
{"", Any, T} ->
eldap:substrings(Name, [{any, X} || X<-Any]++[{final, T}]);
{H, Any, T} ->
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]++[{final, T}])
end;
parse_attr(_, _, _) ->
false.
%%%--------------------
%%% do_sub/2,3
%%%--------------------
check([], _) ->
[];
check(Buf, S) ->
[{str, 1, do_sub(lists:reverse(Buf), S)}].
-define(MAX_RECURSION, 100).
@ -234,9 +150,9 @@ do_sub(S, {RegExp, New}, Iter) ->
{ok, NewS, _} when Iter =< ?MAX_RECURSION ->
do_sub(NewS, {RegExp, New}, Iter+1);
{ok, _, _} when Iter > ?MAX_RECURSION ->
throw({regexp, max_substitute_recursion});
erlang:error(max_substitute_recursion);
_ ->
throw({regexp, bad_regexp})
erlang:error(bad_regexp)
end;
do_sub(S, {_, _, N}, _) when N<1 ->
@ -251,52 +167,11 @@ do_sub(S, {RegExp, New, Times}, Iter) ->
{ok, NewS, _} ->
NewS;
_ ->
throw({regexp, bad_regexp})
erlang:error(bad_regexp)
end.
remove_extra_asterisks(String) ->
{Res, _} = lists:foldl(
fun(X, {Acc, Last}) ->
case X of
$* when Last==$* ->
{Acc, X};
_ ->
{Acc ++ [X], X}
end
end,
{"", ""}, String),
Res.
replace_amps(String) ->
lists:foldl(
fun(X, Acc) ->
if
X == $& ->
Acc ++ "\\&";
true ->
Acc ++ [X]
end
end,
"", String).
split_attribute(String) ->
split_attribute(String, "", $0).
split_attribute([], _, _) ->
{error, "", ""};
split_attribute([H|Tail], Acc, Last) ->
case H of
$= when Last==$> ->
{greater, lists:sublist(Acc, 1, length(Acc)-1), Tail};
$= when Last==$< ->
{less, lists:sublist(Acc, 1, length(Acc)-1), Tail};
$= when Last==$~ ->
{approx, lists:sublist(Acc, 1, length(Acc)-1), Tail};
$= when Last==$: ->
{equal, lists:sublist(Acc, 1, length(Acc)-1), Tail};
$= ->
{equal, Acc, Tail};
_ ->
split_attribute(Tail, Acc++[H], H)
end.
lists:map(
fun($&) -> "\\&";
(Chr) -> Chr
end, String).

View File

@ -0,0 +1,71 @@
Nonterminals
filter filtercomp filterlist item
simple present substring extensible
initial any final matchingrule xattr
attr value.
Terminals str
'(' ')' '&' '|' '!' '=' '~=' '>=' '<=' '=*' '*' ':dn' ':' ':='.
Rootsymbol filter.
filter -> '(' filtercomp ')': '$2'.
filtercomp -> '&' filterlist: 'and'('$2').
filtercomp -> '|' filterlist: 'or'('$2').
filtercomp -> '!' filter: 'not'('$2').
filtercomp -> item: '$1'.
filterlist -> filter: '$1'.
filterlist -> filter filterlist: flatten(['$1', '$2']).
item -> simple: '$1'.
item -> present: '$1'.
item -> substring: '$1'.
item -> extensible: '$1'.
simple -> attr '=' value: equal('$1', '$3').
simple -> attr '~=' value: approx('$1', '$3').
simple -> attr '>=' value: greater('$1', '$3').
simple -> attr '<=' value: less('$1', '$3').
present -> attr '=*': present('$1').
substring -> attr '=' initial '*' any: substrings('$1', ['$3', '$5']).
substring -> attr '=' '*' any final: substrings('$1', ['$4', '$5']).
substring -> attr '=' initial '*' any final: substrings('$1', ['$3', '$5', '$6']).
substring -> attr '=' '*' any: substrings('$1', ['$4']).
any -> any value '*': 'any'('$1', '$2').
any -> '$empty': [].
initial -> value: initial('$1').
final -> value: final('$1').
extensible -> xattr ':dn' ':' matchingrule ':=' value: extensible('$6', ['$1', '$4']).
extensible -> xattr ':' matchingrule ':=' value: extensible('$5', ['$1', '$3']).
extensible -> xattr ':dn' ':=' value: extensible('$4', ['$1']).
extensible -> xattr ':=' value: extensible('$3', ['$1']).
extensible -> ':dn' ':' matchingrule ':=' value: extensible('$5', ['$3']).
extensible -> ':' matchingrule ':=' value: extensible('$4', ['$2']).
xattr -> value: xattr('$1').
matchingrule -> value: matchingrule('$1').
attr -> str: value_of('$1').
value -> str: value_of('$1').
Erlang code.
'and'(Value) -> eldap:'and'(Value).
'or'(Value) -> eldap:'or'(Value).
'not'(Value) -> eldap:'not'(Value).
equal(Desc, Value) -> eldap:equalityMatch(Desc, Value).
approx(Desc, Value) -> eldap:approxMatch(Desc, Value).
greater(Desc, Value) -> eldap:greaterOrEqual(Desc, Value).
less(Desc, Value) -> eldap:lessOrEqual(Desc, Value).
present(Value) -> eldap:present(Value).
extensible(Value, Opts) -> eldap:extensibleMatch(Value, Opts).
substrings(Desc, ValueList) -> eldap:substrings(Desc, flatten(ValueList)).
initial(Value) -> {initial, Value}.
final(Value) -> {final, Value}.
'any'(Token, Value) -> [Token, {any, Value}].
xattr(Value) -> {type, Value}.
matchingrule(Value) -> {matchingRule, Value}.
value_of(Token) -> element(3, Token).
flatten(List) -> lists:flatten(List).