2
* Copyright (C) 2014 Canonical Ltd
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU Lesser General Public License version 3 as
6
* published by the Free Software Foundation.
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.
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/>.
16
* Authored by: Marcus Tomlinson <marcus.tomlinson@canonical.com>
19
#include <unity/scopes/internal/OnlineAccountClientImpl.h>
21
#include <unity/UnityExceptions.h>
34
static void free_error(GError* e)
42
static void free_variant(GVariant* v)
50
static OnlineAccountClient::ServiceStatus info_to_details(AccountInfo* info, std::string const& error = "")
52
char* client_id = nullptr;
53
char* client_secret = nullptr;
54
char* access_token = nullptr;
55
char* token_secret = nullptr;
57
std::lock_guard<std::mutex> info_lock(info->mutex);
59
if (info->auth_params)
61
// Look up OAuth 2 parameters
62
g_variant_lookup(info->auth_params.get(), "ClientId", "&s", &client_id);
63
g_variant_lookup(info->auth_params.get(), "ClientSecret", "&s", &client_secret);
64
// If OAuth 2 parameters are not found, fall back to OAuth 1 parameters
67
g_variant_lookup(info->auth_params.get(), "ConsumerKey", "&s", &client_id);
71
g_variant_lookup(info->auth_params.get(), "ConsumerSecret", "&s", &client_secret);
74
if (info->session_data)
76
g_variant_lookup(info->session_data.get(), "AccessToken", "&s", &access_token);
77
g_variant_lookup(info->session_data.get(), "TokenSecret", "&s", &token_secret);
80
// Gather the latest service details then trigger the client callback
81
OnlineAccountClient::ServiceStatus service_status;
82
service_status.account_id = info->account_id;
83
service_status.service_enabled = info->service_enabled;
84
service_status.service_authenticated = access_token ? info->service_enabled : false;
85
service_status.client_id = client_id ? client_id : "";
86
service_status.client_secret = client_secret ? client_secret : "";
87
service_status.access_token = access_token ? access_token : "";
88
service_status.token_secret = token_secret ? token_secret : "";
89
service_status.error = error;
91
return service_status;
94
static gboolean main_loop_is_running_cb(void* user_data)
96
OnlineAccountClientImpl* account_client = reinterpret_cast<OnlineAccountClientImpl*>(user_data);
97
account_client->main_loop_state_notify(true);
98
return G_SOURCE_REMOVE;
101
static gboolean main_loop_is_stopping_cb(void* user_data)
103
OnlineAccountClientImpl* account_client = reinterpret_cast<OnlineAccountClientImpl*>(user_data);
104
account_client->main_loop_state_notify(false);
105
return G_SOURCE_REMOVE;
108
static gboolean wake_up_event_loop_cb(void* user_data)
110
GMainLoop* event_loop = reinterpret_cast<GMainLoop*>(user_data);
111
g_main_loop_quit(event_loop);
112
return G_SOURCE_REMOVE;
115
static void service_login_cb(GObject* source, GAsyncResult* result, void* user_data)
117
SignonAuthSession* session = reinterpret_cast<SignonAuthSession*>(source);
118
AccountInfo* info = reinterpret_cast<AccountInfo*>(user_data);
120
std::unique_lock<std::mutex> info_lock(info->mutex);
122
// Get session data then send a notification with the login result
123
GError* error = nullptr;
124
info->session_data.reset(signon_auth_session_process_finish(session, result, &error), free_variant);
125
std::shared_ptr<GError> error_cleanup(error, free_error);
128
info->account_client->callback(info, error ? error->message : "");
131
static void service_update_cb(AgAccountService* account_service, gboolean enabled, AccountInfo* info)
133
std::unique_lock<std::mutex> info_lock(info->mutex);
135
// If another session is currently busy, cancel it
138
signon_auth_session_cancel(info->session.get());
141
// Service state has updated, clear the old session data
142
info->service_enabled = enabled;
146
// Get authorization data then create a new authorization session
147
std::shared_ptr<AgAuthData> auth_data(ag_account_service_get_auth_data(account_service), ag_auth_data_unref);
149
GError* error = nullptr;
150
info->session.reset(signon_auth_session_new(
151
ag_auth_data_get_credentials_id(auth_data.get()), ag_auth_data_get_method(auth_data.get()), &error), g_object_unref);
152
std::shared_ptr<GError> error_cleanup(error, free_error);
156
// Send notification that the authorization session failed
157
info_lock.unlock(); // LCOV_EXCL_LINE
158
info->account_client->callback(info, error->message); // LCOV_EXCL_LINE
162
// Get authorization parameters then attempt to signon
163
info->auth_params.reset(
164
g_variant_ref_sink(ag_auth_data_get_login_parameters(auth_data.get(), nullptr)), free_variant);
166
// Start signon process
167
signon_auth_session_process_async(info->session.get(),
168
info->auth_params.get(),
169
ag_auth_data_get_mechanism(auth_data.get()),
176
// Send notification that account has been disabled
178
info->account_client->callback(info);
182
static void account_enabled_cb(AgManager* manager, AgAccountId account_id, OnlineAccountClientImpl* account_client)
184
if (account_client->has_account(account_id))
186
// We are already watching this account
189
std::shared_ptr<AgAccount> account(ag_manager_get_account(manager, account_id), g_object_unref);
192
// The account was not found
193
return; // LCOV_EXCL_LINE
195
// Find the service we're concerned with
196
std::shared_ptr<GList> services(ag_account_list_services(account.get()), ag_service_list_free);
198
for (it = services.get(); it; it = it->next)
200
AgService* service = reinterpret_cast<AgService*>(it->data);
201
if (account_client->service_name() == ag_service_get_name(service))
203
std::shared_ptr<AgAccountService> account_service(ag_account_service_new(account.get(), service), g_object_unref);
204
std::shared_ptr<AccountInfo> info(new AccountInfo);
205
info->service_enabled = false;
206
info->account_client = account_client;
207
info->account_service.reset(reinterpret_cast<AgAccountService*>(g_object_ref(account_service.get())), g_object_unref);
209
AgAccount* account = ag_account_service_get_account(account_service.get());
210
g_object_get(account, "id", &info->account_id, nullptr);
212
// Watch for changes to this service
213
info->service_update_signal_id_ = g_signal_connect(account_service.get(), "enabled", G_CALLBACK(service_update_cb), info.get());
216
service_update_cb(account_service.get(), ag_account_service_get_enabled(account_service.get()), info.get());
217
account_client->add_account(account_id, info);
223
static void account_deleted_cb(AgManager*, AgAccountId account_id, OnlineAccountClientImpl* account_client)
225
// A disabled event should have been sent prior to this, so no
226
// need to send any notification.
227
account_client->remove_account(account_id);
230
OnlineAccountClientImpl::OnlineAccountClientImpl(std::string const& service_name,
231
std::string const& service_type,
232
std::string const& provider_name,
233
OnlineAccountClient::MainLoopSelect main_loop_select)
234
: service_name_(service_name)
235
, service_type_(service_type)
236
, provider_name_(provider_name)
237
, main_loop_select_(main_loop_select)
238
, main_loop_is_running_(main_loop_select != OnlineAccountClient::CreateInternalMainLoop)
240
// If we are responsible for the main loop
241
if (main_loop_select_ == OnlineAccountClient::CreateInternalMainLoop)
243
// Wait here until either the main loop begins running, or the thread exits
244
std::unique_lock<std::mutex> lock(mutex_);
245
main_loop_thread_ = std::thread(&OnlineAccountClientImpl::main_loop_thread, this);
246
cond_.wait_for(lock, std::chrono::seconds(5), [this] { return main_loop_is_running_; });
248
if (!main_loop_is_running_)
253
// Quit the main loop, causing the thread to exit
254
g_main_loop_quit(main_loop_.get());
256
if (main_loop_thread_.joinable())
259
main_loop_thread_.join();
262
if (thread_exception_)
264
std::rethrow_exception(thread_exception_);
268
throw unity::ResourceException("OnlineAccountClientImpl(): main_loop_thread failed to start.");
279
OnlineAccountClientImpl::~OnlineAccountClientImpl()
282
std::lock_guard<std::mutex> lock(mutex_);
283
if (thread_exception_)
288
std::rethrow_exception(thread_exception_);
290
catch (std::exception const& e)
292
std::cerr << "~OnlineAccountClientImpl(): main_loop_thread threw an exception: " << e.what() << std::endl;
296
std::cerr << "~OnlineAccountClientImpl(): main_loop_thread threw an unknown exception" << std::endl;
302
// If we are responsible for the main loop
303
if (main_loop_select_ == OnlineAccountClient::CreateInternalMainLoop)
305
// Invoke tear_down() from within the main loop
306
std::unique_lock<std::mutex> lock(mutex_);
307
g_main_context_invoke(main_loop_context_.get(), main_loop_is_stopping_cb, this);
308
cond_.wait(lock, [this] { return !main_loop_is_running_; });
312
// Quit the main loop, causing the thread to exit
313
g_main_loop_quit(main_loop_.get());
315
if (main_loop_thread_.joinable())
318
main_loop_thread_.join();
327
std::unique_lock<std::mutex> lock(callback_mutex_);
328
if (callback_thread_.joinable())
331
callback_thread_.join();
336
void OnlineAccountClientImpl::set_service_update_callback(OnlineAccountClient::ServiceUpdateCallback callback)
338
std::unique_lock<std::mutex> lock(mutex_);
339
if (thread_exception_)
341
std::rethrow_exception(thread_exception_); // LCOV_EXCL_LINE
344
std::lock_guard<std::mutex> callback_lock(callback_mutex_);
345
callback_ = callback;
347
if (callback_ != nullptr)
349
std::vector<OnlineAccountClient::ServiceStatus> known_statuses;
350
for (auto const& info : accounts_)
352
// We only want to invoke the callback for non-busy sessions, as signon sessions that are currently
353
// busy will end with a callback anyway. info->session is cleared once a signon process has fully
354
// completed, therefore, if the session is null, we know that it is not currently busy.
355
std::unique_lock<std::mutex> info_lock(info.second->mutex);
356
if (info.second->session == nullptr)
359
known_statuses.push_back(info_to_details(info.second.get()));
363
// Invoke callback for all known service statuses
365
for (auto const& status : known_statuses)
372
void OnlineAccountClientImpl::refresh_service_statuses()
374
std::unique_lock<std::mutex> lock(mutex_);
375
if (thread_exception_)
377
std::rethrow_exception(thread_exception_); // LCOV_EXCL_LINE
380
std::shared_ptr<GList> enabled_accounts(ag_manager_list(manager_.get()), ag_manager_list_free);
382
for (it = enabled_accounts.get(); it; it = it->next)
384
AgAccountId account_id = GPOINTER_TO_UINT(it->data);
385
std::shared_ptr<AgAccount> account(ag_manager_get_account(manager_.get(), account_id), g_object_unref);
386
std::string provider_name = ag_account_get_provider_name(account.get());
387
if (provider_name == provider_name_)
390
account_enabled_cb(manager_.get(), account_id, this);
396
std::vector<OnlineAccountClient::ServiceStatus> OnlineAccountClientImpl::get_service_statuses()
398
std::unique_lock<std::mutex> lock(mutex_);
399
if (thread_exception_)
401
std::rethrow_exception(thread_exception_); // LCOV_EXCL_LINE
404
// Return all service statuses
405
std::vector<OnlineAccountClient::ServiceStatus> service_statuses;
406
for (auto const& info : accounts_)
408
flush_pending_session(info.second.get(), lock);
409
service_statuses.push_back(info_to_details(info.second.get()));
411
return service_statuses;
414
void OnlineAccountClientImpl::register_account_login_item(Result& result,
415
CannedQuery const& query,
416
OnlineAccountClient::PostLoginAction login_passed_action,
417
OnlineAccountClient::PostLoginAction login_failed_action)
419
// If no URI is set on this result, default to a canned query to refresh the scope results
420
if (result.uri().empty())
422
const unity::scopes::CannedQuery refresh_query(query.scope_id(), query.query_string(), query.query_string().empty() ? query.department_id() : "");
423
result.set_uri(refresh_query.to_uri());
426
VariantMap account_details_map;
427
account_details_map["service_name"] = service_name_;
428
account_details_map["service_type"] = service_type_;
429
account_details_map["provider_name"] = provider_name_;
430
account_details_map["login_passed_action"] = static_cast<int>(login_passed_action);
431
account_details_map["login_failed_action"] = static_cast<int>(login_failed_action);
433
result["online_account_details"] = Variant(account_details_map);
436
void OnlineAccountClientImpl::register_account_login_item(PreviewWidget& widget,
437
OnlineAccountClient::PostLoginAction login_passed_action,
438
OnlineAccountClient::PostLoginAction login_failed_action)
440
VariantMap account_details_map;
441
account_details_map["service_name"] = service_name_;
442
account_details_map["service_type"] = service_type_;
443
account_details_map["provider_name"] = provider_name_;
444
account_details_map["login_passed_action"] = static_cast<int>(login_passed_action);
445
account_details_map["login_failed_action"] = static_cast<int>(login_failed_action);
447
widget.add_attribute_value("online_account_details", Variant(account_details_map));
450
void OnlineAccountClientImpl::construct()
452
manager_.reset(ag_manager_new_for_service_type(service_type_.c_str()), g_object_unref);
454
// Watch for changes to accounts
455
account_enabled_signal_id_ = g_signal_connect(manager_.get(), "enabled-event", G_CALLBACK(account_enabled_cb), this);
456
account_deleted_signal_id_ = g_signal_connect(manager_.get(), "account-deleted", G_CALLBACK(account_deleted_cb), this);
458
// Now check initial state
459
refresh_service_statuses();
462
void OnlineAccountClientImpl::tear_down()
464
// Disconnect signal handlers
465
g_signal_handler_disconnect(manager_.get(), account_enabled_signal_id_);
466
g_signal_handler_disconnect(manager_.get(), account_deleted_signal_id_);
468
// Remove all accounts
470
std::unique_lock<std::mutex> lock(mutex_);
471
for (auto const& info : accounts_)
473
// Before we nuke the map, ensure that any pending sessions are done
475
std::lock_guard<std::mutex> info_lock(info.second->mutex);
476
g_signal_handler_disconnect(info.second->account_service.get(), info.second->service_update_signal_id_);
478
flush_pending_session(info.second.get(), lock);
484
void OnlineAccountClientImpl::flush_pending_session(AccountInfo* info, std::unique_lock<std::mutex>& lock)
486
std::unique_lock<std::mutex> info_lock(info->mutex);
488
// Wait until all currently running login sessions are done
489
// (ensures that accounts_ is up to date)
490
std::shared_ptr<GMainLoop> event_loop;
491
event_loop.reset(g_main_loop_new(main_loop_context_.get(), true), g_main_loop_unref);
494
// We need to wait inside an event loop to allow for the main application loop to
495
// process its pending events
496
std::shared_ptr<GSource> source;
497
source.reset(g_timeout_source_new(10), g_source_unref);
498
g_source_set_callback(source.get(), wake_up_event_loop_cb, event_loop.get(), NULL);
499
g_source_attach(source.get(), main_loop_context_.get());
503
g_main_loop_run(event_loop.get());
509
void OnlineAccountClientImpl::main_loop_state_notify(bool is_running)
511
bool was_running = false;
513
std::lock_guard<std::mutex> lock(mutex_);
514
was_running = main_loop_is_running_;
516
if (!was_running && is_running)
520
else if (was_running && !is_running)
525
std::lock_guard<std::mutex> lock(mutex_);
526
main_loop_is_running_ = is_running;
530
std::shared_ptr<AgManager> OnlineAccountClientImpl::manager()
535
std::string OnlineAccountClientImpl::service_name()
537
return service_name_;
540
std::shared_ptr<GMainContext> OnlineAccountClientImpl::main_loop_context()
542
return main_loop_context_;
545
void OnlineAccountClientImpl::callback(AccountInfo* info, std::string const& error)
547
std::unique_lock<std::mutex> lock(callback_mutex_);
548
if (callback_thread_.joinable())
551
callback_thread_.join();
555
OnlineAccountClient::ServiceStatus status = info_to_details(info, error);
556
callback_thread_ = std::thread([this, status]
558
std::lock_guard<std::mutex> lock(callback_mutex_);
565
// Clear session info
566
std::lock_guard<std::mutex> info_lock(info->mutex);
567
info->session = nullptr;
570
bool OnlineAccountClientImpl::has_account(AgAccountId const& account_id)
572
std::lock_guard<std::mutex> lock(mutex_);
573
return accounts_.find(account_id) != accounts_.end();
576
void OnlineAccountClientImpl::add_account(AgAccountId const& account_id, std::shared_ptr<AccountInfo> account_info)
578
std::lock_guard<std::mutex> lock(mutex_);
579
accounts_.insert(std::make_pair(account_id, account_info));
582
void OnlineAccountClientImpl::remove_account(AgAccountId const& account_id)
584
std::unique_lock<std::mutex> lock(mutex_);
587
auto info_it = accounts_.find(account_id);
588
if (info_it == accounts_.end())
592
auto info = info_it->second;
594
// Before we nuke the pointer, ensure that any pending sessions are done
596
std::lock_guard<std::mutex> info_lock(info->mutex);
597
g_signal_handler_disconnect(info->account_service.get(), info->service_update_signal_id_);
599
flush_pending_session(info.get(), lock);
601
// Remove account info from accounts_ map
602
accounts_.erase(account_id);
605
void OnlineAccountClientImpl::main_loop_thread()
607
// If something goes wrong causing the thread to abort, the destruction of this pointer update the
608
// main_loop_is_running_ state accordingly.
609
std::shared_ptr<void> thread_exit_notifier(nullptr, [this](void*){ main_loop_state_notify(false); });
613
main_loop_context_.reset(g_main_context_new(), g_main_context_unref);
614
g_main_context_push_thread_default(main_loop_context_.get());
616
// Stick a method call into the main loop to notify the constructor when the main loop begins running
617
g_main_context_invoke(main_loop_context_.get(), main_loop_is_running_cb, this);
620
main_loop_.reset(g_main_loop_new(main_loop_context_.get(), true), g_main_loop_unref);
621
g_main_loop_run(main_loop_.get());
624
catch (std::exception const& e)
626
std::cerr << "OnlineAccountClientImpl::main_loop_thread(): Thread aborted: " << e.what() << std::endl;
627
std::lock_guard<std::mutex> lock(mutex_);
628
thread_exception_ = std::current_exception();
632
std::cerr << "OnlineAccountClientImpl::main_loop_thread(): Thread aborted: unknown exception" << std::endl;
633
std::lock_guard<std::mutex> lock(mutex_);
634
thread_exception_ = std::current_exception();
639
} // namespace internal
641
} // namespace scopes