From 1de668a9e5ac8bf09768e7b3a85fa54c7b2cf8d9 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 30 Nov 2019 02:42:49 -0600 Subject: [PATCH 1/2] onboarding work, now waits to start daemon timers --- src/communicator/__init__.py | 71 +++++++++++++++--------- src/config/__init__.py | 2 + src/config/onboarding.py | 30 +++++++++- src/httpapi/miscclientapi/endpoints.py | 4 ++ src/onionrcommands/daemonlaunch.py | 2 + src/onionrtypes/__init__.py | 2 + static-data/default_config.json | 4 ++ static-data/www/onboarding/onboarding.js | 2 + static-data/www/shared/panel.js | 13 +++++ 9 files changed, 102 insertions(+), 28 deletions(-) diff --git a/src/communicator/__init__.py b/src/communicator/__init__.py index 734bd5d7..52001089 100755 --- a/src/communicator/__init__.py +++ b/src/communicator/__init__.py @@ -4,6 +4,36 @@ This file contains both the OnionrCommunicate class for communcating with peers and code to operate as a daemon, getting commands from the command queue database (see core.Core.daemonQueue) ''' +import os +import time + +import config +import logger +import onionrpeers +import onionrplugins as plugins +from . import onlinepeers, uploadqueue +from communicatorutils import servicecreator +from communicatorutils import onionrcommunicatortimers +from communicatorutils import downloadblocks +from communicatorutils import lookupblocks +from communicatorutils import lookupadders +from communicatorutils import connectnewpeers +from communicatorutils import uploadblocks +from communicatorutils import daemonqueuehandler +from communicatorutils import announcenode, deniableinserts +from communicatorutils import cooldownpeer +from communicatorutils import housekeeping +from communicatorutils import netcheck +from onionrutils import localcommand +from onionrutils import epoch +from etc import humanreadabletime +import onionrservices +import filepaths +from onionrblocks import storagecounter +from coredb import daemonqueue +from coredb import dbfiles +from netcontroller import NetController + ''' 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 @@ -18,37 +48,20 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import sys, os, time -import config, logger -import onionrexceptions, onionrpeers -from onionrblocks import onionrblockapi as block -from onionrplugins import onionrevents as events -import onionrplugins as plugins -from . import onlinepeers, uploadqueue -from communicatorutils import servicecreator, onionrcommunicatortimers -from communicatorutils import downloadblocks, lookupblocks, lookupadders -from communicatorutils import servicecreator, connectnewpeers -from communicatorutils import uploadblocks -from communicatorutils import daemonqueuehandler, announcenode, deniableinserts -from communicatorutils import cooldownpeer, housekeeping, netcheck -from onionrutils import localcommand, epoch -from etc import humanreadabletime -import onionrservices, filepaths -from onionrblocks import storagecounter -from coredb import daemonqueue, dbfiles -from utils import gettransports -from netcontroller import NetController OnionrCommunicatorTimers = onionrcommunicatortimers.OnionrCommunicatorTimers config.reload() class OnionrCommunicatorDaemon: - def __init__(self, shared_state, developmentMode=config.get('general.dev_mode', False)): + def __init__(self, shared_state, developmentMode=None): + if developmentMode is None: + developmentMode = config.get('general.dev_mode', False) + # configure logger and stuff self.config = config self.storage_counter = storagecounter.StorageCounter() - self.isOnline = True # Assume we're connected to the internet - self.shared_state = shared_state # TooManyObjects module + self.isOnline = True # Assume we're connected to the internet + self.shared_state = shared_state # TooManyObjects module # list of timer instances self.timers = [] @@ -69,8 +82,10 @@ class OnionrCommunicatorDaemon: self.offlinePeers = [] self.cooldownPeer = {} self.connectTimes = {} - self.peerProfiles = [] # list of peer's profiles (onionrpeers.PeerProfile instances) - self.newPeers = [] # Peers merged to us. Don't add to db until we know they're reachable + # list of peer's profiles (onionrpeers.PeerProfile instances) + self.peerProfiles = [] + # Peers merged to us. Don't add to db until we know they're reachable + self.newPeers = [] self.announceProgress = {} self.announceCache = {} @@ -182,6 +197,12 @@ class OnionrCommunicatorDaemon: if config.get('general.use_bootstrap', True): bootstrappeers.add_bootstrap_list_to_peer_list(self, [], db_only=True) + if not config.get('onboarding.done', True): + logger.info('First run detected. Run openhome to get setup.', terminal=True) + + while not config.get('onboarding.done', True): + time.sleep(5) + # Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking try: while not self.shutdown: diff --git a/src/config/__init__.py b/src/config/__init__.py index bc26c1aa..7f8b0e79 100755 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -21,6 +21,8 @@ import os, json, logger import filepaths +from . import onboarding + _configfile = filepaths.config_file _config = {} diff --git a/src/config/onboarding.py b/src/config/onboarding.py index 66141196..217f5903 100644 --- a/src/config/onboarding.py +++ b/src/config/onboarding.py @@ -4,9 +4,12 @@ Setup config from onboarding choices """ from pathlib import Path +from typing import Union from filepaths import onboarding_mark_file -import onionrtypes +from onionrtypes import JSONSerializable +from onionrtypes import OnboardingConfig +import config """ 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 @@ -23,8 +26,29 @@ import onionrtypes """ -def set_config_from_onboarding(config_settings: onionrtypes.OnboardingConfig): - return +def _get_val_or_none(json: dict, key: str) -> Union[None, JSONSerializable]: + try: + return json['configInfo'][key] + except KeyError: + return None + + +def set_config_from_onboarding(config_settings: OnboardingConfig): + + network_security_level = 0 + theme = "dark" + + if _get_val_or_none(config_settings, 'stateTarget') == True: + config.set('general.security_level', 1) + + if _get_val_or_none(config_settings, 'useDark') == False: + config.set('ui.theme', 'light') + + config.set('general.store_plaintext_blocks', + _get_val_or_none(config_settings, 'plainContrib')) + + + config.set('onboarding.done', True, savefile=True) def set_onboarding_finished(): """Create the onboarding completed setting file""" diff --git a/src/httpapi/miscclientapi/endpoints.py b/src/httpapi/miscclientapi/endpoints.py index 4f49f094..46459369 100644 --- a/src/httpapi/miscclientapi/endpoints.py +++ b/src/httpapi/miscclientapi/endpoints.py @@ -140,3 +140,7 @@ class PrivateEndpoints: @private_endpoints_bp.route('/gettorsocks') def get_tor_socks(): return Response(str(client_api._too_many.get(NetController).socksPort)) + + @private_endpoints_bp.route('/setonboarding', methods=['POST']) + def set_onboarding(): + return Response(config.onboarding.set_config_from_onboarding(request.get_json())) diff --git a/src/onionrcommands/daemonlaunch.py b/src/onionrcommands/daemonlaunch.py index 63ff512b..72d648f6 100755 --- a/src/onionrcommands/daemonlaunch.py +++ b/src/onionrcommands/daemonlaunch.py @@ -20,6 +20,7 @@ import os, time, sys, platform, sqlite3, signal from threading import Thread +from gevent import time import toomanyobjs @@ -104,6 +105,7 @@ def daemon(): time.sleep(1) except KeyboardInterrupt: pass + events.event('init', threaded = False) events.event('daemon_start') communicator.startCommunicator(shared_state) diff --git a/src/onionrtypes/__init__.py b/src/onionrtypes/__init__.py index 7b881270..37cf3c51 100644 --- a/src/onionrtypes/__init__.py +++ b/src/onionrtypes/__init__.py @@ -8,3 +8,5 @@ DeterministicKeyPassphrase = NewType('DeterministicKeyPassphrase', str) BlockHash = NewType('BlockHash', str) OnboardingConfig = NewType('OnboardingConfig', str) + +JSONSerializable = NewType('JSONSerializable', str) diff --git a/static-data/default_config.json b/static-data/default_config.json index d8ff8e7e..05c89fc6 100755 --- a/static-data/default_config.json +++ b/static-data/default_config.json @@ -73,5 +73,9 @@ "timers": { "lookupBlocks": 25, "getBlocks": 10 + }, + + "onboarding": { + "done": false } } diff --git a/static-data/www/onboarding/onboarding.js b/static-data/www/onboarding/onboarding.js index 5e60e5a8..bde0f8b9 100644 --- a/static-data/www/onboarding/onboarding.js +++ b/static-data/www/onboarding/onboarding.js @@ -39,6 +39,7 @@ function sendConfig(configInfo){ }, body: JSON.stringify({configInfo}) }).then(function(data) { + window.location.href = window.location.origin + '/' + window.location.hash }) } @@ -54,6 +55,7 @@ document.getElementById('onboardingForm').onsubmit = function(e){ submitInfo.deterministic = getCheckValue('useDeterministic') submitInfo.mail = getCheckValue('useMail') submitInfo.circles = getCheckValue('useCircles') + submitInfo.useDark = getCheckValue('useDarkTheme') if (submitInfo.donate){ openDonateModal(submitInfo) diff --git a/static-data/www/shared/panel.js b/static-data/www/shared/panel.js index c88cf552..671ab40d 100755 --- a/static-data/www/shared/panel.js +++ b/static-data/www/shared/panel.js @@ -41,3 +41,16 @@ restartBtn.onclick = function(){ } } + +fetch('/config/get/onboarding.done', { + method: 'GET', + headers: { + "content-type": "application/json", + "token": webpass + }}) +.then((resp) => resp.text()) // Transform the data into text +.then(function(data) { + if (data === 'false'){ + window.location.href = window.location.pathname = "/onboarding/" + window.location.hash + } + }) \ No newline at end of file From 8a4102cbd101fa50d03e08d8489f0a738b4f4ba8 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 1 Dec 2019 08:10:00 +0000 Subject: [PATCH 2/2] Merge nick's fixes --- src/apiservers/private/__init__.py | 5 +- .../cryptoutils/replayvalidation.py | 5 +- src/onionrpeers/peercleanup.py | 6 +- src/utils/reconstructhash.py | 16 +++- static-data/www/board/board.js | 79 +++++++++---------- 5 files changed, 52 insertions(+), 59 deletions(-) diff --git a/src/apiservers/private/__init__.py b/src/apiservers/private/__init__.py index af80be37..80b608b6 100644 --- a/src/apiservers/private/__init__.py +++ b/src/apiservers/private/__init__.py @@ -85,10 +85,7 @@ class PrivateAPI: logger.error("client password needs to be set") return False try: - if not hmac.compare_digest(self.clientToken, token): - return False - else: - return True + return hmac.compare_digest(self.clientToken, token) except TypeError: return False diff --git a/src/onionrcrypto/cryptoutils/replayvalidation.py b/src/onionrcrypto/cryptoutils/replayvalidation.py index 59c338eb..fc8e8fd0 100644 --- a/src/onionrcrypto/cryptoutils/replayvalidation.py +++ b/src/onionrcrypto/cryptoutils/replayvalidation.py @@ -1,6 +1,3 @@ from onionrutils import epoch def replay_timestamp_validation(timestamp): - if epoch.get_epoch() - int(timestamp) > 2419200: - return False - else: - return True \ No newline at end of file + return epoch.get_epoch() - int(timestamp) <= 2419200 \ No newline at end of file diff --git a/src/onionrpeers/peercleanup.py b/src/onionrpeers/peercleanup.py index c332828b..6173a041 100644 --- a/src/onionrpeers/peercleanup.py +++ b/src/onionrpeers/peercleanup.py @@ -41,10 +41,8 @@ def peer_cleanup(): if peerprofiles.PeerProfiles(address).score < min_score: keydb.removekeys.remove_address(address) try: - if (int(epoch.get_epoch()) - int(keydb.transportinfo.get_address_info(address, 'lastConnect'))) >= 600: - expireTime = 600 - else: - expireTime = 86400 + lastConnect = int(keydb.transportinfo.get_address_info(address, 'lastConnect')) + expireTime = 86400 - int(epoch.get_epoch()) - lastConnect blacklist.addToDB(address, dataType=1, expire=expireTime) except sqlite3.IntegrityError: #TODO just make sure its not a unique constraint issue pass diff --git a/src/utils/reconstructhash.py b/src/utils/reconstructhash.py index ae50c0b2..435d25ec 100644 --- a/src/utils/reconstructhash.py +++ b/src/utils/reconstructhash.py @@ -1,8 +1,10 @@ ''' Onionr - Private P2P Communication - z-fill (zero fill) a string to a specific length, intended for reconstructing block hashes + z-fill (zero fill) a string to a specific length + intended for reconstructing block hashes ''' +from typing import Union ''' 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 @@ -17,10 +19,16 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -def reconstruct_hash(hex_hash, length=64): + + +def reconstruct_hash(hex_hash: Union[str, bytes], + length: int = 64) -> Union[str, bytes]: + """Pad hash hex string with zeros, return result""" return hex_hash.zfill(length) -def deconstruct_hash(hex_hash): + +def deconstruct_hash(hex_hash: Union[str, bytes]) -> Union[str, bytes]: + """Remove leading zeros from hex hash, return result""" new_hash = '' ret_bytes = False try: @@ -40,4 +48,4 @@ def deconstruct_hash(hex_hash): if ret_bytes: new_hash = new_hash.encode() - return new_hash \ No newline at end of file + return new_hash diff --git a/static-data/www/board/board.js b/static-data/www/board/board.js index c3d7f556..12b2e89f 100755 --- a/static-data/www/board/board.js +++ b/static-data/www/board/board.js @@ -17,10 +17,6 @@ along with this program. If not, see . */ -requested = [] - -var windowHeight = window.innerHeight; -webpassword = webpass newPostForm = document.getElementById('addMsg') firstLoad = true lastLoadedBoard = 'global' @@ -30,14 +26,12 @@ loadingTimeout = 8000 let toggleLoadingMessage = function(){ switch (loadingMessage.style.display){ - case "block": - case "inline": case "inline-block": loadingMessage.style.display = "none" - break; + break; default: loadingMessage.style.display = "initial" - break; + break; } } @@ -45,37 +39,36 @@ fetch('/flow/version', { method: 'GET', headers: { "token": webpass - }}) -.then((resp) => resp.text()) -.then(function(data) { - document.getElementById('circlesVersion').innerText = data +}}) +.then((ver) => ver.text()) +.then(function(ver) { + document.getElementById('circlesVersion').innerText = ver }) -function appendMessages(msg, blockHash, beforeHash, channel){ - if (channel !== document.getElementById('feedIDInput').value){return} +function appendMessages(msg, blockHash, beforeHash, channel) { + if (channel !== document.getElementById('feedIDInput').value) return // ignore if channel name isn't matching + if (msg.length == 0) return // ignore empty messages + var humanDate = new Date(0) - if (msg.length == 0){ - return - } - //var msg = JSON.parse(msg) - var el = document.createElement('div') var msgDate = msg['meta']['time'] var feed = document.getElementById("feed") var beforeEl = null if (msgDate === undefined){ msgDate = 'unknown' - } - else{ + } else { humanDate.setUTCSeconds(msgDate) msgDate = humanDate.toLocaleTimeString() + ' ' + humanDate.toLocaleDateString() } + + var el = document.createElement('div') el.className = 'entry' el.innerText = msg['content'] - if (beforeHash !== null){ - for (x = 0; x < feed.children.length; x++){ - if (feed.children[x].getAttribute('data-bl') === beforeHash){ - beforeEl = feed.children[x] + + if (beforeHash !== null) { + for (i = 0; i < feed.children.length; i++) { + if (feed.children[i].getAttribute('data-bl') === beforeHash) { + beforeEl = feed.children[i] } } } @@ -133,12 +126,13 @@ function getBlocks(){ var feed = document.getElementById("feed") var ch = document.getElementById('feedIDInput').value if (lastLoadedBoard !== ch){ + requested = [] + toggleLoadingMessage() loadedAny = false - while (feed.firstChild) { - feed.removeChild(feed.firstChild); - } - requested = [] // reset requested list + + while (feed.firstChild) feed.removeChild(feed.firstChild); // remove all messages from feed + setTimeout(function(){ if (! loadedAny && ch == document.getElementById('feedIDInput').value){ PNotify.notice("There are no posts for " + ch + ". You can be the first!") @@ -156,13 +150,12 @@ function getBlocks(){ var blockList = feedText.split(',') for (i = 0; i < blockList.length; i++){ - while (blockList[i].length < 64) { blockList[i] = "0" + blockList[i] } + blockList[i] = "0".repeat(64 - blockList[i].length) + blockList[i] // pad hash with zeroes + if (! requested.includes(blockList[i])){ - if (blockList[i].length == 0){ - continue - } - requested.push(blockList[i]) - loadMessage(blockList[i], blockList, i, ch) + if (blockList[i].length == 0) continue + else requested.push(blockList[i]) + loadMessage(blockList[i], blockList, i, ch); } } } @@ -172,15 +165,14 @@ function loadMessage(blockHash, blockList, count, channel){ method: 'GET', headers: { "token": webpass - }}) + }}) .then((resp) => resp.json()) .then(function(data) { let before = blockList[count - 1] let delay = 2000 if (typeof before == "undefined"){ before = null - } - else{ + } else { let existing = document.getElementsByClassName('cMsgBox') for (x = 0; x < existing.length; x++){ if (existing[x].getAttribute('data-bl') === before){ @@ -190,10 +182,10 @@ function loadMessage(blockHash, blockList, count, channel){ } setTimeout(function(){appendMessages(data, blockHash, before, channel)}, delay) //appendMessages(data, blockHash, before) - }) + }) } -document.getElementById('refreshFeed').onclick = function(){ +document.getElementById('refreshFeed').onclick = function() { getBlocks() } @@ -211,7 +203,8 @@ newPostForm.onsubmit = function(){ headers: { "content-type": "application/json", "token": webpass - }}) + } + }) .then((resp) => resp.text()) // Transform the data into json .then(function(data) { newPostForm.style.display = 'block' @@ -223,8 +216,8 @@ newPostForm.onsubmit = function(){ } PNotify.success({ text: "Message queued for posting" - }) + }) setTimeout(function(){getBlocks()}, 500) - }) + }) return false }