Skip to content
New issue

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

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

Already on GitHub? # to your account

Protection of intentURL attack using interactive dialog confirmation #315

Merged
merged 2 commits into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>