New Release!

[All versions] Now the app supports using plaintext passwords for
authentication, as some subsonic implementations need it to work. Please
try not to use this on unsecure connections (i.e. http://) as the
password travels unencrypted through the network.

Closes #2. Thanks @epoupon
This commit is contained in:
Carlos Perez
2022-06-21 23:35:19 -03:00
parent 4d9a2d2342
commit 4666a2fa51
8 changed files with 40 additions and 21 deletions

View File

@@ -163,11 +163,13 @@ class BackendPlugin : Plugin(), IBroadcastObserver {
val username = data.getString("username") ?: throw ParameterException("username")
val password = data.getString("password") ?: throw Exception("password")
val url = data.getString("url") ?: throw Exception("url")
val usePlaintext = data.getBoolean("usePlaintext")
try {
val account = subsonicClient!!.login(
username,
password,
url
url,
usePlaintext
)
setActiveAccount(account)
call.resolve(okResponse(account))

View File

@@ -35,7 +35,7 @@ class KeyValueStorage {
val account: Account = Gson().fromJson(activeAccount, Account::class.java)
account
} catch (exception: Exception) {
Account(null, "", "", "")
Account(null, "", "", "", false)
}
}

View File

@@ -6,7 +6,6 @@ package tech.logica10.soniclair
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.Uri
import android.support.v4.media.MediaBrowserCompat
@@ -40,7 +39,7 @@ import java.util.concurrent.TimeUnit
class SubsonicClient(var initialAccount: Account) {
companion object {
var account: Account = Account(null, "", "", "")
var account: Account = Account(null, "", "", "", false)
var spotifyToken: String = ""
var downloadQueue: MutableList<Song> = mutableListOf()
var downloadQueueForce: HashMap<String, Boolean> = HashMap()
@@ -75,11 +74,12 @@ class SubsonicClient(var initialAccount: Account) {
BigInteger(1, md.digest(saltedPassword.toByteArray())).toString(16).padStart(32, '0')
return BasicParams(
account.username ?: "",
hash,
salt,
if(account.usePlaintext) null else hash,
if(account.usePlaintext) null else salt,
"1.16.1",
"soniclair",
"json",
if(account.usePlaintext) account.password else null
)
}
@@ -646,6 +646,7 @@ class SubsonicClient(var initialAccount: Account) {
uriBuilder.appendQueryParameter(key, map[key])
}
uriBuilder.appendQueryParameter("id", song.id)
uriBuilder.appendQueryParameter("estimateContentLength", "true")
if (KeyValueStorage.getSettings().transcoding != "" && connectivityManager.getNetworkCapabilities(
@@ -654,7 +655,6 @@ class SubsonicClient(var initialAccount: Account) {
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) == false
) {
uriBuilder.appendQueryParameter("format", KeyValueStorage.getSettings().transcoding)
uriBuilder.appendQueryParameter("estimateContentLength", "true")
}
return uriBuilder.build().toString()
@@ -678,7 +678,7 @@ class SubsonicClient(var initialAccount: Account) {
)!!.song
}
fun login(username: String, password: String, url: String): Account {
fun login(username: String, password: String, url: String, usePlaintext: Boolean): Account {
val salt = "abcd1234"
val saltedPassword = "${password}${salt}"
val md = MessageDigest.getInstance("MD5")
@@ -686,11 +686,12 @@ class SubsonicClient(var initialAccount: Account) {
BigInteger(1, md.digest(saltedPassword.toByteArray())).toString(16).padStart(32, '0')
val basicParams = BasicParams(
username,
hash,
salt,
if(usePlaintext) null else hash,
if(usePlaintext) null else salt,
"1.16.1",
"soniclair",
"json",
if(usePlaintext) password else null
)
val uriBuilder = Uri.parse(url).buildUpon()
.appendPath("rest")
@@ -715,7 +716,7 @@ class SubsonicClient(var initialAccount: Account) {
if (ret.status != "ok") {
throw Exception(ret.error?.message)
}
account = Account(username, password, url, ret.type)
account = Account(username, password, url, ret.type ?: "Unknown Server", usePlaintext)
KeyValueStorage.setActiveAccount(account)
val accounts = KeyValueStorage.getAccounts()
val exists = accounts.filter { it.url == url }.size == 1

View File

@@ -1,4 +1,4 @@
package tech.logica10.soniclair.models
class Account(val username: String?, val password: String, val url: String, var type: String)
class Account(val username: String?, val password: String, val url: String, var type: String, var usePlaintext: Boolean)

View File

@@ -2,17 +2,25 @@ package tech.logica10.soniclair.models
class BasicParams(
val u: String,
val t: String,
val s: String,
val t: String?,
val s: String?,
val v: String,
val c: String,
val f: String
val f: String,
val p: String?,
) {
fun asMap(): HashMap<String, String> {
val ret = HashMap<String, String>()
ret["u"] = u
ret["t"] = t
ret["s"] = s
if (t != null) {
ret["t"] = t
}
if (s != null) {
ret["s"] = s
}
if (p != null) {
ret["p"] = p
}
ret["v"] = v
ret["c"] = c
ret["f"] = f

View File

@@ -2,5 +2,5 @@ package tech.logica10.soniclair.models
class Context(
val accounts: List<Account> = emptyList(),
val activeAccount: Account = Account(null, "", "", ""),
val activeAccount: Account = Account(null, "", "", "", false),
)

View File

@@ -3,7 +3,7 @@ package tech.logica10.soniclair.models
open class SubsonicResponse {
val serverVersion: String = ""
val status: String = ""
val type: String = ""
val type: String? = ""
val version: String = ""
val error: SubsonicError? = null
}

View File

@@ -270,10 +270,18 @@ export default function PlayTest() {
className="form-check-input"
type="checkbox"
id="flexSwitchCheckDefault"
{...register("usePlaintext", { required: true })}
{...register("usePlaintext")}
/>
<label className="w-100 text-start form-label text-white"> Use plaintext password (insecure on http connections, needed for some servers)</label>
<label className="w-100 text-start form-label text-white">
Use plaintext password (insecure on http
connections, needed for some servers)
</label>
</div>
{errors && errors.url && (
<div className="col-12 text-danger">
{errors.usePlaintext?.message}
</div>
)}
<button
ref={buttonRef}