Merge pull request #315 from cyb3rko/intent-url-protection
Protection of intentURL attack using interactive dialog confirmation
This commit is contained in:
@@ -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" />
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
@@ -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) }
|
||||
|
||||
52
app/src/main/res/layout/activity_dialog_intent_url.xml
Normal file
52
app/src/main/res/layout/activity_dialog_intent_url.xml
Normal file
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user