Add box for self-signed
parent
49f97e2895
commit
b0c5927d9b
|
@ -93,5 +93,15 @@ input.m-input:focus {
|
|||
|
||||
.login-button {
|
||||
margin: 10px;
|
||||
margin-top: 20px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
div.trust {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
vertical-align: -3px;
|
||||
}
|
|
@ -18,9 +18,20 @@
|
|||
type="url"
|
||||
id="server-url"
|
||||
class="m-input"
|
||||
placeholder="https://nextcloud.example.com"
|
||||
placeholder="nextcloud.example.com"
|
||||
/>
|
||||
|
||||
<div class="trust">
|
||||
<label for="trust-all">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="trust-all"
|
||||
class="m-checkbox"
|
||||
/>
|
||||
Disable certificate verification (unsafe)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="m-button login-button" id="login">
|
||||
Continue to Login
|
||||
</button>
|
||||
|
@ -93,7 +104,11 @@
|
|||
|
||||
// Login signal
|
||||
const encUrl = encodeURIComponent(encodeURIComponent(getMemoriesUrl().toString()));
|
||||
await fetch(`http://127.0.0.1/api/login/${encUrl}`, {
|
||||
|
||||
// Trust all certificates
|
||||
const trustAll = document.getElementById("trust-all").checked ? "1" : "0";
|
||||
|
||||
await fetch(`http://127.0.0.1/api/login/${encUrl}?trustAll=${trustAll}`, {
|
||||
method: "GET",
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
|
|
@ -179,7 +179,7 @@ class MainActivity : AppCompatActivity() {
|
|||
// WebView.setWebContentsDebuggingEnabled(true);
|
||||
|
||||
// Welcome page or actual app
|
||||
nativex.account.refreshAuthHeader()
|
||||
nativex.account.refreshCredentials()
|
||||
val isApp = loadDefaultUrl()
|
||||
|
||||
// Start version check if loaded account
|
||||
|
|
|
@ -224,7 +224,12 @@ class NativeX(private val mCtx: MainActivity) {
|
|||
|
||||
val parts = path.split("/").toTypedArray()
|
||||
return if (path.matches(API.LOGIN)) {
|
||||
makeResponse(account.login(URLDecoder.decode(parts[3], "UTF-8")))
|
||||
makeResponse(
|
||||
account.login(
|
||||
URLDecoder.decode(parts[3], "UTF-8"),
|
||||
request.url.getBooleanQueryParameter("trustAll", false)
|
||||
)
|
||||
)
|
||||
} else if (path.matches(API.DAYS)) {
|
||||
makeResponse(query.getDays())
|
||||
} else if (path.matches(API.DAY)) {
|
||||
|
|
|
@ -17,17 +17,19 @@ class AccountService(private val mCtx: MainActivity, private val mHttp: HttpServ
|
|||
}
|
||||
|
||||
private val store = SecureStorage(mCtx)
|
||||
private var mTrustAll = false
|
||||
|
||||
/**
|
||||
* Make the first request to log in
|
||||
* @param url The URL of the Nextcloud server
|
||||
* @param trustAll Whether to trust all certificates
|
||||
*/
|
||||
fun login(url: String) {
|
||||
fun login(url: String, trustAll: Boolean) {
|
||||
try {
|
||||
Log.v(TAG, "login: Connecting to ${url}api/describe")
|
||||
mHttp.setBaseUrl(url)
|
||||
val res = mHttp.getApiDescription()
|
||||
mTrustAll = trustAll
|
||||
mHttp.build(url, trustAll)
|
||||
|
||||
val res = mHttp.getApiDescription()
|
||||
if (res.code != 200) {
|
||||
throw Exception("${url}api/describe (status ${res.code})")
|
||||
}
|
||||
|
@ -185,36 +187,31 @@ class AccountService(private val mCtx: MainActivity, private val mHttp: HttpServ
|
|||
* @param password The password to store
|
||||
*/
|
||||
fun storeCredentials(url: String, user: String, password: String) {
|
||||
store.saveCredentials(url, user, password)
|
||||
mHttp.setBaseUrl(url)
|
||||
mHttp.setAuthHeader(Pair(user, password))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored credentials
|
||||
* @return The stored credentials
|
||||
*/
|
||||
fun getCredentials(): Pair<String, String>? {
|
||||
val saved = store.getCredentials()
|
||||
if (saved == null) return null
|
||||
mHttp.setBaseUrl(saved.first)
|
||||
return Pair(saved.second, saved.third)
|
||||
store.saveCredentials(Credential(
|
||||
url = url,
|
||||
trustAll = mTrustAll,
|
||||
username = user,
|
||||
token = password,
|
||||
))
|
||||
refreshCredentials()
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the stored credentials
|
||||
*/
|
||||
fun deleteCredentials() {
|
||||
mHttp.setAuthHeader(null)
|
||||
mHttp.setBaseUrl(null)
|
||||
store.deleteCredentials()
|
||||
mHttp.setAuthHeader(null)
|
||||
mHttp.build(null, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the authorization header
|
||||
*/
|
||||
fun refreshAuthHeader() {
|
||||
mHttp.setAuthHeader(getCredentials())
|
||||
fun refreshCredentials() {
|
||||
val cred = store.getCredentials() ?: return
|
||||
mHttp.build(cred.url, cred.trustAll)
|
||||
mHttp.setAuthHeader(Pair(cred.username, cred.token))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package gallery.memories.service
|
||||
|
||||
data class Credential(
|
||||
var url: String,
|
||||
var trustAll: Boolean,
|
||||
var username: String,
|
||||
var token: String,
|
||||
)
|
|
@ -10,15 +10,22 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
|||
import okhttp3.Response
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
|
||||
class HttpService {
|
||||
companion object {
|
||||
val TAG = HttpService::class.java.simpleName
|
||||
}
|
||||
|
||||
private val client = OkHttpClient()
|
||||
private var client = OkHttpClient()
|
||||
private var authHeader: String? = null
|
||||
private var memoriesUrl: String? = null
|
||||
private var baseUrl: String? = null
|
||||
|
||||
/**
|
||||
* Check if the HTTP service is logged in
|
||||
|
@ -28,11 +35,45 @@ class HttpService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the Memories URL
|
||||
* Build the HTTP client
|
||||
* @param url The URL to use
|
||||
* @param trustAll Whether to trust all certificates
|
||||
*/
|
||||
fun setBaseUrl(url: String?) {
|
||||
memoriesUrl = url
|
||||
fun build(url: String?, trustAll: Boolean) {
|
||||
baseUrl = url
|
||||
client = if (trustAll) {
|
||||
val trustAllCerts = arrayOf<TrustManager>(
|
||||
object : X509TrustManager {
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(
|
||||
chain: Array<X509Certificate>,
|
||||
authType: String
|
||||
) {
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(
|
||||
chain: Array<X509Certificate>,
|
||||
authType: String
|
||||
) {
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> {
|
||||
return arrayOf()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val sslContext = SSLContext.getInstance("SSL")
|
||||
sslContext.init(null, trustAllCerts, SecureRandom())
|
||||
|
||||
OkHttpClient.Builder()
|
||||
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
|
||||
.hostnameVerifier({ hostname, session -> true })
|
||||
.build()
|
||||
} else {
|
||||
OkHttpClient()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,8 +97,8 @@ class HttpService {
|
|||
*/
|
||||
fun loadWebView(webView: WebView, subpath: String? = null): String? {
|
||||
// Load app interface if authenticated
|
||||
if (authHeader != null && memoriesUrl != null) {
|
||||
var url = memoriesUrl
|
||||
if (authHeader != null && baseUrl != null) {
|
||||
var url = baseUrl
|
||||
if (subpath != null) url += subpath
|
||||
|
||||
// Get host name
|
||||
|
@ -104,20 +145,24 @@ class HttpService {
|
|||
/** Make login flow request */
|
||||
@Throws(Exception::class)
|
||||
fun postLoginFlow(loginFlowUrl: String): Response {
|
||||
return runRequest(Request.Builder()
|
||||
.url(loginFlowUrl)
|
||||
.header("User-Agent", "Memories")
|
||||
.post("".toRequestBody("application/json".toMediaTypeOrNull()))
|
||||
.build())
|
||||
return runRequest(
|
||||
Request.Builder()
|
||||
.url(loginFlowUrl)
|
||||
.header("User-Agent", "Memories")
|
||||
.post("".toRequestBody("application/json".toMediaTypeOrNull()))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
/** Make login polling request */
|
||||
@Throws(Exception::class)
|
||||
fun getPollLogin(pollUrl: String, pollToken: String): Response {
|
||||
return runRequest(Request.Builder()
|
||||
.url(pollUrl)
|
||||
.post("token=$pollToken".toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull()))
|
||||
.build())
|
||||
return runRequest(
|
||||
Request.Builder()
|
||||
.url(pollUrl)
|
||||
.post("token=$pollToken".toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull()))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
/** Run a request and get a JSON object */
|
||||
|
@ -129,7 +174,7 @@ class HttpService {
|
|||
/** Build a GET request */
|
||||
private fun buildGet(path: String, auth: Boolean = true): Request {
|
||||
val builder = Request.Builder()
|
||||
.url(memoriesUrl + path)
|
||||
.url(baseUrl + path)
|
||||
.header("User-Agent", "Memories")
|
||||
.get()
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.security.keystore.KeyProperties.KEY_ALGORITHM_AES
|
|||
import android.security.keystore.KeyProperties.PURPOSE_DECRYPT
|
||||
import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT
|
||||
import android.util.Base64
|
||||
import gallery.memories.service.Credential
|
||||
import java.security.KeyStore
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
|
@ -23,34 +24,35 @@ class SecureStorage(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun saveCredentials(url: String, username: String, token: String) {
|
||||
fun saveCredentials(cred: Credential) {
|
||||
val cipher = getCipher()
|
||||
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())
|
||||
val encryptedToken = cipher.doFinal(token.toByteArray())
|
||||
val encryptedToken = cipher.doFinal(cred.token.toByteArray())
|
||||
|
||||
context.getSharedPreferences("credentials", Context.MODE_PRIVATE).edit()
|
||||
.putString("url", url)
|
||||
.putString("username", username)
|
||||
.putString("url", cred.url)
|
||||
.putBoolean("trustAll", cred.trustAll)
|
||||
.putString("username", cred.username)
|
||||
.putString("encryptedToken", Base64.encodeToString(encryptedToken, Base64.DEFAULT))
|
||||
.putString("iv", Base64.encodeToString(cipher.iv, Base64.DEFAULT))
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun getCredentials(): Triple<String, String, String>? {
|
||||
fun getCredentials(): Credential? {
|
||||
val sharedPreferences = context.getSharedPreferences("credentials", Context.MODE_PRIVATE)
|
||||
|
||||
val url = sharedPreferences.getString("url", null)
|
||||
val trustAll = sharedPreferences.getBoolean("trustAll", false)
|
||||
val username = sharedPreferences.getString("username", null)
|
||||
val encryptedToken = sharedPreferences.getString("encryptedToken", null)
|
||||
val ivStr = sharedPreferences.getString("iv", null)
|
||||
|
||||
if (url != null && username != null && encryptedToken != null && ivStr != null) {
|
||||
val iv = Base64.decode(ivStr, Base64.DEFAULT)
|
||||
|
||||
val cipher = getCipher()
|
||||
val iv = Base64.decode(ivStr, Base64.DEFAULT)
|
||||
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), IvParameterSpec(iv))
|
||||
|
||||
val token = String(cipher.doFinal(Base64.decode(encryptedToken, Base64.DEFAULT)))
|
||||
return Triple(url, username, token)
|
||||
return Credential(url, trustAll, username, token)
|
||||
}
|
||||
|
||||
return null
|
||||
|
@ -59,6 +61,7 @@ class SecureStorage(private val context: Context) {
|
|||
fun deleteCredentials() {
|
||||
context.getSharedPreferences("credentials", Context.MODE_PRIVATE).edit()
|
||||
.remove("url")
|
||||
.remove("trustAll")
|
||||
.remove("encryptedUsername")
|
||||
.remove("encryptedToken")
|
||||
.remove("iv")
|
||||
|
|
Loading…
Reference in New Issue