Public secret_attributes_validate method

This makes the internal logic of _secret_attributes_validate public,
so applications can check and recover when an invalid attributes table
is passed to other libsecret API, such as secret_service_clear.
This commit is contained in:
Henry Rovner 2023-11-11 22:44:17 +00:00 committed by Daiki Ueno
parent 4c5941505e
commit f610c44a92
6 changed files with 197 additions and 23 deletions

View File

@ -179,11 +179,23 @@ secret_attributes_buildv (const SecretSchema *schema,
return attributes; return attributes;
} }
/**
* secret_attributes_validate:
* @schema: the schema for the attributes
* @attributes: the attributes to be validated
* @error: place to report errors encountered
*
* Check if attributes are valid according to the provided schema.
*
* Verifies schema name if available, attribute names and parsing
* of attribute values.
*
* Returns: whether or not the given attributes table is valid
*/
gboolean gboolean
_secret_attributes_validate (const SecretSchema *schema, secret_attributes_validate (const SecretSchema *schema,
GHashTable *attributes, GHashTable *attributes,
const char *pretty_function, GError **error)
gboolean matching)
{ {
const SecretSchemaAttribute *attribute; const SecretSchemaAttribute *attribute;
GHashTableIter iter; GHashTableIter iter;
@ -204,8 +216,10 @@ _secret_attributes_validate (const SecretSchema *schema,
name. */ name. */
if (g_str_equal (key, "xdg:schema")) { if (g_str_equal (key, "xdg:schema")) {
if (!g_str_equal (value, schema->name)) { if (!g_str_equal (value, schema->name)) {
g_critical ("%s: xdg:schema value %s differs from schema %s:", g_set_error_literal (error,
pretty_function, value, schema->name); SECRET_ERROR,
SECRET_ERROR_MISMATCHED_SCHEMA,
"Schema attribute doesn't match schema name");
return FALSE; return FALSE;
} }
continue; continue;
@ -227,16 +241,22 @@ _secret_attributes_validate (const SecretSchema *schema,
} }
if (attribute == NULL) { if (attribute == NULL) {
g_critical ("%s: invalid %s attribute for %s schema", g_set_error (error,
pretty_function, key, schema->name); SECRET_ERROR,
SECRET_ERROR_NO_MATCHING_ATTRIBUTE,
"Schema does not contain any attributes matching %s",
key);
return FALSE; return FALSE;
} }
switch (attribute->type) { switch (attribute->type) {
case SECRET_SCHEMA_ATTRIBUTE_BOOLEAN: case SECRET_SCHEMA_ATTRIBUTE_BOOLEAN:
if (!g_str_equal (value, "true") && !g_str_equal (value, "false")) { if (!g_str_equal (value, "true") && !g_str_equal (value, "false")) {
g_critical ("%s: invalid %s boolean value for %s schema: %s", g_set_error (error,
pretty_function, key, schema->name, value); SECRET_ERROR,
SECRET_ERROR_WRONG_TYPE,
"Attribute %s could not be parsed into a boolean",
key);
return FALSE; return FALSE;
} }
break; break;
@ -244,35 +264,70 @@ _secret_attributes_validate (const SecretSchema *schema,
end = NULL; end = NULL;
g_ascii_strtoll (value, &end, 10); g_ascii_strtoll (value, &end, 10);
if (!end || end[0] != '\0') { if (!end || end[0] != '\0') {
g_warning ("%s: invalid %s integer value for %s schema: %s", g_set_error (error,
pretty_function, key, schema->name, value); SECRET_ERROR,
SECRET_ERROR_WRONG_TYPE,
"Attribute %s could not be parsed into an integer",
key);
return FALSE; return FALSE;
} }
break; break;
case SECRET_SCHEMA_ATTRIBUTE_STRING: case SECRET_SCHEMA_ATTRIBUTE_STRING:
if (!g_utf8_validate (value, -1, NULL)) { if (!g_utf8_validate (value, -1, NULL)) {
g_warning ("%s: invalid %s string value for %s schema: %s", g_set_error (error,
pretty_function, key, schema->name, value); SECRET_ERROR,
SECRET_ERROR_WRONG_TYPE,
"Attribute %s could not be parsed into a string",
key);
return FALSE; return FALSE;
} }
break; break;
default: default:
g_warning ("%s: invalid %s value type in %s schema", g_set_error (error,
pretty_function, key, schema->name); SECRET_ERROR,
SECRET_ERROR_WRONG_TYPE,
"%s: Invalid attribute type",
key);
return FALSE; return FALSE;
} }
} }
/* Nothing to match on, resulting search would match everything :S */ /* Nothing to match on, resulting search would match everything :S */
if (matching && !any && schema->flags & SECRET_SCHEMA_DONT_MATCH_NAME) { if (!any && schema->flags & SECRET_SCHEMA_DONT_MATCH_NAME) {
g_warning ("%s: must specify at least one attribute to match", g_set_error_literal (error,
pretty_function); SECRET_ERROR,
SECRET_ERROR_EMPTY_TABLE,
"Must have at least one attribute to check");
return FALSE; return FALSE;
} }
return TRUE; return TRUE;
} }
// Private function to be used internally
gboolean
_secret_attributes_validate (const SecretSchema *schema,
GHashTable *attributes,
const char *pretty_function,
gboolean matching)
{
GError *error = NULL;
if (!secret_attributes_validate (schema, attributes, &error)) {
// if matching is false, an empty table is fine
if ((!matching) && (error->code == SECRET_ERROR_EMPTY_TABLE)) {
g_error_free (error);
return TRUE;
}
g_warning ("%s: error validating schema: %s", pretty_function, error->message);
g_error_free (error);
return FALSE;
}
return TRUE;
}
GHashTable * GHashTable *
_secret_attributes_copy (GHashTable *attributes) _secret_attributes_copy (GHashTable *attributes)
{ {

View File

@ -32,6 +32,10 @@ GHashTable * secret_attributes_build (const SecretSchema *schema
GHashTable * secret_attributes_buildv (const SecretSchema *schema, GHashTable * secret_attributes_buildv (const SecretSchema *schema,
va_list va); va_list va);
gboolean secret_attributes_validate (const SecretSchema *schema,
GHashTable *attributes,
GError **error);
G_END_DECLS G_END_DECLS

View File

@ -33,6 +33,10 @@ typedef enum {
SECRET_ERROR_NO_SUCH_OBJECT = 3, SECRET_ERROR_NO_SUCH_OBJECT = 3,
SECRET_ERROR_ALREADY_EXISTS = 4, SECRET_ERROR_ALREADY_EXISTS = 4,
SECRET_ERROR_INVALID_FILE_FORMAT = 5, SECRET_ERROR_INVALID_FILE_FORMAT = 5,
SECRET_ERROR_MISMATCHED_SCHEMA = 6,
SECRET_ERROR_NO_MATCHING_ATTRIBUTE = 7,
SECRET_ERROR_WRONG_TYPE = 8,
SECRET_ERROR_EMPTY_TABLE = 9,
} SecretError; } SecretError;
#define SECRET_COLLECTION_DEFAULT "default" #define SECRET_COLLECTION_DEFAULT "default"

View File

@ -29,6 +29,14 @@
* Service * Service
* @SECRET_ERROR_ALREADY_EXISTS: a relevant item or collection already exists * @SECRET_ERROR_ALREADY_EXISTS: a relevant item or collection already exists
* @SECRET_ERROR_INVALID_FILE_FORMAT: the file format is not valid * @SECRET_ERROR_INVALID_FILE_FORMAT: the file format is not valid
* @SECRET_ERROR_MISMATCHED_SCHEMA: the xdg:schema attribute of the table does
* not match the schema name
* @SECRET_ERROR_NO_MATCHING_ATTRIBUTE: attribute contained in table not found
* in corresponding schema
* @SECRET_ERROR_WRONG_TYPE: attribute could not be parsed according to its type
* reported in the table's schema
* @SECRET_ERROR_EMPTY_TABLE: attribute list passed to secret_attributes_validate
* has no elements to validate
* *
* Errors returned by the Secret Service. * Errors returned by the Secret Service.
* *

View File

@ -29,7 +29,7 @@
static const SecretSchema MOCK_SCHEMA = { static const SecretSchema MOCK_SCHEMA = {
"org.mock.Schema", "org.mock.Schema",
SECRET_SCHEMA_NONE, SECRET_SCHEMA_DONT_MATCH_NAME,
{ {
{ "number", SECRET_SCHEMA_ATTRIBUTE_INTEGER }, { "number", SECRET_SCHEMA_ATTRIBUTE_INTEGER },
{ "string", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "string", SECRET_SCHEMA_ATTRIBUTE_STRING },
@ -152,7 +152,41 @@ test_validate_schema (void)
} }
static void static void
test_validate_schema_bad (void) test_validate_schema_empty_ok (void)
{
GHashTable *attributes;
gboolean ret;
attributes = g_hash_table_new (g_str_hash, g_str_equal);
ret = _secret_attributes_validate (&MOCK_SCHEMA, attributes, G_STRFUNC, FALSE);
g_assert_true (ret);
g_hash_table_unref (attributes);
}
static void
test_validate_schema_bad_empty_not_ok (void)
{
GHashTable *attributes;
gboolean ret;
if (g_test_subprocess ()) {
attributes = g_hash_table_new (g_str_hash, g_str_equal);
ret = _secret_attributes_validate (&MOCK_SCHEMA, attributes, G_STRFUNC, TRUE);
g_assert_false (ret);
g_hash_table_unref (attributes);
return;
}
g_test_trap_subprocess ("/attributes/validate-schema-bad-empty-not-ok", 0, G_TEST_SUBPROCESS_INHERIT_STDOUT);
g_test_trap_assert_failed ();
}
static void
test_validate_schema_bad_mismatched_schema (void)
{ {
GHashTable *attributes; GHashTable *attributes;
gboolean ret; gboolean ret;
@ -170,7 +204,57 @@ test_validate_schema_bad (void)
return; return;
} }
g_test_trap_subprocess ("/attributes/validate-schema-bad", 0, G_TEST_SUBPROCESS_INHERIT_STDOUT); g_test_trap_subprocess ("/attributes/validate-schema-bad-mismatched-schema", 0, G_TEST_SUBPROCESS_INHERIT_STDOUT);
g_test_trap_assert_failed ();
}
static void
test_validate_schema_bad_wrong_type (void)
{
GHashTable *attributes;
gboolean ret;
char non_utf8_string[] = {(char) 128, '\0'};
if (g_test_subprocess ()) {
attributes = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_replace (attributes, "number", "string_in_wrong_place");
g_hash_table_replace (attributes, "string", non_utf8_string);
g_hash_table_replace (attributes, "even", "neither_true_nor_false");
g_hash_table_replace (attributes, "xdg:schema", "org.mock.Schema");
ret = _secret_attributes_validate (&MOCK_SCHEMA, attributes, G_STRFUNC, TRUE);
g_assert_false (ret);
g_hash_table_unref (attributes);
return;
}
g_test_trap_subprocess ("/attributes/validate-schema-bad-wrong-type", 0, G_TEST_SUBPROCESS_INHERIT_STDOUT);
g_test_trap_assert_failed ();
}
static void
test_validate_schema_bad_fake_key (void)
{
GHashTable *attributes;
gboolean ret;
if (g_test_subprocess ()) {
attributes = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_replace (attributes, "number", "1");
g_hash_table_replace (attributes, "string", "test");
g_hash_table_replace (attributes, "xdg:schema", "org.mock.Schema");
g_hash_table_replace (attributes, "made_up_key", "not_valid");
ret = _secret_attributes_validate (&MOCK_SCHEMA, attributes, G_STRFUNC, TRUE);
g_assert_false (ret);
g_hash_table_unref (attributes);
return;
}
g_test_trap_subprocess ("/attributes/validate-schema-bad-fake-key", 0, G_TEST_SUBPROCESS_INHERIT_STDOUT);
g_test_trap_assert_failed ();
} }
static void static void
@ -203,7 +287,11 @@ main (int argc, char **argv)
g_test_add_func ("/attributes/build-bad-type", test_build_bad_type); g_test_add_func ("/attributes/build-bad-type", test_build_bad_type);
g_test_add_func ("/attributes/validate-schema", test_validate_schema); g_test_add_func ("/attributes/validate-schema", test_validate_schema);
g_test_add_func ("/attributes/validate-schema-bad", test_validate_schema_bad); g_test_add_func ("/attributes/validate-schema-empty-ok", test_validate_schema_empty_ok);
g_test_add_func ("/attributes/validate-schema-bad-empty-not-ok", test_validate_schema_bad_empty_not_ok);
g_test_add_func ("/attributes/validate-schema-bad-mismatched-schema", test_validate_schema_bad_mismatched_schema);
g_test_add_func ("/attributes/validate-schema-bad-wrong-type", test_validate_schema_bad_wrong_type);
g_test_add_func ("/attributes/validate-schema-bad-fake-key", test_validate_schema_bad_fake_key);
g_test_add_func ("/attributes/validate-libgnomekeyring", test_validate_libgnomekeyring); g_test_add_func ("/attributes/validate-libgnomekeyring", test_validate_libgnomekeyring);
return g_test_run (); return g_test_run ();

View File

@ -12,6 +12,20 @@
Secret.Schema schema; Secret.Schema schema;
Secret.Schema no_name_schema; Secret.Schema no_name_schema;
private void test_attributes_validate () {
try {
var attributes = new GLib.HashTable<string,string> (GLib.str_hash, GLib.str_equal);
attributes["even"] = "false";
attributes["string"] = "one";
attributes["number"] = "1";
bool valid = Secret.attributes_validate (schema, attributes);
GLib.assert (valid = true);
} catch ( GLib.Error e ) {
GLib.error (e.message);
}
}
private void test_lookup_sync () { private void test_lookup_sync () {
try { try {
string? password = Secret.password_lookup_sync (schema, null, "even", false, "string", "one", "number", 1); string? password = Secret.password_lookup_sync (schema, null, "even", false, "string", "one", "number", 1);
@ -166,6 +180,7 @@ private static int main (string[] args) {
"string", Secret.SchemaAttributeType.STRING); "string", Secret.SchemaAttributeType.STRING);
} }
GLib.Test.add_data_func ("/vala/attributes/validate", test_attributes_validate);
GLib.Test.add_data_func ("/vala/lookup/sync", test_lookup_sync); GLib.Test.add_data_func ("/vala/lookup/sync", test_lookup_sync);
GLib.Test.add_data_func ("/vala/lookup/async", test_lookup_async); GLib.Test.add_data_func ("/vala/lookup/async", test_lookup_async);
GLib.Test.add_data_func ("/vala/lookup/no-name", test_lookup_no_name); GLib.Test.add_data_func ("/vala/lookup/no-name", test_lookup_no_name);