mirror of
https://gitlab.gnome.org/GNOME/libsecret.git
synced 2024-12-22 04:38:55 +00:00
Testing item stuff, and fixing bugs
This commit is contained in:
parent
dea9a3a043
commit
d797ef2ba3
@ -38,23 +38,29 @@
|
||||
|
||||
static const char HEXC[] = "0123456789ABCDEF";
|
||||
|
||||
static gchar*
|
||||
hex_dump (const guchar *data, gsize n_data)
|
||||
gchar *
|
||||
egg_test_escape_data (const guchar *data,
|
||||
gsize n_data)
|
||||
{
|
||||
GString *result;
|
||||
gchar c;
|
||||
gsize i;
|
||||
guchar j;
|
||||
|
||||
g_assert (data);
|
||||
g_assert (data != NULL);
|
||||
|
||||
result = g_string_sized_new (n_data * 2 + 1);
|
||||
for (i = 0; i < n_data; ++i) {
|
||||
g_string_append (result, "\\x");
|
||||
|
||||
j = data[i] >> 4 & 0xf;
|
||||
g_string_append_c (result, HEXC[j]);
|
||||
j = data[i] & 0xf;
|
||||
g_string_append_c (result, HEXC[j]);
|
||||
c = data[i];
|
||||
if (g_ascii_isprint (c) && !strchr ("\n\r\v", c)) {
|
||||
g_string_append_c (result, c);
|
||||
} else {
|
||||
g_string_append (result, "\\x");
|
||||
j = c >> 4 & 0xf;
|
||||
g_string_append_c (result, HEXC[j]);
|
||||
j = c & 0xf;
|
||||
g_string_append_c (result, HEXC[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return g_string_free (result, FALSE);
|
||||
@ -109,8 +115,8 @@ egg_assertion_message_cmpmem (const char *domain,
|
||||
gsize n_arg2)
|
||||
{
|
||||
char *a1, *a2, *s;
|
||||
a1 = arg1 ? hex_dump (arg1, n_arg1) : g_strdup ("NULL");
|
||||
a2 = arg2 ? hex_dump (arg2, n_arg2) : g_strdup ("NULL");
|
||||
a1 = arg1 ? egg_test_escape_data (arg1, n_arg1) : g_strdup ("NULL");
|
||||
a2 = arg2 ? egg_test_escape_data (arg2, n_arg2) : g_strdup ("NULL");
|
||||
s = g_strdup_printf ("assertion failed (%s): (%s %s %s)", expr, a1, cmp, a2);
|
||||
g_free (a1);
|
||||
g_free (a2);
|
||||
|
@ -53,6 +53,9 @@ void egg_assertion_message_cmpmem (const char *domain, const char *
|
||||
gsize n_arg1, const char *cmp,
|
||||
gconstpointer arg2, gsize n_arg2);
|
||||
|
||||
gchar * egg_test_escape_data (const guchar *data,
|
||||
gsize size);
|
||||
|
||||
void egg_test_wait_stop (void);
|
||||
|
||||
#define egg_test_wait() g_assert (egg_test_wait_until (20000) != FALSE)
|
||||
|
@ -962,6 +962,7 @@ gsecret_item_set_secret (GSecretItem *self,
|
||||
user_data, gsecret_item_set_secret);
|
||||
closure = g_slice_new0 (SetClosure);
|
||||
closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
|
||||
closure->value = gsecret_value_ref (value);
|
||||
g_simple_async_result_set_op_res_gpointer (res, closure, set_closure_free);
|
||||
|
||||
gsecret_service_ensure_session (self->pv->service, cancellable,
|
||||
|
@ -119,6 +119,8 @@
|
||||
|
||||
<property name="Modified" type="t" access="read"/>
|
||||
|
||||
<property name="Schema" type="s" access="read"/>
|
||||
|
||||
<method name="Delete">
|
||||
<arg name="Prompt" type="o" direction="out"/>
|
||||
</method>
|
||||
|
@ -179,11 +179,13 @@ class SecretSession(dbus.service.Object):
|
||||
|
||||
class SecretItem(dbus.service.Object):
|
||||
def __init__(self, collection, identifier, label="Item", attributes={ },
|
||||
secret="", confirm=False, content_type="text/plain"):
|
||||
secret="", confirm=False, content_type="text/plain",
|
||||
schema="org.freedesktop.Secret.Generic"):
|
||||
self.collection = collection
|
||||
self.identifier = identifier
|
||||
self.label = label
|
||||
self.label = label or "Unnamed item"
|
||||
self.secret = secret
|
||||
self.schema = schema
|
||||
self.attributes = attributes
|
||||
self.content_type = content_type
|
||||
self.path = "%s/%s" % (collection.path, identifier)
|
||||
@ -219,6 +221,15 @@ class SecretItem(dbus.service.Object):
|
||||
raise IsLocked("secret is locked: %s" % self.path)
|
||||
return session.encode_secret(self.secret, self.content_type)
|
||||
|
||||
@dbus.service.method('org.freedesktop.Secret.Item', sender_keyword='sender', byte_arrays=True)
|
||||
def SetSecret(self, secret, sender=None):
|
||||
session = objects.get(secret[0], None)
|
||||
if not session or session.sender != sender:
|
||||
raise InvalidArgs("session invalid: %s" % secret[0])
|
||||
if self.get_locked():
|
||||
raise IsLocked("secret is locked: %s" % self.path)
|
||||
(self.secret, self.content_type) = session.decode_secret(secret)
|
||||
|
||||
@dbus.service.method('org.freedesktop.Secret.Item', sender_keyword='sender')
|
||||
def Delete(self, sender=None):
|
||||
item = self
|
||||
@ -242,10 +253,11 @@ class SecretItem(dbus.service.Object):
|
||||
if interface_name == 'org.freedesktop.Secret.Item':
|
||||
return {
|
||||
'Locked': self.get_locked(),
|
||||
'Attributes': dbus.Dictionary(self.attributes, signature='ss'),
|
||||
'Attributes': dbus.Dictionary(self.attributes, signature='ss', variant_level=1),
|
||||
'Label': self.label,
|
||||
'Created': dbus.UInt64(self.created),
|
||||
'Modified': dbus.UInt64(self.modified)
|
||||
'Modified': dbus.UInt64(self.modified),
|
||||
'Schema': self.schema
|
||||
}
|
||||
else:
|
||||
raise InvalidArgs('Unknown %s interface' % interface_name)
|
||||
@ -271,7 +283,7 @@ class SecretCollection(dbus.service.Object):
|
||||
def __init__(self, service, identifier, label="Collection", locked=False, confirm=False):
|
||||
self.service = service
|
||||
self.identifier = identifier
|
||||
self.label = label
|
||||
self.label = label or "Unnamed collection"
|
||||
self.locked = locked
|
||||
self.items = { }
|
||||
self.confirm = confirm
|
||||
@ -357,7 +369,7 @@ class SecretCollection(dbus.service.Object):
|
||||
'Label': self.label,
|
||||
'Created': dbus.UInt64(self.created),
|
||||
'Modified': dbus.UInt64(self.modified),
|
||||
'Items': dbus.Array([dbus.ObjectPath(i.path) for i in self.items.values()], signature='o')
|
||||
'Items': dbus.Array([dbus.ObjectPath(i.path) for i in self.items.values()], signature='o', variant_level=1)
|
||||
}
|
||||
else:
|
||||
raise InvalidArgs('Unknown %s interface' % interface_name)
|
||||
@ -405,7 +417,9 @@ class SecretService(dbus.service.Object):
|
||||
|
||||
def add_standard_objects(self):
|
||||
collection = SecretCollection(self, "english", label="Collection One", locked=False)
|
||||
SecretItem(collection, "item_one", label="Item One", attributes={ "number": "1", "string": "one", "even": "false" }, secret="111")
|
||||
SecretItem(collection, "item_one", label="Item One",
|
||||
attributes={ "number": "1", "string": "one", "even": "false" },
|
||||
secret="111", schema="org.mock.schema.Store")
|
||||
SecretItem(collection, "item_two", attributes={ "number": "2", "string": "two", "even": "true" }, secret="222")
|
||||
SecretItem(collection, "item_three", attributes={ "number": "3", "string": "three", "even": "false" }, secret="3333")
|
||||
|
||||
@ -489,7 +503,7 @@ class SecretService(dbus.service.Object):
|
||||
|
||||
@dbus.service.method('org.freedesktop.Secret.Service', sender_keyword='sender')
|
||||
def CreateCollection(self, properties, alias, sender=None):
|
||||
label = properties.get("org.freedesktop.Secret.Item.Label", None)
|
||||
label = properties.get("org.freedesktop.Secret.Collection.Label", None)
|
||||
service = self
|
||||
def prompt_callback():
|
||||
collection = SecretCollection(service, next_identifier('c'), label, locked=False, confirm=True)
|
||||
@ -530,7 +544,7 @@ class SecretService(dbus.service.Object):
|
||||
def GetAll(self, interface_name):
|
||||
if interface_name == 'org.freedesktop.Secret.Service':
|
||||
return {
|
||||
'Collections': dbus.Array([dbus.ObjectPath(c.path) for c in self.collections.values()], signature='o')
|
||||
'Collections': dbus.Array([dbus.ObjectPath(c.path) for c in self.collections.values()], signature='o', variant_level=1)
|
||||
}
|
||||
else:
|
||||
raise InvalidArgs('Unknown %s interface' % interface_name)
|
||||
|
@ -97,19 +97,6 @@ test_new_sync (Test *test,
|
||||
egg_assert_not_object (collection);
|
||||
}
|
||||
|
||||
static void
|
||||
test_new_sync_noexist (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
const gchar *collection_path = "/org/freedesktop/secrets/collection/nonexistant";
|
||||
GError *error = NULL;
|
||||
GSecretCollection *collection;
|
||||
|
||||
collection = gsecret_collection_new_sync (test->service, collection_path, NULL, &error);
|
||||
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
|
||||
g_assert (collection == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_new_async (Test *test,
|
||||
gconstpointer unused)
|
||||
@ -134,6 +121,19 @@ test_new_async (Test *test,
|
||||
egg_assert_not_object (collection);
|
||||
}
|
||||
|
||||
static void
|
||||
test_new_sync_noexist (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
const gchar *collection_path = "/org/freedesktop/secrets/collection/nonexistant";
|
||||
GError *error = NULL;
|
||||
GSecretCollection *collection;
|
||||
|
||||
collection = gsecret_collection_new_sync (test->service, collection_path, NULL, &error);
|
||||
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
|
||||
g_assert (collection == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_new_async_noexist (Test *test,
|
||||
gconstpointer unused)
|
||||
@ -154,6 +154,50 @@ test_new_async_noexist (Test *test,
|
||||
g_object_unref (result);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_create_sync (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GSecretCollection *collection;
|
||||
|
||||
collection = gsecret_collection_create_sync (test->service, "Train", NULL, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_assert (g_str_has_prefix (g_dbus_proxy_get_object_path (G_DBUS_PROXY (collection)), "/org/freedesktop/secrets/collection"));
|
||||
g_assert_cmpstr (gsecret_collection_get_label (collection), ==, "Train");
|
||||
g_assert (gsecret_collection_get_locked (collection) == FALSE);
|
||||
|
||||
g_object_unref (collection);
|
||||
egg_assert_not_object (collection);
|
||||
}
|
||||
|
||||
static void
|
||||
test_create_async (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GSecretCollection *collection;
|
||||
GAsyncResult *result = NULL;
|
||||
|
||||
gsecret_collection_create (test->service, "Train", NULL, NULL, on_async_result, &result);
|
||||
g_assert (result == NULL);
|
||||
|
||||
egg_test_wait ();
|
||||
|
||||
collection = gsecret_collection_create_finish (result, &error);
|
||||
g_assert_no_error (error);
|
||||
g_object_unref (result);
|
||||
|
||||
g_assert (g_str_has_prefix (g_dbus_proxy_get_object_path (G_DBUS_PROXY (collection)), "/org/freedesktop/secrets/collection"));
|
||||
g_assert_cmpstr (gsecret_collection_get_label (collection), ==, "Train");
|
||||
g_assert (gsecret_collection_get_locked (collection) == FALSE);
|
||||
|
||||
g_object_unref (collection);
|
||||
egg_assert_not_object (collection);
|
||||
}
|
||||
|
||||
static void
|
||||
test_properties (Test *test,
|
||||
gconstpointer unused)
|
||||
@ -466,9 +510,11 @@ main (int argc, char **argv)
|
||||
g_type_init ();
|
||||
|
||||
g_test_add ("/collection/new-sync", Test, "mock-service-normal.py", setup, test_new_sync, teardown);
|
||||
g_test_add ("/collection/new-sync-noexist", Test, "mock-service-normal.py", setup, test_new_sync_noexist, teardown);
|
||||
g_test_add ("/collection/new-async", Test, "mock-service-normal.py", setup, test_new_async, teardown);
|
||||
g_test_add ("/collection/new-sync-noexist", Test, "mock-service-normal.py", setup, test_new_sync_noexist, teardown);
|
||||
g_test_add ("/collection/new-async-noexist", Test, "mock-service-normal.py", setup, test_new_async_noexist, teardown);
|
||||
g_test_add ("/collection/create-sync", Test, "mock-service-normal.py", setup, test_create_sync, teardown);
|
||||
g_test_add ("/collection/create-async", Test, "mock-service-normal.py", setup, test_create_async, teardown);
|
||||
g_test_add ("/collection/properties", Test, "mock-service-normal.py", setup, test_properties, teardown);
|
||||
g_test_add ("/collection/items", Test, "mock-service-normal.py", setup, test_items, teardown);
|
||||
g_test_add ("/collection/items-empty", Test, "mock-service-normal.py", setup, test_items_empty, teardown);
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gsecret-collection.h"
|
||||
#include "gsecret-item.h"
|
||||
#include "gsecret-service.h"
|
||||
#include "gsecret-private.h"
|
||||
@ -152,6 +153,89 @@ test_new_async_noexist (Test *test,
|
||||
g_object_unref (result);
|
||||
}
|
||||
|
||||
static void
|
||||
test_create_sync (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
const gchar *collection_path = "/org/freedesktop/secrets/collection/english";
|
||||
GSecretCollection *collection;
|
||||
GError *error = NULL;
|
||||
GSecretItem *item;
|
||||
GHashTable *attributes;
|
||||
GSecretValue *value;
|
||||
|
||||
collection = gsecret_collection_new_sync (test->service, collection_path, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
attributes = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
g_hash_table_insert (attributes, "even", "true");
|
||||
g_hash_table_insert (attributes, "string", "ten");
|
||||
g_hash_table_insert (attributes, "number", "10");
|
||||
|
||||
value = gsecret_value_new ("Hoohah", -1, "text/plain");
|
||||
|
||||
item = gsecret_item_create_sync (collection, "org.mock.Schema", "Tunnel",
|
||||
attributes, value, FALSE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_hash_table_unref (attributes);
|
||||
g_object_unref (collection);
|
||||
gsecret_value_unref (value);
|
||||
|
||||
g_assert (g_str_has_prefix (g_dbus_proxy_get_object_path (G_DBUS_PROXY (item)), collection_path));
|
||||
g_assert_cmpstr (gsecret_item_get_label (item), ==, "Tunnel");
|
||||
g_assert (gsecret_item_get_locked (item) == FALSE);
|
||||
g_assert_cmpstr (gsecret_item_get_schema (item), ==, "org.freedesktop.Secret.Generic");
|
||||
|
||||
g_object_unref (item);
|
||||
egg_assert_not_object (item);
|
||||
}
|
||||
|
||||
static void
|
||||
test_create_async (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
const gchar *collection_path = "/org/freedesktop/secrets/collection/english";
|
||||
GSecretCollection *collection;
|
||||
GAsyncResult *result = NULL;
|
||||
GError *error = NULL;
|
||||
GSecretItem *item;
|
||||
GHashTable *attributes;
|
||||
GSecretValue *value;
|
||||
|
||||
collection = gsecret_collection_new_sync (test->service, collection_path, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
attributes = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
g_hash_table_insert (attributes, "even", "true");
|
||||
g_hash_table_insert (attributes, "string", "ten");
|
||||
g_hash_table_insert (attributes, "number", "10");
|
||||
|
||||
value = gsecret_value_new ("Hoohah", -1, "text/plain");
|
||||
|
||||
gsecret_item_create (collection, "org.mock.Schema", "Tunnel",
|
||||
attributes, value, FALSE, NULL, on_async_result, &result);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_hash_table_unref (attributes);
|
||||
g_object_unref (collection);
|
||||
gsecret_value_unref (value);
|
||||
|
||||
egg_test_wait ();
|
||||
|
||||
item = gsecret_item_create_finish (result, &error);
|
||||
g_assert_no_error (error);
|
||||
g_object_unref (result);
|
||||
|
||||
g_assert (g_str_has_prefix (g_dbus_proxy_get_object_path (G_DBUS_PROXY (item)), collection_path));
|
||||
g_assert_cmpstr (gsecret_item_get_label (item), ==, "Tunnel");
|
||||
g_assert (gsecret_item_get_locked (item) == FALSE);
|
||||
g_assert_cmpstr (gsecret_item_get_schema (item), ==, "org.freedesktop.Secret.Generic");
|
||||
|
||||
g_object_unref (item);
|
||||
egg_assert_not_object (item);
|
||||
}
|
||||
|
||||
static void
|
||||
test_properties (Test *test,
|
||||
gconstpointer unused)
|
||||
@ -164,6 +248,7 @@ test_properties (Test *test,
|
||||
guint64 created;
|
||||
guint64 modified;
|
||||
gboolean locked;
|
||||
gchar *schema;
|
||||
gchar *label;
|
||||
|
||||
item = gsecret_item_new_sync (test->service, item_path, NULL, &error);
|
||||
@ -173,6 +258,10 @@ test_properties (Test *test,
|
||||
g_assert_cmpuint (gsecret_item_get_created (item), <=, time (NULL));
|
||||
g_assert_cmpuint (gsecret_item_get_modified (item), <=, time (NULL));
|
||||
|
||||
schema = gsecret_item_get_schema (item);
|
||||
g_assert_cmpstr (schema, ==, "org.mock.schema.Store");
|
||||
g_free (schema);
|
||||
|
||||
label = gsecret_item_get_label (item);
|
||||
g_assert_cmpstr (label, ==, "Item One");
|
||||
g_free (label);
|
||||
@ -189,6 +278,7 @@ test_properties (Test *test,
|
||||
"created", &created,
|
||||
"modified", &modified,
|
||||
"label", &label,
|
||||
"schema", &schema,
|
||||
"attributes", &attributes,
|
||||
"service", &service,
|
||||
NULL);
|
||||
@ -200,6 +290,9 @@ test_properties (Test *test,
|
||||
g_assert_cmpstr (label, ==, "Item One");
|
||||
g_free (label);
|
||||
|
||||
g_assert_cmpstr (schema, ==, "org.mock.schema.Store");
|
||||
g_free (schema);
|
||||
|
||||
g_assert_cmpstr (g_hash_table_lookup (attributes, "string"), ==, "one");
|
||||
g_assert_cmpstr (g_hash_table_lookup (attributes, "number"), ==, "1");
|
||||
g_assert_cmpstr (g_hash_table_lookup (attributes, "even"), ==, "false");
|
||||
@ -484,6 +577,41 @@ test_get_secret_async (Test *test,
|
||||
g_object_unref (item);
|
||||
}
|
||||
|
||||
static void
|
||||
test_set_secret_sync (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
const gchar *item_path = "/org/freedesktop/secrets/collection/english/item_one";
|
||||
GError *error = NULL;
|
||||
GSecretItem *item;
|
||||
gconstpointer data;
|
||||
GSecretValue *value;
|
||||
gsize length;
|
||||
gboolean ret;
|
||||
|
||||
value = gsecret_value_new ("Sinking", -1, "strange/content-type");
|
||||
|
||||
item = gsecret_item_new_sync (test->service, item_path, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
ret = gsecret_item_set_secret_sync (item, value, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret == TRUE);
|
||||
|
||||
gsecret_value_unref (value);
|
||||
|
||||
value = gsecret_item_get_secret_sync (item, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (value != NULL);
|
||||
|
||||
data = gsecret_value_get (value, &length);
|
||||
egg_assert_cmpmem (data, length, ==, "Sinking", 7);
|
||||
g_assert_cmpstr (gsecret_value_get_content_type (value), ==, "strange/content-type");
|
||||
|
||||
gsecret_value_unref (value);
|
||||
g_object_unref (item);
|
||||
}
|
||||
|
||||
static void
|
||||
test_delete_sync (Test *test,
|
||||
gconstpointer unused)
|
||||
@ -544,9 +672,11 @@ main (int argc, char **argv)
|
||||
g_type_init ();
|
||||
|
||||
g_test_add ("/item/new-sync", Test, "mock-service-normal.py", setup, test_new_sync, teardown);
|
||||
g_test_add ("/item/new-sync-noexist", Test, "mock-service-normal.py", setup, test_new_sync_noexist, teardown);
|
||||
g_test_add ("/item/new-async", Test, "mock-service-normal.py", setup, test_new_async, teardown);
|
||||
g_test_add ("/item/new-sync-noexist", Test, "mock-service-normal.py", setup, test_new_sync_noexist, teardown);
|
||||
g_test_add ("/item/new-async-noexist", Test, "mock-service-normal.py", setup, test_new_async_noexist, teardown);
|
||||
g_test_add ("/item/create-sync", Test, "mock-service-normal.py", setup, test_create_sync, teardown);
|
||||
g_test_add ("/item/create-async", Test, "mock-service-normal.py", setup, test_create_async, teardown);
|
||||
g_test_add ("/item/properties", Test, "mock-service-normal.py", setup, test_properties, teardown);
|
||||
g_test_add ("/item/set-label-sync", Test, "mock-service-normal.py", setup, test_set_label_sync, teardown);
|
||||
g_test_add ("/item/set-label-async", Test, "mock-service-normal.py", setup, test_set_label_async, teardown);
|
||||
@ -556,6 +686,7 @@ main (int argc, char **argv)
|
||||
g_test_add ("/item/set-attributes-prop", Test, "mock-service-normal.py", setup, test_set_attributes_prop, teardown);
|
||||
g_test_add ("/item/get-secret-sync", Test, "mock-service-normal.py", setup, test_get_secret_sync, teardown);
|
||||
g_test_add ("/item/get-secret-async", Test, "mock-service-normal.py", setup, test_get_secret_async, teardown);
|
||||
g_test_add ("/item/set-secret-sync", Test, "mock-service-normal.py", setup, test_set_secret_sync, teardown);
|
||||
g_test_add ("/item/delete-sync", Test, "mock-service-normal.py", setup, test_delete_sync, teardown);
|
||||
g_test_add ("/item/delete-async", Test, "mock-service-normal.py", setup, test_delete_async, teardown);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user