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 |
166 |
#section{ |
56 |
|
items = #{<<"bucket_type">> => #option{type = binary, |
57 |
|
validate = non_empty}}, |
58 |
|
defaults = #{<<"bucket_type">> => <<"users">>} |
59 |
|
}. |
60 |
|
|
61 |
|
-spec supports_sasl_module(mongooseim:host_type(), cyrsasl:sasl_module()) -> boolean(). |
62 |
:-( |
supports_sasl_module(_HostType, cyrsasl_plain) -> true; |
63 |
:-( |
supports_sasl_module(HostType, cyrsasl_digest) -> not mongoose_scram:enabled(HostType); |
64 |
:-( |
supports_sasl_module(HostType, Mechanism) -> mongoose_scram:enabled(HostType, Mechanism). |
65 |
|
|
66 |
|
-spec supported_features() -> [atom()]. |
67 |
:-( |
supported_features() -> [dynamic_domains]. |
68 |
|
|
69 |
|
-spec set_password(mongooseim:host_type(), jid:luser(), jid:lserver(), binary()) |
70 |
|
-> ok | {error, not_allowed | invalid_jid}. |
71 |
|
set_password(HostType, LUser, LServer, Password) -> |
72 |
:-( |
case prepare_password(HostType, Password) of |
73 |
|
false -> |
74 |
:-( |
{error, invalid_password}; |
75 |
|
Password -> |
76 |
:-( |
User = mongoose_riak:fetch_type(bucket_type(HostType, LServer), LUser), |
77 |
:-( |
do_set_password(User, HostType, LUser, LServer, Password); |
78 |
|
{<<>>, Scram} -> |
79 |
:-( |
User = mongoose_riak:fetch_type(bucket_type(HostType, LServer), LUser), |
80 |
:-( |
do_set_password(User, HostType, LUser, LServer, {<<>>, Scram}) |
81 |
|
end. |
82 |
|
|
83 |
|
-spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()} |
84 |
|
| {error, any()}. |
85 |
|
authorize(Creds) -> |
86 |
:-( |
ejabberd_auth:authorize_with_check_password(?MODULE, Creds). |
87 |
|
|
88 |
|
-spec check_password(mongooseim:host_type(), jid:luser(), jid:lserver(), binary()) -> boolean(). |
89 |
|
check_password(HostType, LUser, LServer, Password) -> |
90 |
:-( |
case do_get_password(HostType, LUser, LServer) of |
91 |
|
false -> |
92 |
:-( |
false; |
93 |
|
Scram when is_record(Scram, scram) orelse is_map(Scram)-> |
94 |
:-( |
mongoose_scram:check_password(Password, Scram); |
95 |
|
Password when is_binary(Password) -> |
96 |
:-( |
Password /= <<"">>; |
97 |
|
_ -> |
98 |
:-( |
false |
99 |
|
end. |
100 |
|
|
101 |
|
-spec check_password(mongooseim:host_type(), |
102 |
|
jid:luser(), |
103 |
|
jid:lserver(), |
104 |
|
binary(), |
105 |
|
binary(), |
106 |
|
fun()) -> boolean(). |
107 |
|
check_password(HostType, LUser, LServer, Password, Digest, DigestGen) -> |
108 |
:-( |
case do_get_password(HostType, LUser, LServer) of |
109 |
|
false -> |
110 |
:-( |
false; |
111 |
|
Scram when is_record(Scram, scram) orelse is_map(Scram) -> |
112 |
:-( |
mongoose_scram:check_digest(Scram, Digest, DigestGen, Password); |
113 |
|
PassRiak when is_binary(PassRiak) -> |
114 |
:-( |
ejabberd_auth:check_digest(Digest, DigestGen, Password, PassRiak) |
115 |
|
end. |
116 |
|
|
117 |
|
-spec try_register(HostType :: mongooseim:host_type(), |
118 |
|
User :: jid:luser(), |
119 |
|
Server :: jid:lserver(), |
120 |
|
Password :: binary() |
121 |
|
) -> ok | {error, term()}. |
122 |
|
try_register(HostType, LUser, LServer, Password) -> |
123 |
:-( |
try_register_if_does_not_exist(HostType, LUser, LServer, Password). |
124 |
|
|
125 |
|
-spec get_registered_users(mongooseim:host_type(), jid:lserver(), list()) -> |
126 |
|
[jid:simple_bare_jid()]. |
127 |
|
get_registered_users(HostType, LServer, _Opts) -> |
128 |
:-( |
case mongoose_riak:list_keys(bucket_type(HostType, LServer)) of |
129 |
|
{ok, Users} -> |
130 |
:-( |
[{User, LServer} || User <- Users]; |
131 |
|
_ -> |
132 |
:-( |
[] |
133 |
|
end. |
134 |
|
|
135 |
|
-spec get_registered_users_number(mongooseim:host_type(), jid:lserver(), list()) -> |
136 |
|
non_neg_integer(). |
137 |
|
get_registered_users_number(HostType, LServer, Opts) -> |
138 |
:-( |
length(get_registered_users(HostType, LServer, Opts)). |
139 |
|
|
140 |
|
-spec get_password(mongooseim:host_type(), jid:luser(), jid:lserver()) -> |
141 |
|
ejabberd_auth:passterm() | false. |
142 |
|
get_password(HostType, LUser, LServer) -> |
143 |
:-( |
case do_get_password(HostType, LUser, LServer) of |
144 |
|
false -> |
145 |
:-( |
false; |
146 |
|
Scram when is_map(Scram) -> |
147 |
:-( |
Scram; |
148 |
|
#scram{} = Scram -> |
149 |
:-( |
mongoose_scram:scram_record_to_map(Scram); |
150 |
|
Password -> |
151 |
:-( |
Password |
152 |
|
end. |
153 |
|
|
154 |
|
-spec get_password_s(mongooseim:host_type(), jid:luser(), jid:lserver()) -> binary(). |
155 |
|
get_password_s(HostType, LUser, LServer) -> |
156 |
:-( |
case get_password(HostType, LUser, LServer) of |
157 |
|
Password when is_binary(Password) -> |
158 |
:-( |
Password; |
159 |
|
_ -> |
160 |
:-( |
<<"">> |
161 |
|
end. |
162 |
|
|
163 |
|
-spec does_user_exist(mongooseim:host_type(), jid:luser(), jid:lserver()) -> boolean(). |
164 |
|
does_user_exist(HostType, LUser, LServer) -> |
165 |
:-( |
case mongoose_riak:fetch_type(bucket_type(HostType, LServer), LUser) of |
166 |
|
{ok, _} -> |
167 |
:-( |
true; |
168 |
|
{error, {notfound, map}} -> |
169 |
:-( |
false |
170 |
|
end. |
171 |
|
|
172 |
|
-spec remove_user(mongooseim:host_type(), jid:luser(), jid:lserver()) -> |
173 |
|
ok | {error, not_allowed}. |
174 |
|
remove_user(HostType, LUser, LServer) -> |
175 |
:-( |
case mongoose_riak:delete(bucket_type(HostType, LServer), LUser) of |
176 |
:-( |
ok -> ok; |
177 |
|
Error -> |
178 |
:-( |
?LOG_WARNING(#{what => remove_user_failed, reason => Error, |
179 |
:-( |
user => LUser, server => LServer}), |
180 |
:-( |
{error, not_allowed} |
181 |
|
end. |
182 |
|
|
183 |
|
-spec bucket_type(mongooseim:host_type(), jid:lserver()) -> {binary(), jid:lserver()}. |
184 |
|
bucket_type(HostType, LServer) -> |
185 |
:-( |
BucketType = mongoose_config:get_opt([{auth, HostType}, riak, bucket_type]), |
186 |
:-( |
{BucketType, LServer}. |
187 |
|
|
188 |
|
%% ----------------------------------------------------------------------------- |
189 |
|
%% Internal functions |
190 |
|
%% ----------------------------------------------------------------------------- |
191 |
|
|
192 |
|
try_register_if_does_not_exist(_, LUser, LServer, _) |
193 |
|
when LUser =:= error; LServer =:= error -> |
194 |
:-( |
{error, invalid_jid}; |
195 |
|
try_register_if_does_not_exist(HostType, LUser, LServer, PasswordIn) -> |
196 |
:-( |
case does_user_exist(HostType, LUser, LServer) of |
197 |
|
false -> |
198 |
:-( |
Password = prepare_password(HostType, PasswordIn), |
199 |
:-( |
try_register_with_password(HostType, LUser, LServer, Password); |
200 |
|
true -> |
201 |
:-( |
{error, exists} |
202 |
|
end. |
203 |
|
|
204 |
|
try_register_with_password(HostType, LUser, LServer, Password) -> |
205 |
:-( |
Now = integer_to_binary(os:system_time(second)), |
206 |
:-( |
Ops = [{{<<"created">>, register}, |
207 |
:-( |
fun(R) -> riakc_register:set(Now, R) end}, |
208 |
|
set_password_map_op(Password)], |
209 |
:-( |
UserMap = mongoose_riak:create_new_map(Ops), |
210 |
:-( |
mongoose_riak:update_type(bucket_type(HostType, LServer), LUser, riakc_map:to_op(UserMap)). |
211 |
|
|
212 |
|
do_get_password(HostType, LUser, LServer) -> |
213 |
:-( |
case mongoose_riak:fetch_type(bucket_type(HostType, LServer), LUser) of |
214 |
|
{ok, Map} -> |
215 |
:-( |
case extract_password(Map) of |
216 |
|
false -> |
217 |
:-( |
?LOG_WARNING(#{what => scram_serialisation_incorrect, |
218 |
:-( |
user => LUser, server => LServer}); |
219 |
:-( |
Pwd -> Pwd |
220 |
|
end; |
221 |
|
_ -> |
222 |
:-( |
false |
223 |
|
end. |
224 |
|
|
225 |
|
do_set_password({ok, Map}, HostType, LUser, LServer, Password) -> |
226 |
:-( |
Ops = [set_password_map_op(Password)], |
227 |
:-( |
UpdateMap = mongoose_riak:update_map(Map, Ops), |
228 |
:-( |
mongoose_riak:update_type(bucket_type(HostType, LServer), LUser, riakc_map:to_op(UpdateMap)). |
229 |
|
|
230 |
|
prepare_password(HostType, Iterations, Password) when is_integer(Iterations) -> |
231 |
:-( |
Scram = mongoose_scram:password_to_scram(HostType, Password, Iterations), |
232 |
:-( |
PassDetails = mongoose_scram:serialize(Scram), |
233 |
:-( |
{<<"">>, PassDetails}. |
234 |
|
|
235 |
|
prepare_password(HostType, Password) -> |
236 |
:-( |
case mongoose_scram:enabled(HostType) of |
237 |
|
true -> |
238 |
:-( |
prepare_password(HostType, mongoose_scram:iterations(HostType), Password); |
239 |
|
_ -> |
240 |
:-( |
Password |
241 |
|
end. |
242 |
|
|
243 |
|
set_password_map_op({_, Scram}) -> |
244 |
:-( |
{{<<"scram">>, register}, fun(R) -> riakc_register:set(Scram, R) end}; |
245 |
|
set_password_map_op(Password) -> |
246 |
:-( |
{{<<"password">>, register}, fun(R) -> riakc_register:set(Password, R) end}. |
247 |
|
|
248 |
|
extract_password(Map) -> |
249 |
:-( |
case riakc_map:find({<<"password">>, register}, Map) of |
250 |
|
error -> |
251 |
:-( |
maybe_extract_scram_password(riakc_map:find({<<"scram">>, register}, Map)); |
252 |
|
{ok, Password} -> |
253 |
:-( |
Password |
254 |
|
end. |
255 |
|
|
256 |
|
-spec maybe_extract_scram_password({ok, binary()} | error) -> mongoose_scram:scram() | false. |
257 |
|
maybe_extract_scram_password({ok, ScramSerialised}) -> |
258 |
:-( |
case mongoose_scram:deserialize(ScramSerialised) of |
259 |
|
{ok, Scram} -> |
260 |
:-( |
Scram; |
261 |
|
_ -> |
262 |
:-( |
false |
263 |
|
end; |
264 |
|
maybe_extract_scram_password(_) -> |
265 |
:-( |
false. |