diff --git a/.gitignore b/.gitignore index 8065388..578fbda 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ *.pc *.tar.gz *.typelib +*.vapi .deps .cproject .libs @@ -39,7 +40,8 @@ stamp* .cproject /build/coverage* -/build/m4 +/build/m4/* +!/build/m4/vapigen.m4 /build/valgrind-suppressions /docs/reference/libsecret/version.xml @@ -67,8 +69,11 @@ stamp* /library/secret-dbus-generated.[ch] /library/secret-enum-types.[ch] /library/tests/test-* +/library/tests/*.metadata !/library/tests/test-*.c !/library/tests/test-*.js !/library/tests/test-*.py +!/library/tests/test-*.vala +/library/tests/test-vala-lang.c /tool/secret-tool diff --git a/build/m4/vapigen.m4 b/build/m4/vapigen.m4 new file mode 100644 index 0000000..17558df --- /dev/null +++ b/build/m4/vapigen.m4 @@ -0,0 +1,96 @@ +dnl vapigen.m4 +dnl +dnl Copyright 2012 Evan Nemerson +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License, or (at your option) any later version. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library; if not, write to the Free Software +dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# VAPIGEN_CHECK([VERSION], [API_VERSION], [FOUND-INTROSPECTION], [DEFAULT]) +# -------------------------------------- +# Check vapigen existence and version +# +# See http://live.gnome.org/Vala/UpstreamGuide for detailed documentation +AC_DEFUN([VAPIGEN_CHECK], +[ + AC_BEFORE([GOBJECT_INTROSPECTION_CHECK],[$0]) + AC_BEFORE([GOBJECT_INTROSPECTION_REQUIRE],[$0]) + + AC_ARG_ENABLE([vala], + [AS_HELP_STRING([--enable-vala[=@<:@no/auto/yes@:>@]],[build Vala bindings @<:@default=]ifelse($4,,auto,$4)[@:>@])],,[ + AS_IF([test "x$4" = "x"], [ + enable_vala=auto + ], [ + enable_vala=$4 + ]) + ]) + + AS_CASE([$enable_vala], [no], [enable_vala=no], + [yes], [ + AS_IF([test "x$3" != "xyes" -a "x$found_introspection" != "xyes"], [ + AC_MSG_ERROR([Vala bindings require GObject Introspection]) + ]) + ], [auto], [ + AS_IF([test "x$3" != "xyes" -a "x$found_introspection" != "xyes"], [ + enable_vala=no + ]) + ], [ + AC_MSG_ERROR([Invalid argument passed to --enable-vala, should be one of @<:@no/auto/yes@:>@]) + ]) + + AS_IF([test "x$2" = "x"], [ + vapigen_pkg_name=vapigen + ], [ + vapigen_pkg_name=vapigen-$2 + ]) + AS_IF([test "x$1" = "x"], [ + vapigen_pkg="$vapigen_pkg_name" + ], [ + vapigen_pkg="$vapigen_pkg_name >= $1" + ]) + + PKG_PROG_PKG_CONFIG + + PKG_CHECK_EXISTS([$vapigen_pkg], [ + AS_IF([test "$enable_vala" = "auto"], [ + enable_vala=yes + ]) + ], [ + AS_CASE([$enable_vala], [yes], [ + AC_MSG_ERROR([$vapigen_pkg not found]) + ], [auto], [ + enable_vala=no + ]) + ]) + + AC_MSG_CHECKING([for vapigen]) + + AS_CASE([$enable_vala], + [yes], [ + VAPIGEN=`$PKG_CONFIG --variable=vapigen vapigen` + VAPIGEN_MAKEFILE=`$PKG_CONFIG --variable=datadir vapigen`/vala/Makefile.vapigen + AS_IF([test "x$2" = "x"], [ + VAPIGEN_VAPIDIR=`$PKG_CONFIG --variable=vapidir vapigen` + ], [ + VAPIGEN_VAPIDIR=`$PKG_CONFIG --variable=vapidir_versioned vapigen` + ]) + ]) + + AC_MSG_RESULT([$enable_vala]) + + AC_SUBST([VAPIGEN]) + AC_SUBST([VAPIGEN_VAPIDIR]) + AC_SUBST([VAPIGEN_MAKEFILE]) + + AM_CONDITIONAL(ENABLE_VAPIGEN, test "x$enable_vala" = "xyes") +]) diff --git a/configure.ac b/configure.ac index 3077c39..26a3388 100644 --- a/configure.ac +++ b/configure.ac @@ -71,6 +71,18 @@ GTK_DOC_CHECK(1.9) GOBJECT_INTROSPECTION_CHECK([1.29]) AC_PATH_PROG(GLIB_MKENUMS, glib-mkenums) +# -------------------------------------------------------------------- +# Vala + +VALA_REQUIRED=0.17.2.12 + +VAPIGEN_CHECK($VALA_REQUIRED) + +if test "$enable_vala" != "no"; then + AC_PATH_PROG([VALAC], [valac], []) +fi +AM_CONDITIONAL(HAVE_VALAC, test "x$VALAC" != "x") + # -------------------------------------------------------------------- # libgcrypt diff --git a/docs/reference/libsecret/libsecret-examples.sgml b/docs/reference/libsecret/libsecret-examples.sgml index f885799..67c3bce 100644 --- a/docs/reference/libsecret/libsecret-examples.sgml +++ b/docs/reference/libsecret/libsecret-examples.sgml @@ -614,4 +614,172 @@ + + Vala examples + +
+ Vala example: Define a password schema + + Each stored password has a set of attributes which are later + used to lookup the password. The names and types of the attributes + are defined in a schema. The schema is usually defined once globally. + Here's how to define a schema: + + + + See the other examples for how + to use the schema. +
+ +
+ Vala example: Store a password + + Here's how to store a password in the running secret service, + like gnome-keyring or ksecretservice. + + Each stored password has a set of attributes which are later + used to lookup the password. The attributes should not contain + secrets, as they are not stored in an encrypted fashion. + + These examples use the example + schema. + + This first example stores a password asynchronously, and is + appropriate for GUI applications so that the UI does not block. + + (); + attributes["number"] = "8"; + attributes["string"] = "eight"; + attributes["even"] = "true"; + + Secret.password_storev.begin (example_schema, attributes, Secret.COLLECTION_DEFAULT, + "The label", "the password", null, (obj, async_res) => { + bool res = Secret.password_store.end (async_res); + /* ... do something now that the password has been stored */ + }); + ]]> + + If you are already inside of an async function, you can also + use the yield keyword: + + (); + attributes["number"] = "8"; + attributes["string"] = "eight"; + attributes["even"] = "true"; + + bool res = yield Secret.password_storev (example_schema, attributes, + Secret.COLLECTION_DEFAULT, "The label", + "the password", null); + ]]> + + If you would like to avoid creating a hash table for the + attributes you can just use the variadic version: + + + + This next example stores a password synchronously. The function + call will block until the password is stored. So this is appropriate for + non GUI applications. + + +
+ +
+ Vala example: Lookup a password + + Here's how to lookup a password in the running secret service, + like gnome-keyring or ksecretservice. + + Each stored password has a set of attributes which are + used to lookup the password. If multiple passwords match the + lookup attributes, then the one stored most recently is returned. + + These examples use the example + schema. + + This first example looks up a password asynchronously, and is + appropriate for GUI applications so that the UI does not block. + + (); + attributes["number"] = "8"; + attributes["string"] = "eight"; + attributes["even"] = "true"; + + Secret.password_lookupv.begin (example_schema, attributes, null, (obj, async_res) => { + string password = Secret.password_lookup.end (async_res); + }); + ]]> + + This next example looks up a password synchronously. The function + call will block until the lookup completes. So this is appropriate for + non GUI applications. + + +
+ +
+ Vala example: Remove a password + + Here's how to remove a password from the running secret service, + like gnome-keyring or ksecretservice. + + Each stored password has a set of attributes which are + used to find which password to remove. If multiple passwords match the + attributes, then the one stored most recently is removed. + + These examples use the example + schema. + + This first example removes a password asynchronously, and is + appropriate for GUI applications so that the UI does not block. + + (); + attributes["number"] = "8"; + attributes["string"] = "eight"; + attributes["even"] = "true"; + + Secret.password_removev.begin (example_schema, attributes, null, (obj, async_res) => { + bool removed = Secret.password_removev.end (async_res); + }); + ]]> + + This next example removes a password synchronously. The function + call will block until the removal completes. So this is appropriate for + non GUI applications. + + (); + attributes["number"] = "8"; + attributes["string"] = "eight"; + attributes["even"] = "true"; + + bool removed = Secret.password_remove_sync (example_schema, null, + "number", 8, "string", "eight", "even", true); + /* removed will be true if the password was removed */ + ]]> +
+ +
+ diff --git a/library/Makefile.am b/library/Makefile.am index c6c27ce..3acea9f 100644 --- a/library/Makefile.am +++ b/library/Makefile.am @@ -94,6 +94,8 @@ secret-enum-types.h: secret-enum-types.h.template $(HEADER_FILES) secret-enum-types.c: secret-enum-types.c.template $(HEADER_FILES) $(AM_V_GEN) $(GLIB_MKENUMS) --template $^ > $@ +CLEANFILES = + # ------------------------------------------------------------------ # INTROSPECTION @@ -121,7 +123,24 @@ gir_DATA = $(INTROSPECTION_GIRS) typelibsdir = $(libdir)/girepository-1.0 typelibs_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) -endif +if ENABLE_VAPIGEN +include $(VAPIGEN_MAKEFILE) + +libsecret-@SECRET_MAJOR@.vapi: Secret-@SECRET_MAJOR@.gir Secret-@SECRET_MAJOR@.metadata + +VAPIGEN_VAPIS = libsecret-@SECRET_MAJOR@.vapi + +libsecret_@SECRET_MAJOR@_vapi_DEPS = gio-2.0 +libsecret_@SECRET_MAJOR@_vapi_METADATADIRS = $(srcdir) +libsecret_@SECRET_MAJOR@_vapi_FILES = Secret-@SECRET_MAJOR@.gir + +vapidir = $(datadir)/vala/vapi +vapi_DATA = $(VAPIGEN_VAPIS) + +CLEANFILES += $(VAPIGEN_VAPIS) + +endif # ENABLE_VAPIGEN +endif # HAVE_INTROSPECTION # ------------------------------------------------------------------ # PKG CONFIG @@ -139,9 +158,10 @@ EXTRA_DIST = \ secret-enum-types.h.template \ secret-enum-types.c.template \ org.freedesktop.Secrets.xml \ + Secret-@SECRET_MAJOR@.metadata \ $(NULL) -CLEANFILES = \ +CLEANFILES += \ $(pkgconfig_DATA) \ $(gir_DATA) \ $(typelibs_DATA) \ diff --git a/library/Secret-0.metadata b/library/Secret-0.metadata new file mode 100644 index 0000000..35ce419 --- /dev/null +++ b/library/Secret-0.metadata @@ -0,0 +1,26 @@ +// Metadata file for Vala API generation. +// See https://live.gnome.org/Vala/UpstreamGuide for more information + +Service + .lookup skip=false + .lookupv finish_name="secret_service_lookup_finish" + .remove skip=false + .removev finish_name="secret_service_remove_finish" + .store skip=false + .storev finish_name="secret_service_store_finish" + +Schema + .new skip=false + +password_lookup skip=false +password_lookup_sync skip=false throws="GLib.Error" + .error skip +password_lookupv finish_name="secret_password_lookup_finish" +password_remove skip=false +password_remove_sync skip=false throws="GLib.Error" + .error skip +password_removev finish_name="secret_password_remove_finish" +password_store skip=false +password_store_sync skip=false throws="GLib.Error" + .error skip +password_storev finish_name="secret_password_store_finish" diff --git a/library/tests/Makefile.am b/library/tests/Makefile.am index 0e7908b..77c0d28 100644 --- a/library/tests/Makefile.am +++ b/library/tests/Makefile.am @@ -10,7 +10,8 @@ INCLUDES = \ noinst_LTLIBRARIES = libmock_service.la libmock_service_la_SOURCES = \ - mock-service.c mock-service.h \ + mock-service.c \ + mock-service.h \ $(NULL) libmock_service_la_CFLAGS = \ @@ -30,7 +31,7 @@ LDADD = \ libmock_service.la \ $(NULL) -TEST_PROGS = \ +C_TESTS = \ test-value \ test-prompt \ test-service \ @@ -41,6 +42,10 @@ TEST_PROGS = \ test-collection \ $(NULL) +TEST_PROGS = \ + $(C_TESTS) \ + $(NULL) + check_PROGRAMS = \ $(TEST_PROGS) @@ -63,8 +68,36 @@ PY_TESTS = \ PY_ENV = $(JS_ENV) -test-c: $(TEST_PROGS) - @gtester --verbose -m $(TEST_MODE) --g-fatal-warnings $(TEST_PROGS) +if HAVE_VALAC + +VALA_V = $(VALA_V_$(V)) +VALA_V_ = $(VALA_V_$(AM_DEFAULT_VERBOSITY)) +VALA_V_0 = @echo " VALAC " $^; + +VALA_TESTS = \ + test-vala-lang \ + $(NULL) + +test-vala-lang.c: test-vala-lang.vala libsecret-@SECRET_MAJOR@.vapi mock-service-0.vapi + $(VALA_V)$(VALAC) -C --pkg gio-2.0 $^ + +TEST_PROGS += $(VALA_TESTS) + +test_vala_lang_CFLAGS = -w + +DISTCLEANFILES = test-vala-lang.c + +test-vala: $(VALA_TESTS) + @gtester --verbose -m $(TEST_MODE) --g-fatal-warnings $(VALA_TESTS) + +else + +test-vala: + +endif # HAVE_VALAC + +test-c: $(C_TESTS) + @gtester --verbose -m $(TEST_MODE) --g-fatal-warnings $(C_TESTS) test-js: @for js in $(JS_TESTS); do echo "TEST: $$js"; $(JS_ENV) gjs $(srcdir)/$$js; done @@ -72,7 +105,7 @@ test-js: test-py: @for py in $(PY_TESTS); do echo "TEST: $$py"; $(PY_ENV) python $(srcdir)/$$py; done -test: test-c test-py test-js +test: test-c test-py test-js test-vala # ------------------------------------------------------------------ # INTROSPECTION @@ -91,7 +124,7 @@ MockService_0_gir_PACKAGES = gobject-2.0 gio-2.0 MockService_0_gir_EXPORT_PACKAGES = mock-service-0 MockService_0_gir_INCLUDES = GObject-2.0 Gio-2.0 MockService_0_gir_LIBS = libmock_service.la -MockService_0_gir_CFLAGS = -I$(top_srcdir) -I$(top_builddir) +MockService_0_gir_CFLAGS = -I$(top_srcdir) -I$(top_builddir) -I$(srcdir) MockService_0_gir_FILES = $(libmock_service_la_SOURCES) MockService_0_gir_SCANNERFLAGS = --c-include "mock-service.h" @@ -99,7 +132,40 @@ noinst_DATA = \ $(INTROSPECTION_GIRS) \ $(INTROSPECTION_GIRS:.gir=.typelib) -endif +if ENABLE_VAPIGEN +include $(VAPIGEN_MAKEFILE) + +mock-service-0.vapi: MockService-0.gir libsecret-@SECRET_MAJOR@.vapi + +VAPIGEN_VAPIS = mock-service-0.vapi + +mock_service_0_vapi_DEPS = gio-2.0 libsecret-@SECRET_MAJOR@ +mock_service_0_vapi_METADATADIRS = $(builddir) +mock_service_0_vapi_VAPIDIRS = $(builddir) +mock_service_0_vapi_FILES = MockService-0.gir + +vapidir = $(datadir)/vala/vapi +vapi_DATA = mock-service-0.vapi + +# We have to make a version of the VAPI which references the +# uninstalled C headers. + +VAPIGEN_VAPIS += libsecret-@SECRET_MAJOR@.vapi + +Secret-@SECRET_MAJOR@.metadata: $(top_srcdir)/library/Secret-@SECRET_MAJOR@.metadata + $(AM_V_GEN) echo "* cheader_filename=\"secret-collection.h,secret-item.h,secret-password.h,secret-prompt.h,secret-schema.h,secret-schemas.h,secret-service.h,secret-types.h,secret-value.h\"" > $@ && \ + cat < $^ >> $@ + +libsecret-@SECRET_MAJOR@.vapi: Secret-@SECRET_MAJOR@.metadata $(top_builddir)/library/Secret-@SECRET_MAJOR@.gir + +libsecret_@SECRET_MAJOR@_vapi_DEPS = gio-2.0 +libsecret_@SECRET_MAJOR@_vapi_METADATADIRS = $(srcdir) +libsecret_@SECRET_MAJOR@_vapi_FILES = $(top_builddir)/library/Secret-@SECRET_MAJOR@.gir + +noinst_DATA += $(VAPIGEN_VAPIS) + +endif # ENABLE_VAPIGEN +endif # HAVE_INTROSPECTION #-------------------------------------------------------------------- @@ -110,6 +176,8 @@ EXTRA_DIST = \ mock-service-normal.py \ mock-service-only-plain.py \ mock-service-prompt.py \ + Secret-@SECRET_MAJOR@.metadata \ + test-vala-lang.vala \ $(JS_TESTS) \ $(PY_TESTS) \ $(NULL) diff --git a/library/tests/test-vala-lang.vala b/library/tests/test-vala-lang.vala new file mode 100644 index 0000000..3bcee8d --- /dev/null +++ b/library/tests/test-vala-lang.vala @@ -0,0 +1,172 @@ +Secret.Schema schema; +Secret.Schema no_name_schema; + +private void test_lookup_sync () { + try { + string? password = Secret.password_lookup_sync (schema, null, "even", false, "string", "one", "number", 1); + GLib.assert (password == "111"); + } catch ( GLib.Error e ) { + GLib.error (e.message); + } +} + +public async void test_lookup_async_ex () { + try { + string? password = yield Secret.password_lookup (schema, null, "even", false, "string", "one", "number", 1); + GLib.assert (password == "111"); + } catch ( GLib.Error e ) { + GLib.error (e.message); + } +} + +private void test_lookup_async () { + var loop = new GLib.MainLoop (); + test_lookup_async_ex.begin ((obj, async_res) => { + loop.quit (); + }); + loop.run (); +} + +private void test_lookup_no_name () { + try { + string? password = Secret.password_lookup_sync (schema, null, "number", 5); + GLib.assert (password == null); + + password = Secret.password_lookup_sync (no_name_schema, null, "number", 5); + GLib.assert (password == "555"); + } catch ( GLib.Error e ) { + GLib.error (e.message); + } +} + +private void test_store_sync () { + try { + var attributes = new GLib.HashTable (GLib.str_hash, GLib.str_equal); + attributes["even"] = "false"; + attributes["string"] = "nine"; + attributes["number"] = "9"; + + string? password = Secret.password_lookupv_sync (schema, attributes); + GLib.assert (password == null); + + bool stored = Secret.password_storev_sync (schema, attributes, Secret.COLLECTION_DEFAULT, "The number ", "999"); + GLib.assert (stored); + + password = Secret.password_lookupv_sync (schema, attributes); + GLib.assert (password == "999"); + } catch ( GLib.Error e ) { + GLib.error (e.message); + } +} + +private async void test_store_async_ex () { + var attributes = new GLib.HashTable (GLib.str_hash, GLib.str_equal); + attributes["even"] = "true"; + attributes["string"] = "eight"; + attributes["number"] = "8"; + + try { + string? password = yield Secret.password_lookupv (schema, attributes, null); + GLib.assert (password == null); + + bool stored = yield Secret.password_storev (schema, attributes, Secret.COLLECTION_DEFAULT, "The number nine", "999", null); + GLib.assert (stored); + + password = yield Secret.password_lookupv (schema, attributes, null); + GLib.assert (password == "999"); + } catch ( GLib.Error e ) { + GLib.error (e.message); + } +} + +private void test_store_async () { + var loop = new GLib.MainLoop (); + test_store_async_ex.begin ((obj, async_res) => { + loop.quit (); + }); + loop.run (); +} + +private void test_remove_sync () { + try { + var attributes = new GLib.HashTable (GLib.str_hash, GLib.str_equal); + attributes["even"] = "false"; + attributes["string"] = "nine"; + attributes["number"] = "9"; + + string? password = Secret.password_lookupv_sync (schema, attributes); + GLib.assert (password == "999"); + + bool removed = Secret.password_removev_sync (schema, attributes, null); + GLib.assert (removed); + + password = Secret.password_lookupv_sync (schema, attributes); + GLib.assert (password == null); + } catch ( GLib.Error e ) { + GLib.error (e.message); + } +} + +private async void test_remove_async_ex () { + var attributes = new GLib.HashTable (GLib.str_hash, GLib.str_equal); + attributes["even"] = "true"; + attributes["string"] = "eight"; + attributes["number"] = "8"; + + try { + string? password = yield Secret.password_lookupv (schema, attributes, null); + GLib.assert (password == "999"); + + bool removed = yield Secret.password_removev (schema, attributes, null); + GLib.assert (removed); + + password = yield Secret.password_lookupv (schema, attributes, null); + GLib.assert (password == null); + } catch ( GLib.Error e ) { + GLib.error (e.message); + } +} + +private void test_remove_async () { + var loop = new GLib.MainLoop (); + test_remove_async_ex.begin ((obj, async_res) => { + loop.quit (); + }); + loop.run (); +} + +private static int main (string[] args) { + GLib.Test.init (ref args); + + try { + MockService.start ("mock-service-normal.py"); + } catch ( GLib.Error e ) { + GLib.error ("Unable to start mock service: %s", e.message); + } + + { + schema = new Secret.Schema ("org.mock.Schema", Secret.SchemaFlags.NONE, + "number", Secret.SchemaAttributeType.INTEGER, + "string", Secret.SchemaAttributeType.STRING, + "even", Secret.SchemaAttributeType.BOOLEAN); + + no_name_schema = new Secret.Schema ("unused.Schema.Name", Secret.SchemaFlags.DONT_MATCH_NAME, + "number", Secret.SchemaAttributeType.INTEGER, + "string", Secret.SchemaAttributeType.STRING); + } + + 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/no-name", test_lookup_no_name); + GLib.Test.add_data_func ("/vala/store/sync", test_store_sync); + GLib.Test.add_data_func ("/vala/store/async", test_store_async); + GLib.Test.add_data_func ("/vala/remove/sync", test_remove_sync); + GLib.Test.add_data_func ("/vala/remove/async", test_remove_async); + + var res = GLib.Test.run (); + + MockService.stop (); + schema = null; + + return res; +}