Add requestPermission API method (#255)

* Add requestPermission Api Method

* Add documentation about requestPermission method

* Update version documentation
This commit is contained in:
DegrangeM 2021-05-08 05:33:06 +02:00 committed by GitHub
parent 17d8ecf60f
commit 9fec86f7fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 13 deletions

View File

@ -1,13 +1,49 @@
# Miscellaneous Actions
* **version**
* **requestPermission**
Gets the version of the API exposed by this plugin. Currently versions `1` through `6` are defined.
Request permission to use the API exposed by this plugin. Only request coming from origin listed in the
`webCorsOriginList` option are allowed to use the Api. Calling this method will display a popup asking the user
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
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.
This should be the first call you make to make sure that your application and AnkiConnect are able to communicate
properly with each other. New versions of AnkiConnect are backwards compatible; as long as you are using actions
which are available in the reported AnkiConnect version or earlier, everything should work fine.
*Sample request*:
```json
{
"action": "requestPermission",
"version": 6
}
```
*Samples results*:
```json
{
"result": {
"permission": "granted",
"requireApiKey": false,
"version": 6
},
"error": null
}
```
```json
{
"result": {
"permission": "denied"
},
"error": null
}
```
* **version**
Gets the version of the API exposed by this plugin. Currently versions `1` through `6` are defined.
*Sample request*:
```json
{

View File

@ -26,6 +26,7 @@ import string
import time
import unicodedata
from PyQt5 import QtCore
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QMessageBox
@ -97,7 +98,7 @@ class AnkiConnect:
reply = {'result': None, 'error': None}
try:
if key != util.setting('apiKey'):
if key != util.setting('apiKey') and name != 'requestPermission':
raise Exception('valid api key must be provided')
method = None
@ -311,6 +312,55 @@ class AnkiConnect:
def version(self):
return util.setting('apiVersion')
@util.api()
def requestPermission(self, origin, allowed):
if allowed:
return {
"permission": "granted",
"requireApikey": bool(util.setting('apiKey')),
"version": util.setting('apiVersion')
}
if origin in util.setting('ignoreOriginList') :
return {
"permission": "denied",
}
msg = QMessageBox(None)
msg.setWindowTitle("A website want to access to Anki")
msg.setText(origin + " request permission to use Anki through AnkiConnect.\nDo you want to give it access ?")
msg.setInformativeText("By giving permission, the website will be able to do actions on anki, including destructives actions like deck deletion.")
msg.setWindowIcon(self.window().windowIcon())
msg.setIcon(QMessageBox.Question)
msg.setStandardButtons(QMessageBox.Yes|QMessageBox.Ignore|QMessageBox.No)
msg.setDefaultButton(QMessageBox.No)
msg.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
pressedButton = msg.exec_()
if pressedButton == QMessageBox.Yes:
config = aqt.mw.addonManager.getConfig(__name__)
config["webCorsOriginList"] = util.setting('webCorsOriginList')
config["webCorsOriginList"].append(origin)
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 = {
"permission": "granted",
"requireApikey": bool(util.setting('apiKey')),
"version": util.setting('apiVersion')
}
else :
results = {
"permission": "denied",
}
return results
@util.api()
def getProfiles(self):

View File

@ -3,5 +3,6 @@
"apiLogPath": null,
"webBindAddress": "127.0.0.1",
"webBindPort": 8765,
"webCorsOriginList": ["http://localhost"]
"webCorsOriginList": ["http://localhost"],
"ignoreOriginList": []
}

View File

@ -76,6 +76,7 @@ def setting(key):
'webBindPort': 8765,
'webCorsOrigin': os.getenv('ANKICONNECT_CORS_ORIGIN', None),
'webCorsOriginList': ['http://localhost'],
'ignoreOriginList': [],
'webTimeout': 10000,
}

View File

@ -185,16 +185,25 @@ class WebServer:
allowed = True
resp = bytes()
paramsError = False
try:
params = json.loads(req.body.decode('utf-8'))
except ValueError:
body = json.dumps(None).encode('utf-8')
paramsError = True
if allowed :
if allowed or not paramsError and params.get('action', '') == 'requestPermission':
if len(req.body) == 0:
body = 'AnkiConnect v.{}'.format(util.setting('apiVersion')).encode('utf-8')
else:
try:
params = json.loads(req.body.decode('utf-8'))
if params.get('action', '') == 'requestPermission':
params['params'] = params.get('params', {})
params['params']['allowed'] = allowed
params['params']['origin'] = b'origin' in req.headers and req.headers[b'origin'].decode() or ''
if not allowed :
corsOrigin = params['params']['origin']
body = json.dumps(self.handler(params)).encode('utf-8')
except ValueError:
body = json.dumps(None).encode('utf-8')
headers = [
['HTTP/1.1 200 OK', None],