diff --git a/docs/USER_GROUP_API.md b/docs/USER_GROUP_API.md index 1109ad81da..3341c77986 100644 --- a/docs/USER_GROUP_API.md +++ b/docs/USER_GROUP_API.md @@ -175,6 +175,7 @@ method GetUserRecord( dispositionMask: ?[]string, uidMin: ?int, uidMax: ?int, + uuid: ?string, service : string ) -> ( record : object, @@ -188,6 +189,7 @@ method GetGroupRecord( dispositionMask: ?[]string, gidMin: ?int, gidMax: ?int, + uuid: ?string, service : string ) -> ( record : object, @@ -222,7 +224,7 @@ If neither of the two parameters are set the whole user database is enumerated. In this case the method call needs to be made with `more` set, so that multiple method call replies may be generated as effect, each carrying one user record. -The `fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax` fields permit +The `fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax` and `uuid` fields permit *additional* filtering of the returned set of user records. The `fuzzyNames` parameter shall be one or more strings that shall be searched for in "fuzzy" way. What specifically this means is left for the backend to decide, but @@ -232,19 +234,20 @@ carry identifying information for the user. The `dispositionMask` field shall be one of more user record `disposition` strings. If specified only user records matching one of the specified dispositions should be enumerated. The `uidMin` and `uidMax` fields specify a minimum and maximum value for the UID of -returned records. Inline searching for `uid` and `userName` support for +returned records. The `uuid` field specifies to search for the user record associated +with the specified UUID. Inline searching for `uid` and `userName` support for filtering with these four additional parameters is optional, and clients are expected to be able to do client-side filtering in case the parameters are not supported by a service. The service should return the usual `InvalidParameter` error for the relevant parameter if one is passed and it does not support it. If a request is made specifying `uid` or `userName` and a suitable record is found, but the specified filter via `fuzzyNames`, `dispositionMask`, -`uidMin`, or `uidMax` does not match, a `NonMatchingRecordFound` error should +`uidMin`, `uidMax` or `uuid` does not match, a `NonMatchingRecordFound` error should be returned. Or to say this differently: the *primary search keys* are `userName`/`groupName` and `uid`/`gid` and the *secondary search filters* are -`fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax`. If no entry matching +`fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax`, `uuid`. If no entry matching either of the primary search keys are found `NoRecordFound()` is returned. If one is found that matches one but not the other primary search key `ConflictingRecordFound()` is returned. If an entry is found that matches the diff --git a/man/userdbctl.xml b/man/userdbctl.xml index c705b6a783..5100e9db93 100644 --- a/man/userdbctl.xml +++ b/man/userdbctl.xml @@ -227,6 +227,15 @@ + + + + When used with the user or group command, + filters the output to the record with the specified UUID. If unspecified, no UUID-based filtering is applied. + + + + diff --git a/src/shared/user-record.c b/src/shared/user-record.c index b55875d6ce..1fd292dba8 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -2777,7 +2777,8 @@ bool userdb_match_is_set(const UserDBMatch *match) { return !strv_isempty(match->fuzzy_names) || !FLAGS_SET(match->disposition_mask, USER_DISPOSITION_MASK_ALL) || match->uid_min > 0 || - match->uid_max < UID_INVALID-1; + match->uid_max < UID_INVALID-1 || + !sd_id128_is_null(match->uuid); } void userdb_match_done(UserDBMatch *match) { @@ -2836,6 +2837,9 @@ bool user_record_match(UserRecord *u, const UserDBMatch *match) { if (!BIT_SET(match->disposition_mask, user_record_disposition(u))) return false; + if (!sd_id128_is_null(match->uuid) && !sd_id128_equal(match->uuid, u->uuid)) + return false; + if (!strv_isempty(match->fuzzy_names)) { /* Note this array of names is sparse, i.e. various entries listed in it will be diff --git a/src/shared/user-record.h b/src/shared/user-record.h index f411214453..e4384e50b7 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -513,6 +513,7 @@ typedef struct UserDBMatch { uid_t uid_max; gid_t gid_max; }; + sd_id128_t uuid; } UserDBMatch; #define USER_DISPOSITION_MASK_ALL ((UINT64_C(1) << _USER_DISPOSITION_MAX) - UINT64_C(1)) @@ -522,6 +523,7 @@ typedef struct UserDBMatch { .disposition_mask = USER_DISPOSITION_MASK_ALL, \ .uid_min = 0, \ .uid_max = UID_INVALID-1, \ + .uuid = SD_ID128_NULL, \ } /* Maybe useful when we want to resolve root and system user/group but want to refuse nobody user/group. */ diff --git a/src/shared/userdb.c b/src/shared/userdb.c index 49850ff216..c7a1595211 100644 --- a/src/shared/userdb.c +++ b/src/shared/userdb.c @@ -799,27 +799,29 @@ nomatch: return 0; } -static int query_append_disposition_mask(sd_json_variant **query, uint64_t mask) { +static int query_append_common(sd_json_variant **query, const UserDBMatch *match) { int r; + _cleanup_strv_free_ char **dispositions = NULL; assert(query); - if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL)) - return 0; + uint64_t mask = match->disposition_mask; + if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL)) { + for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) { + if (!BIT_SET(mask, d)) + continue; - _cleanup_strv_free_ char **dispositions = NULL; - for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) { - if (!BITS_SET(mask, d)) - continue; - - r = strv_extend(&dispositions, user_disposition_to_string(d)); - if (r < 0) - return r; + r = strv_extend(&dispositions, user_disposition_to_string(d)); + if (r < 0) + return r; + } } return sd_json_variant_merge_objectbo( query, - SD_JSON_BUILD_PAIR_STRV("dispositionMask", dispositions)); + SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)), + SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(match->uuid), "uuid", SD_JSON_BUILD_UUID(match->uuid)), + SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(dispositions), "dispositionMask", SD_JSON_BUILD_STRV(dispositions))); } static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *match) { @@ -832,13 +834,12 @@ static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *ma r = sd_json_variant_merge_objectbo( query, - SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)), SD_JSON_BUILD_PAIR_CONDITION(match->uid_min > 0, "uidMin", SD_JSON_BUILD_UNSIGNED(match->uid_min)), SD_JSON_BUILD_PAIR_CONDITION(match->uid_max < UID_INVALID-1, "uidMax", SD_JSON_BUILD_UNSIGNED(match->uid_max))); if (r < 0) return r; - return query_append_disposition_mask(query, match->disposition_mask); + return query_append_common(query, match); } static int userdb_by_name_fallbacks( @@ -1298,13 +1299,13 @@ static int query_append_gid_match(sd_json_variant **query, const UserDBMatch *ma r = sd_json_variant_merge_objectbo( query, - SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)), SD_JSON_BUILD_PAIR_CONDITION(match->gid_min > 0, "gidMin", SD_JSON_BUILD_UNSIGNED(match->gid_min)), SD_JSON_BUILD_PAIR_CONDITION(match->gid_max < GID_INVALID-1, "gidMax", SD_JSON_BUILD_UNSIGNED(match->gid_max))); + if (r < 0) return r; - return query_append_disposition_mask(query, match->disposition_mask); + return query_append_common(query, match); } static int groupdb_by_name_fallbacks( diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 4fbf8f671c..8efdc7c133 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -57,6 +57,7 @@ static bool arg_chain = false; static uint64_t arg_disposition_mask = UINT64_MAX; static uid_t arg_uid_min = 0; static uid_t arg_uid_max = UID_INVALID-1; +static sd_id128_t arg_uuid = SD_ID128_NULL; static bool arg_fuzzy = false; static bool arg_boundaries = true; static sd_json_variant *arg_from_file = NULL; @@ -411,7 +412,7 @@ static int display_user(int argc, char *argv[], void *userdata) { int ret = 0, r; if (arg_output < 0) - arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) ? OUTPUT_FRIENDLY : OUTPUT_TABLE; + arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) || !sd_id128_is_null(arg_uuid) ? OUTPUT_FRIENDLY : OUTPUT_TABLE; if (arg_output == OUTPUT_TABLE) { table = table_new(" ", "name", "disposition", "uid", "gid", "realname", "home", "shell", "order"); @@ -431,6 +432,7 @@ static int display_user(int argc, char *argv[], void *userdata) { .disposition_mask = arg_disposition_mask, .uid_min = arg_uid_min, .uid_max = arg_uid_max, + .uuid = arg_uuid, }; if (arg_from_file) { @@ -753,7 +755,7 @@ static int display_group(int argc, char *argv[], void *userdata) { int ret = 0, r; if (arg_output < 0) - arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) ? OUTPUT_FRIENDLY : OUTPUT_TABLE; + arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) || !sd_id128_is_null(arg_uuid) ? OUTPUT_FRIENDLY : OUTPUT_TABLE; if (arg_output == OUTPUT_TABLE) { table = table_new(" ", "name", "disposition", "gid", "description", "order"); @@ -772,6 +774,7 @@ static int display_group(int argc, char *argv[], void *userdata) { .disposition_mask = arg_disposition_mask, .gid_min = arg_uid_min, .gid_max = arg_uid_max, + .uuid = arg_uuid, }; if (arg_from_file) { @@ -1588,6 +1591,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CHAIN, ARG_UID_MIN, ARG_UID_MAX, + ARG_UUID, ARG_DISPOSITION, ARG_BOUNDARIES, }; @@ -1608,6 +1612,7 @@ static int parse_argv(int argc, char *argv[]) { { "chain", no_argument, NULL, ARG_CHAIN }, { "uid-min", required_argument, NULL, ARG_UID_MIN }, { "uid-max", required_argument, NULL, ARG_UID_MAX }, + { "uuid", required_argument, NULL, ARG_UUID }, { "fuzzy", no_argument, NULL, 'z' }, { "disposition", required_argument, NULL, ARG_DISPOSITION }, { "boundaries", required_argument, NULL, ARG_BOUNDARIES }, @@ -1791,6 +1796,12 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to parse --uid-max= value: %s", optarg); break; + case ARG_UUID: + r = sd_id128_from_string(optarg, &arg_uuid); + if (r < 0) + return log_error_errno(r, "Failed to parse --uuid= value: %s", optarg); + break; + case 'z': arg_fuzzy = true; break; diff --git a/src/userdb/userwork.c b/src/userdb/userwork.c index 0282cc37dc..fc698e841d 100644 --- a/src/userdb/userwork.c +++ b/src/userdb/userwork.c @@ -149,6 +149,7 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete { "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 }, { "uidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_min), 0 }, { "uidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_max), 0 }, + { "uuid", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(LookupParameters, match.uuid), 0 }, {} }; @@ -293,6 +294,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet { "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 }, { "gidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_min), 0 }, { "gidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_max), 0 }, + { "uuid", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(LookupParameters, match.uuid), 0 }, {} };