1%% This Source Code Form is subject to the terms of the Mozilla Public
2%% License, v. 2.0. If a copy of the MPL was not distributed with this
3%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4%%
5%% Copyright (c) 2020-2021 VMware, Inc. or its affiliates.  All rights reserved.
6%%
7
8-module(internal_user).
9
10-include_lib("rabbit_common/include/rabbit.hrl").
11
12-export([
13  new/0,
14  new/1,
15  record_version_to_use/0,
16  fields/0,
17  fields/1,
18  upgrade/1,
19  upgrade_to/2,
20  pattern_match_all/0,
21  get_username/1,
22  get_password_hash/1,
23  get_tags/1,
24  get_hashing_algorithm/1,
25  get_limits/1,
26  create_user/3,
27  set_password_hash/3,
28  set_tags/2,
29  update_limits/3,
30  clear_limits/1
31]).
32
33-define(record_version, internal_user_v2).
34
35-type(username() :: binary()).
36
37-type(password_hash() :: binary()).
38
39-type internal_user() :: internal_user_v1:internal_user_v1() | internal_user_v2().
40
41-record(internal_user, {
42    username :: username() | '_',
43    password_hash :: password_hash() | '_',
44    tags :: [atom()] | '_',
45    %% password hashing implementation module,
46    %% typically rabbit_password_hashing_* but can
47    %% come from a plugin
48    hashing_algorithm :: atom() | '_',
49    limits = #{} :: map() | '_'}).
50
51-type(internal_user_v2() ::
52        #internal_user{username          :: username() | '_',
53                       password_hash     :: password_hash() | '_',
54                       tags              :: [atom()] | '_',
55                       hashing_algorithm :: atom() | '_',
56                       limits            :: map()}).
57
58-type internal_user_pattern() :: internal_user_v1:internal_user_v1_pattern() |
59                                 internal_user_v2_pattern().
60
61-type internal_user_v2_pattern() :: #internal_user{
62                                                  username :: username() | '_',
63                                                  password_hash :: '_',
64                                                  tags :: '_',
65                                                  hashing_algorithm :: '_',
66                                                  limits :: '_'
67                                                 }.
68
69-export_type([username/0,
70              password_hash/0,
71              internal_user/0,
72              internal_user_v2/0,
73              internal_user_pattern/0,
74              internal_user_v2_pattern/0]).
75
76-spec new() -> internal_user().
77new() ->
78    case record_version_to_use() of
79        ?record_version ->
80            #internal_user{
81                username = <<"">>,
82                password_hash = <<"">>,
83                tags = []
84            };
85        _ ->
86            internal_user_v1:new()
87    end.
88
89-spec new(tuple()) -> internal_user().
90new({hashing_algorithm, HashingAlgorithm}) ->
91    case record_version_to_use() of
92        ?record_version ->
93            #internal_user{
94                username = <<"">>,
95                password_hash = <<"">>,
96                tags = [],
97                hashing_algorithm = HashingAlgorithm
98            };
99        _ ->
100            internal_user_v1:new({hashing_algorithm, HashingAlgorithm})
101    end;
102new({tags, Tags}) ->
103    case record_version_to_use() of
104	?record_version ->
105	    #internal_user{
106            username = <<"">>,
107            password_hash = <<"">>,
108            tags = Tags
109        };
110	_ ->
111	    internal_user_v1:new({tags, Tags})
112    end.
113
114-spec record_version_to_use() -> internal_user_v1 | internal_user_v2.
115record_version_to_use() ->
116    case rabbit_feature_flags:is_enabled(user_limits) of
117        true  -> ?record_version;
118        false -> internal_user_v1:record_version_to_use()
119    end.
120
121-spec fields() -> list().
122fields() ->
123    case record_version_to_use() of
124        ?record_version -> fields(?record_version);
125        _               -> internal_user_v1:fields()
126    end.
127
128-spec fields(atom()) -> list().
129fields(?record_version) -> record_info(fields, internal_user);
130fields(Version)         -> internal_user_v1:fields(Version).
131
132-spec upgrade(internal_user()) -> internal_user().
133upgrade(#internal_user{} = User) -> User;
134upgrade(OldUser)         -> upgrade_to(record_version_to_use(), OldUser).
135
136-spec upgrade_to
137(internal_user_v2, internal_user()) -> internal_user_v2();
138(internal_user_v1, internal_user_v1:internal_user_v1()) -> internal_user_v1:internal_user_v1().
139
140upgrade_to(?record_version, #internal_user{} = User) ->
141    User;
142upgrade_to(?record_version, OldUser) ->
143    Fields = erlang:tuple_to_list(OldUser) ++ [#{}],
144    #internal_user{} = erlang:list_to_tuple(Fields);
145upgrade_to(Version, OldUser) ->
146    internal_user_v1:upgrade_to(Version, OldUser).
147
148-spec pattern_match_all() -> internal_user_pattern().
149pattern_match_all() ->
150    case record_version_to_use() of
151        ?record_version -> #internal_user{_ = '_'};
152        _               -> internal_user_v1:pattern_match_all()
153    end.
154
155-spec get_username(internal_user()) -> username().
156get_username(#internal_user{username = Value}) -> Value;
157get_username(User) -> internal_user_v1:get_username(User).
158
159-spec get_password_hash(internal_user()) -> password_hash().
160get_password_hash(#internal_user{password_hash = Value}) -> Value;
161get_password_hash(User) -> internal_user_v1:get_password_hash(User).
162
163-spec get_tags(internal_user()) -> [atom()].
164get_tags(#internal_user{tags = Value}) -> Value;
165get_tags(User) -> internal_user_v1:get_tags(User).
166
167-spec get_hashing_algorithm(internal_user()) -> atom().
168get_hashing_algorithm(#internal_user{hashing_algorithm = Value}) -> Value;
169get_hashing_algorithm(User) -> internal_user_v1:get_hashing_algorithm(User).
170
171-spec get_limits(internal_user()) -> map().
172get_limits(#internal_user{limits = Value}) -> Value;
173get_limits(User) -> internal_user_v1:get_limits(User).
174
175-spec create_user(username(), password_hash(), atom()) -> internal_user().
176create_user(Username, PasswordHash, HashingMod) ->
177    case record_version_to_use() of
178        ?record_version ->
179            #internal_user{username = Username,
180                           password_hash = PasswordHash,
181                           tags = [],
182                           hashing_algorithm = HashingMod,
183                           limits = #{}
184                          };
185        _ ->
186            internal_user_v1:create_user(Username, PasswordHash, HashingMod)
187    end.
188
189-spec set_password_hash(internal_user(), password_hash(), atom()) -> internal_user().
190set_password_hash(#internal_user{} = User, PasswordHash, HashingAlgorithm) ->
191    User#internal_user{password_hash = PasswordHash,
192                       hashing_algorithm = HashingAlgorithm};
193set_password_hash(User, PasswordHash, HashingAlgorithm) ->
194    internal_user_v1:set_password_hash(User, PasswordHash, HashingAlgorithm).
195
196-spec set_tags(internal_user(), [atom()]) -> internal_user().
197set_tags(#internal_user{} = User, Tags) ->
198    User#internal_user{tags = Tags};
199set_tags(User, Tags) ->
200    internal_user_v1:set_tags(User, Tags).
201
202-spec update_limits
203(add, internal_user(), map()) -> internal_user();
204(remove, internal_user(), term()) -> internal_user().
205update_limits(add, #internal_user{limits = Limits} = User, Term) ->
206    User#internal_user{limits = maps:merge(Limits, Term)};
207update_limits(remove, #internal_user{limits = Limits} = User, LimitType) ->
208    User#internal_user{limits = maps:remove(LimitType, Limits)};
209update_limits(Action, User, Term) ->
210    internal_user_v1:update_limits(Action, User, Term).
211
212-spec clear_limits(internal_user()) -> internal_user().
213clear_limits(#internal_user{} = User) ->
214    User#internal_user{limits = #{}};
215clear_limits(User) ->
216    internal_user_v1:clear_limits(User).
217