Merge branch 'communicator-multithreading' of github.com:beardog108/onionr into communicator-multithreading

This commit is contained in:
Kevin Froman 2018-05-14 11:32:01 -05:00
commit 361f1e3d54
12 changed files with 525 additions and 148 deletions

View File

@ -447,7 +447,7 @@ class OnionrCommunicate:
if isThread:
self.lookupBlocksThreads += 1
peerList = self._core.listAdders()
blocks = ''
blockList = list()
for i in peerList:
if self.peerStatusTaken(i, 'getBlockHashes') or self.peerStatusTaken(i, 'getDBHash'):
@ -476,18 +476,15 @@ class OnionrCommunicate:
if lastDB != currentDB:
logger.debug('Fetching hash from %s - %s current hash.' % (str(i), currentDB))
try:
blocks += self.performGet('getBlockHashes', i)
blockList.append(self.performGet('getBlockHashes', i))
except TypeError:
logger.warn('Failed to get data hash from %s' % str(i))
self.peerData[i]['failCount'] -= 1
if self._utils.validateHash(currentDB):
self._core.setAddressInfo(i, "DBHash", currentDB)
if len(blocks.strip()) != 0:
if len(blockList) != 0:
pass
#logger.debug('BLOCKS:' + blocks)
blockList = blocks.split('\n')
for i in blockList:
if len(i.strip()) == 0:
@ -517,7 +514,7 @@ class OnionrCommunicate:
'''
if isThread:
self.processBlocksThreads += 1
for i in self._core.getBlockList(unsaved=True).split("\n"):
for i in self._core.getBlockList(unsaved = True):
if i != "":
if i in self.blocksProcessing or i in self.ignoredHashes:
#logger.debug('already processing ' + i)
@ -553,43 +550,39 @@ class OnionrCommunicate:
pass
try:
#blockMetadata = json.loads(self._core.getData(i)).split('}')[0] + '}'
blockMetadata = json.loads(blockContent[:blockContent.rfind(b'}') + 1].decode())
blockMetadata = json.loads(blockContent[:blockContent.find(b'\n')].decode())
try:
blockMeta2 = json.loads(blockMetadata['meta'])
except KeyError:
blockMeta2 = {'type': ''}
pass
blockContent = blockContent[blockContent.rfind(b'}') + 1:]
blockContent = blockContent[blockContent.find(b'\n') + 1:]
try:
blockContent = blockContent.decode()
except AttributeError:
pass
if not self.verifyPow(blockContent, blockMeta2):
if not self._crypto.verifyPow(blockContent, blockMeta2):
logger.warn("%s has invalid or insufficient proof of work token, deleting..." % str(i))
self._core.removeBlock(i)
continue
try:
blockMetadata['sig']
blockMeta2['id']
except KeyError:
pass
else:
#blockData = json.dumps(blockMetadata['meta']) + blockMetadata[blockMetadata.rfind(b'}') + 1:]
if (('sig' in blockMetadata) and ('id' in blockMeta2)): # id doesn't exist in blockMeta2, so this won't workin the first place
#blockData = json.dumps(blockMetadata['meta']) + blockMetadata[blockMetadata.rfind(b'}') + 1:]
creator = self._utils.getPeerByHashId(blockMeta2['id'])
try:
creator = creator.decode()
except AttributeError:
pass
creator = self._utils.getPeerByHashId(blockMeta2['id'])
try:
creator = creator.decode()
except AttributeError:
pass
if self._core._crypto.edVerify(blockMetaData['meta'] + blockContent, creator, blockMetadata['sig'], encodedData=True):
logger.info('%s was signed' % str(i))
self._core.updateBlockInfo(i, 'sig', 'true')
else:
logger.warn('%s has an invalid signature' % str(i))
self._core.updateBlockInfo(i, 'sig', 'false')
if self._core._crypto.edVerify(blockMetadata['meta'] + blockContent, creator, blockMetadata['sig'], encodedData=True):
logger.info('%s was signed' % str(i))
self._core.updateBlockInfo(i, 'sig', 'true')
else:
logger.warn('%s has an invalid signature' % str(i))
self._core.updateBlockInfo(i, 'sig', 'false')
try:
logger.info('Block type is %s' % str(blockMeta2['type']))
self._core.updateBlockInfo(i, 'dataType', blockMeta2['type'])
@ -605,12 +598,7 @@ class OnionrCommunicate:
return
def removeBlockFromProcessingList(self, block):
try:
self.blocksProcessing.remove(block)
except ValueError:
return False
else:
return True
return block in blocksProcessing
def downloadBlock(self, hash, peerTries=3):
'''
@ -666,41 +654,6 @@ class OnionrCommunicate:
return retVal
def verifyPow(self, blockContent, metadata):
'''
Verifies the proof of work associated with a block
'''
retData = False
try:
metadata['powToken']
metadata['powHash']
token = metadata['powToken']
except KeyError:
return False
dataLen = len(blockContent)
expectedHash = self._crypto.blake2bHash(base64.b64decode(metadata['powToken']) + self._crypto.blake2bHash(blockContent.encode()))
difficulty = 0
try:
expectedHash = expectedHash.decode()
except AttributeError:
pass
if metadata['powHash'] == expectedHash:
difficulty = math.floor(dataLen/1000000)
mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
puzzle = mainHash[0:difficulty]
if metadata['powHash'][0:difficulty] == puzzle:
logger.info('Validated block pow')
retData = True
else:
logger.warn("Invalid token (#1)")
else:
logger.warn('Invalid token (#2): Expected hash %s, got hash %s...' % (metadata['powHash'], expectedHash))
return retData
def urlencode(self, data):
'''
URL encodes the data

View File

@ -277,11 +277,12 @@ class Core:
return
def getData(self,hash):
def getData(self, hash):
'''
Simply return the data associated to a hash
'''
try:
# logger.debug('Opening %s' % (str(self.blockDataLocation) + str(hash) + '.dat'))
dataFile = open(self.blockDataLocation + hash + '.dat', 'rb')
data = dataFile.read()
dataFile.close()
@ -576,22 +577,22 @@ class Core:
return
def getBlockList(self, unsaved = False):
def getBlockList(self, unsaved = False): # TODO: Use unsaved
'''
Get list of our blocks
'''
conn = sqlite3.connect(self.blockDB)
c = conn.cursor()
retData = ''
if unsaved:
execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
else:
execute = 'SELECT hash FROM hashes ORDER BY RANDOM();'
rows = list()
for row in c.execute(execute):
for i in row:
retData += i + "\n"
rows.append(i)
return retData
return rows
def getBlocksByType(self, blockType):
'''
@ -599,14 +600,14 @@ class Core:
'''
conn = sqlite3.connect(self.blockDB)
c = conn.cursor()
retData = ''
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
args = (blockType,)
rows = list()
for row in c.execute(execute, args):
for i in row:
retData += i + "\n"
rows.append(i)
return retData.split('\n')
return rows
def setBlockType(self, hash, blockType):
'''
@ -677,7 +678,7 @@ class Core:
signature = ''
if sign:
signature = self._crypto.edSign(metadata + data, self._crypto.privKey, encodeResult=True)
signature = self._crypto.edSign(metadata + b'\n' + data, self._crypto.privKey, encodeResult=True)
ourID = self._crypto.pubKeyHashID()
# Convert from bytes on some py versions?
try:
@ -691,7 +692,7 @@ class Core:
if len(data) == 0:
logger.error('Will not insert empty block')
else:
addedHash = self.setData(metadata + data)
addedHash = self.setData(metadata + b'\n' + data)
self.addToBlockDB(addedHash, selfInsert=True)
self.setBlockType(addedHash, header)
retData = addedHash

View File

@ -25,7 +25,7 @@ import sys
if sys.version_info[0] == 2 or sys.version_info[1] < 5:
print('Error, Onionr requires Python 3.4+')
sys.exit(1)
import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json
import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json, getpass
from threading import Thread
import api, core, config, logger, onionrplugins as plugins, onionrevents as events
import onionrutils
@ -108,7 +108,7 @@ class Onionr:
if not os.path.exists('data/blocks/'):
os.mkdir('data/blocks/')
# Copy default plugins into plugins folder
# Copy default plugins into plugins folder
if not os.path.exists(plugins.get_plugins_folder()):
if os.path.exists('static-data/default-plugins/'):
names = [f for f in os.listdir("static-data/default-plugins/") if not os.path.isfile(f)]
@ -463,7 +463,10 @@ class Onionr:
os.makedirs(plugins.get_plugins_folder(plugin_name))
with open(plugins.get_plugins_folder(plugin_name) + '/main.py', 'a') as main:
main.write(open('static-data/default_plugin.py').read().replace('$user', os.getlogin()).replace('$date', datetime.datetime.now().strftime('%Y-%m-%d')))
main.write(open('static-data/default_plugin.py').read().replace('$user', os.getlogin()).replace('$date', datetime.datetime.now().strftime('%Y-%m-%d')).replace('$name', plugin_name))
with open(plugins.get_plugins_folder(plugin_name) + '/info.json', 'a') as main:
main.write(json.dumps({'author' : 'anonymous', 'description' : 'the default description of the plugin', 'version' : '1.0'}))
logger.info('Enabling plugin "%s"...' % plugin_name)
plugins.enable(plugin_name, self)

View File

@ -17,7 +17,7 @@
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 nacl.signing, nacl.encoding, nacl.public, nacl.hash, nacl.secret, os, binascii, base64, hashlib, logger, onionrproofs, time
import nacl.signing, nacl.encoding, nacl.public, nacl.hash, nacl.secret, os, binascii, base64, hashlib, logger, onionrproofs, time, math
class OnionrCrypto:
def __init__(self, coreInstance):
@ -26,7 +26,7 @@ class OnionrCrypto:
self.keyPowFile = 'data/keyPow.txt'
self.pubKey = None
self.privKey = None
self.pubKeyPowToken = None
self.pubKeyPowHash = None
@ -88,7 +88,7 @@ class OnionrCrypto:
except nacl.exceptions.BadSignatureError:
pass
return retData
def edSign(self, data, key, encodeResult=False):
'''Ed25519 sign data'''
try:
@ -196,7 +196,7 @@ class OnionrCrypto:
if returnEncoded:
decrypted = base64.b64encode(decrypted)
return decrypted
def generateSymmetricPeer(self, peer):
'''Generate symmetric key for a peer and save it to the peer database'''
key = self.generateSymmetric()
@ -212,7 +212,7 @@ class OnionrCrypto:
private_key = nacl.signing.SigningKey.generate()
public_key = private_key.verify_key.encode(encoder=nacl.encoding.Base32Encoder())
return (public_key.decode(), private_key.encode(encoder=nacl.encoding.Base32Encoder()).decode())
def pubKeyHashID(self, pubkey=''):
'''Accept a ed25519 public key, return a truncated result of X many sha3_256 hash rounds'''
if pubkey == '':
@ -234,10 +234,43 @@ class OnionrCrypto:
hasher = hashlib.sha3_256()
hasher.update(data)
return hasher.hexdigest()
def blake2bHash(self, data):
try:
data = data.encode()
except AttributeError:
pass
return nacl.hash.blake2b(data)
return nacl.hash.blake2b(data)
def verifyPow(self, blockContent, metadata):
'''
Verifies the proof of work associated with a block
'''
retData = False
if not (('powToken' in metadata) and ('powHash' in metadata)):
return False
dataLen = len(blockContent)
expectedHash = self.blake2bHash(base64.b64decode(metadata['powToken']) + self.blake2bHash(blockContent.encode()))
difficulty = 0
try:
expectedHash = expectedHash.decode()
except AttributeError:
pass
if metadata['powHash'] == expectedHash:
difficulty = math.floor(dataLen / 1000000)
mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
puzzle = mainHash[:difficulty]
if metadata['powHash'][:difficulty] == puzzle:
# logger.debug('Validated block pow')
retData = True
else:
logger.debug("Invalid token (#1)")
else:
logger.debug('Invalid token (#2): Expected hash %s, got hash %s...' % (metadata['powHash'], expectedHash))
return retData

View File

@ -18,7 +18,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
import onionrplugins as plugins, logger
import onionrplugins, logger
class DaemonAPI:
def __init__(self, pluginapi):
@ -52,34 +52,34 @@ class PluginAPI:
self.pluginapi = pluginapi
def start(self, name):
plugins.start(name)
onionrplugins.start(name)
def stop(self, name):
plugins.stop(name)
onionrplugins.stop(name)
def reload(self, name):
plugins.reload(name)
onionrplugins.reload(name)
def enable(self, name):
plugins.enable(name)
onionrplugins.enable(name)
def disable(self, name):
plugins.disable(name)
onionrplugins.disable(name)
def event(self, name, data = {}):
events.event(name, data = data, onionr = self.pluginapi.get_onionr())
def is_enabled(self, name):
return plugins.is_enabled(name)
return onionrplugins.is_enabled(name)
def get_enabled_plugins(self):
return plugins.get_enabled()
return onionrplugins.get_enabled()
def get_folder(self, name = None, absolute = True):
return plugins.get_plugins_folder(name = name, absolute = absolute)
return onionrplugins.get_plugins_folder(name = name, absolute = absolute)
def get_data_folder(self, name, absolute = True):
return plugins.get_plugin_data_folder(name, absolute = absolute)
return onionrplugins.get_plugin_data_folder(name, absolute = absolute)
def daemon_event(self, event, plugin = None):
return # later make local command like /client/?action=makeEvent&event=eventname&module=modulename
@ -153,6 +153,9 @@ class pluginapi:
def get_utils(self):
return self.get_onionr().onionrUtils
def get_crypto(self):
return self.get_core()._crypto
def get_daemonapi(self):
return self.daemon

View File

@ -62,17 +62,20 @@ def enable(name, onionr = None, start_event = True):
if exists(name):
enabled_plugins = get_enabled_plugins()
enabled_plugins.append(name)
config_plugins = config.get('plugins')
config_plugins['enabled'] = enabled_plugins
config.set('plugins', config_plugins, True)
if not name in enabled_plugins:
enabled_plugins.append(name)
config_plugins = config.get('plugins')
config_plugins['enabled'] = enabled_plugins
config.set('plugins', config_plugins, True)
events.call(get_plugin(name), 'enable', onionr)
events.call(get_plugin(name), 'enable', onionr)
if start_event is True:
start(name)
if start_event is True:
start(name)
return True
return True
else:
return False
else:
logger.error('Failed to enable plugin \"' + name + '\", disabling plugin.')
disable(name)

View File

@ -71,7 +71,7 @@ class OnionrUtils:
if block == '':
logger.error('Could not send PM')
else:
logger.info('Sent PM, hash: ' + block)
logger.info('Sent PM, hash: %s' % block)
except Exception as error:
logger.error('Failed to send PM.', error=error)
@ -103,14 +103,14 @@ class OnionrUtils:
for key in newKeyList.split(','):
key = key.split('-')
if len(key[0]) > 60 or len(key[1]) > 1000:
logger.warn(key[0] + ' or its pow value is too large.')
logger.warn('%s or its pow value is too large.' % key[0])
continue
if self._core._crypto.blake2bHash(base64.b64decode(key[1]) + key[0].encode()).startswith('0000'):
if not key[0] in self._core.listPeers(randomOrder=False) and type(key) != None and key[0] != self._core._crypto.pubKey:
if self._core.addPeer(key[0], key[1]):
retVal = True
else:
logger.warn(key[0] + 'pow failed')
logger.warn('%s pow failed' % key[0])
return retVal
except Exception as error:
logger.error('Failed to merge keys.', error=error)
@ -127,7 +127,7 @@ class OnionrUtils:
for adder in newAdderList.split(','):
if not adder in self._core.listAdders(randomOrder = False) and adder.strip() != self.getMyAddress():
if self._core.addAddress(adder):
logger.info('Added ' + adder + ' to db.', timestamp = True)
logger.info('Added %s to db.' % adder, timestamp = True)
retVal = True
else:
logger.debug('%s is either our address or already in our DB' % adder)
@ -156,7 +156,7 @@ class OnionrUtils:
retData = requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('client')['port']) + '/client/?action=' + command + '&token=' + str(config.get('client')['client_hmac']) + '&timingToken=' + self.timingToken).text
except Exception as error:
if not silent:
logger.error('Failed to make local request (command: ' + str(command) + ').', error=error)
logger.error('Failed to make local request (command: %s).' % command, error=error)
retData = False
return retData
@ -334,7 +334,7 @@ class OnionrUtils:
'''
Find, decrypt, and return array of PMs (array of dictionary, {from, text})
'''
#blocks = self._core.getBlockList().split('\n')
#blocks = self._core.getBlockList()
blocks = self._core.getBlocksByType('pm')
message = ''
sender = ''
@ -344,8 +344,8 @@ class OnionrUtils:
try:
with open('data/blocks/' + i + '.dat', 'r') as potentialMessage:
potentialMessage = potentialMessage.read()
blockMetadata = json.loads(potentialMessage[:potentialMessage.rfind('}') + 1])
blockContent = potentialMessage[potentialMessage.rfind('}') + 1:]
blockMetadata = json.loads(potentialMessage[:potentialMessage.find('\n')])
blockContent = potentialMessage[potentialMessage.find('\n') + 1:]
try:
message = self._core._crypto.pubKeyDecrypt(blockContent, encodedData=True, anonymous=True)
@ -362,8 +362,7 @@ class OnionrUtils:
except json.decoder.JSONDecodeError:
pass
else:
print('--------------------')
logger.info('Decrypted ' + i + ':')
logger.info('Decrypted %s:' % i)
logger.info(message["msg"])
signer = message["id"]
@ -371,16 +370,16 @@ class OnionrUtils:
if self.validatePubKey(signer):
if self._core._crypto.edVerify(message["msg"], signer, sig, encodedData=True):
logger.info("Good signature by " + signer)
logger.info("Good signature by %s" % signer)
else:
logger.warn("Bad signature by " + signer)
logger.warn("Bad signature by %s" % signer)
else:
logger.warn("Bad sender id: " + signer)
logger.warn('Bad sender id: %s' % signer)
except FileNotFoundError:
pass
except Exception as error:
logger.error('Failed to open block ' + str(i) + '.', error=error)
logger.error('Failed to open block %s.' % i, error=error)
return
def getPeerByHashId(self, hash):
@ -438,14 +437,14 @@ class OnionrUtils:
scanDir += '/'
for block in glob.glob(scanDir + "*.dat"):
if block.replace(scanDir, '').replace('.dat', '') not in blockList:
logger.info("Found new block on dist " + block)
logger.info('Found new block on dist %s' % block)
with open(block, 'rb') as newBlock:
block = block.replace(scanDir, '').replace('.dat', '')
if self._core._crypto.sha3Hash(newBlock.read()) == block.replace('.dat', ''):
self._core.addToBlockDB(block.replace('.dat', ''), dataSaved=True)
logger.info('Imported block.')
logger.info('Imported block %s.' % block)
else:
logger.warn('Failed to verify hash for ' + block)
logger.warn('Failed to verify hash for %s' % block)
def progressBar(self, value = 0, endvalue = 100, width = None):

View File

@ -0,0 +1,5 @@
{
"name" : "gui",
"version" : "1.0",
"author" : "onionr"
}

View File

@ -19,6 +19,8 @@
import logger, config
import os, sqlite3, core
plugin_name = 'gui'
def sendMessage():
global sendEntry
@ -73,7 +75,7 @@ def openGUI():
nodeInfo = tkinter.Frame(root)
keyInfo = tkinter.Frame(root)
hostname = pluginapi.get_onionr().get_hostname()
logger.debug('hostname: %s' % hostname)
idText = hostname

View File

@ -0,0 +1,5 @@
{
"name" : "pluginmanager",
"version" : "1.0",
"author" : "onionr"
}

View File

@ -4,11 +4,11 @@
# useful libraries
import logger, config
import os, sys, json, time, random
import os, sys, json, time, random, shutil, base64, getpass, datetime, re
plugin_name = 'pluginmanager'
keys_data = {'keys' : {}}
keys_data = {'keys' : {}, 'plugins' : [], 'repositories' : {}}
# key functions
@ -35,6 +35,7 @@ def getKey(plugin):
Returns the public key for a given plugin
'''
global keys_data
readKeys()
return (keys_data['keys'][plugin] if plugin in keys_data['keys'] else None)
@ -43,9 +44,72 @@ def saveKey(plugin, key):
Saves the public key for a plugin to keystore
'''
global keys_data
readKeys()
keys_data['keys'][plugin] = key
writeKeys()
def getPlugins():
'''
Returns a list of plugins installed by the plugin manager
'''
global keys_data
readKeys()
return keys_data['plugins']
def addPlugin(plugin):
'''
Saves the plugin name, to remember that it was installed by the pluginmanager
'''
global keys_data
readKeys()
if not plugin in keys_data['plugins']:
keys_data['plugins'].append(plugin)
writeKeys()
def removePlugin(plugin):
'''
Removes the plugin name from the pluginmanager's records
'''
global keys_data
readKeys()
if plugin in keys_data['plugins']:
keys_data['plugins'].remove(plugin)
writeKeys()
def getRepositories():
'''
Returns a list of plugins installed by the plugin manager
'''
global keys_data
readKeys()
return keys_data['repositories']
def addRepository(repositories, data):
'''
Saves the plugin name, to remember that it was installed by the pluginmanager
'''
global keys_data
readKeys()
keys_data['repositories'][repositories] = data
writeKeys()
def removeRepository(repositories):
'''
Removes the plugin name from the pluginmanager's records
'''
global keys_data
readKeys()
if plugin in keys_data['repositories']:
del keys_data['repositories'][repositories]
writeKeys()
def check():
'''
Checks to make sure the keystore file still exists
@ -56,19 +120,220 @@ def check():
if not os.path.isfile(keys_file):
writeKeys()
# plugin management
def sanitize(name):
return re.sub('[^0-9a-zA-Z]+', '', str(name).lower())[:255]
def blockToPlugin(block):
try:
blockContent = pluginapi.get_core().getData(block)
blockContent = blockContent[blockContent.rfind(b'\n') + 1:].decode()
blockContent = json.loads(blockContent)
name = sanitize(blockContent['name'])
author = blockContent['author']
date = blockContent['date']
version = None
if 'version' in blockContent['info']:
version = blockContent['info']['version']
content = base64.b64decode(blockContent['content'].encode())
source = pluginapi.plugins.get_data_folder(plugin_name) + 'plugin.zip'
destination = pluginapi.plugins.get_folder(name)
with open(source, 'wb') as f:
f.write(content)
if os.path.exists(destination) and not os.path.isfile(destination):
shutil.rmtree(destination)
shutil.unpack_archive(source, destination)
pluginapi.plugins.enable(name)
logger.info('Installation of %s complete.' % name)
return True
except Exception as e:
logger.error('Failed to install plugin.', error = e, timestamp = False)
return False
def pluginToBlock(plugin, import_block = True):
try:
plugin = sanitize(plugin)
directory = pluginapi.get_pluginapi().get_folder(plugin)
data_directory = pluginapi.get_pluginapi().get_data_folder(plugin)
zipfile = pluginapi.get_pluginapi().get_data_folder(plugin_name) + 'plugin.zip'
if os.path.exists(directory) and not os.path.isfile(directory):
if os.path.exists(data_directory) and not os.path.isfile(data_directory):
shutil.rmtree(data_directory)
if os.path.exists(zipfile) and os.path.isfile(zipfile):
os.remove(zipfile)
if os.path.exists(directory + '__pycache__') and not os.path.isfile(directory + '__pycache__'):
shutil.rmtree(directory + '__pycache__')
shutil.make_archive(zipfile[:-4], 'zip', directory)
data = base64.b64encode(open(zipfile, 'rb').read())
author = getpass.getuser()
description = 'Default plugin description'
info = {"name" : plugin}
try:
if os.path.exists(directory + 'info.json'):
info = json.loads(open(directory + 'info.json').read())
if 'author' in info:
author = info['author']
if 'description' in info:
description = info['description']
except:
pass
metadata = {'author' : author, 'date' : str(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')), 'name' : plugin, 'info' : info, 'compiled-by' : plugin_name, 'content' : data.decode('utf-8'), 'description' : description}
hash = pluginapi.get_core().insertBlock(json.dumps(metadata), header = 'plugin', sign = True)
if import_block:
pluginapi.get_utils().importNewBlocks()
return hash
else:
logger.error('Plugin %s does not exist.' % plugin)
except Exception as e:
logger.error('Failed to convert plugin to block.', error = e, timestamp = False)
return False
def parseBlock(hash, key):# deal with block metadata
blockContent = pluginapi.get_core().getData(hash)
try:
blockMetadata = json.loads(blockContent[:blockContent.decode().find('\n')].decode())
try:
blockMeta2 = json.loads(blockMetadata['meta'])
except KeyError:
blockMeta2 = {'type': ''}
pass
blockContent = blockContent[blockContent.rfind(b'\n') + 1:]
try:
blockContent = blockContent.decode()
except AttributeError:
pass
if not pluginapi.get_crypto().verifyPow(blockContent, blockMeta2):
logger.debug("(pluginmanager): %s has invalid or insufficient proof of work" % str(hash))
return False
if not (('sig' in blockMetadata)): # and ('id' in blockMeta2)
logger.debug('(pluginmanager): %s is missing required parameters' % hash)
return False
else:
if pluginapi.get_crypto().edVerify(blockMetadata['meta'] + '\n' + blockContent, key, blockMetadata['sig'], encodedData=True):
# logger.debug('(pluginmanager): %s was signed' % str(hash))
return True
else:
# logger.debug('(pluginmanager): %s has an invalid signature' % str(hash))
return False
except json.decoder.JSONDecodeError as e:
logger.error('(pluginmanager): Could not decode block metadata.', error = e, timestamp = False)
return False
def installBlock(block):
try:
blockContent = pluginapi.get_core().getData(block)
blockContent = blockContent[blockContent.rfind(b'\n') + 1:].decode()
blockContent = json.loads(blockContent)
name = sanitize(blockContent['name'])
author = blockContent['author']
date = blockContent['date']
version = None
if 'version' in blockContent['info']:
version = blockContent['info']['version']
install = False
logger.info(('Will install %s' + (' v' + version if not version is None else '') + ' (%s), by %s') % (name, date, author))
# TODO: Convert to single line if statement
if os.path.exists(pluginapi.plugins.get_folder(name)):
install = logger.confirm(message = 'Continue with installation (will overwrite existing plugin) %s?')
else:
install = logger.confirm(message = 'Continue with installation %s?')
if install:
blockToPlugin(block)
addPlugin(name)
else:
logger.info('Installation cancelled.')
return False
return True
except Exception as e:
logger.error('Failed to install plugin.', error = e, timestamp = False)
return False
def uninstallPlugin(plugin):
try:
plugin = sanitize(plugin)
pluginFolder = pluginapi.plugins.get_folder(plugin)
exists = (os.path.exists(pluginFolder) and not os.path.isfile(pluginFolder))
installedByPluginManager = plugin in getPlugins()
remove = False
if not exists:
logger.warn('Plugin %s does not exist.' % plugin, timestamp = False)
return False
default = 'y'
if not installedByPluginManager:
logger.warn('The plugin %s was not installed by %s.' % (plugin, plugin_name), timestamp = False)
default = 'n'
remove = logger.confirm(message = 'All plugin data will be lost. Are you sure you want to proceed %s?', default = default)
if remove:
if installedByPluginManager:
removePlugin(plugin)
pluginapi.plugins.disable(plugin)
shutil.rmtree(pluginFolder)
logger.info('Uninstallation of %s complete.' % plugin)
return True
else:
logger.info('Uninstallation cancelled.')
except Exception as e:
logger.error('Failed to uninstall plugin.', error = e)
return False
# command handlers
def help():
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin> [public key/block hash]')
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin> [public key/block hash]')
def commandInstallPlugin():
logger.warn('This feature is not functional or is still in development.')
if len(sys.argv) >= 3:
check()
pluginname = sys.argv[2]
pkobh = None # public key or block hash
version = None
if ':' in pluginname:
details = pluginname
pluginname = sanitize(details[0])
version = details[1]
sanitize(pluginname)
if len(sys.argv) >= 4:
# public key or block hash specified
pkobh = sys.argv[3]
@ -77,7 +342,54 @@ def commandInstallPlugin():
pkobh = getKey(pluginname)
if pkobh is None:
logger.error('No key for this plugin found in keystore, please specify.')
# still nothing found, try searching repositories
logger.info('Searching for public key in repositories...')
try:
repos = getRepositories()
distributors = list()
for repo, records in repos.items():
if pluginname in records:
logger.debug('Found %s in repository %s for plugin %s.' % (records[pluginname], repo, pluginname))
distributors.append(records[pluginname])
if len(distributors) != 0:
distributor = None
if len(distributors) == 1:
logger.info('Found distributor: %s' % distributors[0])
distributor = distributors[0]
else:
distributors_message = ''
index = 1
for dist in distributors:
distributors_message += ' ' + logger.colors.bold + str(index) + ') ' + logger.colors.reset + str(dist) + '\n'
index += 1
logger.info((logger.colors.bold + 'Found distributors (%s):' + logger.colors.reset + '\n' + distributors_message) % len(distributors))
valid = False
while not valid:
choice = logger.readline('Select the number of the key to use, from 1 to %s, or press Ctrl+C to cancel:' % (index - 1))
try:
if int(choice) < index and int(choice) >= 1:
distributor = distributors[int(choice)]
valid = True
except KeyboardInterrupt:
logger.info('Installation cancelled.')
return True
except:
pass
if not distributor is None:
pkobh = distributor
except Exception as e:
logger.warn('Failed to lookup plugin in repositories.', timestamp = False)
logger.error('asdf', error = e, timestamp = False)
if pkobh is None:
logger.error('No key for this plugin found in keystore or repositories, please specify.')
help()
return True
@ -96,18 +408,13 @@ def commandInstallPlugin():
if valid_hash and not real_block:
logger.error('Block hash not found. Perhaps it has not been synced yet?')
logger.debug('Is valid hash, but does not belong to a known block.')
return True
elif valid_hash and real_block:
blockhash = str(pkobh)
logger.debug('Using block %s...' % blockhash)
logger.info('Downloading plugin...')
for i in range(0, 100):
pluginapi.get_utils().progressBar(i, 100)
time.sleep(random.random() / 5)
logger.info('Finished downloading plugin, verifying and installing...')
time.sleep(1)
logger.info('Installation successful.')
installBlock(blockhash)
elif valid_key and not real_key:
logger.error('Public key not found. Try adding the node by address manually, if possible.')
logger.debug('Is valid key, but the key is not a known one.')
@ -117,28 +424,86 @@ def commandInstallPlugin():
saveKey(pluginname, pkobh)
logger.info('Downloading plugin...')
for i in range(0, 100):
pluginapi.get_utils().progressBar(i, 100)
time.sleep(random.random() / 5)
logger.info('Finished downloading plugin, verifying and installing...')
time.sleep(1)
logger.info('Installation successful.')
blocks = pluginapi.get_core().getBlocksByType('plugin')
signedBlocks = list()
for hash in blocks:
if parseBlock(hash, publickey):
signedBlocks.append(hash)
mostRecentTimestamp = None
mostRecentVersionBlock = None
for hash in signedBlocks:
try:
blockContent = pluginapi.get_core().getData(hash)
blockContent = blockContent[blockContent.rfind(b'\n') + 1:].decode()
blockContent = json.loads(blockContent)
if not (('author' in blockContent) and ('info' in blockContent) and ('date' in blockContent) and ('name' in blockContent)):
raise ValueError('Missing required parameter `date` in block %s.' % hash)
blockDatetime = datetime.datetime.strptime(blockContent['date'], '%Y-%m-%d %H:%M:%S')
if blockContent['name'] == pluginname:
if ('version' in blockContent['info']) and (blockContent['info']['version'] == version) and (not version is None):
mostRecentTimestamp = blockDatetime
mostRecentVersionBlock = hash
break
elif mostRecentTimestamp is None:
mostRecentTimestamp = blockDatetime
mostRecentVersionBlock = hash
elif blockDatetime > mostRecentTimestamp:
mostRecentTimestamp = blockDatetime
mostRecentVersionBlock = hash
except Exception as e:
pass
logger.warn('Only continue the installation is you are absolutely certain that you trust the plugin distributor. Public key of plugin distributor: %s' % publickey, timestamp = False)
installBlock(mostRecentVersionBlock)
else:
logger.error('Unknown data "%s"; must be public key or block hash.' % str(pkobh))
return
else:
help()
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin> [public key/block hash]')
return True
def commandUninstallPlugin():
logger.info('This feature has not been created yet. Please check back later.')
return
if len(sys.argv) >= 3:
uninstallPlugin(sys.argv[2])
else:
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
return True
def commandSearchPlugin():
logger.info('This feature has not been created yet. Please check back later.')
return
return True
def commandAddRepository():
logger.info('This feature has not been created yet. Please check back later.')
return True
def commandRemoveRepository():
logger.info('This feature has not been created yet. Please check back later.')
return True
def commandPublishPlugin():
if len(sys.argv) >= 3:
check()
pluginname = sanitize(sys.argv[2])
pluginfolder = pluginapi.plugins.get_folder(pluginname)
if os.path.exists(pluginfolder) and not os.path.isfile(pluginfolder):
block = pluginToBlock(pluginname)
logger.info('Plugin saved in block %s.' % block)
else:
logger.error('Plugin %s does not exist.' % pluginname, timestamp = False)
else:
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
# event listeners
@ -151,6 +516,9 @@ def on_init(api, data = None):
api.commands.register(['install-plugin', 'installplugin', 'plugin-install', 'install', 'plugininstall'], commandInstallPlugin)
api.commands.register(['remove-plugin', 'removeplugin', 'plugin-remove', 'uninstall-plugin', 'uninstallplugin', 'plugin-uninstall', 'uninstall', 'remove', 'pluginremove'], commandUninstallPlugin)
api.commands.register(['search', 'filter-plugins', 'search-plugins', 'searchplugins', 'search-plugin', 'searchplugin', 'findplugin', 'find-plugin', 'filterplugin', 'plugin-search', 'pluginsearch'], commandSearchPlugin)
api.commands.register(['add-repo', 'add-repository', 'addrepo', 'addrepository', 'repository-add', 'repo-add', 'repoadd', 'addrepository', 'add-plugin-repository', 'add-plugin-repo', 'add-pluginrepo', 'add-pluginrepository', 'addpluginrepo', 'addpluginrepository'], commandAddRepository)
api.commands.register(['remove-repo', 'remove-repository', 'removerepo', 'removerepository', 'repository-remove', 'repo-remove', 'reporemove', 'removerepository', 'remove-plugin-repository', 'remove-plugin-repo', 'remove-pluginrepo', 'remove-pluginrepository', 'removepluginrepo', 'removepluginrepository', 'rm-repo', 'rm-repository', 'rmrepo', 'rmrepository', 'repository-rm', 'repo-rm', 'reporm', 'rmrepository', 'rm-plugin-repository', 'rm-plugin-repo', 'rm-pluginrepo', 'rm-pluginrepository', 'rmpluginrepo', 'rmpluginrepository'], commandRemoveRepository)
api.commands.register(['publish-plugin', 'plugin-publish', 'publishplugin', 'pluginpublish', 'publish'], commandPublishPlugin)
# add help menus once the features are actually implemented

View File

@ -1,11 +1,13 @@
'''
Default plugin template file
$name plugin template file.
Generated on $date by $user.
'''
# Imports some useful libraries
import logger, config
plugin_name = '$name'
def on_init(api, data = None):
'''
This event is called after Onionr is initialized, but before the command