Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/eldap/asn1/ELDAPv3.asn1
Original file line number Diff line number Diff line change
Expand Up @@ -286,5 +286,16 @@ PasswdModifyRequestValue ::= SEQUENCE {
PasswdModifyResponseValue ::= SEQUENCE {
genPasswd [0] OCTET STRING OPTIONAL }

-- LDAP Control Extension for Simple Paged Results Manipulation
-- https://www.rfc-editor.org/rfc/rfc2696.txt
-- controlType 1.2.840.113556.1.4.319

RealSearchControlValue ::= SEQUENCE {
size INTEGER (0..maxInt),
-- requested page size from client
-- result set size estimate from server
cookie OCTET STRING
}

END

66 changes: 66 additions & 0 deletions lib/eldap/doc/src/eldap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,72 @@
</type>
<desc> <p>Negate a filter.</p> </desc>
</func>
<func>
<name since="OTP 24.3">paged_result_control(PageSize) -&gt;
{control, "1.2.840.113556.1.4.319", true, binary()}</name>
<fsummary>Create a paged result control tuple</fsummary>
<type>
<v>PageSize = positive_integer()</v>
</type>
<desc>
<p>Paged results is an extension to the LDAP protocol
specified by RFC2696</p>
<p>This function creates a control with the specified page
size for use in
<c>search/3</c>, for example:</p>
<code>
Control = eldap:paged_result_control(50),
{ok, SearchResults} = search(Handle, [{base, "dc=example, dc=com"}], [Control]),
</code>
</desc>
</func>
<func>
<name since="OTP 24.3">paged_result_control(PageSize, Cookie)
-&gt; {control, "1.2.840.113556.1.4.319", true,
binary()}</name>
<fsummary>Create a paged result control tuple with the given
Cookie</fsummary>
<type>
<v>PageSize = positive_integer()</v>
<v>Cookie = binary()</v>
</type>
<desc>
<p>Paged results is an extension to the LDAP protocol
specified by RFC2696</p>
<p>This function creates a control with the specified page
size and cookie for use in
<c>search/3</c> to retrieve the next results page.</p>
<p>For example:</p>
<code>
PageSize = 50,
Control1 = eldap:paged_result_control(PageSize),
{ok, SearchResults1} = search(Handle, [{base, "dc=example, dc=com"}], [Control1]),
%% retrieve the returned cookie from the search results
{ok, Cookie1} = eldap:paged_result_cookie(SearchResults1),
Control2 = eldap:paged_result_control(PageSize, Cookie1),
{ok, SearchResults2} = eldap:search(Handle, [{base, "dc=example,dc=com"}], [Control2]),
%% etc
</code>
</desc>
</func>
<func>
<name since="OTP 24.3">paged_result_cookie(SearchResult)
-&gt; binary()</name>
<fsummary>Extract a cookie from search results for use in the
subsequent search.</fsummary>
<type>
<v>SearchResult = #eldap_search_result{}</v>
</type>
<desc>
<p>Paged results is an extension to the LDAP protocol
specified by RFC2696.</p>
<p>This function extracts the cookie returned from the
server as a result of a paged search result.</p>
<p>If the returned cookie is the empty string
<c>""</c>, then these search results represent the last in
the series.</p>
</desc>
</func>

</funcs>

Expand Down
3 changes: 2 additions & 1 deletion lib/eldap/include/eldap.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
%%%
-record(eldap_search_result, {
entries = [], % List of #eldap_entry{} records
referrals = [] % List of referrals
referrals = [], % List of referrals
controls = [] % List of controls
}).

%%%
Expand Down
54 changes: 48 additions & 6 deletions lib/eldap/src/eldap.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
add/3, add/4,
delete/2, delete/3,
modify_dn/5,parse_dn/1,
parse_ldap_url/1]).
parse_ldap_url/1,
paged_result_control/1,
paged_result_control/2,
paged_result_cookie/1]).

-export([neverDerefAliases/0, derefInSearching/0,
derefFindingBaseObj/0, derefAlways/0]).
Expand Down Expand Up @@ -722,7 +725,7 @@ do_search(Data, A, Controls) ->
{error,Emsg} -> {ldap_closed_p(Data, Emsg),Data};
{'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
{{ok,Val},NewData} -> {{ok,Val},NewData};
{ok,Res,Ref,NewData} -> {{ok,polish(Res, Ref)},NewData};
{ok,Res,Ref,ResultControls,NewData} -> {{ok,polish(Res, Ref, ResultControls)},NewData};
{{error,Reason},NewData} -> {{error,Reason},NewData};
Else -> {ldap_closed_p(Data, Else),Data}
end.
Expand All @@ -731,11 +734,11 @@ do_search(Data, A, Controls) ->
%%% Polish the returned search result
%%%

polish(Res, Ref) ->
polish(Res, Ref, Controls) ->
R = polish_result(Res),
%%% No special treatment of referrals at the moment.
#eldap_search_result{entries = R,
referrals = Ref}.
referrals = Ref, controls = Controls}.

polish_result([H|T]) when is_record(H, 'SearchResultEntry') ->
ObjectName = H#'SearchResultEntry'.objectName,
Expand Down Expand Up @@ -778,10 +781,10 @@ collect_search_responses(Data, S, ID, {ok,Msg}, Acc, Ref)
case R#'LDAPResult'.resultCode of
success ->
log2(Data, "search reply = searchResDone ~n", []),
{ok,Acc,Ref,Data};
{ok,Acc,Ref,Msg#'LDAPMessage'.controls,Data};
sizeLimitExceeded ->
log2(Data, "[TRUNCATED] search reply = searchResDone ~n", []),
{ok,Acc,Ref,Data};
{ok,Acc,Ref,Msg#'LDAPMessage'.controls,Data};
referral ->
{{ok, {referral,R#'LDAPResult'.referral}}, Data};
Reason ->
Expand Down Expand Up @@ -1432,3 +1435,42 @@ get_head(Str,Tail) ->
%%% Should always succeed !
get_head([H|Tail],Tail,Rhead) -> lists:reverse([H|Rhead]);
get_head([H|Rest],Tail,Rhead) -> get_head(Rest,Tail,[H|Rhead]).

%%% --------------------------------------------------------------------
%%% Return a paged result control as described by RFC2696
%%% https://www.rfc-editor.org/rfc/rfc2696.txt
%%% --------------------------------------------------------------------

paged_result_control(PageSize) when is_integer(PageSize) ->
paged_result_control(PageSize, "").

paged_result_control(PageSize, Cookie) when is_integer(PageSize) ->
RSCV = #'RealSearchControlValue'{size=PageSize, cookie=Cookie},
{ok, ControlValue} = 'ELDAPv3':encode('RealSearchControlValue', RSCV),

{control, "1.2.840.113556.1.4.319", true, ControlValue}.


%%% --------------------------------------------------------------------
%%% Extract the returned cookie from search results in order to
%%% retrieve the next set of results from the server according to
%%% RFC2696
%%%
%%% https://www.rfc-editor.org/rfc/rfc2696.txt
%%% --------------------------------------------------------------------

paged_result_cookie(#eldap_search_result{controls=Controls}) ->
find_paged_result_cookie(Controls).

find_paged_result_cookie([]) ->
{error, no_cookie};

find_paged_result_cookie([C|Controls]) ->
case C of
#'Control'{controlType="1.2.840.113556.1.4.319",controlValue=ControlValue} ->
{ok, #'RealSearchControlValue'{cookie=Cookie}} =
'ELDAPv3':decode('RealSearchControlValue', ControlValue),
{ok, Cookie};
_ ->
find_paged_result_cookie(Controls)
end.
13 changes: 12 additions & 1 deletion lib/eldap/test/README
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@ erl
> make_certs:all("/dev/null", "eldap_basic_SUITE_data/certs").

2)-------
To start slapd:
To start slapd you have two options:

- Via Docker and provided `run_server.sh` script.

This uses the [bitnami/openldap:2.5](https://hub.docker.com/r/bitnami/openldap)
image to run an openldap/slapd server using docker.

It will also take care of generating the server TLS certificates if they're not
present.

- Using system installed slapd:

sudo slapd -f $ERL_TOP/lib/eldap/test/ldap_server/slapd.conf -F /tmp/slapd/slapd.d -h "ldap://localhost:9876 ldaps://localhost:9877"

This will however not work, since slapd is guarded by apparmor that checks that slapd does not access other than allowed files...
Expand Down
57 changes: 57 additions & 0 deletions lib/eldap/test/eldap_basic_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
search_two_hits/1,
search_extensible_match_with_dn/1,
search_extensible_match_without_dn/1,
search_paged_results/1,
ssl_connection/1,
start_tls_on_ssl_should_fail/1,
start_tls_twice_should_fail/1,
Expand Down Expand Up @@ -136,6 +137,7 @@ groups() ->
search_referral,
search_filter_or_sizelimit_ok,
search_filter_or_sizelimit_exceeded,
search_paged_results,
modify,
modify_referral,
delete,
Expand Down Expand Up @@ -822,6 +824,61 @@ search_referral(Config) ->
filter = eldap:present("description"),
scope=eldap:singleLevel()}).

%%%----------------------------------------------------------------
search_paged_results(Config) ->
H = proplists:get_value(handle, Config),
BasePath = proplists:get_value(eldap_path, Config),
%% Add a lot of objects:
Desc = "Frogs",
Names = ["Frog" ++ integer_to_list(N) || N <- lists:seq(1, 20)],
DNs = [{"cn=Jeremy " ++ N ++ "," ++ BasePath, [{"objectclass", ["person"]},
{"cn", ["Jeremy " ++ N]},
{"sn", [N]},
{"description", [Desc]}]} || N <- Names],
[ok = eldap:add(H, Entry, Attrs) || {Entry, Attrs} <- DNs],

PageSize = 10,

Control1 = eldap:paged_result_control(PageSize),

{ok, SearchResult1} =
eldap:search(H,
#eldap_search{base = BasePath,
filter = eldap:equalityMatch("description", Desc),
scope=eldap:singleLevel()},
[Control1]),


#eldap_search_result{entries=Es1} = Res = SearchResult1,

PageSize = length(Es1),

{ok, Cookie1} = eldap:paged_result_cookie(SearchResult1),

Control2 = eldap:paged_result_control(PageSize, Cookie1),

{ok, SearchResult2} =
eldap:search(H,
#eldap_search{base = BasePath,
filter = eldap:equalityMatch("description", Desc),
scope=eldap:singleLevel()},
[Control2]),

#eldap_search_result{entries=Es2} = SearchResult2,

PageSize = length(Es2),

%% all results have been returned so cookie should be empty
{ok, []} = eldap:paged_result_cookie(SearchResult2),

ExpectedDNs = lists:sort([DN || {DN, _} <- DNs]),
ResultDNs = lists:sort([DN || #eldap_entry{object_name=DN} <- Es1 ++ Es2]),

ExpectedDNs = ResultDNs,

%% Restore the database:
[ok=eldap:delete(H,DN) || {DN, _} <- DNs].

%%%----------------------------------------------------------------
modify(Config) ->
H = proplists:get_value(handle, Config),
Expand Down
4 changes: 2 additions & 2 deletions lib/eldap/test/make_certs.erl
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ req_cnf(C) ->
"default_bits = ", integer_to_list(C#config.default_bits), "\n"
"RANDFILE = $ROOTDIR/RAND\n"
"encrypt_key = no\n"
"default_md = sha1\n"
"default_md = sha256\n"
"#string_mask = pkix\n"
"x509_extensions = ca_ext\n"
"prompt = no\n"
Expand Down Expand Up @@ -393,7 +393,7 @@ ca_cnf(C) ->
["crl_extensions = crl_ext\n" || C#config.v2_crls],
"unique_subject = no\n"
"default_days = 3600\n"
"default_md = sha1\n"
"default_md = sha256\n"
"preserve = no\n"
"policy = policy_match\n"
"\n"
Expand Down
26 changes: 26 additions & 0 deletions lib/eldap/test/run_server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh

__dir__="$(cd "$(dirname "$0")"; pwd)"

if [ ! -d "$__dir__/eldap_basic_SUITE_data/certs" ]; then
echo "Creating certs..."
(
cd $__dir__ \
&& erlc make_certs.erl \
&& erl -noinput -eval 'make_certs:all("/dev/null", "eldap_basic_SUITE_data/certs").' -s init stop
)
fi

docker run \
--rm \
-v "${__dir__}/eldap_basic_SUITE_data/certs:/opt/otp/openldap/certs" \
-e LDAP_ENABLE_TLS=yes \
-e LDAP_TLS_CERT_FILE=/opt/otp/openldap/certs/server/cert.pem \
-e LDAP_TLS_KEY_FILE=/opt/otp/openldap/certs/server/keycert.pem \
-e LDAP_TLS_CA_FILE=/opt/otp/openldap/certs/server/cacerts.pem \
-e LDAP_ROOT="dc=ericsson,dc=se" \
-e LDAP_ADMIN_USERNAME="Manager" \
-e LDAP_ADMIN_PASSWORD="hejsan" \
-p 9877:1636 \
-p 9876:1389 \
bitnami/openldap:2.5