Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/security url and intent check #21

Merged
merged 10 commits into from
Nov 8, 2022
8 changes: 2 additions & 6 deletions demo/src/main/java/ramp/network/demo/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,8 @@ class MainActivity : AppCompatActivity() {
val config = Config(
hostLogoUrl = "https://ramp.network/assets/images/Logo.svg",
hostAppName = "My App",
userAddress = "0x4b7f8e04b82ad7f9e4b4cc9e1f81c5938e1b719f",
url = "https://ri-widget-staging.firebaseapp.com/",
swapAsset = "ETH",
fiatCurrency = "USD",
fiatValue = "10",
selectedCountryCode = "US"
url = "https://ri-widget-dev2.firebaseapp.com",
hostApiKey = "input your host api key"
)
// 4. Implement callbacks
val callback = object : RampCallback {
Expand Down
15 changes: 11 additions & 4 deletions rampsdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ android {
defaultConfig {
minSdkVersion 21
targetSdkVersion 32
versionCode 13
versionName "1.3.10"
versionCode 14
versionName "1.3.11"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
Expand All @@ -39,6 +39,12 @@ android {
buildFeatures {
viewBinding true
}

testOptions {
unitTests.all {
useJUnitPlatform()
}
}
}


Expand Down Expand Up @@ -67,9 +73,10 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'


testImplementation 'junit:junit:4.13.2'
testImplementation "org.junit.jupiter:junit-jupiter:5.8.0"
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

}

afterEvaluate {
Expand All @@ -79,7 +86,7 @@ afterEvaluate {
from components.release
groupId = 'com.github.RampNetwork'
artifactId = 'ramp-sdk-android'
version = '1.3.10'
version = '1.3.11'
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import kotlinx.coroutines.launch
import network.ramp.sdk.events.EventBus
import network.ramp.sdk.events.model.*
import network.ramp.sdk.facade.Config
import network.ramp.sdk.utils.UrlSafeChecker
import timber.log.Timber

internal class RampPresenter(
Expand Down Expand Up @@ -93,7 +94,7 @@ internal class RampPresenter(

override fun buildUrl(config: Config): String {
return config.url +
"?hostAppName=${config.hostAppName}" +
"/?hostAppName=${config.hostAppName}" +
"&hostLogoUrl=${config.hostLogoUrl}" +
concatenateIfNotBlank("&swapAsset=", config.swapAsset) +
concatenateIfNotBlank("&swapAmount=", config.swapAmount) +
Expand Down Expand Up @@ -189,7 +190,9 @@ internal class RampPresenter(
systemOnBackPressed()
}

private fun <T : Event> postMessage(event: T) {
fun isUrlSafe(url: String): Boolean = UrlSafeChecker.isUrlSafe(url)

fun <T : Event> postMessage(event: T) {
val eventJson = moshi
.adapter(Event::class.java)
.toJson(event)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package network.ramp.sdk.ui.activity


import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -48,10 +49,24 @@ internal class RampWidgetActivity : AppCompatActivity(), Contract.View {

if (savedInstanceState == null) {
Timber.d(rampPresenter.buildUrl(config))
binding.webView.loadUrl(rampPresenter.buildUrl(config))
securityCheck(intent)?.let {
binding.webView.loadUrl(it)
} ?: close()
}
}

private fun securityCheck(intent: Intent): String? =
if (isInternalIntent(intent) && rampPresenter.isUrlSafe(config.url))
rampPresenter.buildUrl(config)
else {
Timber.e("SECURITY ALERT - UNAUTHORIZED CALL")
null
}

private fun isInternalIntent(intent: Intent): Boolean =
intent.data?.scheme == null


override fun sendPostMessage(data: String) {
val url = "javascript:(function f() { window.postMessage($data, \"*\"); })()"
binding.webView.post {
Expand Down Expand Up @@ -100,7 +115,5 @@ internal class RampWidgetActivity : AppCompatActivity(), Contract.View {

companion object {
const val ACTION_VIEW_INTENT = "android.intent.action.VIEW"
const val RAMP_PREFIX = "ramp"
const val HTTPS_SCHEME = "https"
}
}
25 changes: 25 additions & 0 deletions rampsdk/src/main/java/network/ramp/sdk/utils/UrlSafeChecker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package network.ramp.sdk.utils

object UrlSafeChecker {

private val listOfSafeUrls = listOf(
"https://ri-widget-dev2.firebaseapp.com",
"https://ri-widget-staging.firebaseapp.com",
"https://buy.ramp.network"
)
private val listOfSafeRegex = listOf("^https://ri-widget-dev-(\\d+)\\.firebaseapp\\.com$")

fun isUrlSafe(url: String) = checkStaticUrls(url) || checkRegexList(url)

private fun checkStaticUrls(url: String): Boolean = listOfSafeUrls.contains(url)

private fun checkRegexList(url: String): Boolean {
var isMatch = false
listOfSafeRegex.forEach {
val regex = Regex(pattern = it, options = setOf(RegexOption.IGNORE_CASE))
if (regex.matches(url))
isMatch = true
}
return isMatch
}
}
17 changes: 0 additions & 17 deletions rampsdk/src/test/java/network/ramp/sdk/ExampleUnitTest.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package network.ramp.sdk.utils

import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test


internal class UrlSafeCheckerTest {

@Test
fun `isUrlSafe should return true fore safe urls`() {
val safeUrl1 = "https://ri-widget-dev2.firebaseapp.com"
val safeUrl2 = "https://ri-widget-staging.firebaseapp.com"
val safeUrl3 = "https://buy.ramp.network"
val safeUrl4 = "https://ri-widget-dev-5.firebaseapp.com"
val safeUrl5 = "https://ri-widget-dev-42.firebaseapp.com"


Assertions.assertAll(
{ Assertions.assertTrue(UrlSafeChecker.isUrlSafe(safeUrl1)) },
{ Assertions.assertTrue(UrlSafeChecker.isUrlSafe(safeUrl2)) },
{ Assertions.assertTrue(UrlSafeChecker.isUrlSafe(safeUrl3)) },
{ Assertions.assertTrue(UrlSafeChecker.isUrlSafe(safeUrl4)) },
{ Assertions.assertTrue(UrlSafeChecker.isUrlSafe(safeUrl5)) }
)
}

@Test
fun `isUrlSafe should return false fore unsafe urls`() {

val unsafeUrl1 = "https://ri-widget-devs2.firebaseapp.com"
val unsafeUrl2 = "ri-widget-staging.firebaseapp.com"
val unsafeUrl3 = "https://ngrok.io/buy.ramp.network"
val unsafeUrl4 = "https://ri-widget-dev-5.firebaseapp.com/sadasd"
val unsafeUrl5 = "https://ri-widget-dev.com/?https://ri-widget-devs2.firebaseapp.com"
val unsafeUrl6 = "https://ri-widget-dev-s.firebaseapp.com"
val unsafeUrl7 = "https://ri-widget-dev-10.firebaseapp.comsd"
val unsafeUrl8 = "https://ri-widget-dev-.firebaseapp.com"


Assertions.assertAll(
{ Assertions.assertFalse(UrlSafeChecker.isUrlSafe(unsafeUrl1)) },
{ Assertions.assertFalse(UrlSafeChecker.isUrlSafe(unsafeUrl2)) },
{ Assertions.assertFalse(UrlSafeChecker.isUrlSafe(unsafeUrl3)) },
{ Assertions.assertFalse(UrlSafeChecker.isUrlSafe(unsafeUrl4)) },
{ Assertions.assertFalse(UrlSafeChecker.isUrlSafe(unsafeUrl5)) },
{ Assertions.assertFalse(UrlSafeChecker.isUrlSafe(unsafeUrl6)) },
{ Assertions.assertFalse(UrlSafeChecker.isUrlSafe(unsafeUrl7)) },
{ Assertions.assertFalse(UrlSafeChecker.isUrlSafe(unsafeUrl8)) }
)
}
}