Skip to content

Commit

Permalink
Merge pull request #315 from cyb3rko/intent-url-protection
Browse files Browse the repository at this point in the history
Protection of intentURL attack using interactive dialog confirmation
  • Loading branch information
jmattheis authored Oct 7, 2023
2 parents 6dd5fe4 + 45b41b5 commit 361c480
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 4 deletions.
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name=".messages.IntentUrlDialogActivity"
android:exported="false"
android:theme="@style/AppTheme.Dialog" />

<service android:name=".service.WebSocketService" />

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.gotify.messages

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.github.gotify.databinding.ActivityDialogIntentUrlBinding

internal class IntentUrlDialogActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFinishOnTouchOutside(false)
val binding = ActivityDialogIntentUrlBinding.inflate(layoutInflater)
val intentUrl = intent.getStringExtra(EXTRA_KEY_URL)
assert(intentUrl != null) { "intentUrl may not be empty" }

binding.urlView.text = intentUrl
binding.openButton.setOnClickListener {
finish()
Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(intentUrl)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(this)
}
}
binding.cancelButton.setOnClickListener { finish() }
setContentView(binding.root)
}

companion object {
const val EXTRA_KEY_URL = "url"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.github.gotify.client.model.Message
import com.github.gotify.log.Log
import com.github.gotify.log.UncaughtExceptionHandler
import com.github.gotify.messages.Extras
import com.github.gotify.messages.IntentUrlDialogActivity
import com.github.gotify.messages.MessagesActivity
import com.github.gotify.picasso.PicassoHandler
import io.noties.markwon.Markwon
Expand Down Expand Up @@ -320,9 +321,10 @@ internal class WebSocketService : Service() {
)

if (intentUrl != null) {
intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(intentUrl)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent = Intent(this, IntentUrlDialogActivity::class.java).apply {
putExtra(IntentUrlDialogActivity.EXTRA_KEY_URL, intentUrl)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
}

Expand Down
39 changes: 39 additions & 0 deletions app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
Expand Down Expand Up @@ -97,6 +99,14 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL
Utils.setExcludeFromRecent(requireContext(), value as Boolean)
return@OnPreferenceChangeListener true
}
findPreference<SwitchPreferenceCompat>(
getString(R.string.setting_key_intent_dialog_permission)
)?.let {
it.setOnPreferenceChangeListener { _, _ ->
openSystemAlertWindowPermissionPage()
}
}
checkSystemAlertWindowPermission()
}

override fun onDisplayPreferenceDialog(preference: Preference) {
Expand All @@ -107,6 +117,35 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL
}
}

override fun onResume() {
super.onResume()
checkSystemAlertWindowPermission()
}

private fun openSystemAlertWindowPermissionPage(): Boolean {
Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:${requireContext().packageName}")
).apply {
startActivity(this)
}
return true
}

private fun checkSystemAlertWindowPermission() {
findPreference<SwitchPreferenceCompat>(
getString(R.string.setting_key_intent_dialog_permission)
)?.let {
val canDrawOverlays = Settings.canDrawOverlays(requireContext())
it.isChecked = canDrawOverlays
it.summary = if (canDrawOverlays) {
getString(R.string.setting_summary_intent_dialog_permission_granted)
} else {
getString(R.string.setting_summary_intent_dialog_permission)
}
}
}

private fun showListPreferenceDialog(preference: ListPreference) {
val dialogFragment = MaterialListPreference()
dialogFragment.arguments = Bundle(1).apply { putString("key", preference.key) }
Expand Down
52 changes: 52 additions & 0 deletions app/src/main/res/layout/activity_dialog_intent_url.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxWidth="560dp"
android:minWidth="280dp"
android:orientation="vertical"
android:padding="24dp">

<com.google.android.material.textview.MaterialTextView
android:id="@+id/message_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_dialog_message"
android:textSize="18sp" />

<com.google.android.material.textview.MaterialTextView
android:id="@+id/url_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:textSize="18sp"
android:textStyle="italic"
tools:text="https://gotify.net" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="24dp">

<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:id="@+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_dialog_button_cancel" />

<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:id="@+id/open_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_dialog_button_open" />

</LinearLayout>

</LinearLayout>
8 changes: 8 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@
<string name="setting_key_notification_channels">notification_channels</string>
<string name="setting_key_exclude_from_recent">exclude_from_recent</string>
<string name="setting_exclude_from_recent">Exclude from recents</string>
<string name="setting_intent_dialog_permission">Intent Action Permission</string>
<string name="setting_key_intent_dialog_permission">intent_dialog_permission</string>
<string name="setting_summary_intent_dialog_permission">To always show incoming intent URLs, give permission to show this app on top of other apps.</string>
<string name="setting_summary_intent_dialog_permission_granted">Permission granted.</string>
<string name="push_message">Push message</string>
<string name="appListDescription">App:</string>
<string name="priorityDescription">Priority:</string>
Expand All @@ -96,6 +100,10 @@
<string name="push_missing_app_info">There are no applications available on the server to push a message to.</string>
<string name="message_copied_to_clipboard">Content copied to clipboard</string>
<string name="not_loggedin_share">Cannot share to Gotify, because you aren\'t logged in.</string>
<string name="action_dialog_missing">Missing URL</string>
<string name="action_dialog_message">You have received a message with an intent url:</string>
<string name="action_dialog_button_open">Open</string>
<string name="action_dialog_button_cancel">Cancel</string>

<string name="websocket_not_connected">Not connected</string>
<string name="websocket_reconnect">Trying to reconnect</string>
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@

<style name="AppTheme.PopupOverlay" parent="AppTheme" />

<style name="AppTheme.Dialog" parent="Theme.Material3.DayNight.Dialog">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

<style name="Preference.SwitchPreferenceCompat" parent="@style/Preference.SwitchPreferenceCompat.Material" tools:ignore="ResourceCycle">
<item name="widgetLayout">@layout/preference_switch</item>
</style>
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/res/xml/root_preferences.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<PreferenceCategory app:title="@string/settings_appearance" >
<ListPreference
Expand Down Expand Up @@ -37,6 +38,11 @@
android:key="@string/setting_key_notification_channels"
android:title="@string/setting_notification_channels"
app:singleLineTitle="false" />

<SwitchPreferenceCompat
android:key="@string/setting_key_intent_dialog_permission"
android:title="@string/setting_intent_dialog_permission"
tools:summary="@string/setting_summary_intent_dialog_permission" />
</PreferenceCategory>

</PreferenceScreen>

0 comments on commit 361c480

Please sign in to comment.