Fixed Subsonic API version detection
Fixed server change detection Minor fixes
This commit is contained in:
parent
356af198e0
commit
a396b4b27b
|
@ -1,9 +1,9 @@
|
|||
package org.moire.ultrasonic.api.subsonic.interceptors
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
import java.util.Locale
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Interceptor.Chain
|
||||
import okhttp3.Response
|
||||
|
@ -15,27 +15,33 @@ import okhttp3.Response
|
|||
* and above.
|
||||
*/
|
||||
class PasswordMD5Interceptor(private val password: String) : Interceptor {
|
||||
private val salt: String by lazy {
|
||||
val secureRandom = SecureRandom()
|
||||
BigInteger(130, secureRandom).toString(32)
|
||||
}
|
||||
|
||||
private val passwordMD5Hash: String by lazy {
|
||||
try {
|
||||
val md5Digest = MessageDigest.getInstance("MD5")
|
||||
md5Digest.digest("$password$salt".toByteArray()).toHexBytes().toLowerCase()
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
}
|
||||
private val secureRandom = SecureRandom()
|
||||
private val saltBytes = ByteArray(16)
|
||||
|
||||
override fun intercept(chain: Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
val salt = getSalt()
|
||||
val updatedUrl = originalRequest.url().newBuilder()
|
||||
.addQueryParameter("t", passwordMD5Hash)
|
||||
.addQueryParameter("t", getPasswordMD5Hash(salt))
|
||||
.addQueryParameter("s", salt)
|
||||
.build()
|
||||
|
||||
return chain.proceed(originalRequest.newBuilder().url(updatedUrl).build())
|
||||
}
|
||||
|
||||
private fun getSalt(): String {
|
||||
secureRandom.nextBytes(saltBytes)
|
||||
return saltBytes.toHexBytes()
|
||||
}
|
||||
|
||||
private fun getPasswordMD5Hash(salt: String): String {
|
||||
try {
|
||||
val md5Digest = MessageDigest.getInstance("MD5")
|
||||
return md5Digest.digest(
|
||||
"$password$salt".toByteArray()
|
||||
).toHexBytes().toLowerCase(Locale.getDefault())
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import android.widget.TextView;
|
|||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.data.ServerSetting;
|
||||
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
|
@ -55,7 +56,7 @@ public class MainActivity extends SubsonicTabActivity
|
|||
{
|
||||
private static boolean infoDialogDisplayed;
|
||||
private static boolean shouldUseId3;
|
||||
private static int lastActiveServer;
|
||||
private static String lastActiveServerProperties;
|
||||
|
||||
private Lazy<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
|
||||
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
|
@ -121,7 +122,7 @@ public class MainActivity extends SubsonicTabActivity
|
|||
final View albumsAlphaByArtistButton = buttons.findViewById(R.id.main_albums_alphaByArtist);
|
||||
final View videosButton = buttons.findViewById(R.id.main_videos);
|
||||
|
||||
lastActiveServer = ActiveServerProvider.Companion.getActiveServerId(this);
|
||||
lastActiveServerProperties = getActiveServerProperties();
|
||||
String name = activeServerProvider.getValue().getActiveServer().getName();
|
||||
|
||||
serverTextView.setText(name);
|
||||
|
@ -260,7 +261,7 @@ public class MainActivity extends SubsonicTabActivity
|
|||
boolean shouldRestart = false;
|
||||
|
||||
boolean id3 = Util.getShouldUseId3Tags(MainActivity.this);
|
||||
int currentActiveServer = ActiveServerProvider.Companion.getActiveServerId(MainActivity.this);
|
||||
String currentActiveServerProperties = getActiveServerProperties();
|
||||
|
||||
if (id3 != shouldUseId3)
|
||||
{
|
||||
|
@ -268,9 +269,9 @@ public class MainActivity extends SubsonicTabActivity
|
|||
shouldRestart = true;
|
||||
}
|
||||
|
||||
if (currentActiveServer != lastActiveServer)
|
||||
if (!currentActiveServerProperties.equals(lastActiveServerProperties))
|
||||
{
|
||||
lastActiveServer = currentActiveServer;
|
||||
lastActiveServerProperties = currentActiveServerProperties;
|
||||
shouldRestart = true;
|
||||
}
|
||||
|
||||
|
@ -378,6 +379,14 @@ public class MainActivity extends SubsonicTabActivity
|
|||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
private String getActiveServerProperties()
|
||||
{
|
||||
ServerSetting currentSetting = activeServerProvider.getValue().getActiveServer();
|
||||
return String.format("%s;%s;%s;%s;%s;%s", currentSetting.getUrl(), currentSetting.getUserName(),
|
||||
currentSetting.getPassword(), currentSetting.getAllowSelfSignedCertificate(),
|
||||
currentSetting.getLdapSupport(), currentSetting.getMinimumApiVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary task to make a ping to server to get it supported api version.
|
||||
*/
|
||||
|
|
|
@ -141,6 +141,20 @@ public class RESTMusicService implements MusicService {
|
|||
public void ping(Context context, ProgressListener progressListener) throws Exception {
|
||||
updateProgressListener(progressListener, R.string.service_connecting);
|
||||
|
||||
if (activeServerProvider.getValue().getActiveServer().getMinimumApiVersion() == null) {
|
||||
try {
|
||||
final Response<SubsonicResponse> response = subsonicAPIClient.getApi().ping().execute();
|
||||
if (response != null && response.body() != null) {
|
||||
String restApiVersion = response.body().getVersion().getRestApiVersion();
|
||||
Timber.i("Server minimum API version set to %s", restApiVersion);
|
||||
activeServerProvider.getValue().setMinimumApiVersion(restApiVersion);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// This Ping is only used to get the API Version, if it fails, that's no problem.
|
||||
}
|
||||
}
|
||||
|
||||
// This Ping will be now executed with the correct API Version, so it shouldn't fail
|
||||
final Response<SubsonicResponse> response = subsonicAPIClient.getApi().ping().execute();
|
||||
checkResponseSuccessful(response);
|
||||
}
|
||||
|
|
|
@ -260,7 +260,17 @@ internal class EditServerActivity : AppCompatActivity() {
|
|||
BuildConfig.DEBUG
|
||||
)
|
||||
val subsonicApiClient = SubsonicAPIClient(configuration)
|
||||
val pingResponse = subsonicApiClient.api.ping().execute()
|
||||
|
||||
// Execute a ping to retrieve the API version. This is accepted to fail if the authentication is incorrect yet.
|
||||
var pingResponse = subsonicApiClient.api.ping().execute()
|
||||
if (pingResponse?.body() != null) {
|
||||
val restApiVersion = pingResponse.body()!!.version.restApiVersion
|
||||
currentServerSetting!!.minimumApiVersion = restApiVersion
|
||||
Timber.i("Server minimum API version set to %s", restApiVersion)
|
||||
}
|
||||
|
||||
// Execute a ping to check the authentication, now using the correct API version.
|
||||
pingResponse = subsonicApiClient.api.ping().execute()
|
||||
checkResponseSuccessful(pingResponse)
|
||||
|
||||
val licenseResponse = subsonicApiClient.api.getLicense().execute()
|
||||
|
|
|
@ -209,7 +209,8 @@ class ServerSettingsModel(
|
|||
false
|
||||
),
|
||||
settings.getBoolean(PREFERENCES_KEY_LDAP_SUPPORT + preferenceId, false),
|
||||
settings.getString(PREFERENCES_KEY_MUSIC_FOLDER_ID + preferenceId, null)
|
||||
settings.getString(PREFERENCES_KEY_MUSIC_FOLDER_ID + preferenceId, null),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,8 @@ class ActiveServerProvider(
|
|||
jukeboxByDefault = false,
|
||||
allowSelfSignedCertificate = false,
|
||||
ldapSupport = false,
|
||||
musicFolderId = ""
|
||||
musicFolderId = "",
|
||||
minimumApiVersion = null
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -79,6 +80,18 @@ class ActiveServerProvider(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum Subsonic API version of the current server.
|
||||
*/
|
||||
fun setMinimumApiVersion(apiVersion: String) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
if (cachedServer != null) {
|
||||
cachedServer!!.minimumApiVersion = apiVersion
|
||||
repository.update(cachedServer!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the Active Server Setting cache
|
||||
* This should be called when the Active Server or one of its properties changes
|
||||
|
|
|
@ -2,11 +2,13 @@ package org.moire.ultrasonic.data
|
|||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
/**
|
||||
* Room Database to be used to store data for Ultrasonic
|
||||
*/
|
||||
@Database(entities = [ServerSetting::class], version = 1)
|
||||
@Database(entities = [ServerSetting::class], version = 2)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
/**
|
||||
|
@ -14,3 +16,11 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
*/
|
||||
abstract fun serverSettingDao(): ServerSettingDao
|
||||
}
|
||||
|
||||
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"ALTER TABLE ServerSetting ADD COLUMN minimumApiVersion TEXT"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,12 +28,13 @@ data class ServerSetting(
|
|||
@ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean,
|
||||
@ColumnInfo(name = "allowSelfSignedCertificate") var allowSelfSignedCertificate: Boolean,
|
||||
@ColumnInfo(name = "ldapSupport") var ldapSupport: Boolean,
|
||||
@ColumnInfo(name = "musicFolderId") var musicFolderId: String?
|
||||
@ColumnInfo(name = "musicFolderId") var musicFolderId: String?,
|
||||
@ColumnInfo(name = "minimumApiVersion") var minimumApiVersion: String?
|
||||
) {
|
||||
constructor() : this (
|
||||
-1, 0, "", "", "", "", false, false, false, null
|
||||
-1, 0, "", "", "", "", false, false, false, null, null
|
||||
)
|
||||
constructor(name: String, url: String) : this(
|
||||
-1, 0, name, url, "", "", false, false, false, null
|
||||
-1, 0, name, url, "", "", false, false, false, null, null
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.koin.dsl.module
|
|||
import org.moire.ultrasonic.activity.ServerSettingsModel
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.AppDatabase
|
||||
import org.moire.ultrasonic.data.MIGRATION_1_2
|
||||
import org.moire.ultrasonic.util.Util
|
||||
|
||||
const val SP_NAME = "Default_SP"
|
||||
|
@ -20,7 +21,9 @@ val appPermanentStorage = module {
|
|||
androidContext(),
|
||||
AppDatabase::class.java,
|
||||
"ultrasonic-database"
|
||||
).build()
|
||||
)
|
||||
.addMigrations(MIGRATION_1_2)
|
||||
.build()
|
||||
}
|
||||
|
||||
single { get<AppDatabase>().serverSettingDao() }
|
||||
|
|
|
@ -46,7 +46,8 @@ val musicServiceModule = module {
|
|||
username = get<ActiveServerProvider>().getActiveServer().userName,
|
||||
password = get<ActiveServerProvider>().getActiveServer().password,
|
||||
minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion(
|
||||
Constants.REST_PROTOCOL_VERSION
|
||||
get<ActiveServerProvider>().getActiveServer().minimumApiVersion
|
||||
?: Constants.REST_PROTOCOL_VERSION
|
||||
),
|
||||
clientID = Constants.REST_CLIENT_ID,
|
||||
allowSelfSignedCertificate = get<ActiveServerProvider>()
|
||||
|
|
|
@ -309,9 +309,9 @@
|
|||
<string name="settings.theme_black">Black</string>
|
||||
<string name="settings.theme_title">Theme</string>
|
||||
<string name="settings.title.allow_self_signed_certificate">Allow self-signed HTTPS certificate</string>
|
||||
<string name="settings.title.enable_ldap_users_support">Enable support for LDAP users</string>
|
||||
<string name="settings.summary.enable_ldap_users_support">This forces app to always send password in old-way,
|
||||
because Subsonic api does not support new authorization for LDAP users.</string>
|
||||
<string name="settings.title.enable_ldap_users_support">Force plain password authentication</string>
|
||||
<string name="settings.summary.enable_ldap_users_support">This forces the app to always send the password unencrypted.
|
||||
Useful if the Subsonic server does not support the new authentication API for the users.</string>
|
||||
<string name="settings.use_folder_for_album_artist">Use Folders For Artist Name</string>
|
||||
<string name="settings.use_folder_for_album_artist_summary">Assume top-level folder is the name of the album artist</string>
|
||||
<string name="settings.use_id3">Browse Using ID3 Tags</string>
|
||||
|
|
Loading…
Reference in New Issue