Skip to content

Commit

Permalink
Use additional http headers to bypass Cloudflare SSO. (Or other SSO)
Browse files Browse the repository at this point in the history
  • Loading branch information
Meister1977 committed May 17, 2023
1 parent 8b32f9b commit 2570714
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 15 deletions.
Expand Up @@ -233,6 +233,13 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
}
}

override fun disableAdditionalHttpHeaders(useCloud: Boolean) {
findPreference<PreferenceCategory>("http_headers")?.let {
it.isEnabled = !useCloud
it.isVisible = !useCloud
}
}

override fun updateExternalUrl(url: String, useCloud: Boolean) {
findPreference<Preference>("connection_external")?.let {
it.summary =
Expand Down
Expand Up @@ -56,6 +56,10 @@ class ServerSettingsPresenterImpl @Inject constructor(
"registration_name" -> serverManager.getServer(serverId)?.deviceName
"connection_internal" -> (serverManager.getServer(serverId)?.connection?.getUrl(isInternal = true, force = true) ?: "").toString()
"session_timeout" -> serverManager.integrationRepository(serverId).getSessionTimeOut().toString()
"header_name_1" -> serverManager.integrationRepository(serverId).getHeaderName1()
"header_name_2" -> serverManager.integrationRepository(serverId).getHeaderName2()
"header_value_1" -> serverManager.integrationRepository(serverId).getHeaderValue1()
"header_value_2" -> serverManager.integrationRepository(serverId).getHeaderValue2()
else -> throw IllegalArgumentException("No string found by this key: $key")
}
}
Expand Down Expand Up @@ -100,6 +104,10 @@ class ServerSettingsPresenterImpl @Inject constructor(
Log.e(TAG, "Issue saving session timeout value", e)
}
}
"header_name_1" -> serverManager.integrationRepository(serverId).saveHeaderName1(value?.ifBlank { null })
"header_name_2" -> serverManager.integrationRepository(serverId).saveHeaderName2(value?.ifBlank { null })
"header_value_1" -> serverManager.integrationRepository(serverId).saveHeaderValue1(value?.ifBlank { null })
"header_value_2" -> serverManager.integrationRepository(serverId).saveHeaderValue2(value?.ifBlank { null })
else -> throw IllegalArgumentException("No string found by this key: $key")
}
}
Expand Down Expand Up @@ -142,6 +150,9 @@ class ServerSettingsPresenterImpl @Inject constructor(
it.connection.getUrl(false)?.toString() ?: "",
it.connection.useCloud && it.connection.canUseCloud()
)
view.disableAdditionalHttpHeaders(
it.connection.useCloud && it.connection.canUseCloud()
)
}
}
mainScope.launch {
Expand Down
Expand Up @@ -3,6 +3,7 @@ package io.homeassistant.companion.android.settings.server
interface ServerSettingsView {
fun updateServerName(name: String)
fun enableInternalConnection(isEnabled: Boolean)
fun disableAdditionalHttpHeaders(useCloud: Boolean)
fun updateExternalUrl(url: String, useCloud: Boolean)
fun updateSsids(ssids: List<String>)
fun onRemovedServer(success: Boolean, hasAnyRemaining: Boolean)
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/res/xml/preferences_server.xml
Expand Up @@ -64,6 +64,36 @@
app:isPreferenceVisible="false"
app:useSimpleSummaryProvider="true"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/http_header_settings"
android:key="http_headers">
<Preference
android:selectable="false"
android:enabled="true"
android:key="http_header_settings_summary"
android:title=""
android:summary="@string/http_header_settings_summary" />
<EditTextPreference
android:key="header_name_1"
android:icon="@drawable/ic_edit"
android:title="@string/header_name_1"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
android:key="header_value_1"
android:icon="@drawable/ic_edit"
android:title="@string/header_value_1"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
android:key="header_name_2"
android:icon="@drawable/ic_edit"
android:title="@string/header_name_2"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
android:key="header_value_2"
android:icon="@drawable/ic_edit"
android:title="@string/header_value_2"
app:useSimpleSummaryProvider="true"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/other_settings">
<Preference
Expand Down
Expand Up @@ -54,6 +54,22 @@ interface IntegrationRepository {

suspend fun setTrusted(trusted: Boolean)

suspend fun getHeaderName1(): String?

suspend fun saveHeaderName1(headerName1: String?)

suspend fun getHeaderName2(): String?

suspend fun saveHeaderName2(headerName2: String?)

suspend fun getHeaderValue1(): String?

suspend fun saveHeaderValue1(headerValue1: String?)

suspend fun getHeaderValue2(): String?

suspend fun saveHeaderValue2(headerValue2: String?)

suspend fun shouldNotifySecurityWarning(): Boolean

suspend fun getConversation(speech: String): String?
Expand Down
Expand Up @@ -57,6 +57,10 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
private const val PREF_SESSION_TIMEOUT = "session_timeout"
private const val PREF_SESSION_EXPIRE = "session_expire"
private const val PREF_TRUSTED = "trusted"
private const val PREF_HEADER_NAME_1 = "header_name_1"
private const val PREF_HEADER_NAME_2 = "header_name_2"
private const val PREF_HEADER_VALUE_1 = "header_value_1"
private const val PREF_HEADER_VALUE_2 = "header_value_2"
private const val PREF_SEC_WARNING_NEXT = "sec_warning_last"
private const val TAG = "IntegrationRepository"
private const val RATE_LIMIT_URL = BuildConfig.RATE_LIMIT_URL
Expand Down Expand Up @@ -87,6 +91,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
integrationService.registerDevice(
url.newBuilder().addPathSegments("api/mobile_app/registrations").build(),
serverManager.authenticationRepository(serverId).buildBearerToken(),
getAdditionalHeaders(),
request
)
try {
Expand Down Expand Up @@ -118,7 +123,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
var causeException: Exception? = null
for (it in server.connection.getApiUrls()) {
try {
if (integrationService.callWebhook(it.toHttpUrlOrNull()!!, request).isSuccessful) {
if (integrationService.callWebhook(it.toHttpUrlOrNull()!!, getAdditionalHeaders(),request).isSuccessful) {
persistDeviceRegistration(deviceRegistration)
return
}
Expand Down Expand Up @@ -161,6 +166,10 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
localStorage.remove("${serverId}_$PREF_SESSION_EXPIRE")
localStorage.remove("${serverId}_$PREF_TRUSTED")
localStorage.remove("${serverId}_$PREF_SEC_WARNING_NEXT")
localStorage.remove("${serverId}_$PREF_HEADER_NAME_1")
localStorage.remove("${serverId}_$PREF_HEADER_NAME_2")
localStorage.remove("${serverId}_$PREF_HEADER_VALUE_1")
localStorage.remove("${serverId}_$PREF_HEADER_VALUE_2")
// app version and push token is device-specific
}

Expand All @@ -174,6 +183,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
try {
return integrationService.getTemplate(
it.toHttpUrlOrNull()!!,
getAdditionalHeaders(),
IntegrationRequest(
"render_template",
mapOf("template" to Template(template, variables))
Expand Down Expand Up @@ -209,6 +219,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
wasSuccess =
integrationService.callWebhook(
it.toHttpUrlOrNull()!!,
getAdditionalHeaders(),
updateLocationRequest
).isSuccessful
} catch (e: Exception) {
Expand Down Expand Up @@ -248,6 +259,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
wasSuccess =
integrationService.callWebhook(
it.toHttpUrlOrNull()!!,
getAdditionalHeaders(),
IntegrationRequest(
"call_service",
serviceCallRequest
Expand Down Expand Up @@ -279,6 +291,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
wasSuccess =
integrationService.callWebhook(
it.toHttpUrlOrNull()!!,
getAdditionalHeaders(),
IntegrationRequest(
"scan_tag",
data
Expand Down Expand Up @@ -315,6 +328,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
wasSuccess =
integrationService.callWebhook(
it.toHttpUrlOrNull()!!,
getAdditionalHeaders(),
IntegrationRequest(
"fire_event",
fireEventRequest
Expand Down Expand Up @@ -347,7 +361,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
var zones: Array<EntityResponse<ZoneAttributes>>? = null
for (it in server.connection.getApiUrls()) {
try {
zones = integrationService.getZones(it.toHttpUrlOrNull()!!, getZonesRequest)
zones = integrationService.getZones(it.toHttpUrlOrNull()!!, getAdditionalHeaders(), getZonesRequest)
} catch (e: Exception) {
if (causeException == null) causeException = e
// Ignore failure until we are out of URLS to try, but use the first exception as cause exception
Expand Down Expand Up @@ -402,6 +416,38 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
override suspend fun setTrusted(trusted: Boolean) =
localStorage.putBoolean("${serverId}_$PREF_TRUSTED", trusted)

override suspend fun getHeaderName1(): String? {
return localStorage.getString("${serverId}_$PREF_HEADER_NAME_1")
}

override suspend fun getHeaderName2(): String? {
return localStorage.getString("${serverId}_$PREF_HEADER_NAME_2")
}

override suspend fun getHeaderValue1(): String? {
return localStorage.getString("${serverId}_$PREF_HEADER_VALUE_1")
}

override suspend fun getHeaderValue2(): String? {
return localStorage.getString("${serverId}_$PREF_HEADER_VALUE_2")
}

override suspend fun saveHeaderName1(headerName1: String?) {
localStorage.putString("${serverId}_$PREF_HEADER_NAME_1", headerName1)
}

override suspend fun saveHeaderName2(headerName2: String?) {
localStorage.putString("${serverId}_$PREF_HEADER_NAME_2", headerName2)
}

override suspend fun saveHeaderValue1(headerValue1: String?) {
localStorage.putString("${serverId}_$PREF_HEADER_VALUE_1", headerValue1)
}

override suspend fun saveHeaderValue2(headerValue2: String?) {
localStorage.putString("${serverId}_$PREF_HEADER_VALUE_2", headerValue2)
}

override suspend fun getNotificationRateLimits(): RateLimitResponse {
val pushToken = localStorage.getString(PREF_PUSH_TOKEN) ?: ""
val requestBody = RateLimitRequest(pushToken)
Expand Down Expand Up @@ -472,7 +518,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(

for (it in server.connection.getApiUrls()) {
try {
response = integrationService.getConfig(it.toHttpUrlOrNull()!!, getConfigRequest)
response = integrationService.getConfig(it.toHttpUrlOrNull()!!, getAdditionalHeaders(), getConfigRequest)
} catch (e: Exception) {
if (causeException == null) causeException = e
// Ignore failure until we are out of URLS to try, but use the first exception as cause exception
Expand Down Expand Up @@ -556,7 +602,8 @@ class IntegrationRepositoryImpl @AssistedInject constructor(

val response = integrationService.getState(
url.newBuilder().addPathSegments("api/states/$entityId").build(),
serverManager.authenticationRepository(serverId).buildBearerToken()
serverManager.authenticationRepository(serverId).buildBearerToken(),
getAdditionalHeaders()
)
return Entity(
response.entityId,
Expand Down Expand Up @@ -649,10 +696,12 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
var causeException: Exception? = null
for (it in server.connection.getApiUrls()) {
try {
integrationService.callWebhook(it.toHttpUrlOrNull()!!, integrationRequest).let {
// If we created sensor or it already exists
if (it.isSuccessful || it.code() == 409) {
return
integrationService
.callWebhook(it.toHttpUrlOrNull()!!, getAdditionalHeaders(), integrationRequest)
.let {
// If we created sensor or it already exists
if (it.isSuccessful || it.code() == 409) {
return
}
}
} catch (e: Exception) {
Expand Down Expand Up @@ -684,11 +733,13 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
var causeException: Exception? = null
for (it in server.connection.getApiUrls()) {
try {
integrationService.updateSensors(it.toHttpUrlOrNull()!!, integrationRequest).let {
it.forEach { (_, response) ->
if (response["success"] == false) {
return false
}
integrationService
.updateSensors(it.toHttpUrlOrNull()!!, getAdditionalHeaders(), integrationRequest)
.let {
it.forEach { (_, response) ->
if (response["success"] == false) {
return false
}
}
return true
}
Expand Down Expand Up @@ -716,6 +767,25 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
}
}

private suspend fun getAdditionalHeaders(): Map<String, String> {

val headers = mutableMapOf<String, String>()
val headerName1: String? = localStorage.getString("${serverId}_$PREF_HEADER_NAME_1")
val headerName2: String? = localStorage.getString("${serverId}_$PREF_HEADER_NAME_2")
val headerValue1: String? = localStorage.getString("${serverId}_$PREF_HEADER_VALUE_1")
val headerValue2: String? = localStorage.getString("${serverId}_$PREF_HEADER_VALUE_2")

if (!headerName1.isNullOrEmpty() && !headerValue1.isNullOrEmpty()) {
headers[headerName1] = headerValue1
}

if (!headerName2.isNullOrEmpty() && !headerValue2.isNullOrEmpty()) {
headers[headerName2] = headerValue2
}

return headers
}

private suspend fun createUpdateRegistrationRequest(deviceRegistration: DeviceRegistration): RegisterDeviceRequest {
val oldDeviceRegistration = getRegistration()
val pushToken = deviceRegistration.pushToken ?: oldDeviceRegistration.pushToken
Expand Down
Expand Up @@ -14,45 +14,51 @@ import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.HeaderMap
import retrofit2.http.POST
import retrofit2.http.Url

interface IntegrationService {

@POST
suspend fun registerDevice(
@Url url: HttpUrl,
@Header("Authorization") auth: String,
@HeaderMap headers : Map<String, String>,
@Body request: RegisterDeviceRequest
): RegisterDeviceResponse

@GET
suspend fun getState(
@Url url: HttpUrl,
@Header("Authorization") auth: String
@Header("Authorization") auth: String,
@HeaderMap headers : Map<String, String>
): EntityResponse<Map<String, Any>>

@POST
suspend fun callWebhook(
@Url url: HttpUrl,
@HeaderMap headers : Map<String, String>,
@Body request: IntegrationRequest
): Response<ResponseBody>

@POST
suspend fun getTemplate(
@Url url: HttpUrl,
@HeaderMap headers : Map<String, String>,
@Body request: IntegrationRequest
): Map<String, String>

@POST
suspend fun getZones(
@Url url: HttpUrl,
@HeaderMap headers : Map<String, String>,
@Body request: IntegrationRequest
): Array<EntityResponse<ZoneAttributes>>

@POST
suspend fun getConfig(
@Url url: HttpUrl,
@HeaderMap headers : Map<String, String>,
@Body request: IntegrationRequest
): GetConfigResponse

Expand All @@ -65,6 +71,7 @@ interface IntegrationService {
@POST
suspend fun updateSensors(
@Url url: HttpUrl,
@HeaderMap headers : Map<String, String>,
@Body request: IntegrationRequest
): Map<String, Map<String, Any>>
}

0 comments on commit 2570714

Please sign in to comment.