1 |
|
%%============================================================================== |
2 |
|
%% Copyright 2015 Erlang Solutions Ltd. |
3 |
|
%% |
4 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
%% you may not use this file except in compliance with the License. |
6 |
|
%% You may obtain a copy of the License at |
7 |
|
%% |
8 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
9 |
|
%% |
10 |
|
%% Unless required by applicable law or agreed to in writing, software |
11 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
12 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
|
%% See the License for the specific language governing permissions and |
14 |
|
%% limitations under the License. |
15 |
|
%%============================================================================== |
16 |
|
-module(ejabberd_auth_riak). |
17 |
|
|
18 |
|
-behaviour(mongoose_gen_auth). |
19 |
|
|
20 |
|
-include("mongoose.hrl"). |
21 |
|
-include("mongoose_config_spec.hrl"). |
22 |
|
-include("scram.hrl"). |
23 |
|
|
24 |
|
%% API |
25 |
|
-export([start/1, |
26 |
|
stop/1, |
27 |
|
config_spec/0, |
28 |
|
supports_sasl_module/2, |
29 |
|
supported_features/0, |
30 |
|
set_password/4, |
31 |
|
authorize/1, |
32 |
|
try_register/4, |
33 |
|
get_registered_users/3, |
34 |
|
get_registered_users_number/3, |
35 |
|
get_password/3, |
36 |
|
get_password_s/3, |
37 |
|
does_user_exist/3, |
38 |
|
remove_user/3 |
39 |
|
]). |
40 |
|
|
41 |
|
%% Internal |
42 |
|
-export([check_password/4, |
43 |
|
check_password/6]). |
44 |
|
|
45 |
|
-spec start(mongooseim:host_type()) -> ok. |
46 |
|
start(_HostType) -> |
47 |
:-( |
ok. |
48 |
|
|
49 |
|
-spec stop(mongooseim:host_type()) -> ok. |
50 |
|
stop(_HostType) -> |
51 |
:-( |
ok. |
52 |
|
|
53 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
54 |
|
config_spec() -> |
55 |
160 |
#section{ |
56 |
|
items = #{<<"bucket_type">> => #option{type = binary, |
57 |
|
validate = non_empty}}, |
58 |
|
defaults = #{<<"bucket_type">> => <<"users">>}, |
59 |
|
format_items = map |
60 |
|
}. |
61 |
|
|
62 |
|
-spec supports_sasl_module(mongooseim:host_type(), cyrsasl:sasl_module()) -> boolean(). |
63 |
:-( |
supports_sasl_module(_HostType, cyrsasl_plain) -> true; |
64 |
:-( |
supports_sasl_module(HostType, cyrsasl_digest) -> not mongoose_scram:enabled(HostType); |
65 |
:-( |
supports_sasl_module(HostType, Mechanism) -> mongoose_scram:enabled(HostType, Mechanism). |
66 |
|
|
67 |
|
-spec supported_features() -> [atom()]. |
68 |
:-( |
supported_features() -> [dynamic_domains]. |
69 |
|
|
70 |
|
-spec set_password(mongooseim:host_type(), jid:luser(), jid:lserver(), binary()) |
71 |
|
-> ok | {error, not_allowed | invalid_jid}. |
72 |
|
set_password(HostType, LUser, LServer, Password) -> |
73 |
:-( |
case prepare_password(HostType, Password) of |
74 |
|
false -> |
75 |
:-( |
{error, invalid_password}; |
76 |
|
Password -> |
77 |
:-( |
User = mongoose_riak:fetch_type(bucket_type(HostType, LServer), LUser), |
78 |
:-( |
do_set_password(User, HostType, LUser, LServer, Password); |
79 |
|
{<<>>, Scram} -> |
80 |
:-( |
User = mongoose_riak:fetch_type(bucket_type(HostType, LServer), LUser), |
81 |
:-( |
do_set_password(User, HostType, LUser, LServer, {<<>>, Scram}) |
82 |
|
end. |
83 |
|
|
84 |
|
-spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()} |
85 |
|
| {error, any()}. |
86 |
|
authorize(Creds) -> |
87 |
:-( |
ejabberd_auth:authorize_with_check_password(?MODULE, Creds). |
88 |
|
|
89 |
|
-spec check_password(mongooseim:host_type(), jid:luser(), jid:lserver(), binary()) -> boolean(). |
90 |
|
check_password(HostType, LUser, LServer, Password) -> |
91 |
:-( |
case do_get_password(HostType, LUser, LServer) of |
92 |
|
false -> |
93 |
:-( |
false; |
94 |
|
Scram when is_record(Scram, scram) orelse is_map(Scram)-> |
95 |
:-( |
mongoose_scram:check_password(Password, Scram); |
96 |
|
Password when is_binary(Password) -> |
97 |
:-( |
Password /= <<"">>; |
98 |
|
_ -> |
99 |
:-( |
false |
100 |
|
end. |
101 |
|
|
102 |
|
-spec check_password(mongooseim:host_type(), |
103 |
|
jid:luser(), |
104 |
|
jid:lserver(), |
105 |
|
binary(), |
106 |
|
binary(), |
107 |
|
fun()) -> boolean(). |
108 |
|
check_password(HostType, LUser, LServer, Password, Digest, DigestGen) -> |
109 |
:-( |
case do_get_password(HostType, LUser, LServer) of |
110 |
|
false -> |
111 |
:-( |
false; |
112 |
|
Scram when is_record(Scram, scram) orelse is_map(Scram) -> |
113 |
:-( |
mongoose_scram:check_digest(Scram, Digest, DigestGen, Password); |
114 |
|
PassRiak when is_binary(PassRiak) -> |
115 |
:-( |
ejabberd_auth:check_digest(Digest, DigestGen, Password, PassRiak) |
116 |
|
end. |
117 |
|
|
118 |
|
-spec try_register(HostType :: mongooseim:host_type(), |
119 |
|
User :: jid:luser(), |
120 |
|
Server :: jid:lserver(), |
121 |
|
Password :: binary() |
122 |
|
) -> ok | {error, term()}. |
123 |
|
try_register(HostType, LUser, LServer, Password) -> |
124 |
:-( |
try_register_if_does_not_exist(HostType, LUser, LServer, Password). |
125 |
|
|
126 |
|
-spec get_registered_users(mongooseim:host_type(), jid:lserver(), list()) -> |
127 |
|
[jid:simple_bare_jid()]. |
128 |
|
get_registered_users(HostType, LServer, _Opts) -> |
129 |
:-( |
case mongoose_riak:list_keys(bucket_type(HostType, LServer)) of |
130 |
|
{ok, Users} -> |
131 |
:-( |
[{User, LServer} || User <- Users]; |
132 |
|
_ -> |
133 |
:-( |
[] |
134 |
|
end. |
135 |
|
|
136 |
|
-spec get_registered_users_number(mongooseim:host_type(), jid:lserver(), list()) -> |
137 |
|
non_neg_integer(). |
138 |
|
get_registered_users_number(HostType, LServer, Opts) -> |
139 |
:-( |
length(get_registered_users(HostType, LServer, Opts)). |
140 |
|
|
141 |
|
-spec get_password(mongooseim:host_type(), jid:luser(), jid:lserver()) -> |
142 |
|
ejabberd_auth:passterm() | false. |
143 |
|
get_password(HostType, LUser, LServer) -> |
144 |
:-( |
case do_get_password(HostType, LUser, LServer) of |
145 |
|
false -> |
146 |
:-( |
false; |
147 |
|
Scram when is_map(Scram) -> |
148 |
:-( |
Scram; |
149 |
|
#scram{} = Scram -> |
150 |
:-( |
mongoose_scram:scram_record_to_map(Scram); |
151 |
|
Password -> |
152 |
:-( |
Password |
153 |
|
end. |
154 |
|
|
155 |
|
-spec get_password_s(mongooseim:host_type(), jid:luser(), jid:lserver()) -> binary(). |
156 |
|
get_password_s(HostType, LUser, LServer) -> |
157 |
:-( |
case get_password(HostType, LUser, LServer) of |
158 |
|
Password when is_binary(Password) -> |
159 |
:-( |
Password; |
160 |
|
_ -> |
161 |
:-( |
<<"">> |
162 |
|
end. |
163 |
|
|
164 |
|
-spec does_user_exist(mongooseim:host_type(), jid:luser(), jid:lserver()) -> boolean(). |
165 |
|
does_user_exist(HostType, LUser, LServer) -> |
166 |
:-( |
case mongoose_riak:fetch_type(bucket_type(HostType, LServer), LUser) of |
167 |
|
{ok, _} -> |
168 |
:-( |
true; |
169 |
|
{error, {notfound, map}} -> |
170 |
:-( |
false |
171 |
|
end. |
172 |
|
|
173 |
|
-spec remove_user(mongooseim:host_type(), jid:luser(), jid:lserver()) -> |
174 |
|
ok | {error, not_allowed}. |
175 |
|
remove_user(HostType, LUser, LServer) -> |
176 |
:-( |
case mongoose_riak:delete(bucket_type(HostType, LServer), LUser) of |
177 |
:-( |
ok -> ok; |
178 |
|
Error -> |
179 |
:-( |
?LOG_WARNING(#{what => remove_user_failed, reason => Error, |
180 |
:-( |
user => LUser, server => LServer}), |
181 |
:-( |
{error, not_allowed} |
182 |
|
end. |
183 |
|
|
184 |
|
-spec bucket_type(mongooseim:host_type(), jid:lserver()) -> {binary(), jid:lserver()}. |
185 |
|
bucket_type(HostType, LServer) -> |
186 |
:-( |
BucketType = mongoose_config:get_opt([{auth, HostType}, riak, bucket_type]), |
187 |
:-( |
{BucketType, LServer}. |
188 |
|
|
189 |
|
%% ----------------------------------------------------------------------------- |
190 |
|
%% Internal functions |
191 |
|
%% ----------------------------------------------------------------------------- |
192 |
|
|
193 |
|
try_register_if_does_not_exist(_, LUser, LServer, _) |
194 |
|
when LUser =:= error; LServer =:= error -> |
195 |
:-( |
{error, invalid_jid}; |
196 |
|
try_register_if_does_not_exist(HostType, LUser, LServer, PasswordIn) -> |
197 |
:-( |
case does_user_exist(HostType, LUser, LServer) of |
198 |
|
false -> |
199 |
:-( |
Password = prepare_password(HostType, PasswordIn), |
200 |
:-( |
try_register_with_password(HostType, LUser, LServer, Password); |
201 |
|
true -> |
202 |
:-( |
{error, exists} |
203 |
|
end. |
204 |
|
|
205 |
|
try_register_with_password(HostType, LUser, LServer, Password) -> |
206 |
:-( |
Now = integer_to_binary(os:system_time(second)), |
207 |
:-( |
Ops = [{{<<"created">>, register}, |
208 |
:-( |
fun(R) -> riakc_register:set(Now, R) end}, |
209 |
|
set_password_map_op(Password)], |
210 |
:-( |
UserMap = mongoose_riak:create_new_map(Ops), |
211 |
:-( |
mongoose_riak:update_type(bucket_type(HostType, LServer), LUser, riakc_map:to_op(UserMap)). |
212 |
|
|
213 |
|
do_get_password(HostType, LUser, LServer) -> |
214 |
:-( |
case mongoose_riak:fetch_type(bucket_type(HostType, LServer), LUser) of |
215 |
|
{ok, Map} -> |
216 |
:-( |
case extract_password(Map) of |
217 |
|
false -> |
218 |
:-( |
?LOG_WARNING(#{what => scram_serialisation_incorrect, |
219 |
:-( |
user => LUser, server => LServer}); |
220 |
:-( |
Pwd -> Pwd |
221 |
|
end; |
222 |
|
_ -> |
223 |
:-( |
false |
224 |
|
end. |
225 |
|
|
226 |
|
do_set_password({ok, Map}, HostType, LUser, LServer, Password) -> |
227 |
:-( |
Ops = [set_password_map_op(Password)], |
228 |
:-( |
UpdateMap = mongoose_riak:update_map(Map, Ops), |
229 |
:-( |
mongoose_riak:update_type(bucket_type(HostType, LServer), LUser, riakc_map:to_op(UpdateMap)). |
230 |
|
|
231 |
|
prepare_password(HostType, Iterations, Password) when is_integer(Iterations) -> |
232 |
:-( |
Scram = mongoose_scram:password_to_scram(HostType, Password, Iterations), |
233 |
:-( |
PassDetails = mongoose_scram:serialize(Scram), |
234 |
:-( |
{<<"">>, PassDetails}. |
235 |
|
|
236 |
|
prepare_password(HostType, Password) -> |
237 |
:-( |
case mongoose_scram:enabled(HostType) of |
238 |
|
true -> |
239 |
:-( |
prepare_password(HostType, mongoose_scram:iterations(HostType), Password); |
240 |
|
_ -> |
241 |
:-( |
Password |
242 |
|
end. |
243 |
|
|
244 |
|
set_password_map_op({_, Scram}) -> |
245 |
:-( |
{{<<"scram">>, register}, fun(R) -> riakc_register:set(Scram, R) end}; |
246 |
|
set_password_map_op(Password) -> |
247 |
:-( |
{{<<"password">>, register}, fun(R) -> riakc_register:set(Password, R) end}. |
248 |
|
|
249 |
|
extract_password(Map) -> |
250 |
:-( |
case riakc_map:find({<<"password">>, register}, Map) of |
251 |
|
error -> |
252 |
:-( |
maybe_extract_scram_password(riakc_map:find({<<"scram">>, register}, Map)); |
253 |
|
{ok, Password} -> |
254 |
:-( |
Password |
255 |
|
end. |
256 |
|
|
257 |
|
-spec maybe_extract_scram_password({ok, binary()} | error) -> mongoose_scram:scram() | false. |
258 |
|
maybe_extract_scram_password({ok, ScramSerialised}) -> |
259 |
:-( |
case mongoose_scram:deserialize(ScramSerialised) of |
260 |
|
{ok, Scram} -> |
261 |
:-( |
Scram; |
262 |
|
_ -> |
263 |
:-( |
false |
264 |
|
end; |
265 |
|
maybe_extract_scram_password(_) -> |
266 |
:-( |
false. |