Add box for self-signed

pull/653/merge
Varun Patil 2023-10-13 23:12:46 -07:00
parent 49f97e2895
commit b0c5927d9b
8 changed files with 136 additions and 53 deletions

View File

@ -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;
}

View File

@ -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,
});

View File

@ -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

View File

@ -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)) {

View File

@ -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))
}
/**

View File

@ -0,0 +1,8 @@
package gallery.memories.service
data class Credential(
var url: String,
var trustAll: Boolean,
var username: String,
var token: String,
)

View File

@ -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()
return runRequest(
Request.Builder()
.url(loginFlowUrl)
.header("User-Agent", "Memories")
.post("".toRequestBody("application/json".toMediaTypeOrNull()))
.build())
.build()
)
}
/** Make login polling request */
@Throws(Exception::class)
fun getPollLogin(pollUrl: String, pollToken: String): Response {
return runRequest(Request.Builder()
return runRequest(
Request.Builder()
.url(pollUrl)
.post("token=$pollToken".toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull()))
.build())
.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()

View File

@ -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")