2 Commits

Author SHA1 Message Date
soraefir 119ae757b9 Migrate to SecureStorage 2026-06-30 19:24:25 +02:00
soraefir c3d380ddb4 Migrate to encryptedPref 2026-06-30 18:59:50 +02:00
9 changed files with 127 additions and 98 deletions
+3
View File
@@ -114,5 +114,8 @@ dependencies {
debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-tooling'
implementation 'androidx.activity:activity-compose:1.13.0' implementation 'androidx.activity:activity-compose:1.13.0'
implementation 'androidx.activity:activity-ktx:1.13.0' implementation 'androidx.activity:activity-ktx:1.13.0'
implementation 'androidx.security:security-crypto:1.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0'
implementation "androidx.datastore:datastore-preferences:1.2.1"
implementation "com.google.crypto.tink:tink-android:1.22.0"
} }
@@ -41,6 +41,7 @@ import net.helcel.cowspent.android.main.MainConstants
import net.helcel.cowspent.theme.ThemeUtils import net.helcel.cowspent.theme.ThemeUtils
import net.helcel.cowspent.util.CospendClientUtil import net.helcel.cowspent.util.CospendClientUtil
import net.helcel.cowspent.util.CospendClientUtil.LoginStatus import net.helcel.cowspent.util.CospendClientUtil.LoginStatus
import net.helcel.cowspent.util.SecureStorage
import java.net.URLDecoder import java.net.URLDecoder
import java.util.Locale import java.util.Locale
@@ -68,7 +69,6 @@ class AccountActivity : AppCompatActivity() {
} }
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
private var oldPassword = ""
private var useWebLogin = true private var useWebLogin = true
private var showLoginDialog by mutableStateOf(false) private var showLoginDialog by mutableStateOf(false)
@@ -128,8 +128,6 @@ class AccountActivity : AppCompatActivity() {
) )
} }
} }
oldPassword = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS) ?: ""
viewModel.validateUrl() viewModel.validateUrl()
} }
@@ -223,11 +221,7 @@ class AccountActivity : AppCompatActivity() {
private fun legacyLogin() { private fun legacyLogin() {
val url = CospendClientUtil.formatURL(viewModel.serverUrl.trim()) val url = CospendClientUtil.formatURL(viewModel.serverUrl.trim())
val username = viewModel.username val username = viewModel.username
var password = viewModel.password val password = viewModel.password
if (password.isEmpty()) {
password = oldPassword
}
performLogin(url, username, password) performLogin(url, username, password)
} }
@@ -310,10 +304,10 @@ class AccountActivity : AppCompatActivity() {
} }
if (status == LoginStatus.OK) { if (status == LoginStatus.OK) {
SecureStorage.savePassword(applicationContext, SETTINGS_PASSWORD, password)
preferences.edit { preferences.edit {
putString(SETTINGS_URL, url) putString(SETTINGS_URL, url)
putString(SETTINGS_USERNAME, username) putString(SETTINGS_USERNAME, username)
putString(SETTINGS_PASSWORD, password)
remove(SETTINGS_KEY_ETAG) remove(SETTINGS_KEY_ETAG)
remove(SETTINGS_KEY_LAST_MODIFIED) remove(SETTINGS_KEY_LAST_MODIFIED)
} }
@@ -13,6 +13,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.helcel.cowspent.util.CospendClientUtil import net.helcel.cowspent.util.CospendClientUtil
import net.helcel.cowspent.util.SecureStorage
class AccountViewModel(application: Application) : AndroidViewModel(application) { class AccountViewModel(application: Application) : AndroidViewModel(application) {
private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
@@ -60,14 +61,15 @@ class AccountViewModel(application: Application) : AndroidViewModel(application)
} else { } else {
preferences.getString(AccountActivity.SETTINGS_USERNAME, "") preferences.getString(AccountActivity.SETTINGS_USERNAME, "")
} }
val password = if (useSso) {
""
} else {
preferences.getString(AccountActivity.SETTINGS_PASSWORD, "")
}
if (!url.isNullOrEmpty() && !username.isNullOrEmpty()) { if (!url.isNullOrEmpty() && !username.isNullOrEmpty()) {
viewModelScope.launch { viewModelScope.launch {
val password = if (useSso) {
""
} else {
SecureStorage.getPassword(getApplication(), AccountActivity.SETTINGS_PASSWORD)
}
isValidatingLogin = true isValidatingLogin = true
isLoggedIn = withContext(Dispatchers.IO) { isLoggedIn = withContext(Dispatchers.IO) {
if (useSso) { if (useSso) {
@@ -87,6 +89,9 @@ class AccountViewModel(application: Application) : AndroidViewModel(application)
} }
fun logout() { fun logout() {
viewModelScope.launch {
SecureStorage.removePassword(getApplication(), AccountActivity.SETTINGS_PASSWORD)
}
preferences.edit { preferences.edit {
remove(AccountActivity.SETTINGS_USE_SSO) remove(AccountActivity.SETTINGS_USE_SSO)
remove(AccountActivity.SETTINGS_SSO_URL) remove(AccountActivity.SETTINGS_SSO_URL)
@@ -121,13 +121,7 @@ fun NewProjectScreen(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
if (viewModel.whatTodoIsCreate) { if (viewModel.whatTodoIsCreate) {
if (viewModel.projectType == ProjectType.COSPEND) { if (viewModel.projectType == ProjectType.LOCAL) {
Button(
onClick = onChooseFromNextcloud
) {
Text(stringResource(R.string.new_project_from_nextcloud_tooltip))
}
} else if (viewModel.projectType == ProjectType.LOCAL) {
Button( Button(
onClick = onImportFile onClick = onImportFile
) { ) {
@@ -12,6 +12,6 @@ class DBAccountProject(
) : Serializable { ) : Serializable {
override fun toString(): String { override fun toString(): String {
return "#DBAccountProject$id/$remoteId,$name, $ncUrl, $password, archivedTs=$archivedTs" return "#DBAccountProject$id/$remoteId,$name, $ncUrl, [SECURE], archivedTs=$archivedTs"
} }
} }
@@ -13,6 +13,7 @@ import androidx.preference.PreferenceManager
import net.helcel.cowspent.R import net.helcel.cowspent.R
import net.helcel.cowspent.android.main.BillsListViewActivity import net.helcel.cowspent.android.main.BillsListViewActivity
import net.helcel.cowspent.model.* import net.helcel.cowspent.model.*
import net.helcel.cowspent.util.SecureStorage
import net.helcel.cowspent.util.SupportUtil import net.helcel.cowspent.util.SupportUtil
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@@ -316,11 +317,12 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
val db = writableDatabase val db = writableDatabase
val values = ContentValues() val values = ContentValues()
values.put(key_remoteId, accountProject.remoteId) values.put(key_remoteId, accountProject.remoteId)
values.put(key_password, accountProject.password)
values.put(key_ncUrl, accountProject.ncUrl) values.put(key_ncUrl, accountProject.ncUrl)
values.put(key_name, accountProject.name) values.put(key_name, accountProject.name)
values.put(key_archived, accountProject.archivedTs ?: 0L) values.put(key_archived, accountProject.archivedTs ?: 0L)
return db.insert(table_account_projects, null, values) val id = db.insert(table_account_projects, null, values)
SecureStorage.savePasswordSync(context, "AccountProjectPassword_$id", accountProject.password)
return id
} }
val accountProjects: List<DBAccountProject> val accountProjects: List<DBAccountProject>
@@ -344,11 +346,13 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
@SuppressLint("Range") @SuppressLint("Range")
private fun getAccountProjectFromCursor(cursor: Cursor): DBAccountProject { private fun getAccountProjectFromCursor(cursor: Cursor): DBAccountProject {
val id = cursor.getLong(cursor.getColumnIndex(key_id))
val archivedTs = cursor.getLong(cursor.getColumnIndex(key_archived)) val archivedTs = cursor.getLong(cursor.getColumnIndex(key_archived))
val password = SecureStorage.getPasswordSync(context, "AccountProjectPassword_$id")
return DBAccountProject( return DBAccountProject(
cursor.getLong(cursor.getColumnIndex(key_id)), id,
cursor.getString(cursor.getColumnIndex(key_remoteId)), cursor.getString(cursor.getColumnIndex(key_remoteId)),
cursor.getString(cursor.getColumnIndex(key_password)), password,
cursor.getString(cursor.getColumnIndex(key_name)), cursor.getString(cursor.getColumnIndex(key_name)),
cursor.getString(cursor.getColumnIndex(key_ncUrl)), cursor.getString(cursor.getColumnIndex(key_ncUrl)),
if (archivedTs > 0) archivedTs else null if (archivedTs > 0) archivedTs else null
@@ -608,14 +612,15 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
val db = writableDatabase val db = writableDatabase
val values = ContentValues() val values = ContentValues()
values.put(key_remoteId, project.remoteId) values.put(key_remoteId, project.remoteId)
values.put(key_password, project.password)
values.put(key_bearer_token, project.bearerToken) values.put(key_bearer_token, project.bearerToken)
values.put(key_email, project.email) values.put(key_email, project.email)
values.put(key_name, project.name) values.put(key_name, project.name)
values.put(key_ihmUrl, project.serverUrl) values.put(key_ihmUrl, project.serverUrl)
values.put(key_type, project.type.id) values.put(key_type, project.type.id)
values.put(key_archived, project.archivedTs ?: 0L) values.put(key_archived, project.archivedTs ?: 0L)
return db.insert(table_projects, null, values) val id = db.insert(table_projects, null, values)
SecureStorage.savePasswordSync(context, "ProjectPassword_$id", project.password)
return id
} }
fun getProject(id: Long): DBProject? { fun getProject(id: Long): DBProject? {
@@ -644,11 +649,13 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
@SuppressLint("Range") @SuppressLint("Range")
private fun getProjectFromCursor(cursor: Cursor): DBProject { private fun getProjectFromCursor(cursor: Cursor): DBProject {
val id = cursor.getLong(cursor.getColumnIndex(key_id))
val archivedTs = cursor.getLong(cursor.getColumnIndex(key_archived)) val archivedTs = cursor.getLong(cursor.getColumnIndex(key_archived))
val password = SecureStorage.getPasswordSync(context, "ProjectPassword_$id") ?: ""
return DBProject( return DBProject(
cursor.getLong(cursor.getColumnIndex(key_id)), id,
cursor.getString(cursor.getColumnIndex(key_remoteId)), cursor.getString(cursor.getColumnIndex(key_remoteId)),
cursor.getString(cursor.getColumnIndex(key_password)), password,
cursor.getString(cursor.getColumnIndex(key_name)), cursor.getString(cursor.getColumnIndex(key_name)),
cursor.getString(cursor.getColumnIndex(key_ihmUrl)), cursor.getString(cursor.getColumnIndex(key_ihmUrl)),
cursor.getString(cursor.getColumnIndex(key_email)), cursor.getString(cursor.getColumnIndex(key_email)),
@@ -671,6 +678,7 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
} }
db.delete(table_members, "$key_projectid = ?", arrayOf(id.toString())) db.delete(table_members, "$key_projectid = ?", arrayOf(id.toString()))
db.delete(table_projects, "$key_id = ?", arrayOf(id.toString())) db.delete(table_projects, "$key_id = ?", arrayOf(id.toString()))
SecureStorage.removePasswordSync(context, "ProjectPassword_$id")
} }
fun updateProject( fun updateProject(
@@ -685,7 +693,7 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
val values = ContentValues() val values = ContentValues()
if (newName != null) values.put(key_name, newName) if (newName != null) values.put(key_name, newName)
if (newEmail != null) values.put(key_email, newEmail) if (newEmail != null) values.put(key_email, newEmail)
if (newPassword != null) values.put(key_password, newPassword) if (newPassword != null) SecureStorage.savePasswordSync(context, "ProjectPassword_$projId", newPassword)
if (newBearerToken != null) values.put(key_bearer_token, newBearerToken) if (newBearerToken != null) values.put(key_bearer_token, newBearerToken)
if (newLastPayerId != null) values.put(key_lastPayerId, newLastPayerId) if (newLastPayerId != null) values.put(key_lastPayerId, newLastPayerId)
if (newLastSyncedTimestamp != null) values.put(key_lastSyncTimestamp, newLastSyncedTimestamp) if (newLastSyncedTimestamp != null) values.put(key_lastSyncTimestamp, newLastSyncedTimestamp)
@@ -725,7 +733,7 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
val values = ContentValues() val values = ContentValues()
if (newName != null) values.put(key_name, newName) if (newName != null) values.put(key_name, newName)
if (newEmail != null) values.put(key_email, newEmail) if (newEmail != null) values.put(key_email, newEmail)
if (newPassword != null) values.put(key_password, newPassword) if (newPassword != null) SecureStorage.savePasswordSync(context, "ProjectPassword_$projId", newPassword)
if (newBearerToken != null) values.put(key_bearer_token, newBearerToken) if (newBearerToken != null) values.put(key_bearer_token, newBearerToken)
if (newLastPayerId != null) values.put(key_lastPayerId, newLastPayerId) if (newLastPayerId != null) values.put(key_lastPayerId, newLastPayerId)
if (newLastSyncedTimestamp != null) values.put(key_lastSyncTimestamp, newLastSyncedTimestamp) if (newLastSyncedTimestamp != null) values.put(key_lastSyncTimestamp, newLastSyncedTimestamp)
@@ -35,13 +35,13 @@ class NextcloudClient(
val method = if (useOcsApi) METHOD_GET else METHOD_POST val method = if (useOcsApi) METHOD_GET else METHOD_POST
return if (nextcloudAPI != null) { return if (nextcloudAPI != null) {
Log.d(javaClass.simpleName, "using SSO to get/sync account projects") Log.d(javaClass.simpleName, "using SSO to get/sync account projects")
Log.d(javaClass.simpleName, "Sync projects target $target") // Log.d(javaClass.simpleName, "Sync projects target $target")
ServerResponse.AccountProjectsResponse( ServerResponse.AccountProjectsResponse(
requestServerWithSSO(nextcloudAPI, target, method, null, useOcsApi), requestServerWithSSO(nextcloudAPI, target, method, null, useOcsApi),
useOcsApi useOcsApi
) )
} else { } else {
Log.d(javaClass.simpleName, "Sync projects target $target") // Log.d(javaClass.simpleName, "Sync projects target $target")
ServerResponse.AccountProjectsResponse( ServerResponse.AccountProjectsResponse(
requestServer(target, method, null, "", true, useOcsApi), requestServer(target, method, null, "", true, useOcsApi),
useOcsApi useOcsApi
@@ -184,7 +184,7 @@ class NextcloudClient(
): VersatileProjectSyncClient.ResponseData { ): VersatileProjectSyncClient.ResponseData {
val result = StringBuilder() val result = StringBuilder()
val targetURL = url + target.replace("^/".toRegex(), "") val targetURL = url + target.replace("^/".toRegex(), "")
Log.d(javaClass.simpleName, "method and target URL: $method $targetURL") // Log.d(javaClass.simpleName, "method and target URL: $method $targetURL")
val httpCon = SupportUtil.getHttpURLConnection(targetURL) val httpCon = SupportUtil.getHttpURLConnection(targetURL)
httpCon.requestMethod = method httpCon.requestMethod = method
if (needLogin) { if (needLogin) {
@@ -206,7 +206,7 @@ class NextcloudClient(
var paramData: ByteArray? = null var paramData: ByteArray? = null
if (params != null) { if (params != null) {
paramData = params.toString().toByteArray() paramData = params.toString().toByteArray()
Log.d(javaClass.simpleName, "Params: $params") // Log.d(javaClass.simpleName, "Params: $params")
httpCon.setFixedLengthStreamingMode(paramData.size) httpCon.setFixedLengthStreamingMode(paramData.size)
httpCon.setRequestProperty("Content-Type", application_json) httpCon.setRequestProperty("Content-Type", application_json)
httpCon.doOutput = true httpCon.doOutput = true
@@ -263,11 +263,11 @@ class NextcloudClient(
httpCon.setRequestProperty("OCS-APIRequest", "true") httpCon.setRequestProperty("OCS-APIRequest", "true")
} }
httpCon.connectTimeout = 10 * 1000 // 10 seconds httpCon.connectTimeout = 10 * 1000 // 10 seconds
Log.d(javaClass.simpleName, "$method $targetURL") // Log.d(javaClass.simpleName, "$method $targetURL")
var paramData: ByteArray? var paramData: ByteArray?
if (params != null) { if (params != null) {
paramData = params.toString().toByteArray() paramData = params.toString().toByteArray()
Log.d(javaClass.simpleName, "Params: $params") // Log.d(javaClass.simpleName, "Params: $params")
httpCon.setFixedLengthStreamingMode(paramData.size) httpCon.setFixedLengthStreamingMode(paramData.size)
httpCon.setRequestProperty("Content-Type", application_json) httpCon.setRequestProperty("Content-Type", application_json)
httpCon.doOutput = true httpCon.doOutput = true
@@ -0,0 +1,84 @@
package net.helcel.cowspent.util
import android.content.Context
import android.util.Base64
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.google.crypto.tink.Aead
import com.google.crypto.tink.KeyTemplates
import com.google.crypto.tink.RegistryConfiguration
import com.google.crypto.tink.aead.AeadConfig
import com.google.crypto.tink.integration.android.AndroidKeysetManager
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "secure_prefs")
object SecureStorage {
private var aead: Aead? = null
@Synchronized
private fun getAead(context: Context): Aead {
aead?.let { return it }
AeadConfig.register()
val masterKeyUri = "android-keystore://secure_storage_master_key"
val keysetManager = AndroidKeysetManager.Builder()
.withSharedPref(context, "tink_keyset", "secure_storage_keyset_prefs")
.withKeyTemplate(KeyTemplates.get("AES256_GCM"))
.withMasterKeyUri(masterKeyUri)
.build()
val newAead = keysetManager.keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead::class.java)
aead = newAead
return newAead
}
private fun encrypt(context: Context, value: String): String {
val encrypted = getAead(context).encrypt(value.toByteArray(), null)
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
}
private fun decrypt(context: Context, encryptedValue: String): String? {
return try {
val decoded = Base64.decode(encryptedValue, Base64.NO_WRAP)
val decrypted = getAead(context).decrypt(decoded, null)
String(decrypted)
} catch (_: Exception) {
null
}
}
suspend fun savePassword(context: Context, key: String, password: String?) {
if (password == null) {
removePassword(context, key)
} else {
val encrypted = encrypt(context, password)
context.dataStore.edit { it[stringPreferencesKey(key)] = encrypted }
}
}
suspend fun getPassword(context: Context, key: String): String? {
val encrypted = context.dataStore.data.map { it[stringPreferencesKey(key)] }.first()
return encrypted?.let { decrypt(context, it) }
}
suspend fun removePassword(context: Context, key: String) {
context.dataStore.edit { it.remove(stringPreferencesKey(key)) }
}
// Synchronous alternatives for legacy code
fun savePasswordSync(context: Context, key: String, password: String?) = runBlocking {
savePassword(context, key, password)
}
fun getPasswordSync(context: Context, key: String): String? = runBlocking {
getPassword(context, key)
}
fun removePasswordSync(context: Context, key: String) = runBlocking {
removePassword(context, key)
}
}
@@ -81,13 +81,9 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for getProjectInfo")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
Log.i(TAG, "using new API for getProjectInfo")
ServerResponse.ProjectResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, null, null, true), true) ServerResponse.ProjectResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, null, null, true), true)
} else { } else {
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId
@@ -99,7 +95,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
Log.i(TAG, "using public API, target is: ${target}for getProjectInfo")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId
@@ -172,13 +167,9 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for editRemoteProject")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
Log.i(TAG, "using new API for editRemoteProject")
ServerResponse.EditRemoteProjectResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, true), true) ServerResponse.EditRemoteProjectResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, true), true)
} else { } else {
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId
@@ -190,7 +181,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
Log.i(TAG, "using public API, target is: ${target}for editRemoteProject")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId
@@ -239,13 +229,9 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members/" + member.remoteId project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members/" + member.remoteId
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for editRemoteMember")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members/" + member.remoteId target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members/" + member.remoteId
Log.i(TAG, "using new API for editRemoteMember")
ServerResponse.EditRemoteMemberResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, true), true) ServerResponse.EditRemoteMemberResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, true), true)
} else { } else {
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/members/" + member.remoteId target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/members/" + member.remoteId
@@ -257,7 +243,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members/" + member.remoteId project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members/" + member.remoteId
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members/" + member.remoteId project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members/" + member.remoteId
Log.i(TAG, "using public API, target is: ${target}for editRemoteMember")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members/" + member.remoteId target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members/" + member.remoteId
@@ -333,9 +318,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills/" + bill.remoteId project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills/" + bill.remoteId
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for editRemoteBill")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills/" + bill.remoteId target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills/" + bill.remoteId
@@ -351,7 +333,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + bill.remoteId project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + bill.remoteId
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + bill.remoteId project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + bill.remoteId
Log.i(TAG, "using public API, target is: ${target}for editRemoteBill")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills/" + bill.remoteId target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills/" + bill.remoteId
@@ -389,9 +370,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for deleteRemoteProject")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
@@ -407,7 +385,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
Log.i(TAG, "using public API, target is: ${target}for deleteRemoteProject")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId
@@ -439,9 +416,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills/" + billRemoteId project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills/" + billRemoteId
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for deleteRemoteBill")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills/" + billRemoteId target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills/" + billRemoteId
@@ -457,7 +431,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + billRemoteId project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + billRemoteId
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + billRemoteId project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + billRemoteId
Log.i(TAG, "using public API, target is: ${target}for deleteRemoteProject")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills/" + billRemoteId target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills/" + billRemoteId
@@ -528,9 +501,7 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects" project.getRequestBaseUrl(false) + "/api-priv/projects"
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for createAuthenticatedRemoteProject")
}
return ServerResponse.CreateRemoteProjectResponse( return ServerResponse.CreateRemoteProjectResponse(
requestServer( requestServer(
target, METHOD_POST, paramKeys, paramValues, target, METHOD_POST, paramKeys, paramValues,
@@ -594,9 +565,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills" project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills"
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for createRemoteBill")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills" target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills"
@@ -612,7 +580,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills" project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills"
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills" project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills"
Log.i(TAG, "using public API, target is: ${target}for createRemoteBill")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills" target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills"
@@ -664,9 +631,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members" project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members"
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for createRemoteMember")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members" target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members"
@@ -682,7 +646,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members" project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members" project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
Log.i(TAG, "using public API, target is: ${target}for createRemoteBill")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members" target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members"
@@ -717,9 +680,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills?lastchanged=" + tsLastSync project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills?lastchanged=" + tsLastSync
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for getBills")
}
return ServerResponse.BillsResponse( return ServerResponse.BillsResponse(
requestServer( requestServer(
target, METHOD_GET, null, null, target, METHOD_GET, null, null,
@@ -750,7 +710,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastChanged=" + tsLastSync project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastChanged=" + tsLastSync
else else
project.getRequestBaseUrl(false) + "/apiv2/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastchanged=" + tsLastSync project.getRequestBaseUrl(false) + "/apiv2/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastchanged=" + tsLastSync
Log.i(TAG, "using public API, target is: ${target}for getBills")
return ServerResponse.BillsResponse( return ServerResponse.BillsResponse(
requestServer( requestServer(
target, METHOD_GET, null, null, target, METHOD_GET, null, null,
@@ -790,9 +749,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members" project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members"
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for getMembers, projectId: " + project.remoteId)
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members" target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members"
@@ -808,7 +764,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members" project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members" project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
Log.i(TAG, "using public API, target is: ${target}for getMembers")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members" target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members"
@@ -847,9 +802,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency" project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency"
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for createRemoteCurrency")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency" target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency"
@@ -865,7 +817,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency" project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency"
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency" project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency"
Log.i(TAG, "using public API, target is: ${target}for createRemoteCurrency")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency" target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency"
@@ -904,9 +855,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency/" + currency.remoteId project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency/" + currency.remoteId
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for editRemoteCurrency")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency/" + currency.remoteId target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency/" + currency.remoteId
@@ -922,7 +870,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currency.remoteId project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currency.remoteId
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currency.remoteId project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currency.remoteId
Log.i(TAG, "using public API, target is: ${target}for createRemoteCurrency")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency/" + currency.remoteId target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency/" + currency.remoteId
@@ -954,9 +901,6 @@ class VersatileProjectSyncClient(
else else
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency/" + currencyRemoteId project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency/" + currencyRemoteId
useOcsApiRequest = cospendVersionGT161 useOcsApiRequest = cospendVersionGT161
if (cospendVersionGT161) {
Log.i(TAG, "using new API (weblogin, $username:$password) for deleteRemoteCurrency")
}
} else if (canAccessProjectWithSSO(project)) { } else if (canAccessProjectWithSSO(project)) {
return if (cospendVersionGT161) { return if (cospendVersionGT161) {
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency/" + currencyRemoteId target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency/" + currencyRemoteId
@@ -972,7 +916,6 @@ class VersatileProjectSyncClient(
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currencyRemoteId project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currencyRemoteId
else else
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currencyRemoteId project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currencyRemoteId
Log.i(TAG, "using public API, target is: ${target}for deleteRemoteCurrency")
} }
} else { } else {
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency/" + currencyRemoteId target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency/" + currencyRemoteId
@@ -1071,7 +1014,6 @@ class VersatileProjectSyncClient(
httpCon.setRequestProperty("Accept", "application/json") httpCon.setRequestProperty("Accept", "application/json")
} }
httpCon.connectTimeout = 10 * 1000 // 10 seconds httpCon.connectTimeout = 10 * 1000 // 10 seconds
Log.d(javaClass.simpleName, "$method $target")
if (paramKeys != null && paramValues != null) { if (paramKeys != null && paramValues != null) {
var dataString = "" var dataString = ""
for (i in paramKeys.indices) { for (i in paramKeys.indices) {
@@ -1084,7 +1026,6 @@ class VersatileProjectSyncClient(
dataString += URLEncoder.encode(value, "UTF-8") dataString += URLEncoder.encode(value, "UTF-8")
} }
val data = dataString.toByteArray() val data = dataString.toByteArray()
Log.d(javaClass.simpleName, "Params: $dataString")
httpCon.setFixedLengthStreamingMode(data.size) httpCon.setFixedLengthStreamingMode(data.size)
httpCon.setRequestProperty("Content-Type", "application/x-www-form-urlencoded") httpCon.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
httpCon.setRequestProperty("Content-Length", data.size.toString()) httpCon.setRequestProperty("Content-Length", data.size.toString())