diff --git a/app/src/main/java/com/wbrawner/pihelper/AddPiHelperViewModel.kt b/app/src/main/java/com/wbrawner/pihelper/AddPiHelperViewModel.kt index 1bbf755..40d871d 100644 --- a/app/src/main/java/com/wbrawner/pihelper/AddPiHelperViewModel.kt +++ b/app/src/main/java/com/wbrawner/pihelper/AddPiHelperViewModel.kt @@ -6,15 +6,11 @@ import androidx.core.content.edit import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.wbrawner.piholeclient.PiHoleApiService -import com.wbrawner.piholeclient.Summary import com.wbrawner.piholeclient.VersionResponse import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import java.net.ConnectException import java.net.SocketTimeoutException -import java.util.regex.Pattern -import java.util.regex.Pattern.DOTALL const val KEY_BASE_URL = "baseUrl" const val KEY_API_KEY = "apiKey" @@ -56,7 +52,9 @@ class AddPiHelperViewModel( suspend fun beginScanning(deviceIpAddress: String) { val addressParts = deviceIpAddress.split(".").toMutableList() var chunks = 1 - val ipAddresses = mutableListOf() + // If the Pi-hole is correctly set up, then there should be a special host for it as + // "pi.hole" + val ipAddresses = mutableListOf("pi.hole") while (chunks <= IP_MAX) { val chunkSize = (IP_MAX - IP_MIN + 1) / chunks if (chunkSize == 1) { @@ -111,16 +109,8 @@ class AddPiHelperViewModel( } suspend fun authenticateWithPassword(password: String) { - // Perform the login to get the PHPSESSID cookie set - apiService.login(password) - val html = apiService.getApiToken() - val matcher = Pattern.compile(".*Raw API Token: ([a-z0-9]+).*", DOTALL) - .matcher(html) - if (!matcher.matches()) { - throw RuntimeException("Unable to retrieve API token from password") - } - val apiToken = matcher.group(1)!! - authenticateWithApiKey(apiToken) + // The Pi-hole API key is just the web password hashed twice with SHA-256 + authenticateWithApiKey(password.hash().hash()) } suspend fun authenticateWithApiKey(apiKey: String) { @@ -143,4 +133,4 @@ class AddPiHelperViewModel( baseUrl = null apiKey = null } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/wbrawner/pihelper/Extensions.kt b/app/src/main/java/com/wbrawner/pihelper/Extensions.kt index ab54674..7a41f90 100644 --- a/app/src/main/java/com/wbrawner/pihelper/Extensions.kt +++ b/app/src/main/java/com/wbrawner/pihelper/Extensions.kt @@ -1,3 +1,9 @@ package com.wbrawner.pihelper -fun R.transform(block: (R) -> T): T = block(this) \ No newline at end of file +import java.math.BigInteger +import java.security.MessageDigest + +fun String.hash(): String = BigInteger( + 1, + MessageDigest.getInstance("SHA-256").digest(this.toByteArray()) +).toString(16) diff --git a/app/src/main/java/com/wbrawner/pihelper/InfoFragment.kt b/app/src/main/java/com/wbrawner/pihelper/InfoFragment.kt index e27795b..d60c629 100644 --- a/app/src/main/java/com/wbrawner/pihelper/InfoFragment.kt +++ b/app/src/main/java/com/wbrawner/pihelper/InfoFragment.kt @@ -15,12 +15,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.wbrawner.pihelper.MainActivity.Companion.ACTION_FORGET_PIHOLE -import kotlinx.android.synthetic.main.fragment_main.toolbar import kotlinx.android.synthetic.main.fragment_info.* +import kotlinx.android.synthetic.main.fragment_main.toolbar import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import org.koin.android.ext.android.inject -import java.lang.RuntimeException import kotlin.coroutines.CoroutineContext class InfoFragment : Fragment(), CoroutineScope { @@ -30,7 +29,6 @@ class InfoFragment : Fragment(), CoroutineScope { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) - throw RuntimeException("I crashed!") } override fun onCreateView( diff --git a/piholeclient/src/main/java/com/wbrawner/piholeclient/PiHoleApiService.kt b/piholeclient/src/main/java/com/wbrawner/piholeclient/PiHoleApiService.kt index 1d5027c..c06f575 100644 --- a/piholeclient/src/main/java/com/wbrawner/piholeclient/PiHoleApiService.kt +++ b/piholeclient/src/main/java/com/wbrawner/piholeclient/PiHoleApiService.kt @@ -4,20 +4,14 @@ import com.squareup.moshi.Moshi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.HttpUrl -import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody import kotlin.reflect.KClass interface PiHoleApiService { var baseUrl: String? var apiKey: String? - suspend fun login(password: String): String - - suspend fun getApiToken(): String - suspend fun getSummary( version: Boolean = false, type: Boolean = false @@ -48,8 +42,6 @@ interface PiHoleApiService { } const val BASE_PATH = "/admin/api.php" -const val INDEX_PATH = "/admin/index.php" -const val API_TOKEN_PATH = "/admin/scripts/pi-hole/php/api_token.php" class OkHttpPiHoleApiService( private val okHttpClient: OkHttpClient, @@ -110,26 +102,6 @@ class OkHttpPiHoleApiService( return sendRequest(request.build(), TopItemsResponse::class)!! } - override suspend fun login(password: String): String { - val url = urlBuilder - .encodedPath(INDEX_PATH) - .addQueryParameter("login", "") - val body = "pw=$password".toRequestBody("application/x-www-form-urlencoded".toMediaType()) - val request = Request.Builder() - .post(body) - .url(url.build()) - return sendRequest(request.build(), String::class)!! - } - - override suspend fun getApiToken(): String { - val url = urlBuilder - .encodedPath(API_TOKEN_PATH) - val request = Request.Builder() - .get() - .url(url.build()) - return sendRequest(request.build(), String::class)!! - } - override suspend fun enable(): StatusResponse { val apiToken = this.apiKey ?: throw java.lang.IllegalStateException("No API Token provided") val url = urlBuilder