incomplete work on refactoring client api endpoints
parent
02c71eab2f
commit
54dabefe45
187
onionr/api.py
187
onionr/api.py
|
@ -25,10 +25,11 @@ from flask import request, Response, abort, send_from_directory
|
||||||
import core
|
import core
|
||||||
import onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config, onionrblockapi
|
import onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config, onionrblockapi
|
||||||
import httpapi
|
import httpapi
|
||||||
from httpapi import friendsapi, profilesapi, configapi, miscpublicapi, insertblock
|
from httpapi import friendsapi, profilesapi, configapi, miscpublicapi, miscclientapi, insertblock, onionrsitesapi
|
||||||
from onionrservices import httpheaders
|
from onionrservices import httpheaders
|
||||||
import onionr
|
import onionr
|
||||||
from onionrutils import bytesconverter, stringvalidators, epoch, mnemonickeys
|
from onionrutils import bytesconverter, stringvalidators, epoch, mnemonickeys
|
||||||
|
from httpapi import apiutils
|
||||||
|
|
||||||
config.reload()
|
config.reload()
|
||||||
class FDSafeHandler(WSGIHandler):
|
class FDSafeHandler(WSGIHandler):
|
||||||
|
@ -194,11 +195,6 @@ class API:
|
||||||
bindPort = int(config.get('client.client.port', 59496))
|
bindPort = int(config.get('client.client.port', 59496))
|
||||||
self.bindPort = bindPort
|
self.bindPort = bindPort
|
||||||
|
|
||||||
# Be extremely mindful of this. These are endpoints available without a password
|
|
||||||
self.whitelistEndpoints = ('site', 'www', 'onionrhome', 'homedata', 'board', 'profiles', 'profilesindex',
|
|
||||||
'boardContent', 'sharedContent', 'mail', 'mailindex', 'friends', 'friendsindex',
|
|
||||||
'clandestine', 'clandestineIndex')
|
|
||||||
|
|
||||||
self.clientToken = config.get('client.webpassword')
|
self.clientToken = config.get('client.webpassword')
|
||||||
self.timeBypassToken = base64.b16encode(os.urandom(32)).decode()
|
self.timeBypassToken = base64.b16encode(os.urandom(32)).decode()
|
||||||
|
|
||||||
|
@ -214,67 +210,12 @@ class API:
|
||||||
app.register_blueprint(profilesapi.profile_BP)
|
app.register_blueprint(profilesapi.profile_BP)
|
||||||
app.register_blueprint(configapi.config_BP)
|
app.register_blueprint(configapi.config_BP)
|
||||||
app.register_blueprint(insertblock.ib)
|
app.register_blueprint(insertblock.ib)
|
||||||
|
app.register_blueprint(miscclientapi.getblocks.client_get_blocks)
|
||||||
|
app.register_blueprint(miscclientapi.staticfiles.static_files_bp)
|
||||||
|
app.register_blueprint(onionrsitesapi.site_api)
|
||||||
|
app.register_blueprint(apiutils.shutdown.shutdown_bp)
|
||||||
httpapi.load_plugin_blueprints(app)
|
httpapi.load_plugin_blueprints(app)
|
||||||
|
self.get_block_data = apiutils.GetBlockData(self)
|
||||||
@app.before_request
|
|
||||||
def validateRequest():
|
|
||||||
'''Validate request has set password and is the correct hostname'''
|
|
||||||
# For the purpose of preventing DNS rebinding attacks
|
|
||||||
if request.host != '%s:%s' % (self.host, self.bindPort):
|
|
||||||
abort(403)
|
|
||||||
if request.endpoint in self.whitelistEndpoints:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
if not hmac.compare_digest(request.headers['token'], self.clientToken):
|
|
||||||
if not hmac.compare_digest(request.form['token'], self.clientToken):
|
|
||||||
abort(403)
|
|
||||||
except KeyError:
|
|
||||||
if not hmac.compare_digest(request.form['token'], self.clientToken):
|
|
||||||
abort(403)
|
|
||||||
|
|
||||||
@app.after_request
|
|
||||||
def afterReq(resp):
|
|
||||||
# Security headers
|
|
||||||
resp = httpheaders.set_default_onionr_http_headers(resp)
|
|
||||||
if request.endpoint == 'site':
|
|
||||||
resp.headers['Content-Security-Policy'] = "default-src 'none'; style-src data: 'unsafe-inline'; img-src data:"
|
|
||||||
else:
|
|
||||||
resp.headers['Content-Security-Policy'] = "default-src 'none'; script-src 'self'; object-src 'none'; style-src 'self'; img-src 'self'; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'self'"
|
|
||||||
return resp
|
|
||||||
|
|
||||||
@app.route('/board/', endpoint='board')
|
|
||||||
def loadBoard():
|
|
||||||
return send_from_directory('static-data/www/board/', "index.html")
|
|
||||||
|
|
||||||
@app.route('/mail/<path:path>', endpoint='mail')
|
|
||||||
def loadMail(path):
|
|
||||||
return send_from_directory('static-data/www/mail/', path)
|
|
||||||
@app.route('/mail/', endpoint='mailindex')
|
|
||||||
def loadMailIndex():
|
|
||||||
return send_from_directory('static-data/www/mail/', 'index.html')
|
|
||||||
|
|
||||||
@app.route('/clandestine/<path:path>', endpoint='clandestine')
|
|
||||||
def loadClandestine(path):
|
|
||||||
return send_from_directory('static-data/www/clandestine/', path)
|
|
||||||
@app.route('/clandestine/', endpoint='clandestineIndex')
|
|
||||||
def loadClandestineIndex():
|
|
||||||
return send_from_directory('static-data/www/clandestine/', 'index.html')
|
|
||||||
|
|
||||||
@app.route('/friends/<path:path>', endpoint='friends')
|
|
||||||
def loadContacts(path):
|
|
||||||
return send_from_directory('static-data/www/friends/', path)
|
|
||||||
|
|
||||||
@app.route('/friends/', endpoint='friendsindex')
|
|
||||||
def loadContacts():
|
|
||||||
return send_from_directory('static-data/www/friends/', 'index.html')
|
|
||||||
|
|
||||||
@app.route('/profiles/<path:path>', endpoint='profiles')
|
|
||||||
def loadContacts(path):
|
|
||||||
return send_from_directory('static-data/www/profiles/', path)
|
|
||||||
|
|
||||||
@app.route('/profiles/', endpoint='profilesindex')
|
|
||||||
def loadContacts():
|
|
||||||
return send_from_directory('static-data/www/profiles/', 'index.html')
|
|
||||||
|
|
||||||
@app.route('/serviceactive/<pubkey>')
|
@app.route('/serviceactive/<pubkey>')
|
||||||
def serviceActive(pubkey):
|
def serviceActive(pubkey):
|
||||||
|
@ -285,22 +226,6 @@ class API:
|
||||||
pass
|
pass
|
||||||
return Response('false')
|
return Response('false')
|
||||||
|
|
||||||
@app.route('/board/<path:path>', endpoint='boardContent')
|
|
||||||
def boardContent(path):
|
|
||||||
return send_from_directory('static-data/www/board/', path)
|
|
||||||
@app.route('/shared/<path:path>', endpoint='sharedContent')
|
|
||||||
def sharedContent(path):
|
|
||||||
return send_from_directory('static-data/www/shared/', path)
|
|
||||||
|
|
||||||
@app.route('/', endpoint='onionrhome')
|
|
||||||
def hello():
|
|
||||||
# ui home
|
|
||||||
return send_from_directory('static-data/www/private/', 'index.html')
|
|
||||||
|
|
||||||
@app.route('/private/<path:path>', endpoint='homedata')
|
|
||||||
def homedata(path):
|
|
||||||
return send_from_directory('static-data/www/private/', path)
|
|
||||||
|
|
||||||
@app.route('/www/<path:path>', endpoint='www')
|
@app.route('/www/<path:path>', endpoint='www')
|
||||||
def wwwPublic(path):
|
def wwwPublic(path):
|
||||||
if not config.get("www.private.run", True):
|
if not config.get("www.private.run", True):
|
||||||
|
@ -336,67 +261,11 @@ class API:
|
||||||
def ping():
|
def ping():
|
||||||
# Used to check if client api is working
|
# Used to check if client api is working
|
||||||
return Response("pong!")
|
return Response("pong!")
|
||||||
|
|
||||||
@app.route('/getblocksbytype/<name>')
|
|
||||||
def getBlocksByType(name):
|
|
||||||
blocks = self._core.getBlocksByType(name)
|
|
||||||
return Response(','.join(blocks))
|
|
||||||
|
|
||||||
@app.route('/getblockbody/<name>')
|
|
||||||
def getBlockBodyData(name):
|
|
||||||
resp = ''
|
|
||||||
if stringvalidators.validate_hash(name):
|
|
||||||
try:
|
|
||||||
resp = onionrblockapi.Block(name, decrypt=True).bcontent
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
abort(404)
|
|
||||||
return Response(resp)
|
|
||||||
|
|
||||||
@app.route('/getblockdata/<name>')
|
|
||||||
def getData(name):
|
|
||||||
resp = ""
|
|
||||||
if stringvalidators.validate_hash(name):
|
|
||||||
if name in self._core.getBlockList():
|
|
||||||
try:
|
|
||||||
resp = self.getBlockData(name, decrypt=True)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
abort(404)
|
|
||||||
else:
|
|
||||||
abort(404)
|
|
||||||
return Response(resp)
|
|
||||||
|
|
||||||
@app.route('/getblockheader/<name>')
|
|
||||||
def getBlockHeader(name):
|
|
||||||
resp = self.getBlockData(name, decrypt=True, headerOnly=True)
|
|
||||||
return Response(resp)
|
|
||||||
|
|
||||||
@app.route('/lastconnect')
|
@app.route('/lastconnect')
|
||||||
def lastConnect():
|
def lastConnect():
|
||||||
return Response(str(self.publicAPI.lastRequest))
|
return Response(str(self.publicAPI.lastRequest))
|
||||||
|
|
||||||
@app.route('/site/<name>', endpoint='site')
|
|
||||||
def site(name):
|
|
||||||
bHash = name
|
|
||||||
resp = 'Not Found'
|
|
||||||
if stringvalidators.validate_hash(bHash):
|
|
||||||
try:
|
|
||||||
resp = onionrblockapi.Block(bHash).bcontent
|
|
||||||
except onionrexceptions.NoDataAvailable:
|
|
||||||
abort(404)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
resp = base64.b64decode(resp)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if resp == 'Not Found' or not resp:
|
|
||||||
abort(404)
|
|
||||||
return Response(resp)
|
|
||||||
|
|
||||||
@app.route('/waitforshare/<name>', methods=['post'])
|
@app.route('/waitforshare/<name>', methods=['post'])
|
||||||
def waitforshare(name):
|
def waitforshare(name):
|
||||||
'''Used to prevent the **public** api from sharing blocks we just created'''
|
'''Used to prevent the **public** api from sharing blocks we just created'''
|
||||||
|
@ -410,18 +279,7 @@ class API:
|
||||||
|
|
||||||
@app.route('/shutdown')
|
@app.route('/shutdown')
|
||||||
def shutdown():
|
def shutdown():
|
||||||
try:
|
return apiutils.shutdown.shutdown(self)
|
||||||
self.publicAPI.httpServer.stop()
|
|
||||||
self.httpServer.stop()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
return Response("bye")
|
|
||||||
|
|
||||||
@app.route('/shutdownclean')
|
|
||||||
def shutdownClean():
|
|
||||||
# good for calling from other clients
|
|
||||||
self._core.daemonQueueAdd('shutdown')
|
|
||||||
return Response("bye")
|
|
||||||
|
|
||||||
@app.route('/getstats')
|
@app.route('/getstats')
|
||||||
def getStats():
|
def getStats():
|
||||||
|
@ -474,31 +332,6 @@ class API:
|
||||||
except (AttributeError, NameError):
|
except (AttributeError, NameError):
|
||||||
# Don't error on race condition with startup
|
# Don't error on race condition with startup
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def getBlockData(self, bHash, decrypt=False, raw=False, headerOnly=False):
|
|
||||||
assert stringvalidators.validate_hash(bHash)
|
|
||||||
bl = onionrblockapi.Block(bHash, core=self._core)
|
|
||||||
if decrypt:
|
|
||||||
bl.decrypt()
|
|
||||||
if bl.isEncrypted and not bl.decrypted:
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
if not raw:
|
def getBlockData(self, bHash, decrypt=False, raw=False, headerOnly=False):
|
||||||
if not headerOnly:
|
return self.get_block_data.get_block_data(self, bHash, decrypt, raw, headerOnly)
|
||||||
retData = {'meta':bl.bheader, 'metadata': bl.bmetadata, 'content': bl.bcontent}
|
|
||||||
for x in list(retData.keys()):
|
|
||||||
try:
|
|
||||||
retData[x] = retData[x].decode()
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
validSig = False
|
|
||||||
signer = bytesconverter.bytes_to_str(bl.signer)
|
|
||||||
if bl.isSigned() and stringvalidators.validate_pub_key(signer) and bl.isSigner(signer):
|
|
||||||
validSig = True
|
|
||||||
bl.bheader['validSig'] = validSig
|
|
||||||
bl.bheader['meta'] = ''
|
|
||||||
retData = {'meta': bl.bheader, 'metadata': bl.bmetadata}
|
|
||||||
return json.dumps(retData)
|
|
||||||
else:
|
|
||||||
return bl.raw
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import json
|
||||||
|
import core, onionrblockapi
|
||||||
|
from onionrutils import bytesconverter, stringvalidators
|
||||||
|
from . import shutdown
|
||||||
|
|
||||||
|
class GetBlockData:
|
||||||
|
def __init__(self, client_api_inst=None):
|
||||||
|
if client_api_inst is None:
|
||||||
|
self.client_api_inst = None
|
||||||
|
self.c = core.Core()
|
||||||
|
elif isinstance(client_api_inst, core.Core):
|
||||||
|
self.client_api_inst = None
|
||||||
|
self.c = client_api_inst
|
||||||
|
else:
|
||||||
|
self.client_api_Inst = client_api_inst
|
||||||
|
self.c = core.Core()
|
||||||
|
|
||||||
|
def get_block_data(self, bHash, decrypt=False, raw=False, headerOnly=False):
|
||||||
|
assert stringvalidators.validate_hash(bHash)
|
||||||
|
bl = onionrblockapi.Block(bHash, core=self.c)
|
||||||
|
if decrypt:
|
||||||
|
bl.decrypt()
|
||||||
|
if bl.isEncrypted and not bl.decrypted:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
if not raw:
|
||||||
|
if not headerOnly:
|
||||||
|
retData = {'meta':bl.bheader, 'metadata': bl.bmetadata, 'content': bl.bcontent}
|
||||||
|
for x in list(retData.keys()):
|
||||||
|
try:
|
||||||
|
retData[x] = retData[x].decode()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
validSig = False
|
||||||
|
signer = bytesconverter.bytes_to_str(bl.signer)
|
||||||
|
if bl.isSigned() and stringvalidators.validate_pub_key(signer) and bl.isSigner(signer):
|
||||||
|
validSig = True
|
||||||
|
bl.bheader['validSig'] = validSig
|
||||||
|
bl.bheader['meta'] = ''
|
||||||
|
retData = {'meta': bl.bheader, 'metadata': bl.bmetadata}
|
||||||
|
return json.dumps(retData)
|
||||||
|
else:
|
||||||
|
return bl.raw
|
|
@ -0,0 +1,38 @@
|
||||||
|
'''
|
||||||
|
Onionr - Private P2P Communication
|
||||||
|
|
||||||
|
Shutdown the node either hard or cleanly
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
from flask import Blueprint, Response
|
||||||
|
import core, onionrblockapi, onionrexceptions
|
||||||
|
from onionrutils import stringvalidators
|
||||||
|
|
||||||
|
shutdown_bp = Blueprint('shutdown', __name__)
|
||||||
|
|
||||||
|
def shutdown(client_api_inst):
|
||||||
|
try:
|
||||||
|
client_api_inst.publicAPI.httpServer.stop()
|
||||||
|
client_api_inst.httpServer.stop()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
return Response("bye")
|
||||||
|
|
||||||
|
@shutdown_bp.route('/shutdownclean')
|
||||||
|
def shutdown_clean():
|
||||||
|
# good for calling from other clients
|
||||||
|
core.Core().daemonQueueAdd('shutdown')
|
||||||
|
return Response("bye")
|
|
@ -0,0 +1 @@
|
||||||
|
from . import getblocks, staticfiles
|
|
@ -0,0 +1,66 @@
|
||||||
|
'''
|
||||||
|
Onionr - Private P2P Communication
|
||||||
|
|
||||||
|
Create blocks with the client api server
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
from flask import Blueprint, Response, abort
|
||||||
|
import core, onionrblockapi
|
||||||
|
from httpapi import apiutils
|
||||||
|
from onionrutils import stringvalidators
|
||||||
|
|
||||||
|
c = core.Core()
|
||||||
|
|
||||||
|
client_get_block = apiutils.GetBlockData(c)
|
||||||
|
|
||||||
|
client_get_blocks = Blueprint('miscclient', __name__)
|
||||||
|
|
||||||
|
@client_get_blocks.route('/getblocksbytype/<name>')
|
||||||
|
def getBlocksByType(name):
|
||||||
|
blocks = c.getBlocksByType(name)
|
||||||
|
return Response(','.join(blocks))
|
||||||
|
|
||||||
|
@client_get_blocks.route('/getblockbody/<name>')
|
||||||
|
def getBlockBodyData(name):
|
||||||
|
resp = ''
|
||||||
|
if stringvalidators.validate_hash(name):
|
||||||
|
try:
|
||||||
|
resp = onionrblockapi.Block(name, decrypt=True, core=c).bcontent
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
return Response(resp)
|
||||||
|
|
||||||
|
@client_get_blocks.route('/getblockdata/<name>')
|
||||||
|
def getData(name):
|
||||||
|
resp = ""
|
||||||
|
if stringvalidators.validate_hash(name):
|
||||||
|
if name in c.getBlockList():
|
||||||
|
try:
|
||||||
|
resp = client_get_block.get_block_data(name, decrypt=True)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
return Response(resp)
|
||||||
|
|
||||||
|
@client_get_blocks.route('/getblockheader/<name>')
|
||||||
|
def getBlockHeader(name):
|
||||||
|
resp = client_get_block.get_block_data(name, decrypt=True, headerOnly=True)
|
||||||
|
return Response(resp)
|
|
@ -0,0 +1,75 @@
|
||||||
|
'''
|
||||||
|
Onionr - Private P2P Communication
|
||||||
|
|
||||||
|
Register static file routes
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
from flask import Blueprint, send_from_directory
|
||||||
|
|
||||||
|
static_files_bp = Blueprint('staticfiles', __name__)
|
||||||
|
|
||||||
|
@static_files_bp.route('/board/', endpoint='board')
|
||||||
|
def loadBoard():
|
||||||
|
return send_from_directory('static-data/www/board/', "index.html")
|
||||||
|
|
||||||
|
@static_files_bp.route('/mail/<path:path>', endpoint='mail')
|
||||||
|
def loadMail(path):
|
||||||
|
return send_from_directory('static-data/www/mail/', path)
|
||||||
|
|
||||||
|
@static_files_bp.route('/mail/', endpoint='mailindex')
|
||||||
|
def loadMailIndex():
|
||||||
|
return send_from_directory('static-data/www/mail/', 'index.html')
|
||||||
|
|
||||||
|
@static_files_bp.route('/clandestine/<path:path>', endpoint='clandestine')
|
||||||
|
def loadClandestine(path):
|
||||||
|
return send_from_directory('static-data/www/clandestine/', path)
|
||||||
|
|
||||||
|
@static_files_bp.route('/clandestine/', endpoint='clandestineIndex')
|
||||||
|
def loadClandestineIndex():
|
||||||
|
return send_from_directory('static-data/www/clandestine/', 'index.html')
|
||||||
|
|
||||||
|
@static_files_bp.route('/friends/<path:path>', endpoint='friends')
|
||||||
|
def loadContacts(path):
|
||||||
|
return send_from_directory('static-data/www/friends/', path)
|
||||||
|
|
||||||
|
@static_files_bp.route('/friends/', endpoint='friendsindex')
|
||||||
|
def loadContacts():
|
||||||
|
return send_from_directory('static-data/www/friends/', 'index.html')
|
||||||
|
|
||||||
|
@static_files_bp.route('/profiles/<path:path>', endpoint='profiles')
|
||||||
|
def loadContacts(path):
|
||||||
|
return send_from_directory('static-data/www/profiles/', path)
|
||||||
|
|
||||||
|
@static_files_bp.route('/profiles/', endpoint='profilesindex')
|
||||||
|
def loadContacts():
|
||||||
|
return send_from_directory('static-data/www/profiles/', 'index.html')
|
||||||
|
|
||||||
|
@static_files_bp.route('/board/<path:path>', endpoint='boardContent')
|
||||||
|
def boardContent(path):
|
||||||
|
return send_from_directory('static-data/www/board/', path)
|
||||||
|
|
||||||
|
@static_files_bp.route('/shared/<path:path>', endpoint='sharedContent')
|
||||||
|
def sharedContent(path):
|
||||||
|
return send_from_directory('static-data/www/shared/', path)
|
||||||
|
|
||||||
|
@static_files_bp.route('/', endpoint='onionrhome')
|
||||||
|
def hello():
|
||||||
|
# ui home
|
||||||
|
return send_from_directory('static-data/www/private/', 'index.html')
|
||||||
|
|
||||||
|
@static_files_bp.route('/private/<path:path>', endpoint='homedata')
|
||||||
|
def homedata(path):
|
||||||
|
return send_from_directory('static-data/www/private/', path)
|
|
@ -20,6 +20,7 @@
|
||||||
from flask import Response, abort
|
from flask import Response, abort
|
||||||
import config
|
import config
|
||||||
from onionrutils import bytesconverter, stringvalidators
|
from onionrutils import bytesconverter, stringvalidators
|
||||||
|
|
||||||
def get_public_block_list(clientAPI, publicAPI, request):
|
def get_public_block_list(clientAPI, publicAPI, request):
|
||||||
# Provide a list of our blocks, with a date offset
|
# Provide a list of our blocks, with a date offset
|
||||||
dateAdjust = request.args.get('date')
|
dateAdjust = request.args.get('date')
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
'''
|
||||||
|
Onionr - Private P2P Communication
|
||||||
|
|
||||||
|
view and interact with onionr sites
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
import base64
|
||||||
|
from flask import Blueprint, Response, request, abort
|
||||||
|
import core, onionrblockapi, onionrexceptions
|
||||||
|
from onionrutils import stringvalidators
|
||||||
|
|
||||||
|
site_api = Blueprint('siteapi', __name__)
|
||||||
|
|
||||||
|
@site_api.route('/site/<name>', endpoint='site')
|
||||||
|
def site(name):
|
||||||
|
bHash = name
|
||||||
|
resp = 'Not Found'
|
||||||
|
if stringvalidators.validate_hash(bHash):
|
||||||
|
try:
|
||||||
|
resp = onionrblockapi.Block(bHash).bcontent
|
||||||
|
except onionrexceptions.NoDataAvailable:
|
||||||
|
abort(404)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
resp = base64.b64decode(resp)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if resp == 'Not Found' or not resp:
|
||||||
|
abort(404)
|
||||||
|
return Response(resp)
|
|
@ -0,0 +1 @@
|
||||||
|
from . import client, public
|
|
@ -0,0 +1,36 @@
|
||||||
|
import hmac
|
||||||
|
from flask import Blueprint, request, abort
|
||||||
|
|
||||||
|
# Be extremely mindful of this. These are endpoints available without a password
|
||||||
|
whitelist_endpoints = ('siteapi.site', 'www', 'staticfiles.onionrhome', 'staticfiles.homedata',
|
||||||
|
'staticfiles.board', 'staticfiles.profiles',
|
||||||
|
'staticfiles.profilesindex',
|
||||||
|
'staticfiles.boardContent', 'staticfiles.sharedContent',
|
||||||
|
'staticfiles.mail', 'staticfiles.mailindex', 'staticfiles.friends', 'staticfiles.friendsindex',
|
||||||
|
'staticfiles.clandestine', 'staticfiles.clandestineIndex')
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def validateRequest():
|
||||||
|
'''Validate request has set password and is the correct hostname'''
|
||||||
|
# For the purpose of preventing DNS rebinding attacks
|
||||||
|
if request.host != '%s:%s' % (self.host, self.bindPort):
|
||||||
|
abort(403)
|
||||||
|
if request.endpoint in whitelist_endpoints:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
if not hmac.compare_digest(request.headers['token'], self.clientToken):
|
||||||
|
if not hmac.compare_digest(request.form['token'], self.clientToken):
|
||||||
|
abort(403)
|
||||||
|
except KeyError:
|
||||||
|
if not hmac.compare_digest(request.form['token'], self.clientToken):
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
def afterReq(resp):
|
||||||
|
# Security headers
|
||||||
|
resp = httpheaders.set_default_onionr_http_headers(resp)
|
||||||
|
if request.endpoint == 'site':
|
||||||
|
resp.headers['Content-Security-Policy'] = "default-src 'none'; style-src data: 'unsafe-inline'; img-src data:"
|
||||||
|
else:
|
||||||
|
resp.headers['Content-Security-Policy'] = "default-src 'none'; script-src 'self'; object-src 'none'; style-src 'self'; img-src 'self'; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'self'"
|
||||||
|
return resp
|
|
@ -17,7 +17,7 @@
|
||||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item idLink" href="/">
|
<a class="navbar-item idLink" href="/">
|
||||||
<img src="/shared/images/favicon.ico"> Onionr
|
<img src="/shared/images/favicon.ico" class="navabarLogo"> Onionr
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item idLink" href="/">
|
<a class="navbar-item idLink" href="/">
|
||||||
<img src="/shared/images/favicon.ico"> Onionr
|
<img src="/shared/images/favicon.ico" class="navabarLogo"> Onionr
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item idLink" href="/">
|
<a class="navbar-item idLink" href="/">
|
||||||
<img src="/shared/images/favicon.ico"> Onionr
|
<img src="/shared/images/favicon.ico" class="navabarLogo"> Onionr
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item idLink" href="/">
|
<a class="navbar-item idLink" href="/">
|
||||||
<img src="/shared/images/favicon.ico"> Onionr
|
<img src="/shared/images/favicon.ico" class="navabarLogo"> Onionr
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item idLink" href="/">
|
<a class="navbar-item idLink" href="/">
|
||||||
<img src="/shared/images/favicon.ico"> Onionr
|
<img src="/shared/images/favicon.ico" class='navbarLogo'> Onionr
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||||
|
|
|
@ -15,4 +15,4 @@
|
||||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||||
user-select: none; /* Non-prefixed version, currently
|
user-select: none; /* Non-prefixed version, currently
|
||||||
supported by Chrome and Opera */
|
supported by Chrome and Opera */
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@ html {
|
||||||
display:block; /* P.S: Use `!important` if missing `#content` (selector specificity). */
|
display:block; /* P.S: Use `!important` if missing `#content` (selector specificity). */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.hiddenOverlay {
|
.hiddenOverlay {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -50,4 +48,9 @@ html {
|
||||||
.closeOverlay:after{
|
.closeOverlay:after{
|
||||||
content: '❌';
|
content: '❌';
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbarLogo{
|
||||||
|
margin-right: 5px;
|
||||||
|
color: red;
|
||||||
|
}
|
Loading…
Reference in New Issue