[spotify] Add an API endpoint for logging out of Spotify

This commit is contained in:
ejurgensen 2020-06-29 22:20:29 +02:00
parent 1b74966ef1
commit 5e60527f40
6 changed files with 142 additions and 72 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
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

View File

@ -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 },

View File

@ -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)

View File

@ -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);

View File

@ -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 =

View File

@ -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);