Implemented saving and fetching Credentials

closes #4
This commit is contained in:
2023-01-24 18:51:18 +01:00
parent cccb2a3362
commit 9bd3a06695
5 changed files with 114 additions and 26 deletions

View File

@@ -29,6 +29,7 @@ import static de.b00tload.tools.lastfmtospotifyplaylist.util.Logger.logLn;
public class LastFMToSpotify {
public static final String LINE_SEPERATOR = System.getProperty("line.separator");
public static final String USER_HOME = System.getProperty("user.home");
public static HashMap<String, String> configuration;
public static void main(String[] args) {
@@ -67,7 +68,6 @@ public class LastFMToSpotify {
}
}
try {
logLn("Authenticating with Spotify...", 1);
SpotifyApi.Builder build = SpotifyApi.builder();
@@ -76,13 +76,22 @@ public class LastFMToSpotify {
build.setRedirectUri(URI.create("http://localhost:9876/callback/spotify/"));
SpotifyApi api = build.build();
AtomicBoolean waiting = new AtomicBoolean(true);
if (configuration.containsKey("cache.crypto") && TokenHelper.existsTokens()) {
AuthorizationCodeCredentials oldcred = TokenHelper.fetchTokens();
AuthorizationCodeCredentials newcred = api.authorizationCodeRefresh(api.getClientId(), api.getClientSecret(), oldcred.getRefreshToken()).build().execute();
TokenHelper.saveTokens(newcred);
configuration.put("spotify.access", newcred.getAccessToken());
} else {
try (Javalin webserver = Javalin.create().start(9876)) {
Runtime.getRuntime().addShutdownHook(new Thread(webserver::stop));
// webserver.exception(Exception.class, (exception, ctx) -> {
// ctx.result(exception.getMessage());
// });
webserver.get("/callback/spotify", ctx -> {
if(ctx.queryParamMap().containsKey("code")) {
AuthorizationCodeCredentials cred = api.authorizationCode(ctx.queryParam("code")).build().execute();
configuration.put("spotify.access", cred.getAccessToken());
if(configuration.containsKey("spotify.saveaccess")) TokenHelper.saveTokens(cred);
if(configuration.containsKey("cache.crypto")) TokenHelper.saveTokens(cred);
ctx.result("success. <script>let win = window.open(null, '_self');win.close();</script>").contentType(ContentType.TEXT_HTML).status(HttpStatus.OK);
waiting.set(false);
} else {
@@ -94,6 +103,7 @@ public class LastFMToSpotify {
//TODO: Open auth page in Browser
while (waiting.get());
}
}
logLn("Authenticating with LastFM...", 1);
Caller.getInstance().setApiRootUrl("https://ws.audioscrobbler.com/2.0/");
Caller.getInstance().setUserAgent(configuration.get("requests.useragent"));

View File

@@ -41,6 +41,7 @@ public class ArgumentHandler {
case NAME -> name(value);
case PUBLIC -> access("public");
case COLLABORATIVE -> access("collaborative");
case SPOTIFY_CACHING -> cache(value);
}
}
@@ -229,4 +230,12 @@ public class ArgumentHandler {
configuration.put("playlist.name", name);
}
public static void cache(String value){
if (value == null || value.isEmpty()) {
logLn("--spotifycache must be provided with a password. Check usage: " + Arguments.SPOTIFY_CACHING.getUsage(), 1);
System.exit(500);
}
configuration.put("cache.crypto", value);
}
}

View File

@@ -41,7 +41,9 @@ public enum Arguments {
PUBLIC("public", "[Optional] [EXCLUSIVE: public, collaborative]" + LINE_SEPERATOR
+ "Makes the playlist public.", "--public", "pP"),
COLLABORATIVE("collaborative", "[Optional] [EXCLUSIVE: public, collaborative]" + LINE_SEPERATOR
+ "Makes the playlist collaborative.", "--collaborative", "pC");
+ "Makes the playlist collaborative.", "--collaborative", "pC"),
SPOTIFY_CACHING("spotifycache", "[Optional]" + LINE_SEPERATOR
+ "Saves and (if possible) retrieves auth tokens from a locally saved file.", "--spotifycache <password>", "sTk");
private final String name;
private final String description;

View File

@@ -0,0 +1,64 @@
package de.b00tload.tools.lastfmtospotifyplaylist.util;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Path;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
public class CryptoHelper {
public static SecretKey createKeyFromPassword(String pass){
try {
KeySpec spec = new PBEKeySpec(pass.toCharArray(), "abcdefghijklmnop".getBytes(), 65536, 256); // AES-256
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] key = new byte[0];
key = f.generateSecret(spec).getEncoded();
return new SecretKeySpec(key, "AES");
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static void serializeEncrypted(Serializable obj, Path file, SecretKey key){
try {
if(file.toFile().exists()) file.toFile().delete();
if(!file.getParent().toFile().exists()) file.getParent().toFile().mkdirs();
file.toFile().createNewFile();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec("abcdefghijklmnop".getBytes());
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
SealedObject sealedObject = new SealedObject( obj, cipher);
CipherOutputStream cipherOutputStream = new CipherOutputStream( new BufferedOutputStream( new FileOutputStream( file.toFile() ) ), cipher );
ObjectOutputStream outputStream = new ObjectOutputStream( cipherOutputStream );
outputStream.writeObject( sealedObject );
outputStream.close();
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IOException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
}
public static Serializable deserializeEncrypted(Path file, SecretKey key) {
Serializable ret = null;
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec("abcdefghijklmnop".getBytes());
cipher.init(Cipher.DECRYPT_MODE, key, iv);
CipherInputStream cipherInputStream = new CipherInputStream( new BufferedInputStream( new FileInputStream( file.toFile() ) ), cipher );
ObjectInputStream inputStream = new ObjectInputStream( cipherInputStream );
SealedObject sealedObject = (SealedObject) inputStream.readObject();
ret = (Serializable) sealedObject.getObject(cipher);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IOException | IllegalBlockSizeException | ClassNotFoundException | BadPaddingException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
return ret;
}
}

View File

@@ -2,18 +2,21 @@ package de.b00tload.tools.lastfmtospotifyplaylist.util;
import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials;
import java.nio.file.Path;
import static de.b00tload.tools.lastfmtospotifyplaylist.LastFMToSpotify.USER_HOME;
import static de.b00tload.tools.lastfmtospotifyplaylist.LastFMToSpotify.configuration;
public class TokenHelper {
public static void saveTokens(AuthorizationCodeCredentials cred) {
//TODO: Save tokens
CryptoHelper.serializeEncrypted(cred, Path.of(USER_HOME, "/.lfm2s/spotify.lfm2scred"), CryptoHelper.createKeyFromPassword(configuration.get("cache.crypto")));
}
public static String getAccessToken(){
//TODO: Read AccessToken
return "";
public static AuthorizationCodeCredentials fetchTokens() {
return (AuthorizationCodeCredentials) CryptoHelper.deserializeEncrypted(Path.of(USER_HOME, "/.lfm2s/spotify.lfm2scred"), CryptoHelper.createKeyFromPassword(configuration.get("cache.crypto")));
}
public static String getRefreshToken(){
//TODO: Read RefreshToken
return "";
public static boolean existsTokens(){
return Path.of(USER_HOME, "/.lfm2s/spotify.lfm2scred").toFile().exists();
}
}