feat: Add URL trust options when the user clicks on a link
- add a popup asking the user before clicking on an unfurled link preview - add a checkbox for the above popup to remember the trust for such domain - use local Settings to persist the "trust domain" locally; for global persistence across devices, see https://github.com/status-im/status-go/issues/4132 Closes #12388
This commit is contained in:
parent
56547c0ae1
commit
2abe0358fc
|
@ -55,7 +55,7 @@ QtObject:
|
|||
|
||||
return $(response.result)
|
||||
except Exception as e:
|
||||
error "error: ", procName="removeReaction", errName = e.name, errDesription = e.msg
|
||||
error "error: ", procName="getLinkPreviewWhitelist", errName = e.name, errDesription = e.msg
|
||||
|
||||
proc getDefaultAccount(self: Service): string =
|
||||
try:
|
||||
|
|
|
@ -13,10 +13,12 @@ class StringUtilsInternal : public QObject
|
|||
public:
|
||||
explicit StringUtilsInternal(QQmlEngine* engine, QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE QString escapeHtml(const QString &unsafe) const;
|
||||
Q_INVOKABLE QString escapeHtml(const QString& unsafe) const;
|
||||
|
||||
Q_INVOKABLE QString readTextFile(const QString& filePath) const;
|
||||
|
||||
Q_INVOKABLE QString extractDomainFromLink(const QString& link) const;
|
||||
|
||||
private:
|
||||
QQmlEngine *m_engine{nullptr};
|
||||
QQmlEngine* m_engine{nullptr};
|
||||
};
|
||||
|
|
|
@ -85,6 +85,6 @@ CheckBox {
|
|||
|
||||
HoverHandler {
|
||||
acceptedDevices: PointerDevice.Mouse
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
cursorShape: root.changeCursor ? Qt.PointingHandCursor : undefined
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,8 @@ QtObject {
|
|||
function readTextFile(file) {
|
||||
return Internal.StringUtils.readTextFile(file)
|
||||
}
|
||||
|
||||
function extractDomainFromLink(link) {
|
||||
return Internal.StringUtils.extractDomainFromLink(link)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <QFileSelector>
|
||||
#include <QQmlEngine>
|
||||
#include <QQmlFileSelector>
|
||||
#include <QUrl>
|
||||
|
||||
StringUtilsInternal::StringUtilsInternal(QQmlEngine* engine, QObject* parent)
|
||||
: m_engine(engine)
|
||||
|
@ -33,3 +34,13 @@ QString StringUtilsInternal::readTextFile(const QString& filePath) const
|
|||
|
||||
return file.readAll();
|
||||
}
|
||||
|
||||
QString StringUtilsInternal::extractDomainFromLink(const QString& link) const
|
||||
{
|
||||
const auto url = QUrl::fromUserInput(link);
|
||||
if (!url.isValid()) {
|
||||
qWarning() << Q_FUNC_INFO << "Invalid URL:" << link;
|
||||
return {};
|
||||
}
|
||||
return url.host();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import QtQuick.Controls 2.13
|
|||
import QtQuick.Layouts 1.13
|
||||
import QtMultimedia 5.13
|
||||
import Qt.labs.platform 1.1
|
||||
import Qt.labs.settings 1.1
|
||||
import QtQml.Models 2.14
|
||||
import QtQml 2.15
|
||||
|
||||
|
@ -215,6 +216,11 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: appMainLocalSettings
|
||||
property var whitelistedUnfurledDomains: []
|
||||
}
|
||||
|
||||
Popups {
|
||||
id: popups
|
||||
popupParent: appMain
|
||||
|
@ -222,6 +228,15 @@ Item {
|
|||
communitiesStore: appMain.communitiesStore
|
||||
devicesStore: appMain.rootStore.profileSectionStore.devicesStore
|
||||
isDevBuild: !production
|
||||
|
||||
onOpenExternalLink: globalConns.onOpenLink(link)
|
||||
onSaveDomainToUnfurledWhitelist: {
|
||||
const whitelistedHostnames = appMainLocalSettings.whitelistedUnfurledDomains || []
|
||||
if (!whitelistedHostnames.includes(domain)) {
|
||||
whitelistedHostnames.push(domain)
|
||||
appMainLocalSettings.whitelistedUnfurledDomains = whitelistedHostnames
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
@ -250,8 +265,6 @@ Item {
|
|||
}
|
||||
|
||||
function onOpenLink(link: string) {
|
||||
console.warn("opening external url without asking user")
|
||||
|
||||
// Qt sometimes inserts random HTML tags; and this will break on invalid URL inside QDesktopServices::openUrl(link)
|
||||
link = appMain.rootStore.plainText(link)
|
||||
|
||||
|
@ -267,6 +280,13 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
function onOpenLinkWithConfirmation(link: string, domain: string) {
|
||||
if (appMainLocalSettings.whitelistedUnfurledDomains.includes(domain) || !!localAccountSensitiveSettings.whitelistedUnfurlingSites[domain])
|
||||
globalConns.onOpenLink(link)
|
||||
else
|
||||
popups.openConfirmExternalLinkPopup(link, domain)
|
||||
}
|
||||
|
||||
function onPlaySendMessageSound() {
|
||||
sendMessageSound.stop()
|
||||
sendMessageSound.play()
|
||||
|
@ -1618,7 +1638,7 @@ Item {
|
|||
settingsUpdated = true
|
||||
}
|
||||
whitelistedHostnames.push(site.address)
|
||||
})
|
||||
})
|
||||
// Remove any whitelisted sites from app settings that don't exist in the
|
||||
// whitelist from status-go
|
||||
Object.keys(settings).forEach(settingsHostname => {
|
||||
|
|
|
@ -31,6 +31,9 @@ QtObject {
|
|||
property var devicesStore
|
||||
property bool isDevBuild
|
||||
|
||||
signal openExternalLink(string link)
|
||||
signal saveDomainToUnfurledWhitelist(string domain)
|
||||
|
||||
property var activePopupComponents: []
|
||||
|
||||
Component.onCompleted: {
|
||||
|
@ -290,6 +293,10 @@ QtObject {
|
|||
openPopup(transferOwnershipPopup, { communityName, communityLogo, token, accounts, sendModalPopup })
|
||||
}
|
||||
|
||||
function openConfirmExternalLinkPopup(link, domain) {
|
||||
openPopup(confirmExternalLinkPopup, {link, domain})
|
||||
}
|
||||
|
||||
readonly property list<Component> _components: [
|
||||
Component {
|
||||
id: removeContactConfirmationDialog
|
||||
|
@ -751,6 +758,15 @@ QtObject {
|
|||
TransferOwnershipPopup {
|
||||
onClosed: destroy()
|
||||
}
|
||||
},
|
||||
|
||||
Component {
|
||||
id: confirmExternalLinkPopup
|
||||
ConfirmExternalLinkPopup {
|
||||
destroyOnClose: true
|
||||
onOpenExternalLink: root.openExternalLink(link)
|
||||
onSaveDomainToUnfurledWhitelist: root.saveDomainToUnfurledWhitelist(domain)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
required property string link
|
||||
required property string domain
|
||||
|
||||
signal openExternalLink(string link)
|
||||
signal saveDomainToUnfurledWhitelist(string domain)
|
||||
|
||||
width: 521
|
||||
|
||||
header: StatusDialogHeader {
|
||||
headline.title: qsTr("Before you go")
|
||||
actions.closeButton.onClicked: root.close()
|
||||
leftComponent: StatusRoundIcon {
|
||||
asset.name: "browser"
|
||||
asset.isImage: true
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 20
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
text: qsTr("This link is taking you to the following site. Be careful to double check the URL before you go.")
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 66
|
||||
radius: Style.current.halfPadding
|
||||
color: Theme.palette.baseColor4
|
||||
|
||||
StatusBaseText {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.rightMargin: Style.current.padding
|
||||
anchors.topMargin: 11
|
||||
anchors.bottomMargin: Style.current.halfPadding
|
||||
text: root.link
|
||||
wrapMode: Text.WrapAnywhere
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
StatusCheckBox {
|
||||
id: trustDomainCheckbox
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Trust <b>%1</b> links from now on").arg(root.domain)
|
||||
}
|
||||
}
|
||||
|
||||
footer: StatusDialogFooter {
|
||||
rightButtons: ObjectModel {
|
||||
StatusFlatButton {
|
||||
text: qsTr("Cancel")
|
||||
onClicked: root.close()
|
||||
}
|
||||
StatusButton {
|
||||
text: qsTr("Visit site")
|
||||
onClicked: {
|
||||
// (optionally) save the domain to whitelist
|
||||
if (trustDomainCheckbox.checked) {
|
||||
root.saveDomainToUnfurledWhitelist(root.domain)
|
||||
}
|
||||
|
||||
root.openExternalLink(root.link)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,3 +25,4 @@ RenameGroupPopup 1.0 RenameGroupPopup.qml
|
|||
DeleteMessageConfirmationPopup 1.0 DeleteMessageConfirmationPopup.qml
|
||||
UserAgreementPopup 1.0 UserAgreementPopup.qml
|
||||
AlertPopup 1.0 AlertPopup.qml
|
||||
ConfirmExternalLinkPopup 1.0 ConfirmExternalLinkPopup.qml
|
||||
|
|
|
@ -116,7 +116,7 @@ Flow {
|
|||
root.imageClicked(unfurledLink, mouse, "", url) // request a dumb context menu with just "copy/open link" items
|
||||
break
|
||||
default:
|
||||
Global.openLink(url) // FIXME https://github.com/status-im/status-desktop/issues/12388
|
||||
Global.openLinkWithConfirmation(url, hostname)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,6 @@ Loader {
|
|||
const arr = links.split(separator)
|
||||
const filtered = arr.filter(v => v.toLowerCase().endsWith('.gif') || v.toLowerCase().startsWith(Constants.userLinkPrefix.toLowerCase()))
|
||||
const out = filtered.join(separator)
|
||||
console.log(`<<<${arr}->${out}`)
|
||||
return out
|
||||
}
|
||||
|
||||
|
@ -756,7 +755,6 @@ Loader {
|
|||
|
||||
linksComponent: Component {
|
||||
LinksMessageView {
|
||||
id: linksMessageView
|
||||
linkPreviewModel: root.linkPreviewModel
|
||||
localUnfurlLinks: root.localUnfurlLinks
|
||||
messageStore: root.messageStore
|
||||
|
|
|
@ -56,6 +56,7 @@ QtObject {
|
|||
var sendModalPopup)
|
||||
|
||||
signal openLink(string link)
|
||||
signal openLinkWithConfirmation(string link, string domain)
|
||||
|
||||
signal setNthEnabledSectionActive(int nthSection)
|
||||
signal appSectionBySectionTypeChanged(int sectionType, int subsection)
|
||||
|
|
Loading…
Reference in New Issue