From 1b74966ef14f35bbae6f5d99e1827ca063eb8d95 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 29 Jun 2020 22:17:44 +0200 Subject: [PATCH 1/2] [db] Remove HAVE_SPOTIFY_H conditionals, not required The db functions don't depend on the Spotify library, so remove conditionals --- src/db.c | 4 +--- src/db.h | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/db.c b/src/db.c index 1244f84c..25ec6673 100644 --- a/src/db.c +++ b/src/db.c @@ -4319,7 +4319,6 @@ db_pairing_fetch_byguid(struct pairing_info *pi) #undef Q_TMPL } -#ifdef HAVE_SPOTIFY_H /* Spotify */ void db_spotify_purge(void) @@ -4399,7 +4398,6 @@ db_spotify_files_delete(void) DPRINTF(E_DBG, L_DB, "Deleted %d rows\n", sqlite3_changes(hdl)); #undef Q_TMPL } -#endif /* Admin */ int @@ -4540,7 +4538,7 @@ db_admin_getint64(int64_t *int64val, const char *key) int db_admin_delete(const char *key) { -#define Q_TMPL "DELETE FROM admin where key='%q';" +#define Q_TMPL "DELETE FROM admin WHERE key='%q';" char *query; query = sqlite3_mprintf(Q_TMPL, key); diff --git a/src/db.h b/src/db.h index 76b0850f..22084fce 100644 --- a/src/db.h +++ b/src/db.h @@ -762,7 +762,6 @@ db_pairing_add(struct pairing_info *pi); int db_pairing_fetch_byguid(struct pairing_info *pi); -#ifdef HAVE_SPOTIFY_H /* Spotify */ void db_spotify_purge(void); @@ -772,7 +771,6 @@ db_spotify_pl_delete(int id); void db_spotify_files_delete(void); -#endif /* Admin */ int From 5e60527f402b3245c57f7cefd63f6652c1d2f3ef Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 29 Jun 2020 22:20:29 +0200 Subject: [PATCH 2/2] [spotify] Add an API endpoint for logging out of Spotify --- README.md | 5 +- src/httpd_jsonapi.c | 10 ++++ src/spotify.c | 67 +++++++++++++++-------- src/spotify.h | 3 + src/spotify_webapi.c | 127 +++++++++++++++++++++++++++---------------- src/spotify_webapi.h | 2 + 6 files changed, 142 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index f2cf8c9e..2b002c33 100644 --- a/README.md +++ b/README.md @@ -497,8 +497,9 @@ forked-daapd will not store your password, but will still be able to log you in automatically afterwards, because libspotify saves a login token. You can configure the location of your Spotify user data in the configuration file. -To permanently logout and remove credentials, delete the contents of -`/var/cache/forked-daapd/libspotify` (while forked-daapd is stopped). +To permanently logout and remove Spotify tracks + credentials make a request to +[http://[your_server_address_here]:3689/api/spotify-logout](http://[your_server_address_here]:3689/api/spotify-logout) +and also delete the contents of `/var/cache/forked-daapd/libspotify`. Limitations: You will not be able to do any playlist management through forked-daapd - use diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index ec6922ba..89a9d2e0 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -1251,6 +1251,15 @@ jsonapi_reply_spotify_login(struct httpd_request *hreq) return HTTP_OK; } +static int +jsonapi_reply_spotify_logout(struct httpd_request *hreq) +{ +#ifdef HAVE_SPOTIFY_H + spotify_logout(); +#endif + return HTTP_NOCONTENT; +} + static int jsonapi_reply_lastfm(struct httpd_request *hreq) { @@ -4217,6 +4226,7 @@ static struct httpd_uri_map adm_handlers[] = EVHTTP_REQ_PUT, "^/api/update$", jsonapi_reply_update }, { EVHTTP_REQ_PUT, "^/api/rescan$", jsonapi_reply_meta_rescan }, { EVHTTP_REQ_POST, "^/api/spotify-login$", jsonapi_reply_spotify_login }, + { EVHTTP_REQ_GET, "^/api/spotify-logout$", jsonapi_reply_spotify_logout }, { EVHTTP_REQ_GET, "^/api/spotify$", jsonapi_reply_spotify }, { EVHTTP_REQ_GET, "^/api/pairing$", jsonapi_reply_pairing_get }, { EVHTTP_REQ_POST, "^/api/pairing$", jsonapi_reply_pairing_pair }, diff --git a/src/spotify.c b/src/spotify.c index 9e842972..53ba6655 100644 --- a/src/spotify.c +++ b/src/spotify.c @@ -1340,6 +1340,40 @@ spotify_status_info_get(struct spotify_status_info *info) CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&status_lck)); } +/* Thread: library, httpd */ +static int +logout(char **errmsg) +{ + sp_error err; + + CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck)); + + if (SP_CONNECTION_STATE_LOGGED_IN != fptr_sp_session_connectionstate(g_sess)) + { + CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck)); + return 0; + } + + DPRINTF(E_LOG, L_SPOTIFY, "Logging out of Spotify (current state is %d)\n", g_state); + + fptr_sp_session_player_unload(g_sess); + err = fptr_sp_session_logout(g_sess); + if (SP_ERROR_OK != err) + { + DPRINTF(E_LOG, L_SPOTIFY, "Could not logout of Spotify: %s\n", fptr_sp_error_message(err)); + if (errmsg) + *errmsg = safe_asprintf("Could not logout of Spotify: %s", fptr_sp_error_message(err)); + + CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck)); + return -1; + } + + CHECK_ERR(L_SPOTIFY, pthread_cond_wait(&login_cond, &login_lck)); // Wait for logged_out() + CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck)); + + return 0; +} + /* Thread: library, httpd */ static int login_user(const char *user, const char *password, char **errmsg) @@ -1365,28 +1399,9 @@ login_user(const char *user, const char *password, char **errmsg) return -1; } - if (SP_CONNECTION_STATE_LOGGED_IN == fptr_sp_session_connectionstate(g_sess)) - { - CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck)); - - DPRINTF(E_LOG, L_SPOTIFY, "Logging out of Spotify (current state is %d)\n", g_state); - - fptr_sp_session_player_unload(g_sess); - err = fptr_sp_session_logout(g_sess); - - if (SP_ERROR_OK != err) - { - DPRINTF(E_LOG, L_SPOTIFY, "Could not logout of Spotify: %s\n", fptr_sp_error_message(err)); - if (errmsg) - *errmsg = safe_asprintf("Could not logout of Spotify: %s", fptr_sp_error_message(err)); - - CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck)); - return -1; - } - - CHECK_ERR(L_SPOTIFY, pthread_cond_wait(&login_cond, &login_lck)); - CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck)); - } + ret = logout(errmsg); + if (ret < 0) + return -1; CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck)); @@ -1457,6 +1472,14 @@ spotify_login(char **arglist) spotify_login_user(NULL, NULL, NULL); } +void +spotify_logout(void) +{ + logout(NULL); + + spotifywebapi_purge(); +} + /* Thread: main */ int spotify_init(void) diff --git a/src/spotify.h b/src/spotify.h index 2e2f47c9..610d17b2 100644 --- a/src/spotify.h +++ b/src/spotify.h @@ -50,6 +50,9 @@ spotify_login_user(const char *user, const char *password, char **errmsg); void spotify_login(char **arglist); +void +spotify_logout(void); + void spotify_status_info_get(struct spotify_status_info *info); diff --git a/src/spotify_webapi.c b/src/spotify_webapi.c index 3ebe4818..9ace4d1f 100644 --- a/src/spotify_webapi.c +++ b/src/spotify_webapi.c @@ -94,14 +94,19 @@ struct spotify_playlist }; // Credentials for the web api -static char *spotify_access_token; -static char *spotify_refresh_token; -static char *spotify_granted_scope; -static char *spotify_user_country; -static char *spotify_user; +struct spotify_credentials +{ + char *access_token; + char *refresh_token; + char *granted_scope; + char *user_country; + char *user; -static int32_t expires_in = 3600; -static time_t token_requested = 0; + int32_t token_expires_in; + time_t token_time_requested; +}; + +static struct spotify_credentials spotify_credentials; // Mutex to avoid conflicting requests for access tokens and protects accessing the credentials from different threads static pthread_mutex_t token_lck; @@ -135,6 +140,17 @@ static const char *spotify_playlist_tracks_uri = "https://api.spotify.com/v1/pla static const char *spotify_artist_albums_uri = "https://api.spotify.com/v1/artists/%s/albums?include_groups=album,single"; +static void +free_credentials(void) +{ + free(spotify_credentials.access_token); + free(spotify_credentials.refresh_token); + free(spotify_credentials.granted_scope); + free(spotify_credentials.user_country); + free(spotify_credentials.user); + + memset(&spotify_credentials, 0, sizeof(struct spotify_credentials)); +} static void free_http_client_ctx(struct http_client_ctx *ctx) @@ -155,7 +171,7 @@ free_http_client_ctx(struct http_client_ctx *ctx) static bool token_valid(void) { - return spotify_access_token != NULL; + return spotify_credentials.access_token != NULL; } static int @@ -209,34 +225,34 @@ request_access_tokens(struct keyval *kv, const char **err) goto out_free_input_body; } - free(spotify_access_token); - spotify_access_token = NULL; + free(spotify_credentials.access_token); + spotify_credentials.access_token = NULL; tmp = jparse_str_from_obj(haystack, "access_token"); if (tmp) - spotify_access_token = strdup(tmp); + spotify_credentials.access_token = strdup(tmp); tmp = jparse_str_from_obj(haystack, "refresh_token"); if (tmp) { - free(spotify_refresh_token); - spotify_refresh_token = strdup(tmp); + free(spotify_credentials.refresh_token); + spotify_credentials.refresh_token = strdup(tmp); } tmp = jparse_str_from_obj(haystack, "scope"); if (tmp) { - free(spotify_granted_scope); - spotify_granted_scope = strdup(tmp); + free(spotify_credentials.granted_scope); + spotify_credentials.granted_scope = strdup(tmp); } - expires_in = jparse_int_from_obj(haystack, "expires_in"); - if (expires_in == 0) - expires_in = 3600; + spotify_credentials.token_expires_in = jparse_int_from_obj(haystack, "expires_in"); + if (spotify_credentials.token_expires_in == 0) + spotify_credentials.token_expires_in = 3600; jparse_free(haystack); - if (!spotify_access_token) + if (!spotify_credentials.access_token) { DPRINTF(E_LOG, L_SPOTIFY, "Could not find access token in reply: %s\n", body); @@ -245,10 +261,10 @@ request_access_tokens(struct keyval *kv, const char **err) goto out_free_input_body; } - token_requested = time(NULL); + spotify_credentials.token_time_requested = time(NULL); - if (spotify_refresh_token) - db_admin_set(DB_ADMIN_SPOTIFY_REFRESH_TOKEN, spotify_refresh_token); + if (spotify_credentials.refresh_token) + db_admin_set(DB_ADMIN_SPOTIFY_REFRESH_TOKEN, spotify_credentials.refresh_token); ret = 0; @@ -281,7 +297,7 @@ request_endpoint(const char *uri) ctx->input_body = evbuffer_new(); ctx->url = uri; - snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", spotify_access_token); + snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", spotify_credentials.access_token); if (keyval_add(ctx->output_headers, "Authorization", bearer_token) < 0) { DPRINTF(E_LOG, L_SPOTIFY, "Add bearer_token to keyval failed for request '%s'\n", uri); @@ -331,21 +347,21 @@ request_user_info(void) { json_object *response; - free(spotify_user_country); - spotify_user_country = NULL; - free(spotify_user); - spotify_user = NULL; + free(spotify_credentials.user_country); + spotify_credentials.user_country = NULL; + free(spotify_credentials.user); + spotify_credentials.user = NULL; response = request_endpoint(spotify_me_uri); if (response) { - spotify_user = safe_strdup(jparse_str_from_obj(response, "id")); - spotify_user_country = safe_strdup(jparse_str_from_obj(response, "country")); + spotify_credentials.user = safe_strdup(jparse_str_from_obj(response, "id")); + spotify_credentials.user_country = safe_strdup(jparse_str_from_obj(response, "country")); jparse_free(response); - DPRINTF(E_DBG, L_SPOTIFY, "User '%s', country '%s'\n", spotify_user, spotify_user_country); + DPRINTF(E_DBG, L_SPOTIFY, "User '%s', country '%s'\n", spotify_credentials.user, spotify_credentials.user_country); } return 0; @@ -411,7 +427,7 @@ token_refresh(void) CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck)); - if (token_requested && difftime(time(NULL), token_requested) < expires_in) + if (spotify_credentials.token_time_requested && difftime(time(NULL), spotify_credentials.token_time_requested) < spotify_credentials.token_expires_in) { DPRINTF(E_DBG, L_SPOTIFY, "Spotify token still valid\n"); @@ -524,16 +540,16 @@ request_pagingobject_endpoint(const char *href, paging_item_cb item_cb, paging_r int total; int ret; - if (!with_market || !spotify_user_country) + if (!with_market || !spotify_credentials.user_country) { next_href = safe_strdup(href); } else { if (strchr(href, '?')) - next_href = safe_asprintf("%s&market=%s", href, spotify_user_country); + next_href = safe_asprintf("%s&market=%s", href, spotify_credentials.user_country); else - next_href = safe_asprintf("%s?market=%s", href, spotify_user_country); + next_href = safe_asprintf("%s?market=%s", href, spotify_credentials.user_country); } while (next_href) @@ -1815,6 +1831,19 @@ webapi_rescan(void *arg, int *ret) return COMMAND_END; } +/* Thread: library */ +static enum command_state +webapi_purge(void *arg, int *ret) +{ + free_credentials(); + + db_spotify_purge(); + db_admin_delete(DB_ADMIN_SPOTIFY_REFRESH_TOKEN); + + *ret = 0; + return COMMAND_END; +} + /* Thread: library */ static enum command_state webapi_pl_save(void *arg, int *ret) @@ -1887,6 +1916,12 @@ spotifywebapi_rescan(void) library_exec_async(webapi_rescan, NULL); } +void +spotifywebapi_purge(void) +{ + library_exec_async(webapi_purge, NULL); +} + void spotifywebapi_pl_save(const char *uri) { @@ -1942,17 +1977,17 @@ spotifywebapi_status_info_get(struct spotifywebapi_status_info *info) CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck)); info->token_valid = token_valid(); - if (spotify_user) + if (spotify_credentials.user) { - strncpy(info->user, spotify_user, (sizeof(info->user) - 1)); + strncpy(info->user, spotify_credentials.user, (sizeof(info->user) - 1)); } - if (spotify_user_country) + if (spotify_credentials.user_country) { - strncpy(info->country, spotify_user_country, (sizeof(info->country) - 1)); + strncpy(info->country, spotify_credentials.user_country, (sizeof(info->country) - 1)); } - if (spotify_granted_scope) + if (spotify_credentials.granted_scope) { - strncpy(info->granted_scope, spotify_granted_scope, (sizeof(info->granted_scope) - 1)); + strncpy(info->granted_scope, spotify_credentials.granted_scope, (sizeof(info->granted_scope) - 1)); } if (spotify_scope) { @@ -1971,12 +2006,12 @@ spotifywebapi_access_token_get(struct spotifywebapi_access_token *info) CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck)); - if (token_requested > 0) - info->expires_in = expires_in - difftime(time(NULL), token_requested); + if (spotify_credentials.token_time_requested > 0) + info->expires_in = spotify_credentials.token_expires_in - difftime(time(NULL), spotify_credentials.token_time_requested); else info->expires_in = 0; - info->token = safe_strdup(spotify_access_token); + info->token = safe_strdup(spotify_credentials.access_token); CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&token_lck)); } @@ -1999,11 +2034,7 @@ spotifywebapi_deinit() spotify_deinit(); - free(spotify_access_token); - free(spotify_refresh_token); - free(spotify_granted_scope); - free(spotify_user_country); - free(spotify_user); + free_credentials(); } struct library_source spotifyscanner = diff --git a/src/spotify_webapi.h b/src/spotify_webapi.h index 8c8a04e1..894f1f6b 100644 --- a/src/spotify_webapi.h +++ b/src/spotify_webapi.h @@ -52,6 +52,8 @@ spotifywebapi_fullrescan(void); void spotifywebapi_rescan(void); void +spotifywebapi_purge(void); +void spotifywebapi_pl_save(const char *uri); void spotifywebapi_pl_remove(const char *uri);