Merge branch 'spotify_logout1'

This commit is contained in:
ejurgensen 2020-07-05 20:45:54 +02:00
commit 2f702ed3ef
8 changed files with 143 additions and 77 deletions

View File

@ -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 automatically afterwards, because libspotify saves a login token. You can
configure the location of your Spotify user data in the configuration file. configure the location of your Spotify user data in the configuration file.
To permanently logout and remove credentials, delete the contents of To permanently logout and remove Spotify tracks + credentials make a request to
`/var/cache/forked-daapd/libspotify` (while forked-daapd is stopped). [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: Limitations:
You will not be able to do any playlist management through forked-daapd - use You will not be able to do any playlist management through forked-daapd - use

View File

@ -4319,7 +4319,6 @@ db_pairing_fetch_byguid(struct pairing_info *pi)
#undef Q_TMPL #undef Q_TMPL
} }
#ifdef HAVE_SPOTIFY_H
/* Spotify */ /* Spotify */
void void
db_spotify_purge(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)); DPRINTF(E_DBG, L_DB, "Deleted %d rows\n", sqlite3_changes(hdl));
#undef Q_TMPL #undef Q_TMPL
} }
#endif
/* Admin */ /* Admin */
int int
@ -4540,7 +4538,7 @@ db_admin_getint64(int64_t *int64val, const char *key)
int int
db_admin_delete(const char *key) 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; char *query;
query = sqlite3_mprintf(Q_TMPL, key); query = sqlite3_mprintf(Q_TMPL, key);

View File

@ -762,7 +762,6 @@ db_pairing_add(struct pairing_info *pi);
int int
db_pairing_fetch_byguid(struct pairing_info *pi); db_pairing_fetch_byguid(struct pairing_info *pi);
#ifdef HAVE_SPOTIFY_H
/* Spotify */ /* Spotify */
void void
db_spotify_purge(void); db_spotify_purge(void);
@ -772,7 +771,6 @@ db_spotify_pl_delete(int id);
void void
db_spotify_files_delete(void); db_spotify_files_delete(void);
#endif
/* Admin */ /* Admin */
int int

View File

@ -1251,6 +1251,15 @@ jsonapi_reply_spotify_login(struct httpd_request *hreq)
return HTTP_OK; 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 static int
jsonapi_reply_lastfm(struct httpd_request *hreq) 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/update$", jsonapi_reply_update },
{ EVHTTP_REQ_PUT, "^/api/rescan$", jsonapi_reply_meta_rescan }, { EVHTTP_REQ_PUT, "^/api/rescan$", jsonapi_reply_meta_rescan },
{ EVHTTP_REQ_POST, "^/api/spotify-login$", jsonapi_reply_spotify_login }, { 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/spotify$", jsonapi_reply_spotify },
{ EVHTTP_REQ_GET, "^/api/pairing$", jsonapi_reply_pairing_get }, { EVHTTP_REQ_GET, "^/api/pairing$", jsonapi_reply_pairing_get },
{ EVHTTP_REQ_POST, "^/api/pairing$", jsonapi_reply_pairing_pair }, { EVHTTP_REQ_POST, "^/api/pairing$", jsonapi_reply_pairing_pair },

View File

@ -1340,6 +1340,40 @@ spotify_status_info_get(struct spotify_status_info *info)
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&status_lck)); 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 */ /* Thread: library, httpd */
static int static int
login_user(const char *user, const char *password, char **errmsg) 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; return -1;
} }
if (SP_CONNECTION_STATE_LOGGED_IN == fptr_sp_session_connectionstate(g_sess)) ret = logout(errmsg);
{ if (ret < 0)
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; return -1;
}
CHECK_ERR(L_SPOTIFY, pthread_cond_wait(&login_cond, &login_lck));
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
}
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck)); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck));
@ -1457,6 +1472,14 @@ spotify_login(char **arglist)
spotify_login_user(NULL, NULL, NULL); spotify_login_user(NULL, NULL, NULL);
} }
void
spotify_logout(void)
{
logout(NULL);
spotifywebapi_purge();
}
/* Thread: main */ /* Thread: main */
int int
spotify_init(void) spotify_init(void)

View File

@ -50,6 +50,9 @@ spotify_login_user(const char *user, const char *password, char **errmsg);
void void
spotify_login(char **arglist); spotify_login(char **arglist);
void
spotify_logout(void);
void void
spotify_status_info_get(struct spotify_status_info *info); spotify_status_info_get(struct spotify_status_info *info);

View File

@ -94,14 +94,19 @@ struct spotify_playlist
}; };
// Credentials for the web api // Credentials for the web api
static char *spotify_access_token; struct spotify_credentials
static char *spotify_refresh_token; {
static char *spotify_granted_scope; char *access_token;
static char *spotify_user_country; char *refresh_token;
static char *spotify_user; char *granted_scope;
char *user_country;
char *user;
static int32_t expires_in = 3600; int32_t token_expires_in;
static time_t token_requested = 0; 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 // Mutex to avoid conflicting requests for access tokens and protects accessing the credentials from different threads
static pthread_mutex_t token_lck; 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 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 static void
free_http_client_ctx(struct http_client_ctx *ctx) free_http_client_ctx(struct http_client_ctx *ctx)
@ -155,7 +171,7 @@ free_http_client_ctx(struct http_client_ctx *ctx)
static bool static bool
token_valid(void) token_valid(void)
{ {
return spotify_access_token != NULL; return spotify_credentials.access_token != NULL;
} }
static int static int
@ -209,34 +225,34 @@ request_access_tokens(struct keyval *kv, const char **err)
goto out_free_input_body; goto out_free_input_body;
} }
free(spotify_access_token); free(spotify_credentials.access_token);
spotify_access_token = NULL; spotify_credentials.access_token = NULL;
tmp = jparse_str_from_obj(haystack, "access_token"); tmp = jparse_str_from_obj(haystack, "access_token");
if (tmp) if (tmp)
spotify_access_token = strdup(tmp); spotify_credentials.access_token = strdup(tmp);
tmp = jparse_str_from_obj(haystack, "refresh_token"); tmp = jparse_str_from_obj(haystack, "refresh_token");
if (tmp) if (tmp)
{ {
free(spotify_refresh_token); free(spotify_credentials.refresh_token);
spotify_refresh_token = strdup(tmp); spotify_credentials.refresh_token = strdup(tmp);
} }
tmp = jparse_str_from_obj(haystack, "scope"); tmp = jparse_str_from_obj(haystack, "scope");
if (tmp) if (tmp)
{ {
free(spotify_granted_scope); free(spotify_credentials.granted_scope);
spotify_granted_scope = strdup(tmp); spotify_credentials.granted_scope = strdup(tmp);
} }
expires_in = jparse_int_from_obj(haystack, "expires_in"); spotify_credentials.token_expires_in = jparse_int_from_obj(haystack, "expires_in");
if (expires_in == 0) if (spotify_credentials.token_expires_in == 0)
expires_in = 3600; spotify_credentials.token_expires_in = 3600;
jparse_free(haystack); 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); 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; goto out_free_input_body;
} }
token_requested = time(NULL); spotify_credentials.token_time_requested = time(NULL);
if (spotify_refresh_token) if (spotify_credentials.refresh_token)
db_admin_set(DB_ADMIN_SPOTIFY_REFRESH_TOKEN, spotify_refresh_token); db_admin_set(DB_ADMIN_SPOTIFY_REFRESH_TOKEN, spotify_credentials.refresh_token);
ret = 0; ret = 0;
@ -281,7 +297,7 @@ request_endpoint(const char *uri)
ctx->input_body = evbuffer_new(); ctx->input_body = evbuffer_new();
ctx->url = uri; 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) 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); 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; json_object *response;
free(spotify_user_country); free(spotify_credentials.user_country);
spotify_user_country = NULL; spotify_credentials.user_country = NULL;
free(spotify_user); free(spotify_credentials.user);
spotify_user = NULL; spotify_credentials.user = NULL;
response = request_endpoint(spotify_me_uri); response = request_endpoint(spotify_me_uri);
if (response) if (response)
{ {
spotify_user = safe_strdup(jparse_str_from_obj(response, "id")); spotify_credentials.user = safe_strdup(jparse_str_from_obj(response, "id"));
spotify_user_country = safe_strdup(jparse_str_from_obj(response, "country")); spotify_credentials.user_country = safe_strdup(jparse_str_from_obj(response, "country"));
jparse_free(response); 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; return 0;
@ -411,7 +427,7 @@ token_refresh(void)
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck)); 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"); 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 total;
int ret; int ret;
if (!with_market || !spotify_user_country) if (!with_market || !spotify_credentials.user_country)
{ {
next_href = safe_strdup(href); next_href = safe_strdup(href);
} }
else else
{ {
if (strchr(href, '?')) 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 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) while (next_href)
@ -1815,6 +1831,19 @@ webapi_rescan(void *arg, int *ret)
return COMMAND_END; 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 */ /* Thread: library */
static enum command_state static enum command_state
webapi_pl_save(void *arg, int *ret) webapi_pl_save(void *arg, int *ret)
@ -1887,6 +1916,12 @@ spotifywebapi_rescan(void)
library_exec_async(webapi_rescan, NULL); library_exec_async(webapi_rescan, NULL);
} }
void
spotifywebapi_purge(void)
{
library_exec_async(webapi_purge, NULL);
}
void void
spotifywebapi_pl_save(const char *uri) 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)); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck));
info->token_valid = token_valid(); 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) 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)); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck));
if (token_requested > 0) if (spotify_credentials.token_time_requested > 0)
info->expires_in = expires_in - difftime(time(NULL), token_requested); info->expires_in = spotify_credentials.token_expires_in - difftime(time(NULL), spotify_credentials.token_time_requested);
else else
info->expires_in = 0; 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)); CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&token_lck));
} }
@ -1999,11 +2034,7 @@ spotifywebapi_deinit()
spotify_deinit(); spotify_deinit();
free(spotify_access_token); free_credentials();
free(spotify_refresh_token);
free(spotify_granted_scope);
free(spotify_user_country);
free(spotify_user);
} }
struct library_source spotifyscanner = struct library_source spotifyscanner =

View File

@ -52,6 +52,8 @@ spotifywebapi_fullrescan(void);
void void
spotifywebapi_rescan(void); spotifywebapi_rescan(void);
void void
spotifywebapi_purge(void);
void
spotifywebapi_pl_save(const char *uri); spotifywebapi_pl_save(const char *uri);
void void
spotifywebapi_pl_remove(const char *uri); spotifywebapi_pl_remove(const char *uri);