%%-------------------------------------------------------------------- %% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %%-------------------------------------------------------------------- -module(emqx_acl_ldap). -include("emqx_auth_ldap.hrl"). -include_lib("emqx/include/emqx.hrl"). -include_lib("eldap/include/eldap.hrl"). -include_lib("emqx/include/logger.hrl"). -export([ register_metrics/0 , check_acl/5 , description/0 ]). -spec(register_metrics() -> ok). register_metrics() -> lists:foreach(fun emqx_metrics:ensure/1, ?ACL_METRICS). check_acl(ClientInfo, PubSub, Topic, NoMatchAction, State) -> case do_check_acl(ClientInfo, PubSub, Topic, NoMatchAction, State) of ok -> emqx_metrics:inc(?ACL_METRICS(ignore)), ok; {stop, allow} -> emqx_metrics:inc(?ACL_METRICS(allow)), {stop, allow}; {stop, deny} -> emqx_metrics:inc(?ACL_METRICS(deny)), {stop, deny} end. do_check_acl(#{username := <<$$, _/binary>>}, _PubSub, _Topic, _NoMatchAction, _State) -> ok; do_check_acl(#{username := Username}, PubSub, Topic, _NoMatchAction, #{device_dn := DeviceDn, match_objectclass := ObjectClass, username_attr := UidAttr, custom_base_dn := CustomBaseDN, pool := Pool} = Config) -> Filters = maps:get(filters, Config, []), ReplaceRules = [{"${username_attr}", UidAttr}, {"${user}", binary_to_list(Username)}, {"${device_dn}", DeviceDn}], Filter = emqx_auth_ldap:prepare_filter(Filters, UidAttr, ObjectClass, ReplaceRules), Attribute = case PubSub of publish -> "mqttPublishTopic"; subscribe -> "mqttSubscriptionTopic" end, Attribute1 = "mqttPubSubTopic", ?LOG(debug, "[LDAP] search dn:~p filter:~p, attribute:~p", [DeviceDn, Filter, Attribute]), BaseDN = emqx_auth_ldap:replace_vars(CustomBaseDN, ReplaceRules), case emqx_auth_ldap_cli:search(Pool, BaseDN, Filter, [Attribute, Attribute1]) of {error, noSuchObject} -> ok; {ok, #eldap_search_result{entries = []}} -> ok; {ok, #eldap_search_result{entries = [Entry]}} -> Topics = proplists:get_value(Attribute, Entry#eldap_entry.attributes, []) ++ proplists:get_value(Attribute1, Entry#eldap_entry.attributes, []), match(Topic, Topics); Error -> ?LOG(error, "[LDAP] search error:~p", [Error]), {stop, deny} end. match(_Topic, []) -> ok; match(Topic, [Filter | Topics]) -> case emqx_topic:match(Topic, list_to_binary(Filter)) of true -> {stop, allow}; false -> match(Topic, Topics) end. description() -> "ACL with LDAP".