|
|
|
|
@@ -11,19 +11,27 @@ import androidx.security.crypto.MasterKey
|
|
|
|
|
import de.mm20.launcher2.serialization.Json
|
|
|
|
|
import de.mm20.launcher2.webdav.WebDavApi
|
|
|
|
|
import de.mm20.launcher2.webdav.WebDavFile
|
|
|
|
|
import io.ktor.client.HttpClient
|
|
|
|
|
import io.ktor.client.call.body
|
|
|
|
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
|
|
|
|
import io.ktor.client.plugins.defaultRequest
|
|
|
|
|
import io.ktor.client.request.basicAuth
|
|
|
|
|
import io.ktor.client.request.delete
|
|
|
|
|
import io.ktor.client.request.forms.submitForm
|
|
|
|
|
import io.ktor.client.request.get
|
|
|
|
|
import io.ktor.client.request.header
|
|
|
|
|
import io.ktor.client.request.parameter
|
|
|
|
|
import io.ktor.client.request.post
|
|
|
|
|
import io.ktor.client.statement.bodyAsText
|
|
|
|
|
import io.ktor.http.HttpHeaders
|
|
|
|
|
import io.ktor.http.HttpStatusCode
|
|
|
|
|
import io.ktor.http.parameters
|
|
|
|
|
import io.ktor.http.path
|
|
|
|
|
import io.ktor.http.takeFrom
|
|
|
|
|
import io.ktor.serialization.kotlinx.json.json
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
|
import kotlinx.serialization.SerializationException
|
|
|
|
|
import kotlinx.serialization.json.decodeFromStream
|
|
|
|
|
import okhttp3.Authenticator
|
|
|
|
|
import okhttp3.Credentials
|
|
|
|
|
import okhttp3.FormBody
|
|
|
|
|
import okhttp3.OkHttpClient
|
|
|
|
|
import okhttp3.Request
|
|
|
|
|
import okhttp3.Response
|
|
|
|
|
import okhttp3.Route
|
|
|
|
|
import okhttp3.internal.EMPTY_REQUEST
|
|
|
|
|
import org.json.JSONObject
|
|
|
|
|
import java.io.File
|
|
|
|
|
import java.io.IOException
|
|
|
|
|
|
|
|
|
|
@@ -31,18 +39,18 @@ class NextcloudApiHelper(val context: Context) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private val httpClient by lazy {
|
|
|
|
|
OkHttpClient.Builder()
|
|
|
|
|
.authenticator(object : Authenticator {
|
|
|
|
|
override fun authenticate(route: Route?, response: Response): Request? {
|
|
|
|
|
if (response.priorResponse?.priorResponse != null) return null
|
|
|
|
|
return response.request
|
|
|
|
|
.newBuilder()
|
|
|
|
|
.addHeader("Authorization", getAuthorization() ?: return null)
|
|
|
|
|
.build()
|
|
|
|
|
HttpClient {
|
|
|
|
|
install(ContentNegotiation) {
|
|
|
|
|
json(Json.Lenient)
|
|
|
|
|
}
|
|
|
|
|
defaultRequest {
|
|
|
|
|
val user = getUserName()
|
|
|
|
|
val token = getToken()
|
|
|
|
|
if (user != null && token != null) {
|
|
|
|
|
basicAuth(user, token)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
.build()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val preferences by lazy {
|
|
|
|
|
@@ -81,26 +89,28 @@ class NextcloudApiHelper(val context: Context) {
|
|
|
|
|
* and return the response. Returns null if an error occurs.
|
|
|
|
|
*/
|
|
|
|
|
internal suspend fun startLoginFlow(serverUrl: String): LoginFlowResponse? {
|
|
|
|
|
val request = Request.Builder()
|
|
|
|
|
.url("$serverUrl/index.php/login/v2")
|
|
|
|
|
.method("POST", EMPTY_REQUEST)
|
|
|
|
|
.header("user-agent", context.getString(R.string.app_name))
|
|
|
|
|
.build()
|
|
|
|
|
val response = try {
|
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
|
httpClient.newCall(request).execute()
|
|
|
|
|
httpClient.post {
|
|
|
|
|
url {
|
|
|
|
|
takeFrom(serverUrl)
|
|
|
|
|
path("index.php", "login", "v2")
|
|
|
|
|
}
|
|
|
|
|
header(HttpHeaders.UserAgent, context.getString(R.string.app_name))
|
|
|
|
|
}
|
|
|
|
|
} catch (e: IOException) {
|
|
|
|
|
Log.e("NextcloudApiHelper", "HTTP error", e)
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
if (response?.code != 200 || response.body == null) {
|
|
|
|
|
Log.e("NextcloudApiHelper", "Invalid response: ${response?.code} ${response?.message}")
|
|
|
|
|
if (response?.status != HttpStatusCode.OK) {
|
|
|
|
|
Log.e(
|
|
|
|
|
"NextcloudApiHelper",
|
|
|
|
|
"Invalid response: ${response?.status} ${response?.bodyAsText()}"
|
|
|
|
|
)
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return try {
|
|
|
|
|
Json.Lenient.decodeFromStream<LoginFlowResponse>(response.body!!.byteStream())
|
|
|
|
|
response.body<LoginFlowResponse>()
|
|
|
|
|
} catch (e: SerializationException) {
|
|
|
|
|
Log.e("NextcloudApiHelper", "Invalid response body", e)
|
|
|
|
|
null
|
|
|
|
|
@@ -108,25 +118,27 @@ class NextcloudApiHelper(val context: Context) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal suspend fun pollLoginFlow(loginFlow: LoginFlowResponse): LoginPollResponse? {
|
|
|
|
|
val request = Request.Builder()
|
|
|
|
|
.url(loginFlow.poll.endpoint)
|
|
|
|
|
.method("POST", FormBody.Builder().add("token", loginFlow.poll.token).build())
|
|
|
|
|
.build()
|
|
|
|
|
val response = try {
|
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
|
httpClient.newCall(request).execute()
|
|
|
|
|
}
|
|
|
|
|
httpClient.submitForm(
|
|
|
|
|
url = loginFlow.poll.endpoint,
|
|
|
|
|
formParameters = parameters {
|
|
|
|
|
append("token", loginFlow.poll.token)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
} catch (e: IOException) {
|
|
|
|
|
Log.e("NextcloudApiHelper", "HTTP error", e)
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
if (response?.code != 200 || response.body == null) {
|
|
|
|
|
Log.e("NextcloudApiHelper", "Invalid response: ${response?.code} ${response?.message}")
|
|
|
|
|
if (response?.status != HttpStatusCode.OK) {
|
|
|
|
|
Log.e(
|
|
|
|
|
"NextcloudApiHelper",
|
|
|
|
|
"Invalid response: ${response?.status} ${response?.bodyAsText()}"
|
|
|
|
|
)
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return try {
|
|
|
|
|
Json.Lenient.decodeFromStream<LoginPollResponse>(response.body!!.byteStream())
|
|
|
|
|
response.body<LoginPollResponse>()
|
|
|
|
|
} catch (e: SerializationException) {
|
|
|
|
|
Log.e("NextcloudApiHelper", "Invalid response body", e)
|
|
|
|
|
null
|
|
|
|
|
@@ -164,28 +176,33 @@ class NextcloudApiHelper(val context: Context) {
|
|
|
|
|
|
|
|
|
|
val server = getServer() ?: return null
|
|
|
|
|
|
|
|
|
|
val request = Request.Builder()
|
|
|
|
|
.addHeader("OCS-APIRequest", "true")
|
|
|
|
|
.url("$server/ocs/v1.php/cloud/user?format=json")
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
|
|
val response = runCatching {
|
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
|
httpClient.newCall(request).execute()
|
|
|
|
|
val response = try {
|
|
|
|
|
httpClient.get {
|
|
|
|
|
url {
|
|
|
|
|
takeFrom(server)
|
|
|
|
|
path("ocs", "v1.php", "cloud", "user")
|
|
|
|
|
parameter("format", "json")
|
|
|
|
|
}
|
|
|
|
|
header("OCS-APIRequest", "true")
|
|
|
|
|
}
|
|
|
|
|
}.getOrNull() ?: return getUserName()
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
Log.e("NextcloudApiHelper", "HTTP error", e)
|
|
|
|
|
return getUserName()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.code != 200) {
|
|
|
|
|
if (response.status != HttpStatusCode.OK) {
|
|
|
|
|
logout()
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
val body = response.body ?: return getUserName()
|
|
|
|
|
val body = try {
|
|
|
|
|
response.body<UserReponse>()
|
|
|
|
|
} catch (e: SerializationException) {
|
|
|
|
|
Log.e("NextcloudApiHelper", "Invalid response body", e)
|
|
|
|
|
return getUserName()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return withContext(Dispatchers.IO) {
|
|
|
|
|
val json = JSONObject(body.string())
|
|
|
|
|
val name = json.optJSONObject("ocs")
|
|
|
|
|
?.optJSONObject("data")
|
|
|
|
|
?.optString("display-name")
|
|
|
|
|
val name = body.ocs.data.displayName
|
|
|
|
|
|
|
|
|
|
preferences.edit {
|
|
|
|
|
putString("displayname", name)
|
|
|
|
|
@@ -196,10 +213,6 @@ class NextcloudApiHelper(val context: Context) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getAuthorization(): String? {
|
|
|
|
|
return Credentials.basic(getUserName() ?: return null, getToken() ?: return null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun getServer(): String? {
|
|
|
|
|
return preferences.getString("server", null)
|
|
|
|
|
}
|
|
|
|
|
@@ -225,15 +238,15 @@ class NextcloudApiHelper(val context: Context) {
|
|
|
|
|
val username = getUserName()
|
|
|
|
|
val token = getToken()
|
|
|
|
|
if (server == null || username == null || token == null) return
|
|
|
|
|
val request = Request.Builder()
|
|
|
|
|
.addHeader("OCS-APIREQUEST", "true")
|
|
|
|
|
.delete()
|
|
|
|
|
.url("$server/ocs/v2.php/core/apppassword")
|
|
|
|
|
.build()
|
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
|
try {
|
|
|
|
|
val response = httpClient.newCall(request).execute()
|
|
|
|
|
response
|
|
|
|
|
httpClient.delete {
|
|
|
|
|
url {
|
|
|
|
|
takeFrom(server)
|
|
|
|
|
path("ocs", "v2.php", "core", "apppassword")
|
|
|
|
|
}
|
|
|
|
|
header("OCS-APIREQUEST", "true")
|
|
|
|
|
}
|
|
|
|
|
} catch (e: IOException) {
|
|
|
|
|
Log.e("NextcloudApiHelper", "Error during Nextcloud logout", e)
|
|
|
|
|
}
|
|
|
|
|
|