diff --git a/Makefile b/Makefile index 472ffc2d..c51fc72b 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ setup: sudo pip3 install -r requirements.txt + -@cd onionr/static-data/ui/; ./compile.py install: sudo rm -rf /usr/share/onionr/ diff --git a/docs/whitepaper.md b/docs/whitepaper.md index 6309f4f4..789f8e47 100644 --- a/docs/whitepaper.md +++ b/docs/whitepaper.md @@ -16,9 +16,9 @@ To prevent censorship or loss of information, these measures must be in place: * Anonymization of users by default * The Inability to violently coerce human users (personal threats/"doxxing", or totalitarian regime censorship) -* Economic availability. A system should not rely on a single device to be constantly online, and should not be overtly expensive to use. The majority of people in the world own cell phones, but comparatively few own personal computers, particularly in developing countries. +* Economic availability. A system should not rely on a single device to be constantly online, and should not be overly expensive to use. The majority of people in the world own cell phones, but comparatively few own personal computers, particularly in developing countries. -There are many great projects that tackle decentralization and privacy issues, but there are none which tackle all of the above issue. Some of the existing networks have also not worked well in practice, or are more complicated than they need to be. +There are many great projects that tackle decentralization and privacy issues, but there are none which tackle all of the above issues. Some of the existing networks have also not worked well in practice, or are more complicated than they need to be. # Onionr Design Goals diff --git a/onionr/api.py b/onionr/api.py index 43e7cabc..705a4781 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -30,6 +30,9 @@ class API: ''' Main HTTP API (Flask) ''' + + callbacks = {'public' : {}, 'private' : {}, 'ui' : {}} + def validateToken(self, token): ''' Validate that the client token matches the given token @@ -126,7 +129,7 @@ class API: if not hmac.compare_digest(timingToken, self.timeBypassToken): if elapsed < self._privateDelayTime: time.sleep(self._privateDelayTime - elapsed) - return send_from_directory('static-data/ui/', path) + return send_from_directory('static-data/ui/dist/', path) @app.route('/client/') def private_handler(): @@ -164,6 +167,44 @@ class API: self.mimeType = 'text/html' response = siteData.split(b'-', 2)[-1] resp = Response(response) + elif action == "insertBlock": + response = {'success' : False, 'reason' : 'An unknown error occurred'} + + try: + decoded = json.loads(data) + + block = Block() + + sign = False + + for key in decoded: + val = decoded[key] + + key = key.lower() + + if key == 'type': + block.setType(val) + elif key in ['body', 'content']: + block.setContent(val) + elif key == 'parent': + block.setParent(val) + elif key == 'sign': + sign = (str(val).lower() == 'true') + + hash = block.save(sign = sign) + + if not hash is False: + response['success'] = true + response['hash'] = hash + response['reason'] = 'Successfully wrote block to file' + else: + response['reason'] = 'Faield to save the block' + except Exception as e: + logger.debug('insertBlock api request failed', error = e) + + resp = Response(json.dumps(response)) + elif action in callbacks['private']: + resp = Response(str(getCallback(action, scope = 'private')(request))) else: resp = Response('(O_o) Dude what? (invalid command)') endTime = math.floor(time.time()) @@ -258,6 +299,8 @@ class API: peers = self._core.listPeers(getPow=True) response = ','.join(peers) resp = Response(response) + elif action in callbacks['public']: + resp = Response(str(getCallback(action, scope = 'public')(request))) else: resp = Response("") @@ -328,3 +371,31 @@ class API: # we exit rather than abort to avoid fingerprinting logger.debug('Avoiding fingerprinting, exiting...') sys.exit(1) + + def setCallback(action, callback, scope = 'public'): + if not scope in callbacks: + return False + + callbacks[scope][action] = callback + + return True + + def removeCallback(action, scope = 'public'): + if (not scope in callbacks) or (not action in callbacks[scope]): + return False + + del callbacks[scope][action] + + return True + + def getCallback(action, scope = 'public'): + if (not scope in callbacks) or (not action in callbacks[scope]): + return None + + return callbacks[scope][action] + + def getCallbacks(scope = None): + if (not scope is None) and (scope in callbacks): + return callbacks[scope] + + return callbacks diff --git a/onionr/communicator2.py b/onionr/communicator2.py index b3734cbc..b420fcba 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -36,7 +36,7 @@ class OnionrCommunicatorDaemon: # intalize NIST beacon salt and time self.nistSaltTimestamp = 0 self.powSalt = 0 - + self.blockToUpload = '' # loop time.sleep delay in seconds @@ -309,7 +309,7 @@ class OnionrCommunicatorDaemon: logger.info(i) def peerAction(self, peer, action, data=''): - '''Perform a get request to a peer''' + '''Perform a get request to a peer''' if len(peer) == 0: return False logger.info('Performing ' + action + ' with ' + peer + ' on port ' + str(self.proxyPort)) diff --git a/onionr/onionr.py b/onionr/onionr.py index 11db4351..ec3ec468 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -91,8 +91,6 @@ class Onionr: self.onionrCore = core.Core() self.onionrUtils = OnionrUtils(self.onionrCore) - self.userOS = platform.system() - # Handle commands self.debug = False # Whole application debugging @@ -258,7 +256,7 @@ class Onionr: def getWebPassword(self): return config.get('client.hmac') - + def printWebPassword(self): print(self.getWebPassword()) @@ -542,7 +540,7 @@ class Onionr: subprocess.Popen([communicatorDaemon, "run", str(net.socksPort)]) logger.debug('Started communicator') events.event('daemon_start', onionr = self) - api.API(self.debug) + self.api = api.API(self.debug) return diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index 695d41ab..a49210d3 100644 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -38,7 +38,6 @@ class Block: self.btype = type self.bcontent = content - # initialize variables self.valid = True self.raw = None @@ -71,8 +70,10 @@ class Block: # logic - def decrypt(self, anonymous=True, encodedData=True): - '''Decrypt a block, loading decrypted data into their vars''' + def decrypt(self, anonymous = True, encodedData = True): + ''' + Decrypt a block, loading decrypted data into their vars + ''' if self.decrypted: return True retData = False @@ -100,9 +101,11 @@ class Block: else: logger.warn('symmetric decryption is not yet supported by this API') return retData - + def verifySig(self): - '''Verify if a block's signature is signed by its claimed signer''' + ''' + Verify if a block's signature is signed by its claimed signer + ''' core = self.getCore() if core._crypto.edVerify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True): @@ -227,12 +230,14 @@ class Block: else: self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign) self.update() + return self.getHash() else: logger.warn('Not writing block; it is invalid.') except Exception as e: logger.error('Failed to save block.', error = e, timestamp = False) - return False + + return False # getters @@ -533,7 +538,7 @@ class Block: if relevant: relevant_blocks.append(block) - + if bool(reverse): relevant_blocks.reverse() diff --git a/onionr/onionrpluginapi.py b/onionr/onionrpluginapi.py index bfaf73e8..0120dad7 100644 --- a/onionr/onionrpluginapi.py +++ b/onionr/onionrpluginapi.py @@ -130,6 +130,22 @@ class CommandAPI: def get_commands(self): return self.pluginapi.get_onionr().getCommands() +class WebAPI: + def __init__(self, pluginapi): + self.pluginapi = pluginapi + + def register_callback(self, action, callback, scope = 'public'): + return self.pluginapi.get_onionr().api.setCallback(action, callback, scope = scope) + + def unregister_callback(self, action, scope = 'public'): + return self.pluginapi.get_onionr().api.removeCallback(action, scope = scope) + + def get_callback(self, action, scope = 'public'): + return self.pluginapi.get_onionr().api.getCallback(action, scope= scope) + + def get_callbacks(self, scope = None): + return self.pluginapi.get_onionr().api.getCallbacks(scope = scope) + class pluginapi: def __init__(self, onionr, data): self.onionr = onionr @@ -142,6 +158,7 @@ class pluginapi: self.daemon = DaemonAPI(self) self.plugins = PluginAPI(self) self.commands = CommandAPI(self) + self.web = WebAPI(self) def get_onionr(self): return self.onionr @@ -167,5 +184,8 @@ class pluginapi: def get_commandapi(self): return self.commands + def get_webapi(self): + return self.web + def is_development_mode(self): return self.get_onionr()._developmentMode diff --git a/onionr/static-data/ui/readme.txt b/onionr/static-data/ui/README.md similarity index 100% rename from onionr/static-data/ui/readme.txt rename to onionr/static-data/ui/README.md diff --git a/onionr/static-data/ui/common/footer.html b/onionr/static-data/ui/common/footer.html new file mode 100644 index 00000000..6b5cfb06 --- /dev/null +++ b/onionr/static-data/ui/common/footer.html @@ -0,0 +1,4 @@ + + + + diff --git a/onionr/static-data/ui/common/header.html b/onionr/static-data/ui/common/header.html new file mode 100644 index 00000000..2a2b4f56 --- /dev/null +++ b/onionr/static-data/ui/common/header.html @@ -0,0 +1,30 @@ +<$= LANG.ONIONR_TITLE $> + + + + + + + + + + diff --git a/onionr/static-data/ui/common/onionr-timeline-post.html b/onionr/static-data/ui/common/onionr-timeline-post.html new file mode 100644 index 00000000..ceff5c65 --- /dev/null +++ b/onionr/static-data/ui/common/onionr-timeline-post.html @@ -0,0 +1,31 @@ + +
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+ $content +
+ +
+ like + comment +
+
+
+
+
+ diff --git a/onionr/static-data/ui/compile.py b/onionr/static-data/ui/compile.py new file mode 100755 index 00000000..c93e4aa7 --- /dev/null +++ b/onionr/static-data/ui/compile.py @@ -0,0 +1,123 @@ +#!/usr/bin/python3 + +import shutil, os, re, json, traceback + +# get user's config +settings = {} +with open('config.json', 'r') as file: + settings = json.loads(file.read()) + +# "hardcoded" config, not for user to mess with +HEADER_FILE = 'common/header.html' +FOOTER_FILE = 'common/footer.html' +SRC_DIR = 'src/' +DST_DIR = 'dist/' +HEADER_STRING = '
' +FOOTER_STRING = '