Skip to content

Commit 6011da2

Browse files
authored
Merge pull request #225 from Sambruk/feature/54
Adds functionality for printing the cache file.
2 parents 667cf30 + 5bd63b2 commit 6011da2

File tree

8 files changed

+288
-34
lines changed

8 files changed

+288
-34
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44
#### Features
5+
- Contents of the cache file can now be printed in JSON format (#54)
56
- New functionality for blacklisting users (#223)
67

78
## v2.18 (2024-11-19)

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ endif()
5151
link_libraries(${LDFLAGS})
5252

5353
set(srcroot src)
54-
set(SOURCES ${srcroot}/scim_json_parse.cpp ${srcroot}/utility/simplescim_error_string.cpp ${srcroot}/simplescim_scim_send.cpp ${srcroot}/cache_file.cpp ${srcroot}/model/base_object.cpp ${srcroot}/model/object_list.cpp ${srcroot}/model/object_list.hpp ${srcroot}/simplescim_ldap_attrs_parser.cpp ${srcroot}/config_file.cpp ${srcroot}/config_file_parser.cpp ${srcroot}/scim.cpp ${srcroot}/utility/utils.cpp ${srcroot}/utility/utils.hpp ${srcroot}/utility/utils.cpp ${srcroot}/ldap_wrapper.cpp ${srcroot}/ldap_wrapper.hpp ${srcroot}/data_server.cpp ${srcroot}/data_server.hpp ${srcroot}/json_data_file.cpp ${srcroot}/json_data_file.hpp ${srcroot}/json_template_parser.cpp ${srcroot}/json_template_parser.hpp ${srcroot}/simplescim_ldap.cpp ${srcroot}/generated_load.cpp ${srcroot}/generated_load.hpp ${srcroot}/generated_group_load.cpp ${srcroot}/generated_group_load.hpp ${srcroot}/csv_load.cpp ${srcroot}/csv_load.hpp ${srcroot}/csv_store.cpp ${srcroot}/csv_store.hpp ${srcroot}/csv_file.cpp ${srcroot}/csv_file.hpp ${srcroot}/sql_load.cpp ${srcroot}/sql_load.hpp ${srcroot}/scim_server_info.cpp ${srcroot}/scim_server_info.hpp ${srcroot}/load_limiter.hpp ${srcroot}/load_limiter.cpp ${srcroot}/fedtlsauth/castore_file.hpp ${srcroot}/fedtlsauth/castore_file.cpp ${srcroot}/fedtlsauth/metadata_parser.hpp ${srcroot}/fedtlsauth/metadata_parser.cpp ${srcroot}/utility/indented_logger.hpp ${srcroot}/utility/indented_logger.cpp ${srcroot}/post_processing.hpp ${srcroot}/post_processing.cpp ${srcroot}/sql.hpp ${srcroot}/sql.cpp ${srcroot}/dl.hpp ${srcroot}/dl.cpp ${srcroot}/plugin_config.hpp ${srcroot}/plugin_config.cpp ${srcroot}/load_common.hpp ${srcroot}/load_common.cpp ${srcroot}/load_limiter_impl.hpp ${srcroot}/model/rendered_object.hpp ${srcroot}/model/rendered_object.cpp ${srcroot}/model/rendered_object_list.hpp ${srcroot}/model/rendered_object_list.cpp ${srcroot}/renderer.hpp ${srcroot}/renderer.cpp ${srcroot}/rendered_cache_file.hpp ${srcroot}/rendered_cache_file.cpp ${srcroot}/utility/temporary_umask.hpp ${srcroot}/transformer.hpp ${srcroot}/transformer.cpp ${srcroot}/transformer_impl.hpp ${srcroot}/transformer_impl.cpp ${srcroot}/readable_id.hpp ${srcroot}/readable_id.cpp ${srcroot}/config.hpp ${srcroot}/config.cpp ${srcroot}/dnpactivities.cpp ${srcroot}/dnpactivities.hpp ${srcroot}/utility/simpleurl.cpp ${srcroot}/utility/simpleurl.hpp ${srcroot}/thresholds.hpp ${srcroot}/thresholds.cpp ${srcroot}/audit.hpp ${srcroot}/audit.cpp)
54+
set(SOURCES ${srcroot}/scim_json_parse.cpp ${srcroot}/utility/simplescim_error_string.cpp ${srcroot}/simplescim_scim_send.cpp ${srcroot}/cache_file.cpp ${srcroot}/model/base_object.cpp ${srcroot}/model/object_list.cpp ${srcroot}/model/object_list.hpp ${srcroot}/simplescim_ldap_attrs_parser.cpp ${srcroot}/config_file.cpp ${srcroot}/config_file_parser.cpp ${srcroot}/scim.cpp ${srcroot}/utility/utils.cpp ${srcroot}/utility/utils.hpp ${srcroot}/utility/utils.cpp ${srcroot}/ldap_wrapper.cpp ${srcroot}/ldap_wrapper.hpp ${srcroot}/data_server.cpp ${srcroot}/data_server.hpp ${srcroot}/json_data_file.cpp ${srcroot}/json_data_file.hpp ${srcroot}/json_template_parser.cpp ${srcroot}/json_template_parser.hpp ${srcroot}/simplescim_ldap.cpp ${srcroot}/generated_load.cpp ${srcroot}/generated_load.hpp ${srcroot}/generated_group_load.cpp ${srcroot}/generated_group_load.hpp ${srcroot}/csv_load.cpp ${srcroot}/csv_load.hpp ${srcroot}/csv_store.cpp ${srcroot}/csv_store.hpp ${srcroot}/csv_file.cpp ${srcroot}/csv_file.hpp ${srcroot}/sql_load.cpp ${srcroot}/sql_load.hpp ${srcroot}/scim_server_info.cpp ${srcroot}/scim_server_info.hpp ${srcroot}/load_limiter.hpp ${srcroot}/load_limiter.cpp ${srcroot}/fedtlsauth/castore_file.hpp ${srcroot}/fedtlsauth/castore_file.cpp ${srcroot}/fedtlsauth/metadata_parser.hpp ${srcroot}/fedtlsauth/metadata_parser.cpp ${srcroot}/utility/indented_logger.hpp ${srcroot}/utility/indented_logger.cpp ${srcroot}/post_processing.hpp ${srcroot}/post_processing.cpp ${srcroot}/sql.hpp ${srcroot}/sql.cpp ${srcroot}/dl.hpp ${srcroot}/dl.cpp ${srcroot}/plugin_config.hpp ${srcroot}/plugin_config.cpp ${srcroot}/load_common.hpp ${srcroot}/load_common.cpp ${srcroot}/load_limiter_impl.hpp ${srcroot}/model/rendered_object.hpp ${srcroot}/model/rendered_object.cpp ${srcroot}/model/rendered_object_list.hpp ${srcroot}/model/rendered_object_list.cpp ${srcroot}/renderer.hpp ${srcroot}/renderer.cpp ${srcroot}/rendered_cache_file.hpp ${srcroot}/rendered_cache_file.cpp ${srcroot}/utility/temporary_umask.hpp ${srcroot}/transformer.hpp ${srcroot}/transformer.cpp ${srcroot}/transformer_impl.hpp ${srcroot}/transformer_impl.cpp ${srcroot}/readable_id.hpp ${srcroot}/readable_id.cpp ${srcroot}/config.hpp ${srcroot}/config.cpp ${srcroot}/dnpactivities.cpp ${srcroot}/dnpactivities.hpp ${srcroot}/utility/simpleurl.cpp ${srcroot}/utility/simpleurl.hpp ${srcroot}/thresholds.hpp ${srcroot}/thresholds.cpp ${srcroot}/audit.hpp ${srcroot}/audit.cpp ${srcroot}/print_cache.hpp ${srcroot}/print_cache.cpp)
5555

5656
set(TEST_SOURCES ${srcroot}/tests/main.cpp ${srcroot}/tests/json_template_parser_tests.cpp ${srcroot}/tests/scim_query_tests.cpp ${srcroot}/tests/csv_tests.cpp ${srcroot}/tests/uuid_tests.cpp ${srcroot}/tests/scim_json_parse_tests.cpp ${srcroot}/tests/load_limiter_tests.cpp ${srcroot}/tests/object_list_tests.cpp ${srcroot}/tests/generated_group_tests.cpp ${srcroot}/tests/transformer_tests.cpp ${srcroot}/tests/dnpactivities_tests.cpp ${srcroot}/tests/thresholds_tests.cpp ${srcroot}/tests/audit_tests.cpp)
5757

doc/USAGE.md

+40
Original file line numberDiff line numberDiff line change
@@ -1124,3 +1124,43 @@ we are sending the operation to. Instead you can use a file path that shows what
11241124
the destination is. Note that if you wish to re-use the same configuration files
11251125
for multiple destinations you can use the -D command line argument to set the
11261126
`audit-log-file` variable.
1127+
1128+
## Inspecing the cache file
1129+
Contents of the cache file can be printed to standard output by running the
1130+
EgilSCIMClient with the `--print-cache` argument. For instance:
1131+
1132+
```
1133+
EgilSCIMClient --print-cache master.conf
1134+
```
1135+
1136+
The cache file configured in the specified configuration file will be printed
1137+
in JSON format to standard output. As usual you can override the path to the
1138+
cache file with the `--cache-file` argument. You still need to specify the
1139+
configuration file.
1140+
1141+
If you don't want to print all objects in the cache file you can limit them
1142+
by type and/or attribute values. For instance, to print all `Student` objects:
1143+
1144+
```
1145+
EgilSCIMClient --print-cache --print-cache-type Student master.conf
1146+
```
1147+
1148+
Or to print a specific student:
1149+
1150+
```
1151+
EgilSCIMClient --print-cache --print-cache-type Student --print-cache-where [email protected] master.conf
1152+
```
1153+
1154+
If you wish to print all types of users it can be helpful to specify SCIM
1155+
endpoints rather than EGIL types:
1156+
1157+
```
1158+
EgilSCIMClient --print-cache --print-cache-by-endpoint --print-cache-type Users master.conf
1159+
```
1160+
1161+
If you wish to search for a sub-attribute you can specify a path in the JSON
1162+
object by using '/' as the separator:
1163+
1164+
```
1165+
EgilSCIMClient --print-cache --print-cache-where 'name/familyName=Johansson' master.conf
1166+
```

src/main.cpp

+56-33
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,24 @@
4141
#include "sql.hpp"
4242
#include "generated_group_load.hpp"
4343
#include "thresholds.hpp"
44+
#include "print_cache.hpp"
4445

4546
namespace po = boost::program_options;
4647
namespace filesystem = std::experimental::filesystem;
4748

49+
// TODO: Add the rest of the options names here
50+
namespace options {
51+
const char* CACHE_FILE = "cache-file";
52+
53+
const char* USER_BLACKLIST_FILE = "user-blacklist-file";
54+
const char* USER_BLACKLIST_ATTRIBUTE = "user-blacklist-attribute";
55+
56+
const char* PRINT_CACHE = "print-cache";
57+
const char* PRINT_CACHE_BY_ENDPOINT = "print-cache-by-endpoint";
58+
const char* PRINT_CACHE_TYPE = "print-cache-type";
59+
const char* PRINT_CACHE_WHERE = "print-cache-where";
60+
}
61+
4862
void print_usage(const std::string& program_name,
4963
const po::options_description& options) {
5064
std::cout << "Usage: " << program_name << " [OPTIONS] <config-file>\n\n";
@@ -103,23 +117,6 @@ void write_status(const std::string& file,
103117
of << "}" << std::endl;
104118
}
105119

106-
/**
107-
* Splits a string with format "variable=value" into its parts.
108-
* Used on the command line for overriding config file variables.
109-
*/
110-
void parse_override(const std::string& override_str,
111-
std::string& variable,
112-
std::string& value) {
113-
auto pos = override_str.find('=');
114-
115-
if (pos == std::string::npos) {
116-
throw std::runtime_error("Malformed variable override (" + override_str + ")");
117-
}
118-
119-
variable = override_str.substr(0, pos);
120-
value = override_str.substr(pos+1);
121-
}
122-
123120
/**
124121
* Modifies a set of objects so they will be considered different from what's in the data source,
125122
* used by --force-update.
@@ -150,7 +147,7 @@ void make_dirty(std::shared_ptr<rendered_object_list> objects, const std::vector
150147
* parameter will let the caller know if there was a cache file or not.
151148
*/
152149
std::shared_ptr<rendered_object_list> read_cache(const post_processing::plugins& ppp, bool *cache_file_existed) {
153-
auto cache_path = config_file::instance().get_path("cache-file");
150+
auto cache_path = config_file::instance().get_path(options::CACHE_FILE);
154151

155152
if (cache_path.empty()) {
156153
return nullptr;
@@ -187,30 +184,26 @@ std::shared_ptr<rendered_object_list> read_cache(const post_processing::plugins&
187184
return rendered_cache;
188185
}
189186

190-
// TODO: Add the rest of the options names here
191-
namespace options {
192-
const char* USER_BLACKLIST_FILE = "user-blacklist-file";
193-
const char* USER_BLACKLIST_ATTRIBUTE = "user-blacklist-attribute";
194-
}
195-
196187
int main(int argc, char *argv[]) {
197188
try {
198189
po::options_description cmdline_options("All options");
199190
po::options_description generic("Options");
200191
po::options_description hidden("Hidden options");
201192

202193
generic.add_options()
203-
("help,h", "produce help message")
204-
("version,v", "displays version of this program")
205-
("rebuild-cache,r", "ignores cache file contents and instead queries SCIM server for list of objects")
206-
("skip-load", "don't read from data source, causes delete for all objects in cache")
207-
("skip-thresholds", "don't verify thresholds");
194+
("help,h", "produce help message")
195+
("version,v", "displays version of this program")
196+
("rebuild-cache,r", "ignores cache file contents and instead queries SCIM server for list of objects")
197+
("skip-load", "don't read from data source, causes delete for all objects in cache")
198+
("skip-thresholds", "don't verify thresholds")
199+
(options::PRINT_CACHE, "prints contents of cache file (see --print-cache-type and --print-cache-where)")
200+
(options::PRINT_CACHE_BY_ENDPOINT, "uses the SCIM endpoints instead of EGIL types when printing the cache file");
208201

209202
// Config file variables exposed as command line options
210203
std::vector<config_file_option> common_vars =
211204
{ { "cert", "client certificate", true },
212205
{ "key", "client private key", true },
213-
{ "cache-file", "cache file", true },
206+
{ options::CACHE_FILE,"cache file", true },
214207
{ "metadata-path", "metadata file", true },
215208
{ "metadata-entity", "entity in metadata to connect to", false },
216209
{ "metadata-server",
@@ -240,6 +233,10 @@ int main(int argc, char *argv[]) {
240233
generic.add_options()
241234
(options::USER_BLACKLIST_FILE, po::value<std::string>(), "a file of users which shall be blocked from loading")
242235
(options::USER_BLACKLIST_ATTRIBUTE, po::value<std::string>(), "attribute (in the data source) to match against user blacklist file");
236+
237+
generic.add_options()
238+
(options::PRINT_CACHE_TYPE, po::value<std::vector<std::string>>(), "only print given type(s)")
239+
(options::PRINT_CACHE_WHERE, po::value<std::vector<std::string>>(), "only print objects where attributes match given values");
243240

244241
hidden.add_options()
245242
("config-file", po::value<std::vector<std::string>>(), "config file");
@@ -273,7 +270,7 @@ int main(int argc, char *argv[]) {
273270
std::cerr << e.what() << std::endl;
274271
return EXIT_FAILURE;
275272
}
276-
273+
277274
config_file &config = config_file::instance();
278275

279276
std::string config_file;
@@ -287,8 +284,6 @@ int main(int argc, char *argv[]) {
287284
}
288285

289286
/** Load configuration file */
290-
std::cout << "processing: " << config_file << std::endl;
291-
292287
time_t start_time = time(nullptr);
293288

294289
int err = 0;
@@ -324,6 +319,34 @@ int main(int argc, char *argv[]) {
324319
}
325320
}
326321

322+
if (vm.count(options::PRINT_CACHE)) {
323+
bool by_endpoint = vm.count(options::PRINT_CACHE_BY_ENDPOINT);
324+
auto cache_path = config_file::instance().get_path(options::CACHE_FILE);
325+
326+
std::shared_ptr<rendered_object_list> cache;
327+
try {
328+
cache = rendered_cache_file::get_contents(cache_path);
329+
}
330+
catch (const rendered_cache_file::bad_format &) {
331+
std::cerr << "Unrecognized cache file format" << std::endl;
332+
return EXIT_FAILURE;
333+
}
334+
catch (const std::runtime_error &e) {
335+
std::cerr << "Failed to read cache file: " << e.what() << std::endl;
336+
return EXIT_FAILURE;
337+
}
338+
339+
std::vector<std::string> types, where;
340+
if (vm.count(options::PRINT_CACHE_TYPE)) {
341+
types = vm[options::PRINT_CACHE_TYPE].as<std::vector<std::string>>();
342+
}
343+
if (vm.count(options::PRINT_CACHE_WHERE)) {
344+
where = vm[options::PRINT_CACHE_WHERE].as<std::vector<std::string>>();
345+
}
346+
print_cache(cache, by_endpoint, types, where);
347+
return EXIT_SUCCESS;
348+
}
349+
327350
add_scim_vars_for_virtual_groups();
328351

329352
if (config.get_bool("scim-auth-WEAK")) {

src/print_cache.cpp

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* This file is part of the EGIL SCIM client.
3+
*
4+
* Copyright (C) 2017-2025 Föreningen Sambruk
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
#include "print_cache.hpp"
21+
#include "config_file.hpp"
22+
#include <algorithm>
23+
#include <iostream>
24+
25+
#include <boost/property_tree/json_parser.hpp>
26+
27+
namespace pt = boost::property_tree;
28+
29+
namespace {
30+
/** Checks if all where conditions matches the json.
31+
* A where condition is something like externalId=foo
32+
* Paths can also be used to get sub-attributes, such as name/givenName=Babs
33+
* boost::property_tree paths are used with / as separator.
34+
*/
35+
bool where_matches(const std::vector<std::string> &where, const std::string& json) {
36+
// No need to parse JSON if there are no where conditions
37+
if (where.size() == 0) {
38+
return true;
39+
}
40+
pt::ptree root;
41+
try {
42+
std::stringstream json_stream;
43+
json_stream << json;
44+
pt::read_json(json_stream, root);
45+
}
46+
catch (const std::runtime_error &) {
47+
// Unparsable JSON isn't printed if we have where conditions
48+
return false;
49+
}
50+
51+
bool all_matched = true;
52+
for (auto &condition : where) {
53+
std::string variable, value;
54+
try {
55+
parse_override(condition, variable, value);
56+
if (root.get<std::string>(pt::ptree::path_type{variable, '/'}) != value) {
57+
all_matched = false;
58+
break;
59+
}
60+
} catch (std::runtime_error& e) {
61+
// either we couldn't parse the condition, or the object
62+
// didn't have the variable
63+
all_matched = false;
64+
break;
65+
}
66+
}
67+
return all_matched;
68+
}
69+
}
70+
71+
void print_cache(std::shared_ptr<rendered_object_list> cache,
72+
bool by_endpoint,
73+
const std::vector<std::string> &types,
74+
const std::vector<std::string> &where) {
75+
76+
config_file &conf = config_file::instance();
77+
std::map<std::string, std::vector<std::string>> to_print_per_type;
78+
79+
for (auto &itr : *cache) {
80+
auto obj = itr.second;
81+
82+
auto obj_type = obj->get_type();
83+
84+
if (by_endpoint) {
85+
auto endpoint_variable = obj_type + "-scim-url-endpoint";
86+
if (conf.has(endpoint_variable)) {
87+
obj_type = conf.get(endpoint_variable);
88+
}
89+
}
90+
91+
if (types.size() == 0 || std::find(types.begin(), types.end(), obj_type) != types.end()) {
92+
auto json = obj->get_json();
93+
if (where_matches(where, json)) {
94+
to_print_per_type[obj_type].push_back(json);
95+
}
96+
}
97+
}
98+
99+
std::cout << "{\n";
100+
101+
bool first_type = true;
102+
for (auto &itr : to_print_per_type) {
103+
if (!first_type) {
104+
std::cout << ",\n";
105+
}
106+
auto& current_type = itr.first;
107+
auto& current_objects = itr.second;
108+
109+
std::cout << "\"" << current_type << "\": [\n";
110+
111+
bool first_object = true;
112+
for (auto &jtr : current_objects) {
113+
if (!first_object) {
114+
std::cout << ",\n";
115+
}
116+
std::cout << jtr << "\n";
117+
first_object = false;
118+
}
119+
120+
std::cout << "]";
121+
first_type = false;
122+
}
123+
124+
std::cout << "\n}" << std::endl;
125+
}

src/print_cache.hpp

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* This file is part of the EGIL SCIM client.
3+
*
4+
* Copyright (C) 2017-2025 Föreningen Sambruk
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
#ifndef EGILSCIM_PRINT_CACHE_HPP
21+
#define EGILSCIM_PRINT_CACHE_HPP
22+
23+
#include <memory>
24+
#include <vector>
25+
#include <string>
26+
#include "model/rendered_object_list.hpp"
27+
28+
/** Prints certain objects from the cache to stdout.
29+
* types can be used to limit the objects to certain types, if types is empty
30+
* objects of all types are printed.
31+
* If by_endpoint is true the types are interpreted as SCIM endpoints instead
32+
* of EGIL types.
33+
* where contains conditions that must be met for each object to print, in the
34+
* format attribute=value. For instance [email protected] . If there
35+
* are multiple where conditions all must be met.
36+
*/
37+
void print_cache(std::shared_ptr<rendered_object_list> cache,
38+
bool by_endpoint,
39+
const std::vector<std::string> &types,
40+
const std::vector<std::string> &where);
41+
42+
#endif // EGILSCIM_PRINT_CACHE_HPP

0 commit comments

Comments
 (0)