1 | #include <Access/UsersConfigAccessStorage.h> |
2 | #include <Access/Quota.h> |
3 | #include <Access/RowPolicy.h> |
4 | #include <Common/StringUtils/StringUtils.h> |
5 | #include <Common/quoteString.h> |
6 | #include <Poco/Util/AbstractConfiguration.h> |
7 | #include <Poco/MD5Engine.h> |
8 | #include <cstring> |
9 | |
10 | |
11 | namespace DB |
12 | { |
13 | namespace |
14 | { |
15 | char getTypeChar(std::type_index type) |
16 | { |
17 | if (type == typeid(Quota)) |
18 | return 'Q'; |
19 | if (type == typeid(RowPolicy)) |
20 | return 'P'; |
21 | return 0; |
22 | } |
23 | |
24 | |
25 | UUID generateID(std::type_index type, const String & name) |
26 | { |
27 | Poco::MD5Engine md5; |
28 | md5.update(name); |
29 | char type_storage_chars[] = " USRSXML" ; |
30 | type_storage_chars[0] = getTypeChar(type); |
31 | md5.update(type_storage_chars, strlen(type_storage_chars)); |
32 | UUID result; |
33 | memcpy(&result, md5.digest().data(), md5.digestLength()); |
34 | return result; |
35 | } |
36 | |
37 | |
38 | UUID generateID(const IAccessEntity & entity) { return generateID(entity.getType(), entity.getFullName()); } |
39 | |
40 | QuotaPtr parseQuota(const Poco::Util::AbstractConfiguration & config, const String & quota_name, const Strings & user_names) |
41 | { |
42 | auto quota = std::make_shared<Quota>(); |
43 | quota->setName(quota_name); |
44 | |
45 | using KeyType = Quota::KeyType; |
46 | String quota_config = "quotas." + quota_name; |
47 | if (config.has(quota_config + ".keyed_by_ip" )) |
48 | quota->key_type = KeyType::IP_ADDRESS; |
49 | else if (config.has(quota_config + ".keyed" )) |
50 | quota->key_type = KeyType::CLIENT_KEY_OR_USER_NAME; |
51 | else |
52 | quota->key_type = KeyType::USER_NAME; |
53 | |
54 | Poco::Util::AbstractConfiguration::Keys interval_keys; |
55 | config.keys(quota_config, interval_keys); |
56 | |
57 | for (const String & interval_key : interval_keys) |
58 | { |
59 | if (!startsWith(interval_key, "interval" )) |
60 | continue; |
61 | |
62 | String interval_config = quota_config + "." + interval_key; |
63 | std::chrono::seconds duration{config.getInt(interval_config + ".duration" , 0)}; |
64 | if (duration.count() <= 0) /// Skip quotas with non-positive duration. |
65 | continue; |
66 | |
67 | quota->all_limits.emplace_back(); |
68 | auto & limits = quota->all_limits.back(); |
69 | limits.duration = duration; |
70 | limits.randomize_interval = config.getBool(interval_config + ".randomize" , false); |
71 | |
72 | using ResourceType = Quota::ResourceType; |
73 | limits.max[ResourceType::QUERIES] = config.getUInt64(interval_config + ".queries" , Quota::UNLIMITED); |
74 | limits.max[ResourceType::ERRORS] = config.getUInt64(interval_config + ".errors" , Quota::UNLIMITED); |
75 | limits.max[ResourceType::RESULT_ROWS] = config.getUInt64(interval_config + ".result_rows" , Quota::UNLIMITED); |
76 | limits.max[ResourceType::RESULT_BYTES] = config.getUInt64(interval_config + ".result_bytes" , Quota::UNLIMITED); |
77 | limits.max[ResourceType::READ_ROWS] = config.getUInt64(interval_config + ".read_rows" , Quota::UNLIMITED); |
78 | limits.max[ResourceType::READ_BYTES] = config.getUInt64(interval_config + ".read_bytes" , Quota::UNLIMITED); |
79 | limits.max[ResourceType::EXECUTION_TIME] = Quota::secondsToExecutionTime(config.getUInt64(interval_config + ".execution_time" , Quota::UNLIMITED)); |
80 | } |
81 | |
82 | quota->roles = user_names; |
83 | |
84 | return quota; |
85 | } |
86 | |
87 | |
88 | std::vector<AccessEntityPtr> parseQuotas(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log) |
89 | { |
90 | Poco::Util::AbstractConfiguration::Keys user_names; |
91 | config.keys("users" , user_names); |
92 | std::unordered_map<String, Strings> quota_to_user_names; |
93 | for (const auto & user_name : user_names) |
94 | { |
95 | if (config.has("users." + user_name + ".quota" )) |
96 | quota_to_user_names[config.getString("users." + user_name + ".quota" )].push_back(user_name); |
97 | } |
98 | |
99 | Poco::Util::AbstractConfiguration::Keys quota_names; |
100 | config.keys("quotas" , quota_names); |
101 | std::vector<AccessEntityPtr> quotas; |
102 | quotas.reserve(quota_names.size()); |
103 | for (const auto & quota_name : quota_names) |
104 | { |
105 | try |
106 | { |
107 | auto it = quota_to_user_names.find(quota_name); |
108 | const Strings quota_users = (it != quota_to_user_names.end()) ? std::move(it->second) : Strings{}; |
109 | quotas.push_back(parseQuota(config, quota_name, quota_users)); |
110 | } |
111 | catch (...) |
112 | { |
113 | tryLogCurrentException(log, "Could not parse quota " + backQuote(quota_name)); |
114 | } |
115 | } |
116 | return quotas; |
117 | } |
118 | |
119 | |
120 | std::vector<AccessEntityPtr> parseRowPolicies(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log) |
121 | { |
122 | std::vector<AccessEntityPtr> policies; |
123 | Poco::Util::AbstractConfiguration::Keys user_names; |
124 | config.keys("users" , user_names); |
125 | |
126 | for (const String & user_name : user_names) |
127 | { |
128 | const String databases_config = "users." + user_name + ".databases" ; |
129 | if (config.has(databases_config)) |
130 | { |
131 | Poco::Util::AbstractConfiguration::Keys databases; |
132 | config.keys(databases_config, databases); |
133 | |
134 | /// Read tables within databases |
135 | for (const String & database : databases) |
136 | { |
137 | const String database_config = databases_config + "." + database; |
138 | Poco::Util::AbstractConfiguration::Keys table_names; |
139 | config.keys(database_config, table_names); |
140 | |
141 | /// Read table properties |
142 | for (const String & table_name : table_names) |
143 | { |
144 | const auto filter_config = database_config + "." + table_name + ".filter" ; |
145 | if (config.has(filter_config)) |
146 | { |
147 | try |
148 | { |
149 | auto policy = std::make_shared<RowPolicy>(); |
150 | policy->setFullName(database, table_name, user_name); |
151 | policy->conditions[RowPolicy::SELECT_FILTER] = config.getString(filter_config); |
152 | policy->roles.push_back(user_name); |
153 | policies.push_back(policy); |
154 | } |
155 | catch (...) |
156 | { |
157 | tryLogCurrentException( |
158 | log, |
159 | "Could not parse row policy " + backQuote(user_name) + " on table " + backQuoteIfNeed(database) + "." |
160 | + backQuoteIfNeed(table_name)); |
161 | } |
162 | } |
163 | } |
164 | } |
165 | } |
166 | } |
167 | return policies; |
168 | } |
169 | } |
170 | |
171 | |
172 | UsersConfigAccessStorage::UsersConfigAccessStorage() : IAccessStorage("users.xml" ) |
173 | { |
174 | } |
175 | |
176 | |
177 | UsersConfigAccessStorage::~UsersConfigAccessStorage() {} |
178 | |
179 | |
180 | void UsersConfigAccessStorage::loadFromConfig(const Poco::Util::AbstractConfiguration & config) |
181 | { |
182 | std::vector<std::pair<UUID, AccessEntityPtr>> all_entities; |
183 | for (const auto & entity : parseQuotas(config, getLogger())) |
184 | all_entities.emplace_back(generateID(*entity), entity); |
185 | for (const auto & entity : parseRowPolicies(config, getLogger())) |
186 | all_entities.emplace_back(generateID(*entity), entity); |
187 | memory_storage.setAll(all_entities); |
188 | } |
189 | |
190 | |
191 | std::optional<UUID> UsersConfigAccessStorage::findImpl(std::type_index type, const String & name) const |
192 | { |
193 | return memory_storage.find(type, name); |
194 | } |
195 | |
196 | |
197 | std::vector<UUID> UsersConfigAccessStorage::findAllImpl(std::type_index type) const |
198 | { |
199 | return memory_storage.findAll(type); |
200 | } |
201 | |
202 | |
203 | bool UsersConfigAccessStorage::existsImpl(const UUID & id) const |
204 | { |
205 | return memory_storage.exists(id); |
206 | } |
207 | |
208 | |
209 | AccessEntityPtr UsersConfigAccessStorage::readImpl(const UUID & id) const |
210 | { |
211 | return memory_storage.read(id); |
212 | } |
213 | |
214 | |
215 | String UsersConfigAccessStorage::readNameImpl(const UUID & id) const |
216 | { |
217 | return memory_storage.readName(id); |
218 | } |
219 | |
220 | |
221 | UUID UsersConfigAccessStorage::insertImpl(const AccessEntityPtr & entity, bool) |
222 | { |
223 | throwReadonlyCannotInsert(entity->getType(), entity->getFullName()); |
224 | } |
225 | |
226 | |
227 | void UsersConfigAccessStorage::removeImpl(const UUID & id) |
228 | { |
229 | auto entity = read(id); |
230 | throwReadonlyCannotRemove(entity->getType(), entity->getFullName()); |
231 | } |
232 | |
233 | |
234 | void UsersConfigAccessStorage::updateImpl(const UUID & id, const UpdateFunc &) |
235 | { |
236 | auto entity = read(id); |
237 | throwReadonlyCannotUpdate(entity->getType(), entity->getFullName()); |
238 | } |
239 | |
240 | |
241 | IAccessStorage::SubscriptionPtr UsersConfigAccessStorage::subscribeForChangesImpl(const UUID & id, const OnChangedHandler & handler) const |
242 | { |
243 | return memory_storage.subscribeForChanges(id, handler); |
244 | } |
245 | |
246 | |
247 | IAccessStorage::SubscriptionPtr UsersConfigAccessStorage::subscribeForChangesImpl(std::type_index type, const OnChangedHandler & handler) const |
248 | { |
249 | return memory_storage.subscribeForChanges(type, handler); |
250 | } |
251 | |
252 | |
253 | bool UsersConfigAccessStorage::hasSubscriptionImpl(const UUID & id) const |
254 | { |
255 | return memory_storage.hasSubscription(id); |
256 | } |
257 | |
258 | |
259 | bool UsersConfigAccessStorage::hasSubscriptionImpl(std::type_index type) const |
260 | { |
261 | return memory_storage.hasSubscription(type); |
262 | } |
263 | } |
264 | |