1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2012-2013. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: 
   20: -module(eldap_basic_SUITE).
   21: 
   22: -compile(export_all).
   23: 
   24: %%-include_lib("common_test/include/ct.hrl").
   25: -include_lib("test_server/include/test_server.hrl").
   26: -include_lib("eldap/include/eldap.hrl").
   27: 
   28: -define(TIMEOUT, 120000). % 2 min
   29: 
   30: init_per_suite(Config) ->
   31:     StartSsl = try ssl:start() 
   32:     catch
   33: 	Error:Reason ->
   34: 	    {skip, lists:flatten(io_lib:format("eldap init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]))}
   35:     end,
   36:     case StartSsl of
   37: 	ok ->
   38: 	    chk_config(ldap_server, {"localhost",9876},
   39: 		       chk_config(ldaps_server, {"localhost",9877},
   40: 				  Config));
   41: 	_ ->
   42: 	    StartSsl
   43:     end.
   44: 
   45: end_per_suite(_Config) ->
   46:     ok.
   47: 
   48: init_per_testcase(_TestCase, Config0) ->
   49:     {EldapHost,Port} = proplists:get_value(ldap_server,Config0),
   50:     try
   51: 	{ok, Handle} = eldap:open([EldapHost], [{port,Port}]),
   52: 	ok = eldap:simple_bind(Handle, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
   53: 	{ok, MyHost} = inet:gethostname(),
   54: 	Path = "dc="++MyHost++",dc=ericsson,dc=se",
   55: 	eldap:add(Handle,"dc=ericsson,dc=se",
   56: 		  [{"objectclass", ["dcObject", "organization"]},
   57: 		   {"dc", ["ericsson"]}, {"o", ["Testing"]}]),
   58: 	eldap:add(Handle,Path,
   59: 		  [{"objectclass", ["dcObject", "organization"]},
   60: 		   {"dc", [MyHost]}, {"o", ["Test machine"]}]),
   61: 	[{eldap_path,Path}|Config0]
   62:     catch error:{badmatch,Error} ->
   63: 	    io:format("Eldap init error ~p~n ~p~n",[Error, erlang:get_stacktrace()]),
   64: 	    {skip, lists:flatten(io_lib:format("Ldap init failed with host ~p:~p. Error=~p", [EldapHost,Port,Error]))}
   65:     end.
   66: 
   67: end_per_testcase(_TestCase, Config) ->
   68:     {EHost, Port} = proplists:get_value(ldap_server, Config),
   69:     Path = proplists:get_value(eldap_path, Config),
   70:     {ok, H} = eldap:open([EHost], [{port, Port}]),
   71:     ok = eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
   72:     case eldap:search(H, [{base, Path},
   73: 			  {filter, eldap:present("objectclass")},
   74: 			  {scope,  eldap:wholeSubtree()}])
   75:     of
   76: 	{ok, {eldap_search_result, Entries, _}} ->
   77: 	    [ok = eldap:delete(H, Entry) || {eldap_entry, Entry, _} <- Entries];
   78: 	_ -> ignore
   79:     end,
   80: 
   81:     ok.
   82: 
   83: %% suite() ->
   84: 
   85: all() ->
   86:     [app,
   87:      api,
   88:      ssl_api,
   89:      start_tls,
   90:      tls_operations,
   91:      start_tls_twice,
   92:      start_tls_on_ssl
   93:     ].
   94: 
   95: app(doc) ->  "Test that the eldap app file is ok";
   96: app(suite) -> [];
   97: app(Config) when is_list(Config) ->
   98:     ok = test_server:app_test(public_key).
   99: 
  100: api(doc) -> "Basic test that all api functions works as expected";
  101: api(suite) -> [];
  102: api(Config) ->
  103:     {Host,Port} = proplists:get_value(ldap_server, Config),
  104:     {ok, H} = eldap:open([Host], [{port,Port}]),
  105:     %% {ok, H} = eldap:open([Host], [{port,Port+1}, {ssl, true}]),
  106:     do_api_checks(H, Config),
  107:     eldap:close(H),
  108:     ok.
  109: 
  110: 
  111: ssl_api(doc) -> "Basic test that all api functions works as expected";
  112: ssl_api(suite) -> [];
  113: ssl_api(Config) ->
  114:     {Host,Port} = proplists:get_value(ldaps_server, Config),
  115:     {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]),
  116:     do_api_checks(H, Config),
  117:     eldap:close(H),
  118:     ok.
  119: 
  120: 
  121: start_tls(doc) -> "Test that an existing (tcp) connection can be upgraded to tls";
  122: start_tls(suite) -> [];
  123: start_tls(Config) ->
  124:     {Host,Port} = proplists:get_value(ldap_server, Config),
  125:     {ok, H} = eldap:open([Host], [{port,Port}]),
  126:     ok = eldap:start_tls(H, [
  127: 			     {keyfile, filename:join([proplists:get_value(data_dir,Config),
  128: 						      "certs/client/key.pem"])}
  129: 			    ]),
  130:     eldap:close(H).
  131: 
  132: 
  133: tls_operations(doc) -> "Test that an upgraded connection is usable for ldap stuff";
  134: tls_operations(suite) -> [];
  135: tls_operations(Config) ->
  136:     {Host,Port} = proplists:get_value(ldap_server, Config),
  137:     {ok, H} = eldap:open([Host], [{port,Port}]),
  138:     ok = eldap:start_tls(H, [
  139: 			     {keyfile, filename:join([proplists:get_value(data_dir,Config),
  140: 						      "certs/client/key.pem"])}
  141: 			    ]),
  142:     do_api_checks(H, Config),
  143:     eldap:close(H).
  144: 
  145: start_tls_twice(doc) -> "Test that start_tls on an already upgraded connection fails";
  146: start_tls_twice(suite) -> [];
  147: start_tls_twice(Config) ->
  148:     {Host,Port} = proplists:get_value(ldap_server, Config),
  149:     {ok, H} = eldap:open([Host], [{port,Port}]),
  150:     ok = eldap:start_tls(H, []),
  151:     {error,tls_already_started} = eldap:start_tls(H, []),
  152:     do_api_checks(H, Config),
  153:     eldap:close(H).
  154: 
  155: 
  156: start_tls_on_ssl(doc) -> "Test that start_tls on an ldaps connection fails";
  157: start_tls_on_ssl(suite) -> [];
  158: start_tls_on_ssl(Config) ->
  159:     {Host,Port} = proplists:get_value(ldaps_server, Config),
  160:     {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]),
  161:     {error,tls_already_started} = eldap:start_tls(H, []),
  162:     do_api_checks(H, Config),
  163:     eldap:close(H).
  164: 
  165: 
  166: %%%--------------------------------------------------------------------------------
  167: chk_config(Key, Default, Config) ->
  168:     case catch ct:get_config(ldap_server, undefined) of
  169: 	undefined  -> [{Key,Default} | Config ];
  170: 	{'EXIT',_} -> [{Key,Default} | Config ];
  171: 	Value -> [{Key,Value} | Config]
  172:     end.
  173: 
  174: 
  175: 
  176: do_api_checks(H, Config) ->
  177:     BasePath = proplists:get_value(eldap_path, Config),
  178: 
  179:     All = fun(Where) ->
  180: 		  eldap:search(H, #eldap_search{base=Where,
  181: 						filter=eldap:present("objectclass"),
  182: 						scope= eldap:wholeSubtree()})
  183: 	  end,
  184:     {ok, #eldap_search_result{entries=[_XYZ]}} = All(BasePath),
  185: %%    ct:log("XYZ=~p",[_XYZ]),
  186:     {error, noSuchObject} = All("cn=Bar,"++BasePath),
  187: 
  188:     {error, _} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
  189: 			   [{"objectclass", ["person"]},
  190: 			    {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
  191:     eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
  192: 
  193:     chk_add(H, BasePath),
  194:     {ok,FB} = chk_search(H, BasePath),
  195:     chk_modify(H, FB),
  196:     chk_delete(H, BasePath),
  197:     chk_modify_dn(H, FB).
  198: 
  199: 
  200: chk_add(H, BasePath) ->
  201:     ok = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
  202: 		   [{"objectclass", ["person"]},
  203: 		    {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
  204:     {error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
  205: 					    [{"objectclass", ["person"]},
  206: 					     {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
  207:     ok = eldap:add(H, "cn=Foo Bar," ++ BasePath,
  208: 		   [{"objectclass", ["person"]},
  209: 		    {"cn", ["Foo Bar"]}, {"sn", ["Bar"]}, {"telephoneNumber", ["555-1232", "555-5432"]}]),
  210:     ok = eldap:add(H, "ou=Team," ++ BasePath,
  211: 		   [{"objectclass", ["organizationalUnit"]},
  212: 		    {"ou", ["Team"]}]).
  213: 
  214: chk_search(H, BasePath) ->
  215:     Search = fun(Filter) ->
  216: 		     eldap:search(H, #eldap_search{base=BasePath,
  217: 						   filter=Filter,
  218: 						   scope=eldap:singleLevel()})
  219: 	     end,
  220:     JJSR = {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:equalityMatch("sn", "Jonsson")),
  221:     JJSR = Search(eldap:substrings("sn", [{any, "ss"}])),
  222:     FBSR = {ok, #eldap_search_result{entries=[#eldap_entry{object_name=FB}]}} =
  223: 	Search(eldap:substrings("sn", [{any, "a"}])),
  224:     FBSR = Search(eldap:substrings("sn", [{initial, "B"}])),
  225:     FBSR = Search(eldap:substrings("sn", [{final, "r"}])),
  226:     F_AND = eldap:'and'([eldap:present("objectclass"), eldap:present("ou")]),
  227:     {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(F_AND),
  228:     F_NOT = eldap:'and'([eldap:present("objectclass"), eldap:'not'(eldap:present("ou"))]),
  229:     {ok, #eldap_search_result{entries=[#eldap_entry{}, #eldap_entry{}]}} = Search(F_NOT),
  230:     {ok,FB}.					%% FIXME
  231: 
  232: chk_modify(H, FB) ->
  233:     Mod = [eldap:mod_replace("telephoneNumber", ["555-12345"]),
  234: 	   eldap:mod_add("description", ["Nice guy"])],
  235:     %% io:format("MOD ~p ~p ~n",[FB, Mod]),
  236:     ok = eldap:modify(H, FB, Mod),
  237:     %% DELETE ATTR
  238:     ok = eldap:modify(H, FB, [eldap:mod_delete("telephoneNumber", [])]).
  239: 
  240: 
  241: chk_delete(H, BasePath) ->
  242:     {error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
  243: 					    [{"objectclass", ["person"]},
  244: 					     {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
  245:     ok = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath),
  246:     {error, noSuchObject} = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath).
  247: 
  248: chk_modify_dn(H, FB) ->
  249:     ok = eldap:modify_dn(H, FB, "cn=Niclas Andre", true, "").
  250:     %%io:format("Res ~p~n ~p~n",[R, All(BasePath)]).
  251: 
  252: 
  253: %%%----------------
  254: add(H,  Attr, Value, Path0, Attrs, Class) ->
  255:     Path = case Path0 of
  256: 	       [] -> Attr ++ "=" ++ Value;
  257: 	       _ -> Attr ++ "=" ++ Value ++ "," ++ Path0
  258: 	   end,
  259:     case eldap:add(H, Path, [{"objectclass", Class}, {Attr, [Value]}] ++ Attrs)
  260:     of
  261: 	ok -> {ok, Path};
  262: 	{error, E = entryAlreadyExists} -> {E, Path};
  263: 	R = {error, Reason} ->
  264: 	    io:format("~p:~p: ~s,~s =>~n ~p~n",
  265: 		      [?MODULE,?LINE, Attr, Value, R]),
  266: 	    exit({ldap, add, Reason})
  267:     end.
  268: 
  269: 
  270: 
  271: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  272: %% Develop
  273: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  274: 
  275: test() ->
  276:     run().
  277: 
  278: run() ->
  279:     Cases = all(),
  280:     run(Cases).
  281: 
  282: run(Case) when is_atom(Case) ->
  283:     run([Case]);
  284: run(Cases) when is_list(Cases) ->
  285:     Run = fun(Test, Config0) ->
  286: 		  Config = init_per_testcase(Test, Config0),
  287: 		  try
  288: 		      io:format("~nTest ~p ... ",[Test]),
  289: 		      ?MODULE:Test(Config),
  290: 		      end_per_testcase(Test, Config),
  291: 		      io:format("ok~n",[])
  292: 		  catch _:Reason ->
  293: 			  io:format("~n   FAIL (~p): ~p~n ~p~n",
  294: 				    [Test, Reason, erlang:get_stacktrace()])
  295: 		  end
  296: 	  end,
  297:     process_flag(trap_exit, true),
  298:     Pid = spawn_link(fun() ->
  299: 			     case init_per_suite([]) of
  300: 				 {skip, Reason} -> io:format("Skip ~s~n",[Reason]);
  301: 				 Config ->
  302: 				     try
  303: 					 [Run(Test, Config) || Test <- Cases]
  304: 				     catch _:Err ->
  305: 					     io:format("Error ~p in ~p~n",[Err, erlang:get_stacktrace()])
  306: 				     end,
  307: 				     end_per_suite(Config)
  308: 			     end
  309: 		     end),
  310:     receive
  311: 	{'EXIT', Pid, normal} -> ok;
  312: 	Msg -> io:format("Received ~p (~p)~n",[Msg, Pid])
  313:     after 100 -> ok end,
  314:     process_flag(trap_exit, false),
  315:     ok.