mirror of
https://gitlab.gnome.org/GNOME/libsecret.git
synced 2024-12-22 04:38:55 +00:00
Implement gsecret_service_get_secrets_for_paths() and friends
* Lots of testing, fine tuning and other bits too.
This commit is contained in:
parent
09a9d856d2
commit
17fade3173
7
.gitignore
vendored
7
.gitignore
vendored
@ -31,14 +31,13 @@ stamp*
|
||||
|
||||
/build/coverage*
|
||||
/build/m4
|
||||
/build/valgrind-built.supp
|
||||
/build/valgrind-suppressions
|
||||
|
||||
/po/POTFILES
|
||||
/po/gsecret.pot
|
||||
|
||||
/egg/tests/test-dh
|
||||
/egg/tests/test-hkdf
|
||||
/egg/tests/test-secmem
|
||||
/egg/tests/test-*
|
||||
!/egg/tests/test-*.c
|
||||
|
||||
/library/gsecret-dbus-generated.[ch]
|
||||
/library/tests/test-*
|
||||
|
14
Makefile.am
14
Makefile.am
@ -28,17 +28,3 @@ check-memory:
|
||||
test -d $(builddir)/$$subdir/tests && \
|
||||
make -C $(builddir)/$$subdir/tests check-memory; \
|
||||
done
|
||||
|
||||
if WITH_COVERAGE
|
||||
coverage:
|
||||
mkdir -p $(builddir)/build/coverage
|
||||
$(LCOV) --directory . --capture --output-file $(builddir)/build/coverage.info
|
||||
$(GENHTML) --output-directory $(builddir)/build/coverage $(builddir)/build/coverage.info
|
||||
$(LCOV) --directory . --zerocounters
|
||||
@echo "file://$(abs_top_builddir)/build/coverage/index.html"
|
||||
|
||||
clear-coverage:
|
||||
$(LCOV) --directory . --zerocounters
|
||||
|
||||
.PHONY: coverage
|
||||
endif
|
||||
|
@ -1,11 +1,28 @@
|
||||
NULL =
|
||||
|
||||
perform-memcheck: $(TEST_PROGS) $(top_builddir)/build/valgrind-built.supp
|
||||
TEST_SUPPRESSIONS = $(top_builddir)/build/valgrind-suppressions
|
||||
|
||||
perform-memcheck: $(TEST_PROGS) $(TEST_SUPPRESSIONS)
|
||||
@for test in $(TEST_PROGS); do \
|
||||
G_SLICE=always-malloc libtool --mode=execute \
|
||||
valgrind --trace-children=no --gen-suppressions=all \
|
||||
--suppressions=$(top_builddir)/build/valgrind-built.supp \
|
||||
--suppressions=$(TEST_SUPPRESSIONS) \
|
||||
--leak-check=full --show-reachable=yes --num-callers=16 \
|
||||
--quiet --error-exitcode=33 \
|
||||
$(builddir)/$$test; \
|
||||
done
|
||||
|
||||
if WITH_COVERAGE
|
||||
coverage:
|
||||
mkdir -p $(top_builddir)/build/coverage
|
||||
$(LCOV) --directory . --capture --output-file $(top_builddir)/build/coverage.info
|
||||
$(GENHTML) --output-directory $(top_builddir)/build/coverage $(top_builddir)/build/coverage.info
|
||||
$(LCOV) --directory . --zerocounters
|
||||
@echo "file://$(abs_top_builddir)/build/coverage/index.html"
|
||||
|
||||
clear-coverage:
|
||||
$(LCOV) --directory . --zerocounters
|
||||
|
||||
.PHONY: coverage
|
||||
endif
|
||||
|
@ -9,11 +9,11 @@ SUPPRESSIONS = \
|
||||
pthread.supp \
|
||||
unknown.supp
|
||||
|
||||
valgrind-built.supp: $(SUPPRESSIONS)
|
||||
valgrind-suppressions: $(SUPPRESSIONS)
|
||||
$(AM_V_GEN) cat $(SUPPRESSIONS) > $@
|
||||
|
||||
EXTRA_DIST = \
|
||||
$(VALGRIND_CONTRIB) \
|
||||
$(SUPPRESSIONS)
|
||||
|
||||
all-local: valgrind-built.supp
|
||||
all-local: valgrind-suppressions
|
@ -238,6 +238,15 @@
|
||||
...
|
||||
fun:g_test_run_suite
|
||||
}
|
||||
{
|
||||
g_test_run_suite__timer_new2
|
||||
Memcheck:Leak
|
||||
...
|
||||
fun:g_timer_new
|
||||
fun:test_case_run_suite_internal
|
||||
...
|
||||
fun:g_test_run_suite
|
||||
}
|
||||
{
|
||||
g_test_run_suite__strconcat
|
||||
Memcheck:Leak
|
||||
@ -301,3 +310,10 @@
|
||||
...
|
||||
fun:g_static_mutex_get_mutex_impl
|
||||
}
|
||||
{
|
||||
g_variant_type_info_unref
|
||||
Memcheck:Leak
|
||||
...
|
||||
fun:g_hash_table_remove
|
||||
fun:g_variant_type_info_unref
|
||||
}
|
||||
|
@ -344,3 +344,12 @@
|
||||
...
|
||||
fun:gdbus_shared_thread_func
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
...
|
||||
fun:g_main_context_add_poll_unlocked
|
||||
...
|
||||
fun:g_main_loop_run
|
||||
fun:gdbus_shared_thread_func
|
||||
}
|
||||
|
@ -19,7 +19,11 @@ ENCRYPTION_SRCS =
|
||||
endif
|
||||
|
||||
libegg_la_SOURCES = \
|
||||
egg-hex.c egg-hex.h \
|
||||
egg-secure-memory.c egg-secure-memory.h \
|
||||
egg-testing.c egg-testing.h \
|
||||
$(ENCRYPTION_SRCS) \
|
||||
$(BUILT_SOURCES)
|
||||
$(BUILT_SOURCES)
|
||||
|
||||
check-memory:
|
||||
make -C tests check-memory
|
150
egg/egg-hex.c
Normal file
150
egg/egg-hex.c
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* gnome-keyring
|
||||
*
|
||||
* Copyright (C) 2008 Stefan Walter
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General License as
|
||||
* published by the Free Software Foundation; either version 2.1 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General
|
||||
* License along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "egg-hex.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static const char HEXC_UPPER[] = "0123456789ABCDEF";
|
||||
static const char HEXC_LOWER[] = "0123456789abcdef";
|
||||
|
||||
gpointer
|
||||
egg_hex_decode (const gchar *data, gssize n_data, gsize *n_decoded)
|
||||
{
|
||||
return egg_hex_decode_full (data, n_data, 0, 1, n_decoded);
|
||||
}
|
||||
|
||||
gpointer
|
||||
egg_hex_decode_full (const gchar *data, gssize n_data,
|
||||
gchar delim, guint group, gsize *n_decoded)
|
||||
{
|
||||
guchar *result;
|
||||
guchar *decoded;
|
||||
gushort j;
|
||||
gint state = 0;
|
||||
gint part = 0;
|
||||
const gchar* pos;
|
||||
|
||||
g_return_val_if_fail (data || !n_data, NULL);
|
||||
g_return_val_if_fail (n_decoded, NULL);
|
||||
g_return_val_if_fail (group >= 1, NULL);
|
||||
|
||||
if (n_data == -1)
|
||||
n_data = strlen (data);
|
||||
|
||||
decoded = result = g_malloc0 ((n_data / 2) + 1);
|
||||
*n_decoded = 0;
|
||||
|
||||
while (n_data > 0 && state == 0) {
|
||||
|
||||
if (decoded != result && delim) {
|
||||
if (*data != delim) {
|
||||
state = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
++data;
|
||||
--n_data;
|
||||
}
|
||||
|
||||
while (part < group && n_data > 0) {
|
||||
|
||||
/* Find the position */
|
||||
pos = strchr (HEXC_UPPER, g_ascii_toupper (*data));
|
||||
if (pos == 0) {
|
||||
if (n_data > 0)
|
||||
state = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
j = pos - HEXC_UPPER;
|
||||
if(!state) {
|
||||
*decoded = (j & 0xf) << 4;
|
||||
state = 1;
|
||||
} else {
|
||||
*decoded |= (j & 0xf);
|
||||
(*n_decoded)++;
|
||||
decoded++;
|
||||
state = 0;
|
||||
part++;
|
||||
}
|
||||
|
||||
++data;
|
||||
--n_data;
|
||||
}
|
||||
|
||||
part = 0;
|
||||
}
|
||||
|
||||
/* Parsing error */
|
||||
if (state != 0) {
|
||||
g_free (result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
gchar*
|
||||
egg_hex_encode (gconstpointer data, gsize n_data)
|
||||
{
|
||||
return egg_hex_encode_full (data, n_data, TRUE, '\0', 0);
|
||||
}
|
||||
|
||||
gchar*
|
||||
egg_hex_encode_full (gconstpointer data, gsize n_data,
|
||||
gboolean upper_case, gchar delim, guint group)
|
||||
{
|
||||
GString *result;
|
||||
const gchar *input;
|
||||
const char *hexc;
|
||||
gsize bytes;
|
||||
guchar j;
|
||||
|
||||
g_return_val_if_fail (data || !n_data, NULL);
|
||||
|
||||
input = data;
|
||||
hexc = upper_case ? HEXC_UPPER : HEXC_LOWER;
|
||||
|
||||
result = g_string_sized_new (n_data * 2 + 1);
|
||||
bytes = 0;
|
||||
|
||||
while (n_data > 0) {
|
||||
|
||||
if (group && bytes && (bytes % group) == 0)
|
||||
g_string_append_c (result, delim);
|
||||
|
||||
j = *(input) >> 4 & 0xf;
|
||||
g_string_append_c (result, hexc[j]);
|
||||
|
||||
j = *(input++) & 0xf;
|
||||
g_string_append_c (result, hexc[j]);
|
||||
|
||||
++bytes;
|
||||
--n_data;
|
||||
}
|
||||
|
||||
/* Make sure still null terminated */
|
||||
return g_string_free (result, FALSE);
|
||||
}
|
||||
|
46
egg/egg-hex.h
Normal file
46
egg/egg-hex.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* gnome-keyring
|
||||
*
|
||||
* Copyright (C) 2008 Stefan Walter
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General License as
|
||||
* published by the Free Software Foundation; either version 2.1 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General
|
||||
* License along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef EGG_HEX_H_
|
||||
#define EGG_HEX_H_
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
gpointer egg_hex_decode (const gchar *data,
|
||||
gssize n_data,
|
||||
gsize *n_decoded);
|
||||
|
||||
gpointer egg_hex_decode_full (const gchar *data,
|
||||
gssize n_data,
|
||||
gchar delim,
|
||||
guint group,
|
||||
gsize *n_decoded);
|
||||
|
||||
gchar* egg_hex_encode (gconstpointer data,
|
||||
gsize n_data);
|
||||
|
||||
gchar* egg_hex_encode_full (gconstpointer data,
|
||||
gsize n_data,
|
||||
gboolean upper_case,
|
||||
gchar delim,
|
||||
guint group);
|
||||
|
||||
#endif /* EGG_HEX_H_ */
|
@ -138,7 +138,6 @@ on_loop_wait_timeout (gpointer data)
|
||||
static gboolean
|
||||
loop_wait_until (int timeout)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
gboolean timed_out = FALSE;
|
||||
guint source;
|
||||
|
||||
@ -149,16 +148,10 @@ loop_wait_until (int timeout)
|
||||
|
||||
g_main_loop_run (wait_loop);
|
||||
|
||||
if (timed_out) {
|
||||
g_source_remove (source);
|
||||
ret = FALSE;
|
||||
} else {
|
||||
ret = TRUE;
|
||||
}
|
||||
|
||||
g_source_remove (source);
|
||||
g_main_loop_unref (wait_loop);
|
||||
wait_loop = NULL;
|
||||
return ret;
|
||||
return !timed_out;
|
||||
}
|
||||
|
||||
gint
|
||||
|
@ -12,6 +12,7 @@ LDADD = \
|
||||
$(GLIB_LIBS)
|
||||
|
||||
TEST_PROGS = \
|
||||
test-hex \
|
||||
test-secmem
|
||||
|
||||
if WITH_GCRYPT
|
||||
|
121
egg/tests/test-hex.c
Normal file
121
egg/tests/test-hex.c
Normal file
@ -0,0 +1,121 @@
|
||||
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
|
||||
/* unit-test-util.c: Test gck-util.c
|
||||
|
||||
Copyright (C) 2007 Stefan Walter
|
||||
|
||||
The Gnome Keyring Library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
The Gnome Keyring Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with the Gnome Library; see the file COPYING.LIB. If not,
|
||||
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
Author: Stef Walter <stef@memberwebs.com>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "egg/egg-hex.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static const guchar TEST_DATA[] = { 0x05, 0xD6, 0x95, 0x96, 0x10, 0x12, 0xAE, 0x35 };
|
||||
static const gchar *TEST_HEX = "05D695961012AE35";
|
||||
static const gchar *TEST_HEX_DELIM = "05 D6 95 96 10 12 AE 35";
|
||||
|
||||
static void
|
||||
test_encode (void)
|
||||
{
|
||||
gchar *hex;
|
||||
|
||||
hex = egg_hex_encode (TEST_DATA, sizeof (TEST_DATA));
|
||||
g_assert (hex);
|
||||
g_assert_cmpstr (hex, ==, TEST_HEX);
|
||||
g_free (hex);
|
||||
}
|
||||
|
||||
static void
|
||||
test_encode_spaces (void)
|
||||
{
|
||||
gchar *hex;
|
||||
|
||||
/* Encode without spaces */
|
||||
hex = egg_hex_encode_full (TEST_DATA, sizeof (TEST_DATA), TRUE, 0, 0);
|
||||
g_assert (hex);
|
||||
g_assert_cmpstr (hex, ==, TEST_HEX);
|
||||
g_free (hex);
|
||||
|
||||
/* Encode with spaces */
|
||||
hex = egg_hex_encode_full (TEST_DATA, sizeof (TEST_DATA), TRUE, ' ', 1);
|
||||
g_assert (hex);
|
||||
g_assert_cmpstr (hex, ==, TEST_HEX_DELIM);
|
||||
g_free (hex);
|
||||
}
|
||||
|
||||
static void
|
||||
test_decode (void)
|
||||
{
|
||||
guchar *data;
|
||||
gsize n_data;
|
||||
|
||||
data = egg_hex_decode (TEST_HEX, -1, &n_data);
|
||||
g_assert (data);
|
||||
g_assert (n_data == sizeof (TEST_DATA));
|
||||
g_assert (memcmp (data, TEST_DATA, n_data) == 0);
|
||||
g_free (data);
|
||||
|
||||
/* Nothing in, empty out */
|
||||
data = egg_hex_decode ("AB", 0, &n_data);
|
||||
g_assert (data);
|
||||
g_assert (n_data == 0);
|
||||
g_free (data);
|
||||
|
||||
/* Delimited*/
|
||||
data = egg_hex_decode_full (TEST_HEX_DELIM, -1, ' ', 1, &n_data);
|
||||
g_assert (data);
|
||||
g_assert (n_data == sizeof (TEST_DATA));
|
||||
g_assert (memcmp (data, TEST_DATA, n_data) == 0);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
test_decode_fail (void)
|
||||
{
|
||||
guchar *data;
|
||||
gsize n_data;
|
||||
|
||||
/* Invalid input, null out */
|
||||
data = egg_hex_decode ("AB", 1, &n_data);
|
||||
g_assert (!data);
|
||||
|
||||
/* Bad characters, null out */
|
||||
data = egg_hex_decode ("ABXX", -1, &n_data);
|
||||
g_assert (!data);
|
||||
|
||||
/* Not Delimited, null out*/
|
||||
data = egg_hex_decode_full ("ABABAB", -1, ':', 1, &n_data);
|
||||
g_assert (!data);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/hex/encode", test_encode);
|
||||
g_test_add_func ("/hex/encode_spaces", test_encode_spaces);
|
||||
g_test_add_func ("/hex/decode", test_decode);
|
||||
g_test_add_func ("/hex/decode_fail", test_decode_fail);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
@ -40,3 +40,6 @@ gsecret-dbus-generated.c: $(DBUS_XML_DEFINITIONS) Makefile.am
|
||||
$(AM_V_GEN) sed -i -e 's/gsecret_gen_/_gsecret_gen_/g' gsecret-dbus-generated.[ch]
|
||||
|
||||
gsecret-dbus-generated.h: gsecret-dbus-generated.c
|
||||
|
||||
check-memory:
|
||||
make -C tests check-memory
|
@ -20,6 +20,12 @@
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct {
|
||||
GVariant *in;
|
||||
GVariant *out;
|
||||
GCancellable *cancellable;
|
||||
} GSecretParams;
|
||||
|
||||
#define GSECRET_SERVICE_PATH "/org/freedesktop/secrets"
|
||||
|
||||
#define GSECRET_SERVICE_BUS_NAME "org.freedesktop.Secret.Service"
|
||||
@ -28,6 +34,11 @@ G_BEGIN_DECLS
|
||||
|
||||
#define GSECRET_COLLECTION_INTERFACE "org.freedesktop.Secret.Collection"
|
||||
|
||||
GSecretParams * _gsecret_params_new (GCancellable *cancellable,
|
||||
GVariant *in);
|
||||
|
||||
void _gsecret_params_free (gpointer data);
|
||||
|
||||
gchar * _gsecret_util_parent_path (const gchar *path);
|
||||
|
||||
GVariant * _gsecret_util_variant_for_attributes (GHashTable *attributes);
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "egg/egg-libgcrypt.h"
|
||||
#endif
|
||||
|
||||
#include "egg/egg-hex.h"
|
||||
#include "egg/egg-secure-memory.h"
|
||||
|
||||
#include <glib.h>
|
||||
@ -209,9 +210,18 @@ request_open_session_aes (GSecretSession *session)
|
||||
g_assert (session->publi == NULL);
|
||||
|
||||
/* Initialize our local parameters and values */
|
||||
if (!egg_dh_default_params ("ietf-ike-grp-modp-1024",
|
||||
if (!egg_dh_default_params ("ietf-ike-grp-modp-1536",
|
||||
&session->prime, &base))
|
||||
g_return_val_if_reached (NULL);
|
||||
g_return_val_if_reached (NULL);
|
||||
|
||||
#if 0
|
||||
g_printerr ("\n lib prime: ");
|
||||
gcry_mpi_dump (session->prime);
|
||||
g_printerr ("\n lib base: ");
|
||||
gcry_mpi_dump (base);
|
||||
g_printerr ("\n");
|
||||
#endif
|
||||
|
||||
if (!egg_dh_gen_pair (session->prime, base, 0,
|
||||
&session->publi, &session->privat))
|
||||
g_return_val_if_reached (NULL);
|
||||
@ -255,9 +265,21 @@ response_open_session_aes (GSecretSession *session,
|
||||
g_return_val_if_fail (gcry == 0, FALSE);
|
||||
g_variant_unref (argument);
|
||||
|
||||
#if 0
|
||||
g_printerr (" lib publi: ");
|
||||
gcry_mpi_dump (session->publi);
|
||||
g_printerr ("\n lib peer: ");
|
||||
gcry_mpi_dump (peer);
|
||||
g_printerr ("\n");
|
||||
#endif
|
||||
|
||||
ikm = egg_dh_gen_secret (peer, session->privat, session->prime, &n_ikm);
|
||||
gcry_mpi_release (peer);
|
||||
|
||||
#if 0
|
||||
g_printerr (" lib ikm: %s\n", egg_hex_encode (ikm, n_ikm));
|
||||
#endif
|
||||
|
||||
if (ikm == NULL) {
|
||||
g_warning ("couldn't negotiate a valid AES session key");
|
||||
g_free (session->path);
|
||||
@ -633,9 +655,17 @@ service_decode_aes_secret (GSecretSession *session,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if 0
|
||||
g_printerr (" lib iv: %s\n", egg_hex_encode (param, n_param));
|
||||
#endif
|
||||
|
||||
gcry = gcry_cipher_setiv (cih, param, n_param);
|
||||
g_return_val_if_fail (gcry == 0, NULL);
|
||||
|
||||
#if 0
|
||||
g_printerr (" lib key: %s\n", egg_hex_encode (session->key, session->n_key));
|
||||
#endif
|
||||
|
||||
gcry = gcry_cipher_setkey (cih, session->key, session->n_key);
|
||||
g_return_val_if_fail (gcry == 0, NULL);
|
||||
|
||||
@ -656,7 +686,7 @@ service_decode_aes_secret (GSecretSession *session,
|
||||
if (!pkcs7_unpad_bytes_in_place (padded, &n_padded)) {
|
||||
egg_secure_clear (padded, n_padded);
|
||||
egg_secure_free (padded);
|
||||
g_message ("received an invalid, unencryptable, or non-utf8 secret");
|
||||
g_message ("received an invalid or unencryptable secret");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@ -900,11 +930,11 @@ on_search_items_complete (GObject *source,
|
||||
}
|
||||
|
||||
void
|
||||
gsecret_service_search_paths (GSecretService *self,
|
||||
GHashTable *attributes,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
gsecret_service_search_for_paths (GSecretService *self,
|
||||
GHashTable *attributes,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSimpleAsyncResult *res;
|
||||
|
||||
@ -913,7 +943,7 @@ gsecret_service_search_paths (GSecretService *self,
|
||||
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
|
||||
gsecret_service_search_paths);
|
||||
gsecret_service_search_for_paths);
|
||||
|
||||
g_dbus_proxy_call (G_DBUS_PROXY (self), "SearchItems",
|
||||
g_variant_new ("(@a{ss})",
|
||||
@ -925,18 +955,18 @@ gsecret_service_search_paths (GSecretService *self,
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsecret_service_search_paths_finish (GSecretService *self,
|
||||
GAsyncResult *result,
|
||||
gchar ***unlocked,
|
||||
gchar ***locked,
|
||||
GError **error)
|
||||
gsecret_service_search_for_paths_finish (GSecretService *self,
|
||||
GAsyncResult *result,
|
||||
gchar ***unlocked,
|
||||
gchar ***locked,
|
||||
GError **error)
|
||||
{
|
||||
GVariant *response;
|
||||
GSimpleAsyncResult *res;
|
||||
gchar **dummy = NULL;
|
||||
|
||||
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
|
||||
gsecret_service_search_paths), FALSE);
|
||||
gsecret_service_search_for_paths), FALSE);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||
|
||||
res = G_SIMPLE_ASYNC_RESULT (result);
|
||||
@ -957,12 +987,12 @@ gsecret_service_search_paths_finish (GSecretService *self,
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsecret_service_search_paths_sync (GSecretService *self,
|
||||
GHashTable *attributes,
|
||||
GCancellable *cancellable,
|
||||
gchar ***unlocked,
|
||||
gchar ***locked,
|
||||
GError **error)
|
||||
gsecret_service_search_for_paths_sync (GSecretService *self,
|
||||
GHashTable *attributes,
|
||||
GCancellable *cancellable,
|
||||
gchar ***unlocked,
|
||||
gchar ***locked,
|
||||
GError **error)
|
||||
{
|
||||
gchar **dummy = NULL;
|
||||
GVariant *response;
|
||||
@ -993,3 +1023,251 @@ gsecret_service_search_paths_sync (GSecretService *self,
|
||||
|
||||
return response != NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
on_get_secrets_complete (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
|
||||
GSecretParams *params = g_simple_async_result_get_op_res_gpointer (res);
|
||||
GError *error = NULL;
|
||||
|
||||
params->out = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error);
|
||||
if (error != NULL)
|
||||
g_simple_async_result_take_error (res, error);
|
||||
g_simple_async_result_complete (res);
|
||||
|
||||
g_object_unref (res);
|
||||
}
|
||||
|
||||
static void
|
||||
on_get_secrets_session (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
|
||||
GSecretParams *params = g_simple_async_result_get_op_res_gpointer (res);
|
||||
GError *error = NULL;
|
||||
const gchar *session;
|
||||
|
||||
session = gsecret_service_ensure_session_finish (GSECRET_SERVICE (source),
|
||||
result, &error);
|
||||
if (error != NULL) {
|
||||
g_simple_async_result_take_error (res, error);
|
||||
g_simple_async_result_complete (res);
|
||||
} else {
|
||||
g_dbus_proxy_call (G_DBUS_PROXY (source), "GetSecrets",
|
||||
g_variant_new ("(@aoo)", params->in, session),
|
||||
G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
|
||||
params->cancellable, on_get_secrets_complete,
|
||||
g_object_ref (res));
|
||||
}
|
||||
|
||||
g_object_unref (res);
|
||||
}
|
||||
|
||||
void
|
||||
gsecret_service_get_secret_for_path (GSecretService *self,
|
||||
const gchar *object_path,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSimpleAsyncResult *res;
|
||||
GSecretParams *params;
|
||||
|
||||
g_return_if_fail (GSECRET_IS_SERVICE (self));
|
||||
g_return_if_fail (object_path != NULL);
|
||||
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
|
||||
gsecret_service_get_secret_for_path);
|
||||
|
||||
params = _gsecret_params_new (cancellable, g_variant_new_objv (&object_path, 1));
|
||||
g_simple_async_result_set_op_res_gpointer (res, params, _gsecret_params_free);
|
||||
|
||||
gsecret_service_ensure_session (self, cancellable,
|
||||
on_get_secrets_session,
|
||||
g_object_ref (res));
|
||||
|
||||
g_object_unref (res);
|
||||
}
|
||||
|
||||
static GSecretValue *
|
||||
service_decode_get_secrets_first (GSecretService *self,
|
||||
GVariant *out)
|
||||
{
|
||||
GVariantIter *iter;
|
||||
GVariant *variant;
|
||||
GSecretValue *value;
|
||||
const gchar *path;
|
||||
|
||||
g_variant_get (out, "(a{o(oayays)})", &iter);
|
||||
while (g_variant_iter_next (iter, "{&o@(oayays)}", &path, &variant)) {
|
||||
value = _gsecret_service_decode_secret (self, variant);
|
||||
g_variant_unref (variant);
|
||||
break;
|
||||
}
|
||||
g_variant_iter_free (iter);
|
||||
return value;
|
||||
}
|
||||
|
||||
static GHashTable *
|
||||
service_decode_get_secrets_all (GSecretService *self,
|
||||
GVariant *out)
|
||||
{
|
||||
GVariantIter *iter;
|
||||
GVariant *variant;
|
||||
GHashTable *values;
|
||||
GSecretValue *value;
|
||||
gchar *path;
|
||||
|
||||
values = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free, gsecret_value_unref);
|
||||
g_variant_get (out, "(a{o(oayays)})", &iter);
|
||||
while (g_variant_iter_loop (iter, "{o@(oayays)}", &path, &variant)) {
|
||||
value = _gsecret_service_decode_secret (self, variant);
|
||||
if (value && path)
|
||||
g_hash_table_insert (values, g_strdup (path), value);
|
||||
}
|
||||
g_variant_iter_free (iter);
|
||||
return values;
|
||||
}
|
||||
|
||||
GSecretValue *
|
||||
gsecret_service_get_secret_for_path_finish (GSecretService *self,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
GSimpleAsyncResult *res;
|
||||
GSecretParams *params;
|
||||
|
||||
g_return_val_if_fail (GSECRET_IS_SERVICE (self), NULL);
|
||||
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
|
||||
gsecret_service_get_secret_for_path), NULL);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||||
|
||||
res = G_SIMPLE_ASYNC_RESULT (result);
|
||||
if (g_simple_async_result_propagate_error (res, error))
|
||||
return NULL;
|
||||
|
||||
params = g_simple_async_result_get_op_res_gpointer (res);
|
||||
return service_decode_get_secrets_first (self, params->out);
|
||||
}
|
||||
|
||||
GSecretValue *
|
||||
gsecret_service_get_secret_for_path_sync (GSecretService *self,
|
||||
const gchar *object_path,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *session;
|
||||
GSecretValue *value;
|
||||
GVariant *out;
|
||||
|
||||
g_return_val_if_fail (GSECRET_IS_SERVICE (self), NULL);
|
||||
g_return_val_if_fail (object_path != NULL, NULL);
|
||||
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||||
|
||||
session = gsecret_service_ensure_session_sync (self, cancellable, error);
|
||||
if (!session)
|
||||
return NULL;
|
||||
|
||||
out = g_dbus_proxy_call_sync (G_DBUS_PROXY (self), "GetSecrets",
|
||||
g_variant_new ("(@aoo)",
|
||||
g_variant_new_objv (&object_path, 1),
|
||||
session),
|
||||
G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
|
||||
cancellable, error);
|
||||
if (!out)
|
||||
return NULL;
|
||||
|
||||
value = service_decode_get_secrets_first (self, out);
|
||||
g_variant_unref (out);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void
|
||||
gsecret_service_get_secrets_for_paths (GSecretService *self,
|
||||
const gchar **object_paths,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSimpleAsyncResult *res;
|
||||
GSecretParams *params;
|
||||
|
||||
g_return_if_fail (GSECRET_IS_SERVICE (self));
|
||||
g_return_if_fail (object_paths != NULL);
|
||||
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
|
||||
gsecret_service_get_secret_for_path);
|
||||
|
||||
params = _gsecret_params_new (cancellable, g_variant_new_objv (object_paths, -1));
|
||||
g_simple_async_result_set_op_res_gpointer (res, params, _gsecret_params_free);
|
||||
|
||||
gsecret_service_ensure_session (self, cancellable,
|
||||
on_get_secrets_session,
|
||||
g_object_ref (res));
|
||||
|
||||
g_object_unref (res);
|
||||
}
|
||||
|
||||
GHashTable *
|
||||
gsecret_service_get_secrets_for_paths_finish (GSecretService *self,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
GSimpleAsyncResult *res;
|
||||
GSecretParams *params;
|
||||
|
||||
g_return_val_if_fail (GSECRET_IS_SERVICE (self), NULL);
|
||||
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
|
||||
gsecret_service_get_secret_for_path), NULL);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||||
|
||||
res = G_SIMPLE_ASYNC_RESULT (result);
|
||||
if (g_simple_async_result_propagate_error (res, error))
|
||||
return NULL;
|
||||
|
||||
params = g_simple_async_result_get_op_res_gpointer (res);
|
||||
return service_decode_get_secrets_all (self, params->out);
|
||||
}
|
||||
|
||||
GHashTable *
|
||||
gsecret_service_get_secrets_for_paths_sync (GSecretService *self,
|
||||
const gchar **object_paths,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *session;
|
||||
GHashTable *values;
|
||||
GVariant *out;
|
||||
|
||||
g_return_val_if_fail (GSECRET_IS_SERVICE (self), NULL);
|
||||
g_return_val_if_fail (object_paths != NULL, NULL);
|
||||
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||||
|
||||
session = gsecret_service_ensure_session_sync (self, cancellable, error);
|
||||
if (!session)
|
||||
return NULL;
|
||||
|
||||
out = g_dbus_proxy_call_sync (G_DBUS_PROXY (self), "GetSecrets",
|
||||
g_variant_new ("(@aoo)",
|
||||
g_variant_new_objv (object_paths, -1),
|
||||
session),
|
||||
G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
|
||||
cancellable, error);
|
||||
if (!out)
|
||||
return NULL;
|
||||
|
||||
values = service_decode_get_secrets_all (self, out);
|
||||
g_variant_unref (out);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "gsecret-value.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSECRET_TYPE_SERVICE (gsecret_service_get_type ())
|
||||
@ -110,24 +112,54 @@ gboolean gsecret_service_search_sync (GSecretService *se
|
||||
GError **error);
|
||||
#endif
|
||||
|
||||
void gsecret_service_search_paths (GSecretService *self,
|
||||
GHashTable *attributes,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
void gsecret_service_search_for_paths (GSecretService *self,
|
||||
GHashTable *attributes,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
gboolean gsecret_service_search_paths_finish (GSecretService *self,
|
||||
GAsyncResult *result,
|
||||
gchar ***unlocked,
|
||||
gchar ***locked,
|
||||
GError **error);
|
||||
gboolean gsecret_service_search_for_paths_finish (GSecretService *self,
|
||||
GAsyncResult *result,
|
||||
gchar ***unlocked,
|
||||
gchar ***locked,
|
||||
GError **error);
|
||||
|
||||
gboolean gsecret_service_search_paths_sync (GSecretService *self,
|
||||
GHashTable *attributes,
|
||||
GCancellable *cancellable,
|
||||
gchar ***unlocked,
|
||||
gchar ***locked,
|
||||
GError **error);
|
||||
gboolean gsecret_service_search_for_paths_sync (GSecretService *self,
|
||||
GHashTable *attributes,
|
||||
GCancellable *cancellable,
|
||||
gchar ***unlocked,
|
||||
gchar ***locked,
|
||||
GError **error);
|
||||
|
||||
void gsecret_service_get_secret_for_path (GSecretService *self,
|
||||
const gchar *object_path,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
GSecretValue * gsecret_service_get_secret_for_path_finish (GSecretService *self,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GSecretValue * gsecret_service_get_secret_for_path_sync (GSecretService *self,
|
||||
const gchar *object_path,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
void gsecret_service_get_secrets_for_paths (GSecretService *self,
|
||||
const gchar **object_paths,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
GHashTable * gsecret_service_get_secrets_for_paths_finish (GSecretService *self,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GHashTable * gsecret_service_get_secrets_for_paths_sync (GSecretService *self,
|
||||
const gchar **object_paths,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
#if 0
|
||||
void gsecret_service_lock (GSecretService *self,
|
||||
|
@ -31,6 +31,32 @@ gsecret_error_get_quark (void)
|
||||
return quark;
|
||||
}
|
||||
|
||||
GSecretParams *
|
||||
_gsecret_params_new (GCancellable *cancellable,
|
||||
GVariant *in)
|
||||
{
|
||||
GSecretParams *params;
|
||||
|
||||
params = g_slice_new0 (GSecretParams);
|
||||
params->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
|
||||
params->in = g_variant_ref_sink (in);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
void
|
||||
_gsecret_params_free (gpointer data)
|
||||
{
|
||||
GSecretParams *params = data;
|
||||
|
||||
g_clear_object (¶ms->cancellable);
|
||||
if (params->in)
|
||||
g_variant_unref (params->in);
|
||||
if (params->out)
|
||||
g_variant_unref (params->out);
|
||||
g_slice_free (GSecretParams, params);
|
||||
}
|
||||
|
||||
gchar *
|
||||
_gsecret_util_parent_path (const gchar *path)
|
||||
{
|
||||
|
@ -49,7 +49,7 @@ gsecret_value_new (const gchar *secret, gssize length, const gchar *content_type
|
||||
{
|
||||
gchar *copy;
|
||||
|
||||
g_return_val_if_fail (!secret && length, NULL);
|
||||
g_return_val_if_fail (secret == NULL || length != 0, NULL);
|
||||
g_return_val_if_fail (content_type, NULL);
|
||||
|
||||
if (length < 0)
|
||||
@ -67,14 +67,15 @@ gsecret_value_new_full (gchar *secret, gssize length,
|
||||
{
|
||||
GSecretValue *value;
|
||||
|
||||
g_return_val_if_fail (!secret && length, NULL);
|
||||
g_return_val_if_fail (secret == NULL || length != 0, NULL);
|
||||
g_return_val_if_fail (content_type, NULL);
|
||||
|
||||
if (length < 0)
|
||||
length = strlen (secret);
|
||||
|
||||
value = g_slice_new0 (GSecretValue);
|
||||
value->content_type = strdup (content_type);
|
||||
value->refs = 1;
|
||||
value->content_type = g_strdup (content_type);
|
||||
value->destroy = destroy;
|
||||
value->length = length;
|
||||
value->secret = secret;
|
||||
|
@ -3,15 +3,5 @@
|
||||
import mock
|
||||
|
||||
service = mock.SecretService()
|
||||
|
||||
collection = mock.SecretCollection(service, "collection", locked=False)
|
||||
mock.SecretItem(collection, "item_one", attributes={ "number": "1", "string": "one", "parity": "odd" })
|
||||
mock.SecretItem(collection, "item_two", attributes={ "number": "2", "string": "two", "parity": "even" })
|
||||
mock.SecretItem(collection, "item_three", attributes={ "number": "3", "string": "three", "parity": "odd" })
|
||||
|
||||
collection = mock.SecretCollection(service, "second", locked=True)
|
||||
mock.SecretItem(collection, "item_one", attributes={ "number": "1", "string": "one", "parity": "odd" })
|
||||
mock.SecretItem(collection, "item_two", attributes={ "number": "2", "string": "two", "parity": "even" })
|
||||
mock.SecretItem(collection, "item_three", attributes={ "number": "3", "string": "three", "parity": "odd" })
|
||||
|
||||
service.add_standard_objects()
|
||||
service.listen()
|
@ -3,5 +3,6 @@
|
||||
import mock
|
||||
|
||||
service = mock.SecretService()
|
||||
service.add_standard_objects()
|
||||
service.algorithms = { "plain": mock.PlainAlgorithm() }
|
||||
service.listen()
|
@ -22,14 +22,14 @@
|
||||
import math
|
||||
import random
|
||||
|
||||
PRIME = '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F\xDA\xA2\x21\x68\xC2\x34\xC4' \
|
||||
'\xC6\x62\x8B\x80\xDC\x1C\xD1\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE' \
|
||||
'\xA6\x3B\x13\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD\xEF\x95\x19\xB3\xCD' \
|
||||
'\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1\x35\x6D\x6D\x51\xC2' \
|
||||
'\x45\xE4\x85\xB5\x76\x62\x5E\x7E\xC6\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B' \
|
||||
'\xFF\x5C\xB6\xF4\x06\xB7\xED\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24' \
|
||||
'\x11\x7C\x4B\x1F\xE6\x49\x28\x66\x51\xEC\xE6\x53\x81\xFF\xFF\xFF\xFF\xFF' \
|
||||
'\xFF\xFF\xFF'
|
||||
PRIME = '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F\xDA\xA2\x21\x68\xC2\x34\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1' \
|
||||
'\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE\xA6\x3B\x13\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD' \
|
||||
'\xEF\x95\x19\xB3\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1\x35\x6D\x6D\x51\xC2\x45' \
|
||||
'\xE4\x85\xB5\x76\x62\x5E\x7E\xC6\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B\xFF\x5C\xB6\xF4\x06\xB7\xED' \
|
||||
'\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24\x11\x7C\x4B\x1F\xE6\x49\x28\x66\x51\xEC\xE4\x5B\x3D' \
|
||||
'\xC2\x00\x7C\xB8\xA1\x63\xBF\x05\x98\xDA\x48\x36\x1C\x55\xD3\x9A\x69\x16\x3F\xA8\xFD\x24\xCF\x5F' \
|
||||
'\x83\x65\x5D\x23\xDC\xA3\xAD\x96\x1C\x62\xF3\x56\x20\x85\x52\xBB\x9E\xD5\x29\x07\x70\x96\x96\x6D' \
|
||||
'\x67\x0C\x35\x4E\x4A\xBC\x98\x04\xF1\x74\x6C\x08\xCA\x23\x73\x27\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
|
||||
|
||||
def num_bits(number):
|
||||
if number == 0:
|
||||
@ -67,6 +67,8 @@ def number_to_bytes(number):
|
||||
def generate_pair():
|
||||
prime = bytes_to_number (PRIME)
|
||||
base = 2
|
||||
# print "mock prime: ", hex(prime)
|
||||
# print " mock base: ", hex(base)
|
||||
bits = num_bits(prime)
|
||||
privat = 0
|
||||
while privat == 0:
|
||||
@ -77,4 +79,5 @@ def generate_pair():
|
||||
def derive_key(privat, peer):
|
||||
prime = bytes_to_number (PRIME)
|
||||
key = pow(peer, privat, prime)
|
||||
# print " mock ikm2: ", hex(key)
|
||||
return number_to_bytes(key)
|
||||
|
@ -25,7 +25,10 @@ import dbus.service
|
||||
import dbus.glib
|
||||
import gobject
|
||||
|
||||
COLLECTION_PREFIX = "/org/freedesktop/secrets/collection/"
|
||||
|
||||
bus_name = 'org.freedesktop.Secret.MockService'
|
||||
objects = { }
|
||||
|
||||
class NotSupported(dbus.exceptions.DBusException):
|
||||
def __init__(self, msg):
|
||||
@ -35,38 +38,75 @@ class InvalidArgs(dbus.exceptions.DBusException):
|
||||
def __init__(self, msg):
|
||||
dbus.exceptions.DBusException.__init__(self, msg, name="org.freedesktop.DBus.Error.InvalidArgs")
|
||||
|
||||
class IsLocked(dbus.exceptions.DBusException):
|
||||
def __init__(self, msg):
|
||||
dbus.exceptions.DBusException.__init__(self, msg, name="org.freedesktop.Secret.Error.IsLocked")
|
||||
|
||||
unique_identifier = 0
|
||||
def next_identifier():
|
||||
global unique_identifier
|
||||
unique_identifier += 1
|
||||
return unique_identifier
|
||||
|
||||
def hex_encode(string):
|
||||
return "".join([hex(ord(c))[2:].zfill(2) for c in string])
|
||||
|
||||
|
||||
class PlainAlgorithm():
|
||||
def negotiate(self, service, sender, param):
|
||||
if type (param) != dbus.String:
|
||||
raise InvalidArgs("invalid argument passed to OpenSession")
|
||||
session = SecretSession(service, sender, None)
|
||||
session = SecretSession(service, sender, self, None)
|
||||
return (dbus.String("", variant_level=1), session)
|
||||
|
||||
def encrypt(self, key, data):
|
||||
return ("", data)
|
||||
|
||||
|
||||
class AesAlgorithm():
|
||||
def negotiate(self, service, sender, param):
|
||||
if type (param) != dbus.ByteArray:
|
||||
raise InvalidArgs("invalid argument passed to OpenSession")
|
||||
publi, privat = dh.generate_pair()
|
||||
privat, publi = dh.generate_pair()
|
||||
peer = dh.bytes_to_number(param)
|
||||
# print "mock publi: ", hex(publi)
|
||||
# print " mock peer: ", hex(peer)
|
||||
ikm = dh.derive_key(privat, peer)
|
||||
# print " mock ikm: ", hex_encode(ikm)
|
||||
key = hkdf.hkdf(ikm, 16)
|
||||
session = SecretSession(service, sender, key)
|
||||
# print " mock key: ", hex_encode(key)
|
||||
session = SecretSession(service, sender, self, key)
|
||||
return (dbus.ByteArray(dh.number_to_bytes(publi), variant_level=1), session)
|
||||
|
||||
def encrypt(self, key, data):
|
||||
key = map(ord, key)
|
||||
data = aes.append_PKCS7_padding(data)
|
||||
keysize = len(key)
|
||||
iv = [ord(i) for i in os.urandom(16)]
|
||||
mode = aes.AESModeOfOperation.modeOfOperation["CBC"]
|
||||
moo = aes.AESModeOfOperation()
|
||||
(mode, length, ciph) = moo.encrypt(data, mode, key, keysize, iv)
|
||||
return ("".join([chr(i) for i in iv]),
|
||||
"".join([chr(i) for i in ciph]))
|
||||
|
||||
|
||||
class SecretSession(dbus.service.Object):
|
||||
def __init__(self, service, sender, key):
|
||||
def __init__(self, service, sender, algorithm, key):
|
||||
self.sender = sender
|
||||
self.service = service
|
||||
self.algorithm = algorithm
|
||||
self.key = key
|
||||
self.path = "/org/freedesktop/secrets/sessions/%d" % next_identifier()
|
||||
dbus.service.Object.__init__(self, service.bus_name, self.path)
|
||||
service.add_session(self)
|
||||
objects[self.path] = self
|
||||
|
||||
def encode_secret(self, secret, content_type):
|
||||
(params, data) = self.algorithm.encrypt(self.key, secret)
|
||||
# print " mock iv: ", hex_encode(params)
|
||||
# print " mock ciph: ", hex_encode(data)
|
||||
return dbus.Struct((self.path, dbus.ByteArray(params), dbus.ByteArray(data),
|
||||
dbus.String(content_type)), signature="oayays")
|
||||
|
||||
@dbus.service.method('org.freedesktop.Secret.Session')
|
||||
def Close(self):
|
||||
@ -75,15 +115,19 @@ class SecretSession(dbus.service.Object):
|
||||
|
||||
|
||||
class SecretItem(dbus.service.Object):
|
||||
def __init__(self, collection, identifier, label="Item", attributes={ }, secret=""):
|
||||
def __init__(self, collection, identifier, label="Item", attributes={ },
|
||||
secret="", content_type="text/plain"):
|
||||
self.collection = collection
|
||||
self.identifier = identifier
|
||||
self.label = label
|
||||
self.secret = secret
|
||||
self.attributes = attributes
|
||||
self.path = "/org/freedesktop/secrets/collection/%s/%s" % (collection.identifier, identifier)
|
||||
self.content_type = content_type
|
||||
self.locked = collection.locked
|
||||
self.path = "%s/%s" % (collection.path, identifier)
|
||||
dbus.service.Object.__init__(self, collection.service.bus_name, self.path)
|
||||
collection.items[identifier] = self
|
||||
objects[self.path] = self
|
||||
|
||||
def match_attributes(self, attributes):
|
||||
for (key, value) in attributes.items():
|
||||
@ -91,6 +135,15 @@ class SecretItem(dbus.service.Object):
|
||||
return False
|
||||
return True
|
||||
|
||||
@dbus.service.method('org.freedesktop.Secret.Item', sender_keyword='sender')
|
||||
def GetSecret(self, session_path, sender=None):
|
||||
session = objects.get(session_path, None)
|
||||
if not session or session.sender != sender:
|
||||
raise InvalidArgs("session invalid: %s" % session_path)
|
||||
if self.locked:
|
||||
raise IsLocked("secret is locked: %s" % self.path)
|
||||
return session.encode_secret(self.secret, self.content_type)
|
||||
|
||||
|
||||
class SecretCollection(dbus.service.Object):
|
||||
def __init__(self, service, identifier, label="Collection", locked=False):
|
||||
@ -99,9 +152,10 @@ class SecretCollection(dbus.service.Object):
|
||||
self.label = label
|
||||
self.locked = locked
|
||||
self.items = { }
|
||||
self.path = "/org/freedesktop/secrets/collection/%s" % identifier
|
||||
self.path = "%s%s" % (COLLECTION_PREFIX, identifier)
|
||||
dbus.service.Object.__init__(self, service.bus_name, self.path)
|
||||
service.collections[identifier] = self
|
||||
objects[self.path] = self
|
||||
|
||||
def search_items(self, attributes):
|
||||
results = []
|
||||
@ -136,6 +190,17 @@ class SecretService(dbus.service.Object):
|
||||
'NameOwnerChanged',
|
||||
'org.freedesktop.DBus')
|
||||
|
||||
def add_standard_objects(self):
|
||||
collection = SecretCollection(self, "collection", locked=False)
|
||||
SecretItem(collection, "item_one", attributes={ "number": "1", "string": "one", "parity": "odd" }, secret="uno")
|
||||
SecretItem(collection, "item_two", attributes={ "number": "2", "string": "two", "parity": "even" }, secret="dos")
|
||||
SecretItem(collection, "item_three", attributes={ "number": "3", "string": "three", "parity": "odd" }, secret="tres")
|
||||
|
||||
collection = SecretCollection(self, "second", locked=True)
|
||||
SecretItem(collection, "item_one", attributes={ "number": "1", "string": "one", "parity": "odd" })
|
||||
SecretItem(collection, "item_two", attributes={ "number": "2", "string": "two", "parity": "even" })
|
||||
SecretItem(collection, "item_three", attributes={ "number": "3", "string": "three", "parity": "odd" })
|
||||
|
||||
def listen(self):
|
||||
loop = gobject.MainLoop()
|
||||
loop.run()
|
||||
@ -148,6 +213,13 @@ class SecretService(dbus.service.Object):
|
||||
def remove_session(self, session):
|
||||
self.sessions[session.sender].remove(session)
|
||||
|
||||
def find_item(self, object):
|
||||
if object.startswith(COLLECTION_PREFIX):
|
||||
parts = object[len(COLLECTION_PREFIX):].split("/", 1)
|
||||
if len(parts) == 2 and parts[0] in self.collections:
|
||||
return self.collections[parts[0]].get(parts[1], None)
|
||||
return None
|
||||
|
||||
@dbus.service.method('org.freedesktop.Secret.Service', byte_arrays=True, sender_keyword='sender')
|
||||
def OpenSession(self, algorithm, param, sender=None):
|
||||
assert type(algorithm) == dbus.String
|
||||
@ -170,6 +242,17 @@ class SecretService(dbus.service.Object):
|
||||
unlocked.extend(items)
|
||||
return (dbus.Array(unlocked, "o"), dbus.Array(locked, "o"))
|
||||
|
||||
@dbus.service.method('org.freedesktop.Secret.Service', sender_keyword='sender')
|
||||
def GetSecrets(self, item_paths, session_path, sender=None):
|
||||
session = objects.get(session_path, None)
|
||||
if not session or session.sender != sender:
|
||||
raise InvalidArgs("session invalid: %s" % session_path)
|
||||
results = dbus.Dictionary(signature="o(oayays)")
|
||||
for item_path in item_paths:
|
||||
item = objects.get(item_path, None)
|
||||
if item and not item.locked:
|
||||
results[item_path] = item.GetSecret(session_path, sender)
|
||||
return results
|
||||
|
||||
def parse_options(args):
|
||||
global bus_name
|
||||
|
@ -57,7 +57,7 @@ static void
|
||||
teardown (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
g_clear_object (&test->service);
|
||||
g_object_unref (test->service);
|
||||
egg_assert_not_object (test->service);
|
||||
|
||||
g_clear_object (&test->connection);
|
||||
@ -129,8 +129,8 @@ test_search_paths (Test *test,
|
||||
attributes = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
g_hash_table_insert (attributes, "number", "1");
|
||||
|
||||
ret = gsecret_service_search_paths_sync (test->service, attributes, NULL,
|
||||
&unlocked, &locked, &error);
|
||||
ret = gsecret_service_search_for_paths_sync (test->service, attributes, NULL,
|
||||
&unlocked, &locked, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret == TRUE);
|
||||
|
||||
@ -160,14 +160,14 @@ test_search_paths_async (Test *test,
|
||||
attributes = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
g_hash_table_insert (attributes, "number", "1");
|
||||
|
||||
gsecret_service_search_paths (test->service, attributes, NULL,
|
||||
on_complete_get_result, &result);
|
||||
gsecret_service_search_for_paths (test->service, attributes, NULL,
|
||||
on_complete_get_result, &result);
|
||||
egg_test_wait ();
|
||||
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
ret = gsecret_service_search_paths_finish (test->service, result,
|
||||
&unlocked, &locked,
|
||||
&error);
|
||||
ret = gsecret_service_search_for_paths_finish (test->service, result,
|
||||
&unlocked, &locked,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret == TRUE);
|
||||
|
||||
@ -197,33 +197,33 @@ test_search_paths_nulls (Test *test,
|
||||
attributes = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
g_hash_table_insert (attributes, "number", "1");
|
||||
|
||||
ret = gsecret_service_search_paths_sync (test->service, attributes, NULL,
|
||||
&paths, NULL, &error);
|
||||
ret = gsecret_service_search_for_paths_sync (test->service, attributes, NULL,
|
||||
&paths, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret == TRUE);
|
||||
g_assert (paths != NULL);
|
||||
g_assert_cmpstr (paths[0], ==, "/org/freedesktop/secrets/collection/collection/item_one");
|
||||
g_strfreev (paths);
|
||||
|
||||
ret = gsecret_service_search_paths_sync (test->service, attributes, NULL,
|
||||
NULL, &paths, &error);
|
||||
ret = gsecret_service_search_for_paths_sync (test->service, attributes, NULL,
|
||||
NULL, &paths, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret == TRUE);
|
||||
g_assert (paths != NULL);
|
||||
g_assert_cmpstr (paths[0], ==, "/org/freedesktop/secrets/collection/second/item_one");
|
||||
g_strfreev (paths);
|
||||
|
||||
ret = gsecret_service_search_paths_sync (test->service, attributes, NULL,
|
||||
NULL, NULL, &error);
|
||||
ret = gsecret_service_search_for_paths_sync (test->service, attributes, NULL,
|
||||
NULL, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret == TRUE);
|
||||
|
||||
gsecret_service_search_paths (test->service, attributes, NULL,
|
||||
on_complete_get_result, &result);
|
||||
gsecret_service_search_for_paths (test->service, attributes, NULL,
|
||||
on_complete_get_result, &result);
|
||||
egg_test_wait ();
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
ret = gsecret_service_search_paths_finish (test->service, result,
|
||||
&paths, NULL, &error);
|
||||
ret = gsecret_service_search_for_paths_finish (test->service, result,
|
||||
&paths, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret == TRUE);
|
||||
g_assert (paths != NULL);
|
||||
@ -231,12 +231,12 @@ test_search_paths_nulls (Test *test,
|
||||
g_strfreev (paths);
|
||||
g_clear_object (&result);
|
||||
|
||||
gsecret_service_search_paths (test->service, attributes, NULL,
|
||||
on_complete_get_result, &result);
|
||||
gsecret_service_search_for_paths (test->service, attributes, NULL,
|
||||
on_complete_get_result, &result);
|
||||
egg_test_wait ();
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
ret = gsecret_service_search_paths_finish (test->service, result,
|
||||
NULL, &paths, &error);
|
||||
ret = gsecret_service_search_for_paths_finish (test->service, result,
|
||||
NULL, &paths, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret == TRUE);
|
||||
g_assert (paths != NULL);
|
||||
@ -244,12 +244,12 @@ test_search_paths_nulls (Test *test,
|
||||
g_strfreev (paths);
|
||||
g_clear_object (&result);
|
||||
|
||||
gsecret_service_search_paths (test->service, attributes, NULL,
|
||||
on_complete_get_result, &result);
|
||||
gsecret_service_search_for_paths (test->service, attributes, NULL,
|
||||
on_complete_get_result, &result);
|
||||
egg_test_wait ();
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
ret = gsecret_service_search_paths_finish (test->service, result,
|
||||
NULL, NULL, &error);
|
||||
ret = gsecret_service_search_for_paths_finish (test->service, result,
|
||||
NULL, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret == TRUE);
|
||||
g_clear_object (&result);
|
||||
@ -257,6 +257,154 @@ test_search_paths_nulls (Test *test,
|
||||
g_hash_table_unref (attributes);
|
||||
}
|
||||
|
||||
static void
|
||||
test_secret_for_path (Test *test,
|
||||
gconstpointer used)
|
||||
{
|
||||
GSecretValue *value;
|
||||
GError *error = NULL;
|
||||
const gchar *path;
|
||||
const gchar *password;
|
||||
gsize length;
|
||||
|
||||
path = "/org/freedesktop/secrets/collection/collection/item_one";
|
||||
value = gsecret_service_get_secret_for_path_sync (test->service, path, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (value != NULL);
|
||||
|
||||
password = gsecret_value_get (value, &length);
|
||||
g_assert_cmpuint (length, ==, 3);
|
||||
g_assert_cmpstr (password, ==, "uno");
|
||||
|
||||
password = gsecret_value_get (value, NULL);
|
||||
g_assert_cmpstr (password, ==, "uno");
|
||||
|
||||
gsecret_value_unref (value);
|
||||
}
|
||||
|
||||
static void
|
||||
test_secret_for_path_async (Test *test,
|
||||
gconstpointer used)
|
||||
{
|
||||
GSecretValue *value;
|
||||
GError *error = NULL;
|
||||
const gchar *path;
|
||||
const gchar *password;
|
||||
GAsyncResult *result = NULL;
|
||||
gsize length;
|
||||
|
||||
path = "/org/freedesktop/secrets/collection/collection/item_one";
|
||||
gsecret_service_get_secret_for_path (test->service, path, NULL,
|
||||
on_complete_get_result, &result);
|
||||
g_assert (result == NULL);
|
||||
egg_test_wait ();
|
||||
|
||||
value = gsecret_service_get_secret_for_path_finish (test->service, result, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (value != NULL);
|
||||
g_object_unref (result);
|
||||
|
||||
password = gsecret_value_get (value, &length);
|
||||
g_assert_cmpuint (length, ==, 3);
|
||||
g_assert_cmpstr (password, ==, "uno");
|
||||
|
||||
password = gsecret_value_get (value, NULL);
|
||||
g_assert_cmpstr (password, ==, "uno");
|
||||
|
||||
gsecret_value_unref (value);
|
||||
}
|
||||
|
||||
static void
|
||||
test_secrets_for_paths (Test *test,
|
||||
gconstpointer used)
|
||||
{
|
||||
const gchar *path_item_one = "/org/freedesktop/secrets/collection/collection/item_one";
|
||||
const gchar *path_item_two = "/org/freedesktop/secrets/collection/collection/item_two";
|
||||
const gchar *paths[] = {
|
||||
path_item_one,
|
||||
path_item_two,
|
||||
|
||||
/* This one is locked, and not returned */
|
||||
"/org/freedesktop/secrets/collection/second/item_one",
|
||||
NULL
|
||||
};
|
||||
|
||||
GSecretValue *value;
|
||||
GHashTable *values;
|
||||
GError *error = NULL;
|
||||
const gchar *password;
|
||||
gsize length;
|
||||
|
||||
values = gsecret_service_get_secrets_for_paths_sync (test->service, paths, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_assert (values != NULL);
|
||||
g_assert_cmpuint (g_hash_table_size (values), ==, 2);
|
||||
|
||||
value = g_hash_table_lookup (values, path_item_one);
|
||||
g_assert (value != NULL);
|
||||
password = gsecret_value_get (value, &length);
|
||||
g_assert_cmpuint (length, ==, 3);
|
||||
g_assert_cmpstr (password, ==, "uno");
|
||||
|
||||
value = g_hash_table_lookup (values, path_item_two);
|
||||
g_assert (value != NULL);
|
||||
password = gsecret_value_get (value, &length);
|
||||
g_assert_cmpuint (length, ==, 3);
|
||||
g_assert_cmpstr (password, ==, "dos");
|
||||
|
||||
g_hash_table_unref (values);
|
||||
}
|
||||
|
||||
static void
|
||||
test_secrets_for_paths_async (Test *test,
|
||||
gconstpointer used)
|
||||
{
|
||||
const gchar *path_item_one = "/org/freedesktop/secrets/collection/collection/item_one";
|
||||
const gchar *path_item_two = "/org/freedesktop/secrets/collection/collection/item_two";
|
||||
const gchar *paths[] = {
|
||||
path_item_one,
|
||||
path_item_two,
|
||||
|
||||
/* This one is locked, and not returned */
|
||||
"/org/freedesktop/secrets/collection/second/item_one",
|
||||
NULL
|
||||
};
|
||||
|
||||
GSecretValue *value;
|
||||
GHashTable *values;
|
||||
GError *error = NULL;
|
||||
const gchar *password;
|
||||
GAsyncResult *result = NULL;
|
||||
gsize length;
|
||||
|
||||
gsecret_service_get_secrets_for_paths (test->service, paths, NULL,
|
||||
on_complete_get_result, &result);
|
||||
g_assert (result == NULL);
|
||||
egg_test_wait ();
|
||||
|
||||
values = gsecret_service_get_secrets_for_paths_finish (test->service, result, &error);
|
||||
g_assert_no_error (error);
|
||||
g_object_unref (result);
|
||||
|
||||
g_assert (values != NULL);
|
||||
g_assert_cmpuint (g_hash_table_size (values), ==, 2);
|
||||
|
||||
value = g_hash_table_lookup (values, path_item_one);
|
||||
g_assert (value != NULL);
|
||||
password = gsecret_value_get (value, &length);
|
||||
g_assert_cmpuint (length, ==, 3);
|
||||
g_assert_cmpstr (password, ==, "uno");
|
||||
|
||||
value = g_hash_table_lookup (values, path_item_two);
|
||||
g_assert (value != NULL);
|
||||
password = gsecret_value_get (value, &length);
|
||||
g_assert_cmpuint (length, ==, 3);
|
||||
g_assert_cmpstr (password, ==, "dos");
|
||||
|
||||
g_hash_table_unref (values);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
@ -265,9 +413,14 @@ main (int argc, char **argv)
|
||||
g_type_init ();
|
||||
|
||||
g_test_add_func ("/service/instance", test_instance);
|
||||
g_test_add ("/service/search-paths", Test, "mock-service-normal.py", setup, test_search_paths, teardown);
|
||||
g_test_add ("/service/search-paths-async", Test, "mock-service-normal.py", setup, test_search_paths_async, teardown);
|
||||
g_test_add ("/service/search-paths-nulls", Test, "mock-service-normal.py", setup, test_search_paths_nulls, teardown);
|
||||
g_test_add ("/service/search-for-paths", Test, "mock-service-normal.py", setup, test_search_paths, teardown);
|
||||
g_test_add ("/service/search-for-paths-async", Test, "mock-service-normal.py", setup, test_search_paths_async, teardown);
|
||||
g_test_add ("/service/search-for-paths-nulls", Test, "mock-service-normal.py", setup, test_search_paths_nulls, teardown);
|
||||
g_test_add ("/service/secret-for-path", Test, "mock-service-normal.py", setup, test_secret_for_path, teardown);
|
||||
g_test_add ("/service/secret-for-path-plain", Test, "mock-service-only-plain.py", setup, test_secret_for_path, teardown);
|
||||
g_test_add ("/service/secret-for-path-async", Test, "mock-service-normal.py", setup, test_secret_for_path_async, teardown);
|
||||
g_test_add ("/service/secrets-for-paths", Test, "mock-service-normal.py", setup, test_secrets_for_paths, teardown);
|
||||
g_test_add ("/service/secrets-for-paths-async", Test, "mock-service-normal.py", setup, test_secrets_for_paths_async, teardown);
|
||||
|
||||
return egg_tests_run_with_loop ();
|
||||
}
|
||||
|
@ -60,7 +60,8 @@ teardown (Test *test,
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
g_clear_object (&test->service);
|
||||
g_object_unref (test->service);
|
||||
egg_assert_not_object (test->service);
|
||||
|
||||
g_assert (test->pid);
|
||||
if (kill (test->pid, SIGTERM) < 0)
|
||||
@ -69,7 +70,7 @@ teardown (Test *test,
|
||||
|
||||
g_dbus_connection_flush_sync (test->connection, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_clear_object (&test->connection);
|
||||
g_object_unref (test->connection);
|
||||
}
|
||||
|
||||
static void
|
||||
|
Loading…
Reference in New Issue
Block a user