requestPermission: improve UX & docs (#312)

* requestPermission: change Ignore button to checkbox

* Update requestPermission documentation

- describe the possible results
- document ignoreOriginList
This commit is contained in:
introt 2022-04-14 04:00:52 +00:00 committed by Alex Yatskov
parent 8e9879f80f
commit 81fccbfac6
2 changed files with 58 additions and 48 deletions

View File

@ -1455,11 +1455,18 @@ corresponding to when the API was available for use.
* **requestPermission** * **requestPermission**
Request permission to use the API exposed by this plugin. Only request coming from origin listed in the Requests permission to use the API exposed by this plugin. This method does not require the API key, and is the
`webCorsOriginList` option are allowed to use the Api. Calling this method will display a popup asking the user only one that accepts requests from any origin; the other methods only accept requests from trusted origins,
if he want to allow your origin to use the Api. This is the only method that can be called even if the origin of which are listed under `webCorsOriginList` in the add-on config. `localhost` is trusted by default.
the request isn't in the `webCorsOriginList` list. It also doesn't require the api key. Calling this method will
not display the popup if the origin is already trusted. Calling this method from an untrusted origin will display a popup in Anki asking the user whether they want to
allow your origin to use the API; calls from trusted origins will return the result without displaying the popup.
When denying permission, the user may also choose to ignore further permission requests from that origin. These
origins end up in the `ignoreOriginList`, editable via the add-on config.
The result always contains the `permission` field, which in turn contains either the string `granted` or `denied`,
corresponding to whether your origin is trusted. If your origin is trusted, the fields `requireApiKey` (`true` if
required) and `version` will also be returned.
This should be the first call you make to make sure that your application and Anki-Connect are able to communicate This should be the first call you make to make sure that your application and Anki-Connect are able to communicate
properly with each other. New versions of Anki-Connect are backwards compatible; as long as you are using actions properly with each other. New versions of Anki-Connect are backwards compatible; as long as you are using actions

View File

@ -28,7 +28,7 @@ import unicodedata
from PyQt5 import QtCore from PyQt5 import QtCore
from PyQt5.QtCore import QTimer from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox, QCheckBox
import anki import anki
import anki.exporting import anki.exporting
@ -364,28 +364,33 @@ class AnkiConnect:
def version(self): def version(self):
return util.setting('apiVersion') return util.setting('apiVersion')
@util.api() @util.api()
def requestPermission(self, origin, allowed): def requestPermission(self, origin, allowed):
results = {
"permission": "denied",
}
if allowed: if allowed:
return { results = {
"permission": "granted", "permission": "granted",
"requireApikey": bool(util.setting('apiKey')), "requireApikey": bool(util.setting('apiKey')),
"version": util.setting('apiVersion') "version": util.setting('apiVersion')
} }
if origin in util.setting('ignoreOriginList') : elif origin in util.setting('ignoreOriginList'):
return { pass # defaults to denied
"permission": "denied",
}
else: # prompt the user
msg = QMessageBox(None) msg = QMessageBox(None)
msg.setWindowTitle("A website want to access to Anki") msg.setWindowTitle("A website wants to access to Anki")
msg.setText(origin + " request permission to use Anki through AnkiConnect.\nDo you want to give it access ?") msg.setText('"{}" requests permission to use Anki through AnkiConnect. Do you want to give it access?'.format(origin))
msg.setInformativeText("By giving permission, the website will be able to do actions on anki, including destructives actions like deck deletion.") msg.setInformativeText("By granting permission, you'll allow the website to modify your collection on your behalf, including the execution of destructive actions such as deck deletion.")
msg.setWindowIcon(self.window().windowIcon()) msg.setWindowIcon(self.window().windowIcon())
msg.setIcon(QMessageBox.Question) msg.setIcon(QMessageBox.Question)
msg.setStandardButtons(QMessageBox.Yes|QMessageBox.Ignore|QMessageBox.No) msg.setStandardButtons(QMessageBox.Yes|QMessageBox.No)
msg.setDefaultButton(QMessageBox.No) msg.setDefaultButton(QMessageBox.No)
msg.setCheckBox(QCheckBox(text='Ignore further requests from "{}"'.format(origin), parent=msg))
msg.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) msg.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
pressedButton = msg.exec_() pressedButton = msg.exec_()
@ -394,23 +399,21 @@ class AnkiConnect:
config["webCorsOriginList"] = util.setting('webCorsOriginList') config["webCorsOriginList"] = util.setting('webCorsOriginList')
config["webCorsOriginList"].append(origin) config["webCorsOriginList"].append(origin)
aqt.mw.addonManager.writeConfig(__name__, config) aqt.mw.addonManager.writeConfig(__name__, config)
if pressedButton == QMessageBox.Ignore:
config = aqt.mw.addonManager.getConfig(__name__)
config["ignoreOriginList"] = util.setting('ignoreOriginList')
config["ignoreOriginList"].append(origin)
aqt.mw.addonManager.writeConfig(__name__, config)
if pressedButton == QMessageBox.Yes:
results = { results = {
"permission": "granted", "permission": "granted",
"requireApikey": bool(util.setting('apiKey')), "requireApikey": bool(util.setting('apiKey')),
"version": util.setting('apiVersion') "version": util.setting('apiVersion')
} }
else :
results = { # if the origin isn't an empty string, the user clicks "No", and the ignore box is checked
"permission": "denied", elif origin and pressedButton == QMessageBox.No and msg.checkBox().isChecked():
} config = aqt.mw.addonManager.getConfig(__name__)
config["ignoreOriginList"] = util.setting('ignoreOriginList')
config["ignoreOriginList"].append(origin)
aqt.mw.addonManager.writeConfig(__name__, config)
# else defaults to denied
return results return results