Merge lp:~dbarth/trust-store/cleaner into lp:trust-store

Proposed by David Barth
Status: Needs review
Proposed branch: lp:~dbarth/trust-store/cleaner
Merge into: lp:trust-store
Diff against target: 588 lines (+414/-4)
12 files modified
include/core/trust/store.h (+9/-0)
src/CMakeLists.txt (+31/-0)
src/core/trust/cleaner.cpp (+84/-0)
src/core/trust/cleaner.h (+74/-0)
src/core/trust/cleaner_main.cpp (+26/-0)
src/core/trust/impl/sqlite3/store.cpp (+29/-3)
src/core/trust/impl/sqlite3/store.h (+1/-1)
src/core/trust/resolve.cpp (+6/-0)
tests/CMakeLists.txt (+18/-0)
tests/cleaner_test.cpp (+129/-0)
tests/mock_store.h (+2/-0)
tests/test_data.h.in (+5/-0)
To merge this branch: bzr merge lp:~dbarth/trust-store/cleaner
Reviewer Review Type Date Requested Status
Alexandre Abreu (community) Disapprove
PS Jenkins bot continuous-integration Pending
Review via email: mp+299294@code.launchpad.net

Commit message

Provide a new command line tool to clean the trust dbs of entries related to a particular app. Use when an application is being uninstalled.

Description of the change

Provide a new command line tool to clean the trust dbs of entries related to a particular app. Use when an application is being uninstalled.

To post a comment you must log in.
lp:~dbarth/trust-store/cleaner updated
161. By David Barth

define extra PurgeableStore class to preserve the vtable ABI, as suggested by tvoss; switch to using it

Revision history for this message
David Barth (dbarth) wrote :

I updated the branch with the PurgeableStore suggestion, but got straight to calling the sqlite impl class; i would have preferred to dynamic_cast instead, but my c++11 is not so great :/

Revision history for this message
Alexandre Abreu (abreu-alexandre) wrote :
review: Disapprove

Unmerged revisions

161. By David Barth

define extra PurgeableStore class to preserve the vtable ABI, as suggested by tvoss; switch to using it

160. By David Barth

provide trust-store-cleaner cli tool; add unit tests for the new command

159. By David Barth

wip

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'include/core/trust/store.h'
--- include/core/trust/store.h 2014-07-23 13:55:07 +0000
+++ include/core/trust/store.h 2016-07-06 16:22:59 +0000
@@ -203,6 +203,15 @@
203 };203 };
204};204};
205205
206class PurgeableStore : public trust::Store
207{
208public:
209 /** @brief purge the store from any reference to the given application
210 * When this function returns true, the request has been persisted by the implementation.
211 */
212 virtual void purge(const std::string& id) = 0;
213};
214
206/**215/**
207 * @brief Creates an instance for the default store implementation.216 * @brief Creates an instance for the default store implementation.
208 * @throw Error::ServiceNameMustNotBeEmpty.217 * @throw Error::ServiceNameMustNotBeEmpty.
209218
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 2016-01-11 09:51:51 +0000
+++ src/CMakeLists.txt 2016-07-06 16:22:59 +0000
@@ -192,6 +192,19 @@
192 core/trust/preseed_main.cpp192 core/trust/preseed_main.cpp
193)193)
194194
195add_library(
196 trust-store-cleaner-helper
197
198 core/trust/cleaner.h
199 core/trust/cleaner.cpp
200)
201
202add_executable(
203 trust-store-cleaner
204
205 core/trust/cleaner_main.cpp
206)
207
195target_link_libraries(208target_link_libraries(
196 trust-store209 trust-store
197210
@@ -242,6 +255,19 @@
242 trust-store-preseed-helper255 trust-store-preseed-helper
243)256)
244257
258target_link_libraries(
259 trust-store-cleaner-helper
260
261 trust-store
262)
263
264target_link_libraries(
265 trust-store-cleaner
266
267 trust-store-cleaner-helper
268)
269
270
245# We compile with all symbols visible by default. For the shipping library, we strip271# We compile with all symbols visible by default. For the shipping library, we strip
246# out all symbols that are not in core::trust::*272# out all symbols that are not in core::trust::*
247set(symbol_map "${CMAKE_SOURCE_DIR}/symbols.map")273set(symbol_map "${CMAKE_SOURCE_DIR}/symbols.map")
@@ -277,3 +303,8 @@
277 TARGETS trust-store-preseed303 TARGETS trust-store-preseed
278 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}304 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
279)305)
306
307install(
308 TARGETS trust-store-cleaner
309 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
310)
280311
=== added file 'src/core/trust/cleaner.cpp'
--- src/core/trust/cleaner.cpp 1970-01-01 00:00:00 +0000
+++ src/core/trust/cleaner.cpp 2016-07-06 16:22:59 +0000
@@ -0,0 +1,84 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 * David Barth <david.barth@canonical.com>
18 */
19
20#include <xdg.h>
21
22#include <core/trust/cleaner.h>
23
24#include <core/trust/impl/sqlite3/store.h>
25
26#include <boost/program_options.hpp>
27
28#include <istream>
29
30namespace Options = boost::program_options;
31
32std::istream& operator>>(std::istream& in, core::trust::Feature& f)
33{
34 return in >> f.value;
35}
36
37core::trust::Cleaner::Configuration core::trust::Cleaner::Configuration::parse_from_command_line(int argc, const char** argv)
38{
39 Options::variables_map vm;
40
41 Options::options_description options{"Known options"};
42 options.add_options()
43 (Parameters::ForService::name, Options::value<std::string>()->required(), Parameters::ForService::description)
44 (Parameters::PurgeRequest::name, Options::value<std::vector<std::string>>()->composing(), Parameters::PurgeRequest::description);
45
46 Options::command_line_parser parser
47 {
48 argc,
49 argv
50 };
51
52 auto parsed_options = parser.options(options).allow_unregistered().run();
53 Options::store(parsed_options, vm);
54 Options::notify(vm);
55
56 auto service_name = vm[Parameters::ForService::name].as<std::string>();
57 auto requests = vm[Parameters::PurgeRequest::name].as<std::vector<std::string>>();
58
59 core::trust::Cleaner::Configuration config
60 {
61 core::trust::impl::sqlite::create_for_service(service_name, *xdg::BaseDirSpecification::create()),
62 {} // The empty set.
63 };
64
65 for (const auto& request : requests)
66 {
67 std::stringstream ss{request}; core::trust::Request r;
68
69 // Parse the request.
70 ss >> r.from >> r.feature;
71
72 config.purge_requests.push_back(r);
73 }
74
75 return config;
76}
77
78core::posix::exit::Status core::trust::Cleaner::main(const core::trust::Cleaner::Configuration& configuration)
79{
80 for (const auto& r : configuration.purge_requests)
81 configuration.store->purge(r.from);
82
83 return core::posix::exit::Status::success;
84}
085
=== added file 'src/core/trust/cleaner.h'
--- src/core/trust/cleaner.h 1970-01-01 00:00:00 +0000
+++ src/core/trust/cleaner.h 2016-07-06 16:22:59 +0000
@@ -0,0 +1,74 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 * David Barth <david.barth@canonical.com>
18 */
19
20#ifndef CORE_TRUST_CLEANER_H_
21#define CORE_TRUST_CLEANER_H_
22
23#include <core/trust/store.h>
24
25#include <core/posix/exit.h>
26
27#include <vector>
28
29namespace core
30{
31namespace trust
32{
33// A helper to clean trust caches of authorizations for applications being uninstalled
34// Invoke with:
35// <helper>
36// --for-service MyServiceName
37// --purge "id.of.app.being.removed"
38struct Cleaner
39{
40 // Command-line parameters, their name and their description
41 struct Parameters
42 {
43 Parameters() = delete;
44
45 struct ForService
46 {
47 static constexpr const char* name{"for-service"};
48 static constexpr const char* description{"The name of the service to handle trust for."};
49 };
50
51 struct PurgeRequest
52 {
53 static constexpr const char* name{"purge"};
54 static constexpr const char* description{"App ID which authorizations need be removed from the trust store. Can be specified multiple times."};
55 };
56 };
57
58 // Parameters for execution of the preseed executable.
59 struct Configuration
60 {
61 // Parses command line args and produces a configuration
62 static Configuration parse_from_command_line(int argc, const char** argv);
63 // The store that should be purged of entries for an application
64 std::shared_ptr<core::trust::PurgeableStore> store;
65 // The set of requests for cleaning that should be applied to the store.
66 std::vector<core::trust::Request> purge_requests;
67 };
68
69 static core::posix::exit::Status main(const Configuration& configuration);
70};
71}
72}
73
74#endif // CORE_TRUST_CLEANER_H_
075
=== added file 'src/core/trust/cleaner_main.cpp'
--- src/core/trust/cleaner_main.cpp 1970-01-01 00:00:00 +0000
+++ src/core/trust/cleaner_main.cpp 2016-07-06 16:22:59 +0000
@@ -0,0 +1,26 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 */
18
19#include <core/trust/cleaner.h>
20
21int main(int argc, const char** argv)
22{
23 auto result = core::trust::Cleaner::main(core::trust::Cleaner::Configuration::parse_from_command_line(argc, argv));
24
25 return result == core::posix::exit::Status::success ? EXIT_SUCCESS : EXIT_FAILURE;
26}
027
=== modified file 'src/core/trust/impl/sqlite3/store.cpp'
--- src/core/trust/impl/sqlite3/store.cpp 2015-12-02 10:05:00 +0000
+++ src/core/trust/impl/sqlite3/store.cpp 2016-07-06 16:22:59 +0000
@@ -346,7 +346,7 @@
346346
347// A store implementation persisting requests in an sqlite database.347// A store implementation persisting requests in an sqlite database.
348struct Store348struct Store
349 : public core::trust::Store,349 : public core::trust::PurgeableStore,
350 public std::enable_shared_from_this<Store>350 public std::enable_shared_from_this<Store>
351{351{
352 // Our schema version constant.352 // Our schema version constant.
@@ -473,6 +473,20 @@
473 return s;473 return s;
474 }474 }
475 };475 };
476
477 struct Purge
478 {
479 static const std::string& statement()
480 {
481 static const std::string s
482 {
483 "DELETE FROM " +
484 Store::RequestsTable::name() +
485 " WHERE ApplicationId=?;"
486 };
487 return s;
488 }
489 };
476 };490 };
477491
478 // An implementation of the query interface for the SQLite-based store.492 // An implementation of the query interface for the SQLite-based store.
@@ -696,6 +710,7 @@
696 // From core::trust::Store710 // From core::trust::Store
697 void reset();711 void reset();
698 void add(const Request& request);712 void add(const Request& request);
713 void purge(const std::string& id);
699 std::shared_ptr<core::trust::Store::Query> query();714 std::shared_ptr<core::trust::Store::Query> query();
700715
701 std::mutex guard;716 std::mutex guard;
@@ -704,6 +719,7 @@
704719
705 TaggedPreparedStatement<Statements::Delete> delete_statement;720 TaggedPreparedStatement<Statements::Delete> delete_statement;
706 TaggedPreparedStatement<Statements::Insert> insert_statement;721 TaggedPreparedStatement<Statements::Insert> insert_statement;
722 TaggedPreparedStatement<Statements::Purge> purge_statement;
707};723};
708}724}
709}725}
@@ -721,6 +737,7 @@
721737
722 delete_statement = db.prepare_tagged_statement<Statements::Delete>();738 delete_statement = db.prepare_tagged_statement<Statements::Delete>();
723 insert_statement = db.prepare_tagged_statement<Statements::Insert>();739 insert_statement = db.prepare_tagged_statement<Statements::Insert>();
740 purge_statement = db.prepare_tagged_statement<Statements::Purge>();
724}741}
725742
726sqlite::Store::~Store()743sqlite::Store::~Store()
@@ -771,12 +788,21 @@
771 insert_statement.step();788 insert_statement.step();
772}789}
773790
791void sqlite::Store::purge(const std::string& id)
792{
793 std::lock_guard<std::mutex> lg(guard);
794
795 purge_statement.reset();
796 purge_statement.bind_text<sqlite::Store::RequestsTable::Column::ApplicationId::index>(id);
797 purge_statement.step();
798}
799
774std::shared_ptr<trust::Store::Query> sqlite::Store::query()800std::shared_ptr<trust::Store::Query> sqlite::Store::query()
775{801{
776 return std::shared_ptr<trust::Store::Query>{new sqlite::Store::Query{shared_from_this()}};802 return std::shared_ptr<trust::Store::Query>{new sqlite::Store::Query{shared_from_this()}};
777}803}
778804
779std::shared_ptr<core::trust::Store> core::trust::impl::sqlite::create_for_service(const std::string& name, xdg::BaseDirSpecification& spec)805std::shared_ptr<core::trust::PurgeableStore> core::trust::impl::sqlite::create_for_service(const std::string& name, xdg::BaseDirSpecification& spec)
780{806{
781 if (name.empty())807 if (name.empty())
782 throw core::trust::Errors::ServiceNameMustNotBeEmpty();808 throw core::trust::Errors::ServiceNameMustNotBeEmpty();
@@ -786,5 +812,5 @@
786812
787std::shared_ptr<core::trust::Store> core::trust::create_default_store(const std::string& service_name)813std::shared_ptr<core::trust::Store> core::trust::create_default_store(const std::string& service_name)
788{814{
789 return core::trust::impl::sqlite::create_for_service(service_name, *xdg::BaseDirSpecification::create());815 return core::trust::impl::sqlite::create_for_service(service_name, *xdg::BaseDirSpecification::create());
790}816}
791817
=== modified file 'src/core/trust/impl/sqlite3/store.h'
--- src/core/trust/impl/sqlite3/store.h 2015-11-18 08:28:51 +0000
+++ src/core/trust/impl/sqlite3/store.h 2016-07-06 16:22:59 +0000
@@ -40,7 +40,7 @@
40// create_for_service creates a Store implementation relying on sqlite3, managing40// create_for_service creates a Store implementation relying on sqlite3, managing
41// trust for the service identified by service_name. Uses spec to determine a user-specific41// trust for the service identified by service_name. Uses spec to determine a user-specific
42// directory to place the trust database.42// directory to place the trust database.
43CORE_TRUST_DLL_PUBLIC std::shared_ptr<core::trust::Store> create_for_service(const std::string& service_name, xdg::BaseDirSpecification& spec);43CORE_TRUST_DLL_PUBLIC std::shared_ptr<core::trust::PurgeableStore> create_for_service(const std::string& service_name, xdg::BaseDirSpecification& spec);
44}44}
45}45}
46}46}
4747
=== modified file 'src/core/trust/resolve.cpp'
--- src/core/trust/resolve.cpp 2014-07-29 17:00:35 +0000
+++ src/core/trust/resolve.cpp 2016-07-06 16:22:59 +0000
@@ -195,6 +195,12 @@
195 throw std::runtime_error(response.error().print());195 throw std::runtime_error(response.error().print());
196 }196 }
197197
198 void clean(const std::string& id)
199 {
200 (void)id;
201 throw std::runtime_error("not implemented");
202 }
203
198 void reset()204 void reset()
199 {205 {
200 try206 try
201207
=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 2016-01-11 09:51:51 +0000
+++ tests/CMakeLists.txt 2016-07-06 16:22:59 +0000
@@ -86,6 +86,11 @@
86 preseed_test.cpp86 preseed_test.cpp
87)87)
8888
89add_executable(
90 cleaner_test
91 cleaner_test.cpp
92)
93
89target_link_libraries(94target_link_libraries(
90 bug_138773495 bug_1387734
9196
@@ -241,6 +246,19 @@
241 ${PROCESS_CPP_LIBRARIES}246 ${PROCESS_CPP_LIBRARIES}
242)247)
243248
249target_link_libraries(
250 cleaner_test
251
252 trust-store-cleaner-helper
253
254 gmock
255
256 gtest
257 gtest_main
258
259 ${PROCESS_CPP_LIBRARIES}
260)
261
244add_test(bug_1387734 ${CMAKE_CURRENT_BINARY_DIR}/bug_1387734)262add_test(bug_1387734 ${CMAKE_CURRENT_BINARY_DIR}/bug_1387734)
245add_test(trust_store_test ${CMAKE_CURRENT_BINARY_DIR}/trust_store_test)263add_test(trust_store_test ${CMAKE_CURRENT_BINARY_DIR}/trust_store_test)
246add_test(remote_trust_store_test ${CMAKE_CURRENT_BINARY_DIR}/remote_trust_store_test)264add_test(remote_trust_store_test ${CMAKE_CURRENT_BINARY_DIR}/remote_trust_store_test)
247265
=== added file 'tests/cleaner_test.cpp'
--- tests/cleaner_test.cpp 1970-01-01 00:00:00 +0000
+++ tests/cleaner_test.cpp 2016-07-06 16:22:59 +0000
@@ -0,0 +1,129 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 * David Barth <david.barth@canonical.com>
18 */
19
20#include <core/trust/cleaner.h>
21#include <core/trust/preseed.h>
22
23#include "test_data.h"
24
25#include <core/posix/exec.h>
26#include <core/posix/this_process.h>
27
28#include <gtest/gtest.h>
29
30#include <map>
31
32namespace
33{
34std::map<std::string, std::string> a_copy_of_the_current_env()
35{
36 std::map<std::string, std::string> result;
37
38 core::posix::this_process::env::for_each([&result](const std::string& s, const std::string& t)
39 {
40 result.insert(std::make_pair(s, t));
41 });
42
43 return result;
44}
45}
46
47TEST(Cleaner, accepts_parameters)
48{
49 const std::string service_name{"JustANameForTesting"};
50
51 const std::vector<std::string> argv
52 {
53 "--for-service", service_name,
54 "--purge", "other.app",
55 "--purge", "does.not.exist.app"
56 };
57
58 auto child = core::posix::exec(
59 core::trust::testing::trust_store_cleaner_executable_in_build_dir,
60 argv,
61 a_copy_of_the_current_env(),
62 core::posix::StandardStream::empty);
63
64 auto result = child.wait_for(core::posix::wait::Flags::untraced);
65
66 EXPECT_EQ(core::posix::wait::Result::Status::exited, result.status);
67 EXPECT_EQ(core::posix::exit::Status::success, result.detail.if_exited.status);
68}
69
70TEST(Cleaner, can_remove_authorization_for_app)
71{
72 const std::string service_name{"JustANameForTesting"};
73 const std::string app_name{"does.not.exist.app"};
74 const std::string other_app{"other.app"};
75
76 const std::vector<std::string> argv_preseed
77 {
78 "--for-service", service_name,
79 "--request", "does.not.exist.app 0 granted", // 1
80 "--request", "other.app 1 denied", // 2
81 };
82
83 auto child_preseed = core::posix::exec(
84 core::trust::testing::trust_store_preseed_executable_in_build_dir,
85 argv_preseed,
86 a_copy_of_the_current_env(),
87 core::posix::StandardStream::empty);
88
89 auto result_preseed = child_preseed.wait_for(core::posix::wait::Flags::untraced);
90
91 EXPECT_EQ(core::posix::wait::Result::Status::exited, result_preseed.status);
92 EXPECT_EQ(core::posix::exit::Status::success, result_preseed.detail.if_exited.status);
93
94 // TODO: should verify that the store is preseeded properly
95
96 const std::vector<std::string> argv
97 {
98 "--for-service", service_name,
99 "--purge", app_name
100 };
101
102 auto child = core::posix::exec(
103 core::trust::testing::trust_store_cleaner_executable_in_build_dir,
104 argv,
105 a_copy_of_the_current_env(),
106 core::posix::StandardStream::empty);
107
108 auto result = child.wait_for(core::posix::wait::Flags::untraced);
109
110 EXPECT_EQ(core::posix::wait::Result::Status::exited, result.status);
111 EXPECT_EQ(core::posix::exit::Status::success, result.detail.if_exited.status);
112
113 auto store = core::trust::create_default_store(service_name);
114
115 auto query = store->query();
116
117 EXPECT_NO_THROW(query->execute());
118
119 EXPECT_EQ(core::trust::Store::Query::Status::has_more_results, query->status());
120
121 std::size_t counter{0};
122
123 while (query->status() != core::trust::Store::Query::Status::eor)
124 {
125 EXPECT_NE(app_name, query->current().from);
126 query->next(); counter++;
127 }
128
129}
0130
=== modified file 'tests/mock_store.h'
--- tests/mock_store.h 2014-07-24 11:30:09 +0000
+++ tests/mock_store.h 2016-07-06 16:22:59 +0000
@@ -69,6 +69,8 @@
69 */69 */
70 MOCK_METHOD1(add, void(const core::trust::Request&));70 MOCK_METHOD1(add, void(const core::trust::Request&));
7171
72 MOCK_METHOD1(clean, void(const std::string&));
73
72 /**74 /**
73 * @brief Create a query for this store.75 * @brief Create a query for this store.
74 */76 */
7577
=== modified file 'tests/test_data.h.in'
--- tests/test_data.h.in 2015-11-11 15:31:35 +0000
+++ tests/test_data.h.in 2016-07-06 16:22:59 +0000
@@ -39,6 +39,11 @@
39{39{
40 "@CMAKE_BINARY_DIR@/src/trust-store-preseed"40 "@CMAKE_BINARY_DIR@/src/trust-store-preseed"
41};41};
42
43static constexpr const char* trust_store_cleaner_executable_in_build_dir
44{
45 "@CMAKE_BINARY_DIR@/src/trust-store-cleaner"
46};
42}47}
43}48}
44}49}

Subscribers

People subscribed via source and target branches