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 # 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 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 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. 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*: *Sample request*:
```json ```json
{ {

View File

@ -26,6 +26,7 @@ import string
import time import time
import unicodedata import unicodedata
from PyQt5 import QtCore
from PyQt5.QtCore import QTimer from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
@ -97,7 +98,7 @@ class AnkiConnect:
reply = {'result': None, 'error': None} reply = {'result': None, 'error': None}
try: try:
if key != util.setting('apiKey'): if key != util.setting('apiKey') and name != 'requestPermission':
raise Exception('valid api key must be provided') raise Exception('valid api key must be provided')
method = None method = None
@ -311,6 +312,55 @@ class AnkiConnect:
def version(self): def version(self):
return util.setting('apiVersion') 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() @util.api()
def getProfiles(self): def getProfiles(self):

View File

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

View File

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

View File

@ -185,16 +185,25 @@ class WebServer:
allowed = True allowed = True
resp = bytes() 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: if len(req.body) == 0:
body = 'AnkiConnect v.{}'.format(util.setting('apiVersion')).encode('utf-8') body = 'AnkiConnect v.{}'.format(util.setting('apiVersion')).encode('utf-8')
else: else:
try: if params.get('action', '') == 'requestPermission':
params = json.loads(req.body.decode('utf-8')) 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') body = json.dumps(self.handler(params)).encode('utf-8')
except ValueError:
body = json.dumps(None).encode('utf-8')
headers = [ headers = [
['HTTP/1.1 200 OK', None], ['HTTP/1.1 200 OK', None],