From fe4261c4a2d8f5eb53df7b5a87f0342b900371e2 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 12 May 2018 20:45:32 -0700 Subject: [PATCH 1/6] Various improvements - Adds a lot more to the pluginmanager - Refactors code - Relocates functions --- onionr/communicator.py | 75 +++----------- onionr/core.py | 17 ++-- onionr/onionr.py | 2 +- onionr/onionrcrypto.py | 45 +++++++-- onionr/onionrpluginapi.py | 23 +++-- onionr/onionrutils.py | 27 +++--- .../default-plugins/pluginmanager/main.py | 97 ++++++++++++++++--- 7 files changed, 172 insertions(+), 114 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 34f4edd3..74355c0e 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -565,31 +565,26 @@ class OnionrCommunicate: 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)): + #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 +600,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 +656,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 diff --git a/onionr/core.py b/onionr/core.py index 27cd3a37..534a2c03 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -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): ''' diff --git a/onionr/onionr.py b/onionr/onionr.py index 643bb8ef..53683ec9 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -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)] diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index d400e8b6..315a7b2f 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -26,7 +26,7 @@ class OnionrCrypto: self.keyPowFile = 'data/keyPow.txt' self.pubKey = None self.privKey = None - + self.pubKeyPowToken = None self.pubKeyPowHash = None @@ -89,7 +89,7 @@ class OnionrCrypto: except nacl.exceptions.BadSignatureError: pass return retData - + def edSign(self, data, key, encodeResult=False): '''Ed25519 sign data''' try: @@ -199,7 +199,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() @@ -215,7 +215,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 == '': @@ -237,10 +237,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) \ No newline at end of file + 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.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 diff --git a/onionr/onionrpluginapi.py b/onionr/onionrpluginapi.py index 7aa2b891..51fca1a9 100644 --- a/onionr/onionrpluginapi.py +++ b/onionr/onionrpluginapi.py @@ -18,7 +18,7 @@ along with this program. If not, see . ''' -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(): + return self.get_core().crypto + def get_daemonapi(self): return self.daemon diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index 0270023a..adc10d34 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -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 @@ -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): diff --git a/onionr/static-data/default-plugins/pluginmanager/main.py b/onionr/static-data/default-plugins/pluginmanager/main.py index d4f3129a..fb330fd6 100644 --- a/onionr/static-data/default-plugins/pluginmanager/main.py +++ b/onionr/static-data/default-plugins/pluginmanager/main.py @@ -4,7 +4,7 @@ # useful libraries import logger, config -import os, sys, json, time, random +import os, sys, json, time, random, shutil, base64, getpass, datetime plugin_name = 'pluginmanager' @@ -56,6 +56,78 @@ def check(): if not os.path.isfile(keys_file): writeKeys() +# plugin management + +def installBlock(hash, overwrite = True): + logger.debug('install') + +def pluginToBlock(plugin, import_block = True): + try: + 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()) + + metadata = {'author' : getpass.getuser(), 'date' : str(datetime.datetime.now()), 'content' : data.decode('utf-8'), 'name' : plugin, 'compiled-by' : plugin_name} + + 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(b'}') + 1].decode()) + try: + blockMeta2 = json.loads(blockMetadata['meta']) + except KeyError: + blockMeta2 = {'type': ''} + pass + blockContent = blockContent[blockContent.rfind(b'}') + 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'] + 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 + # command handlers def help(): @@ -101,13 +173,7 @@ def commandInstallPlugin(): 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,13 +183,14 @@ 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') + + for hash in blocks: + logger.debug('Scanning block for plugin: %s' % hash) + if parseBlock(hash, publickey): + logger.info('success') + else: + logger.warn('fail') else: logger.error('Unknown data "%s"; must be public key or block hash.' % str(pkobh)) return From f9b93fd4916d96f1e5eda48bd9adfde522f9fd0b Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 12 May 2018 20:55:34 -0700 Subject: [PATCH 2/6] Add newline delimiter to metadata+content --- onionr/communicator.py | 6 +++--- onionr/core.py | 4 ++-- onionr/onionrutils.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 74355c0e..2c66e2ac 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -517,7 +517,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,13 +553,13 @@ 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: diff --git a/onionr/core.py b/onionr/core.py index 534a2c03..7877c849 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -678,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: @@ -692,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 diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index adc10d34..5c3e59f0 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -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) From f0e842eae4c6d71efda3c23545141510e3257c38 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sat, 12 May 2018 23:37:47 -0700 Subject: [PATCH 3/6] Fix positional argument bug --- onionr/communicator.py | 9 +++------ onionr/onionrcrypto.py | 2 +- onionr/onionrpluginapi.py | 4 ++-- onionr/static-data/default-plugins/pluginmanager/main.py | 4 ++-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/onionr/communicator.py b/onionr/communicator.py index 2c66e2ac..3980472f 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -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: diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 315a7b2f..590122f1 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -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): diff --git a/onionr/onionrpluginapi.py b/onionr/onionrpluginapi.py index 51fca1a9..95f06e46 100644 --- a/onionr/onionrpluginapi.py +++ b/onionr/onionrpluginapi.py @@ -153,8 +153,8 @@ class pluginapi: def get_utils(self): return self.get_onionr().onionrUtils - def get_crypto(): - return self.get_core().crypto + def get_crypto(self): + return self.get_core()._crypto def get_daemonapi(self): return self.daemon diff --git a/onionr/static-data/default-plugins/pluginmanager/main.py b/onionr/static-data/default-plugins/pluginmanager/main.py index fb330fd6..bbe8f9b4 100644 --- a/onionr/static-data/default-plugins/pluginmanager/main.py +++ b/onionr/static-data/default-plugins/pluginmanager/main.py @@ -97,13 +97,13 @@ def parseBlock(hash, key):# deal with block metadata blockContent = pluginapi.get_core().getData(hash) try: - blockMetadata = json.loads(blockContent[:blockContent.decode().find(b'}') + 1].decode()) + 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'}') + 1:] + blockContent = blockContent[blockContent.rfind(b'\n') + 1:] try: blockContent = blockContent.decode() except AttributeError: From b1e9e6143035e0f7188a54970e67f02001ca0098 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sun, 13 May 2018 21:11:31 -0700 Subject: [PATCH 4/6] Make pluginmanager functional --- onionr/communicator.py | 5 +- onionr/onionr.py | 5 +- onionr/onionrcrypto.py | 6 +- .../static-data/default-plugins/gui/info.json | 5 + .../static-data/default-plugins/gui/main.py | 4 +- .../default-plugins/pluginmanager/info.json | 5 + .../default-plugins/pluginmanager/main.py | 338 +++++++++++++++++- onionr/static-data/default_plugin.py | 4 +- 8 files changed, 345 insertions(+), 27 deletions(-) create mode 100644 onionr/static-data/default-plugins/gui/info.json create mode 100644 onionr/static-data/default-plugins/pluginmanager/info.json diff --git a/onionr/communicator.py b/onionr/communicator.py index 3980472f..c55e2dde 100755 --- a/onionr/communicator.py +++ b/onionr/communicator.py @@ -567,7 +567,8 @@ class OnionrCommunicate: self._core.removeBlock(i) continue else: - if (('sig' in blockMetadata) and ('id' in blockMeta2)): + 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']) @@ -576,7 +577,7 @@ class OnionrCommunicate: except AttributeError: pass - if self._core._crypto.edVerify(blockMetaData['meta'] + blockContent, creator, blockMetadata['sig'], encodedData=True): + 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: diff --git a/onionr/onionr.py b/onionr/onionr.py index 53683ec9..081f5a30 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -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) diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 590122f1..6da61cf8 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -269,11 +269,11 @@ class OnionrCrypto: puzzle = mainHash[:difficulty] if metadata['powHash'][:difficulty] == puzzle: - logger.info('Validated block pow') + # logger.debug('Validated block pow') retData = True else: - logger.warn("Invalid token (#1)") + logger.debug("Invalid token (#1)") else: - logger.warn('Invalid token (#2): Expected hash %s, got hash %s...' % (metadata['powHash'], expectedHash)) + logger.debug('Invalid token (#2): Expected hash %s, got hash %s...' % (metadata['powHash'], expectedHash)) return retData diff --git a/onionr/static-data/default-plugins/gui/info.json b/onionr/static-data/default-plugins/gui/info.json new file mode 100644 index 00000000..83d4489a --- /dev/null +++ b/onionr/static-data/default-plugins/gui/info.json @@ -0,0 +1,5 @@ +{ + "name" : "gui", + "version" : "1.0", + "author" : "onionr" +} diff --git a/onionr/static-data/default-plugins/gui/main.py b/onionr/static-data/default-plugins/gui/main.py index 093d8f9c..db82b382 100644 --- a/onionr/static-data/default-plugins/gui/main.py +++ b/onionr/static-data/default-plugins/gui/main.py @@ -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 diff --git a/onionr/static-data/default-plugins/pluginmanager/info.json b/onionr/static-data/default-plugins/pluginmanager/info.json new file mode 100644 index 00000000..06c7f0ab --- /dev/null +++ b/onionr/static-data/default-plugins/pluginmanager/info.json @@ -0,0 +1,5 @@ +{ + "name" : "pluginmanager", + "version" : "1.0", + "author" : "onionr" +} diff --git a/onionr/static-data/default-plugins/pluginmanager/main.py b/onionr/static-data/default-plugins/pluginmanager/main.py index bbe8f9b4..7fc4ebf3 100644 --- a/onionr/static-data/default-plugins/pluginmanager/main.py +++ b/onionr/static-data/default-plugins/pluginmanager/main.py @@ -4,11 +4,11 @@ # useful libraries import logger, config -import os, sys, json, time, random, shutil, base64, getpass, datetime +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 @@ -58,11 +122,49 @@ def check(): # plugin management -def installBlock(hash, overwrite = True): - logger.debug('install') +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' @@ -78,7 +180,19 @@ def pluginToBlock(plugin, import_block = True): shutil.make_archive(zipfile[:-4], 'zip', directory) data = base64.b64encode(open(zipfile, 'rb').read()) - metadata = {'author' : getpass.getuser(), 'date' : str(datetime.datetime.now()), 'content' : data.decode('utf-8'), 'name' : plugin, 'compiled-by' : plugin_name} + author = getpass.getuser() + 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: + author = 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')} hash = pluginapi.get_core().insertBlock(json.dumps(metadata), header = 'plugin', sign = True) @@ -113,34 +227,112 @@ def parseBlock(hash, key):# deal with block metadata logger.debug("(pluginmanager): %s has invalid or insufficient proof of work" % str(hash)) return False - if not (('sig' in blockMetadata) and ('id' in blockMeta2)): + 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'] + blockContent, key, blockMetadata['sig'], encodedData=True): - logger.debug('(pluginmanager): %s was signed' % str(hash)) + 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)) + # 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] + ' [public key/block hash]') + logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' [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] @@ -149,7 +341,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 @@ -168,6 +407,7 @@ 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) @@ -185,27 +425,84 @@ def commandInstallPlugin(): blocks = pluginapi.get_core().getBlocksByType('plugin') + signedBlocks = list() + for hash in blocks: - logger.debug('Scanning block for plugin: %s' % hash) if parseBlock(hash, publickey): - logger.info('success') - else: - logger.warn('fail') + 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] + ' [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] + ' ') + + 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] + ' ') # event listeners @@ -218,6 +515,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 diff --git a/onionr/static-data/default_plugin.py b/onionr/static-data/default_plugin.py index edd8247f..cc6d1d20 100644 --- a/onionr/static-data/default_plugin.py +++ b/onionr/static-data/default_plugin.py @@ -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 From 15d0e7c6fd90d94d6190423fa2fd498747ecd939 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sun, 13 May 2018 21:19:33 -0700 Subject: [PATCH 5/6] Fix small bug with description and authors --- onionr/onionr.py | 2 +- onionr/static-data/default-plugins/pluginmanager/main.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/onionr/onionr.py b/onionr/onionr.py index 081f5a30..d55664a8 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -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 diff --git a/onionr/static-data/default-plugins/pluginmanager/main.py b/onionr/static-data/default-plugins/pluginmanager/main.py index 7fc4ebf3..a3a454d6 100644 --- a/onionr/static-data/default-plugins/pluginmanager/main.py +++ b/onionr/static-data/default-plugins/pluginmanager/main.py @@ -181,6 +181,7 @@ def pluginToBlock(plugin, import_block = True): 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'): @@ -188,11 +189,11 @@ def pluginToBlock(plugin, import_block = True): if 'author' in info: author = info['author'] if 'description' in info: - author = info['description'] + 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')} + 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) From 019f13fe8dd09d3747a257e7e229338bcd49cf54 Mon Sep 17 00:00:00 2001 From: Arinerron Date: Sun, 13 May 2018 21:22:28 -0700 Subject: [PATCH 6/6] Fix bug involving plugin duplicate names --- onionr/onionrplugins.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/onionr/onionrplugins.py b/onionr/onionrplugins.py index bdb7a073..175b2336 100644 --- a/onionr/onionrplugins.py +++ b/onionr/onionrplugins.py @@ -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)