Skip to content

Commit a869f9b

Browse files
authored
fix: blocks http requests (#27)
* fix: blocks http requests * fix: fixes network sec config * fix: retrofit
1 parent d2733c2 commit a869f9b

File tree

6 files changed

+64
-9
lines changed

6 files changed

+64
-9
lines changed

android/src/androidTest/java/com/formbricks/android/FormbricksInstrumentedTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit
2929
class FormbricksInstrumentedTest {
3030

3131
private val environmentId = "environmentId"
32-
private val appUrl = "http://appUrl"
32+
private val appUrl = "https://example.com"
3333
private val userId = "6CCCE716-6783-4D0F-8344-9C7DFA43D8F7"
3434
private val surveyID = "cm6ovw6j7000gsf0kduf4oo4i"
3535

android/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
44
<uses-permission android:name="android.permission.INTERNET" />
55
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="35" />
6+
<application android:networkSecurityConfig="@xml/network_security_config" />
67
</manifest>

android/src/main/java/com/formbricks/android/Formbricks.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ object Formbricks {
5252
return
5353
}
5454

55+
// Validate HTTPS URL
56+
if (!config.appUrl.startsWith("https://", ignoreCase = true)) {
57+
val error = RuntimeException("Only HTTPS URLs are allowed for security reasons. HTTP URLs are not permitted. Provided URL: ${config.appUrl}")
58+
Logger.e(error)
59+
return
60+
}
61+
5562
applicationContext = context
5663

5764
appUrl = config.appUrl

android/src/main/java/com/formbricks/android/network/FormbricksApiService.kt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.formbricks.android.network
22

33
import com.formbricks.android.api.error.FormbricksAPIError
44
import com.formbricks.android.helper.mapToJsonElement
5+
import com.formbricks.android.logger.Logger
56
import com.formbricks.android.model.environment.EnvironmentDataHolder
67
import com.formbricks.android.model.environment.EnvironmentResponse
78
import com.formbricks.android.model.user.PostUserBody
@@ -14,19 +15,25 @@ import retrofit2.Call
1415
import retrofit2.Retrofit
1516

1617
open class FormbricksApiService {
17-
18-
private lateinit var retrofit: Retrofit
18+
private var retrofit: Retrofit? = null
1919

2020
fun initialize(appUrl: String, isLoggingEnabled: Boolean) {
21-
retrofit = FormbricksRetrofitBuilder(appUrl, isLoggingEnabled)
22-
.getBuilder()
23-
.build()
21+
val builder = FormbricksRetrofitBuilder(appUrl, isLoggingEnabled).getBuilder()
22+
if (builder != null) {
23+
retrofit = builder.build()
24+
} else {
25+
// Builder returned null due to HTTP URL - log error and skip initialization
26+
val error = RuntimeException("Failed to initialize API service due to invalid URL configuration. Only HTTPS URLs are allowed.")
27+
Logger.e(error)
28+
retrofit = null
29+
}
2430
}
2531

2632
open fun getEnvironmentStateObject(environmentId: String): Result<EnvironmentDataHolder> {
2733
return try {
34+
val retrofitInstance = retrofit ?: return Result.failure(RuntimeException("API service not initialized due to invalid URL"))
2835
val result = execute {
29-
retrofit.create(FormbricksService::class.java)
36+
retrofitInstance.create(FormbricksService::class.java)
3037
.getEnvironmentState(environmentId)
3138
}
3239
val json = Json { ignoreUnknownKeys = true }
@@ -41,8 +48,9 @@ open class FormbricksApiService {
4148
}
4249

4350
open fun postUser(environmentId: String, body: PostUserBody): Result<UserResponse> {
51+
val retrofitInstance = retrofit ?: return Result.failure(RuntimeException("API service not initialized due to invalid URL"))
4452
return execute {
45-
retrofit.create(FormbricksService::class.java)
53+
retrofitInstance.create(FormbricksService::class.java)
4654
.postUser(environmentId, body)
4755
}
4856
}

android/src/main/java/com/formbricks/android/network/FormbricksRetrofitBuilder.kt

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
package com.formbricks.android.network
22

3+
import com.formbricks.android.logger.Logger
4+
import okhttp3.Interceptor
35
import okhttp3.OkHttpClient
6+
import okhttp3.Response
47
import okhttp3.logging.HttpLoggingInterceptor
58
import retrofit2.Retrofit
69
import retrofit2.converter.gson.GsonConverterFactory
10+
import java.io.IOException
711
import java.util.concurrent.TimeUnit
812

913
class FormbricksRetrofitBuilder(private val baseUrl: String, private val loggingEnabled: Boolean) {
14+
fun getBuilder(): Retrofit.Builder? {
15+
// Validate base URL is HTTPS
16+
if (!baseUrl.startsWith("https://", ignoreCase = true)) {
17+
val error = RuntimeException("Only HTTPS URLs are allowed. HTTP URLs are not permitted for security reasons. Provided URL: $baseUrl")
18+
Logger.e(error)
19+
return null
20+
}
1021

11-
fun getBuilder(): Retrofit.Builder {
1222
val clientBuilder = OkHttpClient.Builder()
1323
.connectTimeout(CONNECT_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
1424
.readTimeout(READ_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
1525
.followSslRedirects(true)
26+
.addInterceptor(HttpsOnlyInterceptor())
27+
1628
if (loggingEnabled) {
1729
val logging = HttpLoggingInterceptor()
1830
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
@@ -25,6 +37,25 @@ class FormbricksRetrofitBuilder(private val baseUrl: String, private val logging
2537
.client(clientBuilder.build())
2638
}
2739

40+
/**
41+
* Interceptor that ensures all requests use HTTPS protocol
42+
*/
43+
private class HttpsOnlyInterceptor : Interceptor {
44+
@Throws(IOException::class)
45+
override fun intercept(chain: Interceptor.Chain): Response {
46+
val request = chain.request()
47+
val url = request.url
48+
49+
if (!url.isHttps) {
50+
val error = RuntimeException("HTTP request blocked. Only HTTPS requests are allowed. Attempted URL: $url")
51+
Logger.e(error)
52+
throw IOException("HTTP request blocked. Only HTTPS requests are allowed. Attempted URL: $url")
53+
}
54+
55+
return chain.proceed(request)
56+
}
57+
}
58+
2859
companion object {
2960
private const val CONNECT_TIMEOUT_MS = 30 * 1000 // 30 seconds
3061
private const val READ_TIMEOUT_MS = 30 * 1000 // 30 seconds
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<network-security-config>
3+
<base-config cleartextTrafficPermitted="false">
4+
<trust-anchors>
5+
<certificates src="system"/>
6+
</trust-anchors>
7+
</base-config>
8+
</network-security-config>

0 commit comments

Comments
 (0)