Describe the bug
403 Status code is returned from a Mercury request when requesting token, see
Screenshots/Stracktraces/Logs
Version/Commit
1.6.6
Patch
As @photovoltex suggested in librespot issue 1532, I've implemented a change to use Login5:
- the login requires a b64 credentials string which we can still issue using AuthStrategy "OAUTH"
- librespot will then create the
credentials.json which we use in the AuthStrategy "STORED" replacing mercury with Login5
- I have added a new helper class that generates the token using Login5 and changed the TokenProvider to use that class ...
Although it is not a complete solution, it works for now.
This is the patch
Replaced_getting_token_via_mercury_with_Login5.patch
Subject: [PATCH] Replaced getting token via mercury with Login5
---
Index: lib/src/main/java/xyz/gianlu/librespot/core/Login5TokenHelper.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/lib/src/main/java/xyz/gianlu/librespot/core/Login5TokenHelper.java b/lib/src/main/java/xyz/gianlu/librespot/core/Login5TokenHelper.java
new file mode 100644
--- /dev/null (revision bf18fb6689373c435bf94fa978770accf222cddd)
+++ b/lib/src/main/java/xyz/gianlu/librespot/core/Login5TokenHelper.java (revision bf18fb6689373c435bf94fa978770accf222cddd)
@@ -0,0 +1,82 @@
+package xyz.gianlu.librespot.core;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.protobuf.ByteString;
+import com.spotify.login5v3.Credentials;
+import com.spotify.login5v3.Login5;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Base64;
+
+public final class Login5TokenHelper {
+ private Login5TokenHelper() {}
+
+ /** Parse credentials.json and build the login5 StoredCredential. */
+ private static Credentials.StoredCredential readStoredCredential(File storedCredentialsFile) throws IOException {
+ try (FileReader reader = new FileReader(storedCredentialsFile)) {
+ JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject();
+ String username = obj.get("username").getAsString();
+ String base64Creds = obj.get("credentials").getAsString();
+ byte[] data = Base64.getDecoder().decode(base64Creds);
+
+ return Credentials.StoredCredential.newBuilder()
+ .setUsername(username)
+ .setData(ByteString.copyFrom(data))
+ .build();
+ }
+ }
+
+ /**
+ * Fetch a StoredToken via login5 using an absolute path to the credentials.json file.
+ *
+ * Requires a public static factory in TokenProvider:
+ *
+ * public static StoredToken fromJsonObject(com.google.gson.JsonObject obj) {
+ * return new StoredToken(obj);
+ * }
+ */
+ public static TokenProvider.StoredToken fetchStoredToken(Session session,
+ Path storedCredentialsPath,
+ String... scopes) throws IOException {
+ File storedFile = storedCredentialsPath.toFile();
+ if (!storedFile.isFile()) {
+ throw new IOException("Credentials file not found: " + storedFile.getAbsolutePath());
+ }
+
+ Credentials.StoredCredential sc = readStoredCredential(storedFile);
+
+ String accessToken;
+ int expiresIn;
+
+ try {
+ Login5Api api = new Login5Api(session);
+ Login5.LoginResponse resp = api.login5(
+ Login5.LoginRequest.newBuilder()
+ .setStoredCredential(sc)
+ .build());
+
+ if (!resp.hasOk()) throw new IOException("login5 did not return OK: " + resp);
+ accessToken = resp.getOk().getAccessToken();
+ expiresIn = resp.getOk().getAccessTokenExpiresIn();
+ } catch (Exception e) {
+ throw new IOException("login5 request failed", e);
+ }
+
+ // Build JSON that StoredToken expects: scope must be an array
+ JsonArray scopeArr = new JsonArray();
+ for (String s : scopes) scopeArr.add(s);
+
+ JsonObject obj = new JsonObject();
+ obj.addProperty("accessToken", accessToken);
+ obj.addProperty("expiresIn", expiresIn);
+ obj.addProperty("tokenType", "Bearer");
+ obj.add("scope", scopeArr);
+
+ return TokenProvider.fromJsonObject(obj); // Requires factory method in TokenProvider
+ }
+}
Index: lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java b/lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java
--- a/lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java (revision 1437b1581efa92c08f9215f0be930237f5e6cda6)
+++ b/lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java (revision bf18fb6689373c435bf94fa978770accf222cddd)
@@ -23,9 +23,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.gianlu.librespot.common.Utils;
-import xyz.gianlu.librespot.json.GenericJson;
import xyz.gianlu.librespot.mercury.MercuryClient;
-import xyz.gianlu.librespot.mercury.MercuryRequests;
import java.io.IOException;
import java.util.ArrayList;
@@ -55,6 +53,7 @@
return null;
}
+
@NotNull
public synchronized StoredToken getToken(@NotNull String... scopes) throws IOException, MercuryClient.MercuryException {
if (scopes.length == 0) throw new IllegalArgumentException();
@@ -66,8 +65,17 @@
}
LOGGER.debug("Token expired or not suitable, requesting again. {scopes: {}, oldToken: {}}", Arrays.asList(scopes), token);
- GenericJson resp = session.mercury().sendSync(MercuryRequests.requestToken(session.deviceId(), String.join(",", scopes)));
- token = new StoredToken(resp.obj);
+
+
+ //GenericJson resp = session.mercury().sendSync(MercuryRequests.requestToken(session.deviceId(), String.join(",", scopes)));
+ //token = new StoredToken(obj);
+ Session.Configuration conf = session.configuration();
+
+ try {
+ token = Login5TokenHelper.fetchStoredToken(session, java.nio.file.Paths.get(conf.storedCredentialsFile.getAbsolutePath()), scopes);
+ } catch (IOException e) {
+ throw new IOException("Failed to get token via login5", e);
+ }
LOGGER.debug("Updated token successfully! {scopes: {}, newToken: {}}", Arrays.asList(scopes), token);
tokens.add(token);
@@ -80,6 +88,10 @@
return getToken(scope).accessToken;
}
+ public static StoredToken fromJsonObject(com.google.gson.JsonObject obj) {
+ return new StoredToken(obj);
+ }
+
public static class StoredToken {
public final int expiresIn;
public final String accessToken;
Describe the bug
403 Status code is returned from a Mercury request when requesting token, see
Screenshots/Stracktraces/Logs
Version/Commit
1.6.6
Patch
As @photovoltex suggested in librespot issue 1532, I've implemented a change to use Login5:
credentials.jsonwhich we use in the AuthStrategy "STORED" replacing mercury with Login5Although it is not a complete solution, it works for now.
This is the patch
Replaced_getting_token_via_mercury_with_Login5.patch