PK!\9<##envision/__init__.pyfrom .envision import envision, cliPK!< <,<,envision/envision.pyfrom os import environ from os.path import isfile, join, isdir import json import base64 import uuid import secrets import string from cryptography.fernet import Fernet from cryptography.fernet import Fernet from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC import warnings import click class envision: def __init__(self, root : str = './'): ''' Envision constructor. ''' self.root = root self.ENVISION_FILE = join(root,'.envision') self.ENVISION_LOCK_FILE = join(root,'.envision.lock') self.ENVISION_KEY_FILE = join(root,'.envision.key') self.GITIGNORE_FILE = join(root,'.gitignore') #Detect if gitignore is correct self.__gitignore_detect() #Detect the current environment mode self.__detect() #Validate environment mode self.__validate_mode() #Validate the data in the json self.__validate_json() #Load the key and algorithm self.__load_key() #Load the JSON self.__load_json() #############GITIGNORE################ def __gitignore_detect(self): gitignore = join(self.root,".gitignore"); if isfile(gitignore): if not '.envision' in open(gitignore).read(): warnings.warn('.gitignore file at {} does not include exclusion of .envision file.') if not '.envision.key' in open(gitignore).read(): warnings.warn('.gitignore file at {} does not include exclusion of .envision.key file.'.format(str(self.root))) ###################################### #############DETECTION################ def __detect(self): if not isdir(self.root): raise EnvironmentError('No directory found at {} .'.format(str(self.root))) elif self.__has_envision(): self.is_development = True elif self.__has_envision_lock(): self.is_development = False else: raise EnvironmentError('No ENVISION files found in the directory {} .'.format(str(self.root))) def __has_envision(self): return isfile(self.ENVISION_FILE) def __has_envision_lock(self): return isfile(self.ENVISION_LOCK_FILE) ###################################### ############VALIDATE MODE############# def __validate_mode(self): if self.is_development: if self.__has_envision_key_env_var(): warnings.warn("ENVISION key variable is set on local enviroment running development mode. Ignored.") else: if not self.__has_envision_key_env_var(): raise EnvironmentError("ENVISION key variable is NOT set on local environment running production mode.") elif self.__has_envision_key_file(): warnings.warn("ENVISION key file is present on local environment running production mode.") def __has_envision_key_env_var(self): return environ.get('ENVISION') is not None def __has_envision_key_file(self): return isfile(self.ENVISION_KEY_FILE) ###################################### ############VALIDATE JSONS############ def __validate_json(self): if self.is_development: self.__validate_envision_json(self.ENVISION_FILE) else: self.__validate_envision_json(self.ENVISION_LOCK_FILE) def __validate_envision_json(self,filepath): with open(filepath) as json_file: try: json_obj = json.load(json_file) except: raise EnvironmentError('Could not parse a valid json object from file: {} .'.format(str(filepath))) for k in json_obj: if not isinstance(json_obj[k],str): raise EnvironmentError('Value {0} of type {1} under key {2} of Envision file {3} is not a string.'.format(str(json_obj[k]),type(json_obj[k]),str(k),str(filepath))) ###################################### ##########LOAD ENVISION KEY########### def __load_key(self): if self.is_development: self.__load_key_development() else: self.__load_key_production() self.__encryption_algorithm = Fernet(self.__get_fernet_key()) def __load_key_development(self): try: with open(self.ENVISION_KEY_FILE, 'r') as key_file: self.key = key_file.read() or self.__generate_key() key_file.close() except: self.key = self.__generate_key() def __load_key_production(self): self.key = environ.get('ENVISION') def __get_fernet_key(self): kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),length=32,salt=b'envision',iterations=100000,backend=default_backend()) return base64.urlsafe_b64encode(kdf.derive(self.key.encode())) def __generate_key(self): return ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(16)) ###################################### #########LOAD ENVISION JSON########### def __load_json(self): if self.is_development: self.__load_json_development() else: self.__load_json_production() def __load_json_development(self): with open(self.ENVISION_FILE) as json_file: self.ENVISION_JSON = json.load(json_file) def __load_json_production(self): with open(self.ENVISION_LOCK_FILE) as json_file: enc_json = json.load(json_file) data = {} for k in enc_json: data[k] = self.__decrypt_value(enc_json[k]) self.ENVISION_JSON = data def __decrypt_value(self,v:str): try: return self.__encryption_algorithm.decrypt(v.encode()).decode() except: raise EnvironmentError('The provided env key token: {} is not valid for file {} .'.format(self.key,str(self.ENVISION_LOCK_FILE))) ####################################### ############### UPDATE ############### def __update(self): if self.is_development: self.__update_file() else: warnings.warn('Updates to environment variables must be done in development mode. Ignored.') def __update_file(self): with open(self.ENVISION_FILE, 'w') as lock_file: json.dump(self.ENVISION_JSON, lock_file, sort_keys = True, indent=0) lock_file.close() ############### LOCK ################ def __lock(self,token = None): if token: self.key = token if self.is_development: self.__lock_lock_file() self.__lock_key_file() else: warnings.warn('Locking environment variables must be done in development mode. Ignored.') def __lock_lock_file(self): enc_data = {} for k in self.ENVISION_JSON: enc_data[k] = self.__encrypt_value(self.ENVISION_JSON[k]) with open(self.ENVISION_LOCK_FILE, 'w') as lock_file: json.dump(enc_data, lock_file, sort_keys = True, indent=0) lock_file.close() def __encrypt_value(self,v:str): return self.__encryption_algorithm.encrypt(v.encode()).decode() def __lock_key_file(self): with open(self.ENVISION_KEY_FILE, 'w') as key_file: key_file.write(self.key) key_file.close() ######################################## ################ USER ################## def get(self, key : str, default = None): try: return self.ENVISION_JSON[key] except: return default ######################################## ################# CLI ################## def __add(self, key:str, value:str, override:bool): if not self.get(key): self.ENVISION_JSON[key] = value elif override: self.ENVISION_JSON[key] = value else: raise EnvironmentError('The key {} is already in the ENVISION configuration file. Add --override parameter to override.'.format(key)) self.__update() self.__lock() def __remove(self,key): if self.get(key): del self.ENVISION_JSON[key] else: warnings.warn('The key {} is NOT in the ENVISION configuration file. Ignored.'.format(key)) self.__update() self.__lock() ######################################### @click.command() @click.option("--root", default='./', help="The root directory of the python project.") @click.option("--token", default=None, help="Desired encryption key token.") def init(root, token): """Simple CLI for initialization of a ENVISION managed project.""" if isdir(root): if isfile(join(root,'.envision')): raise EnvironmentError('ENVISION project already initialized.') else: if token: with open(join(root,'.envision.key'), 'w') as key_file: key_file.write(token) key_file.close() with open(join(root,'.envision'),'w') as env_file: json.dump({}, env_file, sort_keys = True, indent=0) env_file.close() envision(root) else: raise EnvironmentError('No directory found at {} .'.format(str(root))) @click.command() @click.option("--root", default='./', help="The root directory of the python project.") @click.option("--key", prompt="Please, write the key for the environment variable to be added", help="Desired environment variable key to add.") @click.option("--value", prompt="Please, write the value for the environment variable to be added", help="Desired environment variable value to add.") @click.option("--override", is_flag=True, help="Overriding existing keys.") def add(root, key:str,value:str, override:bool): """Simple CLI for adding a environment variable to a ENVISION managed project.""" envision(root)._envision__add(key,value,override) @click.command() @click.option("--root", default='./', help="The root directory of the python project.") @click.option("--key", prompt="Please, write the key for the environment variable to be removed", help="Desired environment variable key to remove.") def remove(root, key:str): """Simple CLI for removing a environment variable to a ENVISION managed project.""" envision(root)._envision__remove(key) @click.command() @click.option("--root", default='./', help="The root directory of the python project.") @click.option("--token", default=None, help="Desired encryption key token.") def lock(root,token): """Simple CLI for lock a ENVISION managed project.""" envision(root)._envision__lock(token) @click.group() def cli(): pass cli.add_command(init) cli.add_command(add) cli.add_command(remove) cli.add_command(lock)PK!envision/tests/__init__.pyPK!yHק''%envision/tests/dev_warnings/.envision{ "key": "value", "key2": "value2" }PK!yHק''%envision/tests/git_warnings/.envision{ "key": "value", "key2": "value2" }PK!&envision/tests/git_warnings/.gitignorePK!ll.envision/tests/invalid_envision_file/.envision[OS] Distributor ID: Ubuntu Description: Ubuntu 18.10 Release: 18.10 Codename: cosmic [Docker] Client: Version: 18.06.1-ce API version: 1.38 Go version: go1.10.4 Git commit: e68fc7a Built: Mon Oct 1 14:25:31 2018 OS/Arch: linux/amd64 Experimental: false Server: Engine: Version: 18.06.1-ce API version: 1.38 (minimum version 1.12) Go version: go1.10.4 Git commit: e68fc7a Built: Mon Oct 1 14:25:33 2018 OS/Arch: linux/amd64 Experimental: falsePK!R3envision/tests/invalid_envision_key_value/.envision{ "key" : 5 }PK!ll8envision/tests/invalid_envision_lock_file/.envision.lock[OS] Distributor ID: Ubuntu Description: Ubuntu 18.10 Release: 18.10 Codename: cosmic [Docker] Client: Version: 18.06.1-ce API version: 1.38 Go version: go1.10.4 Git commit: e68fc7a Built: Mon Oct 1 14:25:31 2018 OS/Arch: linux/amd64 Experimental: false Server: Engine: Version: 18.06.1-ce API version: 1.38 (minimum version 1.12) Go version: go1.10.4 Git commit: e68fc7a Built: Mon Oct 1 14:25:33 2018 OS/Arch: linux/amd64 Experimental: falsePK!ju =envision/tests/invalid_envision_lock_key_value/.envision.lock{"key": 3}PK!{Pc*envision/tests/prod_warnings/.envision.keyL7KWG5RD7MGWUPCDPK!N+envision/tests/prod_warnings/.envision.lock{ "key": "gAAAAABcglOpqvke2mY7DArZx3ZZML8CtonsIxlV1bOyxPHqxO5IezKnJavGhTJQocjyNAuiLATeBILZJ0XP_DHTJKKnXCWQPg==", "key2": "gAAAAABcglOpSlEdZD-YbFyLePXWb55EMNMqGmCgWOGpxVWZ1hHI_9XUVJPhfi9IOfl0OxG2mdAHHK5tyf3ZNf9VAoxudYteTg==" }PK!Y envision/tests/test_envision.pyimport json import pytest from envision import envision from os.path import isfile, join, exists from os import remove, environ def purge(p): if exists(p): remove(p) def test_valid_dev(): root = 'envision/tests/valid_dev' env = envision(root) assert env.get('key') == 'value' env._envision__lock() assert isfile(join(root,'.envision.lock')) assert isfile(join(root,'.envision.key')) env2 = envision(root) assert env.key == env2.key purge(join(root,'.envision.lock')) purge(join(root,'.envision.key')) def test_non_existent_root(): with pytest.raises(EnvironmentError): root = 'envision/tests/non_existent' envision(root) def test_empty_root(): with pytest.raises(EnvironmentError): root = 'envision/tests/empty' envision(root) def test_invalid_envision_file(): with pytest.raises(EnvironmentError): root = 'envision/tests/invalid_envision_file' envision(root) def test_invalid_envision_key_value(): with pytest.raises(EnvironmentError): root = 'envision/tests/invalid_envision_key_value' envision(root) def test_valid_prod(): root = 'envision/tests/valid_prod' environ["ENVISION"] = "L7KWG5RD7MGWUPCD" env = envision(root) assert env.get('key') == 'value' def test_non_existent_env_key(): with pytest.raises(EnvironmentError): environ["ENVISION"] = "J2fHRC_5H629-8f1JSNDdw2FV0Hx2Eesly8JMe_MI3g=" del environ["ENVISION"] root = 'envision/tests/valid_prod' envision(root) def test_incorrect_env_key(): with pytest.raises(EnvironmentError): environ["ENVISION"] = "J2fHRC_5H629-8f10SNDdw2FV0Hx2Eesly8JMe_MI3g=" root = 'envision/tests/valid_prod' envision(root) def test_invalid_envision_lock_file(): with pytest.raises(EnvironmentError): root = 'envision/tests/invalid_envision_lock_file' envision(root) def test_invalid_envision_lock_key_value(): with pytest.raises(EnvironmentError): root = 'envision/tests/invalid_envision_lock_key_value' envision(root) def test_git_warnings(): with pytest.warns(Warning): root = 'envision/tests/git_warnings' envision(root) purge(join(root,'.envision.lock')) purge(join(root,'.envision.key')) def test_dev_warnings(): with pytest.warns(Warning): environ['ENVISION'] = "abc" root = 'envision/tests/dev_warnings' envision(root) purge(join(root,'.envision.lock')) purge(join(root,'.envision.key')) def test_prod_warnings(): with pytest.warns(Warning): environ['ENVISION'] = "L7KWG5RD7MGWUPCD" root = 'envision/tests/prod_warnings' envision(root) PK!yHק''"envision/tests/valid_dev/.envision{ "key": "value", "key2": "value2" }PK!N(envision/tests/valid_prod/.envision.lock{ "key": "gAAAAABcglOpqvke2mY7DArZx3ZZML8CtonsIxlV1bOyxPHqxO5IezKnJavGhTJQocjyNAuiLATeBILZJ0XP_DHTJKKnXCWQPg==", "key2": "gAAAAABcglOpSlEdZD-YbFyLePXWb55EMNMqGmCgWOGpxVWZ1hHI_9XUVJPhfi9IOfl0OxG2mdAHHK5tyf3ZNf9VAoxudYteTg==" }PK!H22I$))envision-0.1.1.dist-info/entry_points.txtN+I/N.,()J+,ϳ1s2PK!HڽTUenvision-0.1.1.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!HP!envision-0.1.1.dist-info/METADATAMO0 >F@ $egz]IN'8=~lylcL5 'j@f"K~jCqy Jc9l&a# İ w@j)CYSu[ݿTZdBMa)iT1*;#} ɥf E)ȒS[Eam˨2-ѴC2U߼Wy uw8Y.f"Evψ'1E'PK!H_envision-0.1.1.dist-info/RECORDIJ-aǃ pia_4=[{T*Gj͋WIp(#ì}?{,%q|Դ _U, ͲYCI1NNt8[Yh|K;W <~Bϵ4;/u, 9N`1̯;DfJQ:Ώ[D ;x#:5H>ASt^GID PK!\9<##envision/__init__.pyPK!< <,<,Uenvision/envision.pyPK!,envision/tests/__init__.pyPK!yHק''%,envision/tests/dev_warnings/.envisionPK!yHק''%e-envision/tests/git_warnings/.envisionPK!&-envision/tests/git_warnings/.gitignorePK!ll..envision/tests/invalid_envision_file/.envisionPK!R30envision/tests/invalid_envision_key_value/.envisionPK!ll8+1envision/tests/invalid_envision_lock_file/.envision.lockPK!ju =3envision/tests/invalid_envision_lock_key_value/.envision.lockPK!{Pc*R4envision/tests/prod_warnings/.envision.keyPK!N+4envision/tests/prod_warnings/.envision.lockPK!Y 5envision/tests/test_envision.pyPK!yHק''"Aenvision/tests/valid_dev/.envisionPK!N(Benvision/tests/valid_prod/.envision.lockPK!H22I$))HCenvision-0.1.1.dist-info/entry_points.txtPK!HڽTUCenvision-0.1.1.dist-info/WHEELPK!HP!CDenvision-0.1.1.dist-info/METADATAPK!H_Eenvision-0.1.1.dist-info/RECORDPK7H