dprojectstools 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dprojectstools/__init__.py +4 -0
- dprojectstools/commands/__init__.py +3 -0
- dprojectstools/commands/commands.py +309 -0
- dprojectstools/crypto/__init__.py +1 -0
- dprojectstools/crypto/aes.py +130 -0
- dprojectstools/secrets/__init__.py +1 -0
- dprojectstools/secrets/secrets.py +105 -0
- dprojectstools-0.0.3.dist-info/LICENSE +21 -0
- dprojectstools-0.0.3.dist-info/METADATA +16 -0
- dprojectstools-0.0.3.dist-info/RECORD +12 -0
- dprojectstools-0.0.3.dist-info/WHEEL +5 -0
- dprojectstools-0.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import types
|
|
3
|
+
from typing import List, get_type_hints
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
# global vars
|
|
7
|
+
order = 0
|
|
8
|
+
SEQUENCE_COLOR_RED = "\033[31m"
|
|
9
|
+
SEQUENCE_COLOR_GREEN = "\033[32m"
|
|
10
|
+
SEQUENCE_COLOR_GREEN_DARK = "\033[2;32m"
|
|
11
|
+
SEQUENCE_COLOR_GRAY_LIGHT = "\033[38;5;250m"
|
|
12
|
+
SEQUENCE_COLOR_GRAY_MEDIUM = "\033[38;5;244m"
|
|
13
|
+
SEQUENCE_COLOR_GRAY_DARK = "\033[38;5;238m"
|
|
14
|
+
SEQUENCE_RESET = "\033[0m"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# decorator (las funciones con este decorator, se catalogaran en el CommandsManager)
|
|
18
|
+
def command(title: str, index: int = 0):
|
|
19
|
+
def decorator(func):
|
|
20
|
+
global order
|
|
21
|
+
order += 1
|
|
22
|
+
aux = func.__name__ .split("_")
|
|
23
|
+
setattr(func, "title", title)
|
|
24
|
+
setattr(func, "order", order)
|
|
25
|
+
setattr(func, "index", index)
|
|
26
|
+
return func
|
|
27
|
+
return decorator
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# decorator
|
|
31
|
+
def example(example: str):
|
|
32
|
+
def decorator(func):
|
|
33
|
+
setattr(func, "example", example)
|
|
34
|
+
return func
|
|
35
|
+
return decorator
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# data classes
|
|
39
|
+
@dataclass
|
|
40
|
+
class CommandArgument:
|
|
41
|
+
name: str
|
|
42
|
+
title: str
|
|
43
|
+
required: bool
|
|
44
|
+
type: str
|
|
45
|
+
default: any
|
|
46
|
+
|
|
47
|
+
# data classes
|
|
48
|
+
@dataclass
|
|
49
|
+
class Command:
|
|
50
|
+
name: List[str]
|
|
51
|
+
title: str
|
|
52
|
+
arguments: List[CommandArgument]
|
|
53
|
+
instance: object
|
|
54
|
+
func: any
|
|
55
|
+
order:int
|
|
56
|
+
index: int
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Manager
|
|
60
|
+
class CommandsManager:
|
|
61
|
+
|
|
62
|
+
def __init__(self, title="", indent = 0):
|
|
63
|
+
# ctor
|
|
64
|
+
self._title = title
|
|
65
|
+
self._indent = indent
|
|
66
|
+
self._name = ""
|
|
67
|
+
self._argv = []
|
|
68
|
+
self._commands = []
|
|
69
|
+
self.exitCode = 0
|
|
70
|
+
|
|
71
|
+
# methods
|
|
72
|
+
def register(self, instance=None, prefix = ""):
|
|
73
|
+
if instance is None:
|
|
74
|
+
for name, obj in inspect.getmembers(__import__('__main__')):
|
|
75
|
+
if inspect.isfunction(obj) and name.startswith(prefix) and hasattr(obj, "title"):
|
|
76
|
+
self.registerFunction(None, obj, prefix)
|
|
77
|
+
else:
|
|
78
|
+
for name, obj in inspect.getmembers(type(instance), predicate=inspect.isfunction):
|
|
79
|
+
if inspect.isfunction(obj) and name.startswith(prefix) and hasattr(obj, "title"):
|
|
80
|
+
self.registerFunction(instance, obj, prefix)
|
|
81
|
+
|
|
82
|
+
def registerFunction(self, instance, func, prefix = ""):
|
|
83
|
+
# carga los Commands partiendo de las funciones cargadas en memoria, que empiezan con un determinado prefijo
|
|
84
|
+
func_name = func.__name__
|
|
85
|
+
command_name = func_name[len(prefix):].split("_")
|
|
86
|
+
command_title = getattr(func, "title")
|
|
87
|
+
command_order = getattr(func, "order")
|
|
88
|
+
command_index = getattr(func, "index")
|
|
89
|
+
command_arguments = []
|
|
90
|
+
|
|
91
|
+
for param_name, param in inspect.signature(func).parameters.items():
|
|
92
|
+
if param_name == "self":
|
|
93
|
+
continue
|
|
94
|
+
# argument name
|
|
95
|
+
command_argument_name = param_name
|
|
96
|
+
# argument title
|
|
97
|
+
command_argument_title = ""
|
|
98
|
+
if hasattr(param.annotation, "__metadata__"):
|
|
99
|
+
command_argument_title = param.annotation.__metadata__[0]
|
|
100
|
+
# argument title
|
|
101
|
+
command_argument_required = (param.default == inspect.Parameter.empty)
|
|
102
|
+
# argument type
|
|
103
|
+
command_argument_type = ""
|
|
104
|
+
if hasattr(param.annotation, "__metadata__"):
|
|
105
|
+
command_argument_type = param.annotation.__origin__.__name__
|
|
106
|
+
# argument default
|
|
107
|
+
command_argument_default = None
|
|
108
|
+
if param.default != inspect.Parameter.empty:
|
|
109
|
+
command_argument_default = param.default
|
|
110
|
+
# crea CommandArgument
|
|
111
|
+
command_argument = CommandArgument(command_argument_name, command_argument_title, command_argument_required, command_argument_type, command_argument_default)
|
|
112
|
+
command_arguments.append(command_argument)
|
|
113
|
+
# crea Comand
|
|
114
|
+
command = Command(name=command_name, title=command_title, arguments=command_arguments, instance=instance, func=func, order=command_order, index=command_index)
|
|
115
|
+
# añade a la lista de Commands
|
|
116
|
+
self._commands.append(command)
|
|
117
|
+
|
|
118
|
+
def sort(self):
|
|
119
|
+
# ordena los commands por order de declaracion de la funcion
|
|
120
|
+
self._commands.sort(key=lambda x: x.order)
|
|
121
|
+
|
|
122
|
+
# asigna indices
|
|
123
|
+
command_ant = None
|
|
124
|
+
index = 0
|
|
125
|
+
for command in self._commands:
|
|
126
|
+
if command.index != 0:
|
|
127
|
+
index = command.index
|
|
128
|
+
elif command_ant != None and command_ant.name[0] != command.name[0]:
|
|
129
|
+
index = int((index + 10) / 10) * 10
|
|
130
|
+
else:
|
|
131
|
+
index += 1
|
|
132
|
+
command.index = index
|
|
133
|
+
command_ant = command
|
|
134
|
+
|
|
135
|
+
self._commands.sort(key=lambda x: x.index)
|
|
136
|
+
|
|
137
|
+
#for command in self._commands:
|
|
138
|
+
# print(command.name, command.index)
|
|
139
|
+
|
|
140
|
+
def showMenu(self):
|
|
141
|
+
# sort
|
|
142
|
+
self.sort()
|
|
143
|
+
# title
|
|
144
|
+
if not self._title == "":
|
|
145
|
+
print(self._title)
|
|
146
|
+
print("*********")
|
|
147
|
+
# menu
|
|
148
|
+
indent = self._indent * " "
|
|
149
|
+
while True:
|
|
150
|
+
# show menu
|
|
151
|
+
print(indent + "Escoge una opción:")
|
|
152
|
+
print(indent + "==================")
|
|
153
|
+
command_ant = None
|
|
154
|
+
for command in self._commands:
|
|
155
|
+
if command_ant != None and command_ant.name[0] != command.name[0]:
|
|
156
|
+
print(indent + f" : ")
|
|
157
|
+
print(indent + f"{command.index:2} : {command.title}")
|
|
158
|
+
command_ant = command
|
|
159
|
+
print(indent + f" : ")
|
|
160
|
+
# read option
|
|
161
|
+
command_to_execute = None
|
|
162
|
+
while command_to_execute == None:
|
|
163
|
+
opcion = input(f" ? : ")
|
|
164
|
+
if opcion == "":
|
|
165
|
+
return 0
|
|
166
|
+
try:
|
|
167
|
+
opcion_index =int(opcion)
|
|
168
|
+
for command in self._commands:
|
|
169
|
+
if command.index == opcion_index:
|
|
170
|
+
command_to_execute = command
|
|
171
|
+
break
|
|
172
|
+
except:
|
|
173
|
+
pass
|
|
174
|
+
if command_to_execute == None:
|
|
175
|
+
print(indent + f"{SEQUENCE_COLOR_RED} índice no válido: {opcion}{SEQUENCE_RESET}")
|
|
176
|
+
# prepara argumentos
|
|
177
|
+
print()
|
|
178
|
+
exec_args = {}
|
|
179
|
+
errors = False
|
|
180
|
+
if len(command_to_execute.arguments) > 0:
|
|
181
|
+
for argument in command_to_execute.arguments:
|
|
182
|
+
argument_attributes = []
|
|
183
|
+
if argument.default != None:
|
|
184
|
+
argument_attributes.append(f"default es '{argument.default}'")
|
|
185
|
+
if argument.required:
|
|
186
|
+
argument_attributes.append(f"*")
|
|
187
|
+
argument_value = input(f" {argument.title}{"" if len(argument_attributes) == 0 else f" ({" ".join(argument_attributes)})"}: ").strip()
|
|
188
|
+
# valor por defecto
|
|
189
|
+
if not argument_value and argument.default != None:
|
|
190
|
+
argument_value = argument.default
|
|
191
|
+
# valida si es requerido
|
|
192
|
+
if argument.required:
|
|
193
|
+
if argument_value == "":
|
|
194
|
+
print(indent + f"{SEQUENCE_COLOR_RED} Error: argumento requerido: '{argument.title}'{SEQUENCE_RESET}")
|
|
195
|
+
errors = True
|
|
196
|
+
break
|
|
197
|
+
# convierte el tipo
|
|
198
|
+
try:
|
|
199
|
+
if argument.type == "int":
|
|
200
|
+
argument_value = int(argument_value)
|
|
201
|
+
elif argument.type == "float":
|
|
202
|
+
argument_value = float(argument_value)
|
|
203
|
+
elif argument.type == "List":
|
|
204
|
+
argument_value = argument_value.split(",")
|
|
205
|
+
except:
|
|
206
|
+
print(indent + f"{SEQUENCE_COLOR_RED} Error: el argumento '{argument.name}' no se ha podido convertir al tipo '{argument.type}': {argument_value}{SEQUENCE_RESET}")
|
|
207
|
+
errors = True
|
|
208
|
+
break
|
|
209
|
+
# set
|
|
210
|
+
exec_args[argument.name] = argument_value;
|
|
211
|
+
# exec
|
|
212
|
+
if not errors:
|
|
213
|
+
# invoke
|
|
214
|
+
if not command_to_execute.instance is None:
|
|
215
|
+
func_bounded = types.MethodType(command_to_execute.func, command_to_execute.instance)
|
|
216
|
+
result = func_bounded(**exec_args)
|
|
217
|
+
else:
|
|
218
|
+
result = command_to_execute.func(**exec_args)
|
|
219
|
+
# empty line
|
|
220
|
+
print()
|
|
221
|
+
|
|
222
|
+
def showHelp(self):
|
|
223
|
+
print(f"usage: {self._name} [<command> [<args>]]")
|
|
224
|
+
print("")
|
|
225
|
+
print("Commands:")
|
|
226
|
+
for command in self._commands:
|
|
227
|
+
line = " "
|
|
228
|
+
line += "Adasd"
|
|
229
|
+
args = []
|
|
230
|
+
for argument in command.arguments:
|
|
231
|
+
args.append(f"--{argument.name}")
|
|
232
|
+
args.append(f"{SEQUENCE_COLOR_GRAY_MEDIUM}<{argument.title}>{SEQUENCE_RESET}")
|
|
233
|
+
print(f" {" ".join(command.name):10} {" ".join(args)} {SEQUENCE_COLOR_GREEN_DARK}# {command.title}{SEQUENCE_RESET}")
|
|
234
|
+
|
|
235
|
+
def execute(self, argv):
|
|
236
|
+
# init
|
|
237
|
+
self._name = argv[0]
|
|
238
|
+
self._argv = argv
|
|
239
|
+
# ejecuta el comando que toque, segun self._argv
|
|
240
|
+
if len(self._argv)==1:
|
|
241
|
+
self.showMenu()
|
|
242
|
+
return
|
|
243
|
+
if len(self._argv)==2 and (self._argv[1] == "--help" or self._argv[1] == "-h"):
|
|
244
|
+
self.showHelp()
|
|
245
|
+
return
|
|
246
|
+
# busca el commando a ejecutar
|
|
247
|
+
command_to_execute = None
|
|
248
|
+
for command in self._commands:
|
|
249
|
+
if len(command.name) <= len(argv) - 1:
|
|
250
|
+
if command.name == argv[1:len(command.name)+1]:
|
|
251
|
+
command_to_execute = command
|
|
252
|
+
break
|
|
253
|
+
if command_to_execute:
|
|
254
|
+
break
|
|
255
|
+
# si no se ha encontrado, muestra el mesnaje de error
|
|
256
|
+
if command_to_execute == None:
|
|
257
|
+
print(f"error: no se ha encontrado el comando")
|
|
258
|
+
return -1
|
|
259
|
+
# ejecuta el comando
|
|
260
|
+
command_args = argv[len(command_to_execute.name)+1:]
|
|
261
|
+
command_args_dict = {}
|
|
262
|
+
command_args_errors = False
|
|
263
|
+
for i in range(0, len(command_args), 2):
|
|
264
|
+
if command_args[i].startswith('--'):
|
|
265
|
+
key = command_args[i][2:]
|
|
266
|
+
value = command_args[i + 1] if i + 1 < len(command_args) else None
|
|
267
|
+
command_args_dict[key] = value
|
|
268
|
+
# valida que no sobre ningun argumento
|
|
269
|
+
for key in command_args_dict.keys():
|
|
270
|
+
if not key in [argument.name for argument in command_to_execute.arguments]:
|
|
271
|
+
print(f"error: argumento inválido: --{key}")
|
|
272
|
+
command_args_errors = True
|
|
273
|
+
# aañade defaults
|
|
274
|
+
for argument in command.arguments:
|
|
275
|
+
if not argument.name in command_args_dict:
|
|
276
|
+
if argument.default != None:
|
|
277
|
+
command_args_dict[argument.name] = argument.default
|
|
278
|
+
# valida que no falte ningun argumento
|
|
279
|
+
for argument in command.arguments:
|
|
280
|
+
if not argument.name in command_args_dict:
|
|
281
|
+
print(f"error: argumento obligatorio: --{argument.name}")
|
|
282
|
+
command_args_errors = True
|
|
283
|
+
# valida que el tipo de argumentos sea correcto
|
|
284
|
+
if not command_args_errors:
|
|
285
|
+
for argument in command.arguments:
|
|
286
|
+
argument_value = command_args_dict[argument.name]
|
|
287
|
+
try:
|
|
288
|
+
if argument.type == "int":
|
|
289
|
+
argument_value = int(argument_value)
|
|
290
|
+
elif argument.type == "float":
|
|
291
|
+
argument_value = float(argument_value)
|
|
292
|
+
elif argument.type == "List":
|
|
293
|
+
argument_value = argument_value.split(",")
|
|
294
|
+
command_args_dict[argument.name] = argument_value
|
|
295
|
+
except:
|
|
296
|
+
print(f"{SEQUENCE_COLOR_RED}error: el argumento '{argument.name}' no se ha podido convertir al tipo '{argument.type}': {argument_value}{SEQUENCE_RESET}")
|
|
297
|
+
command_args_errors = True
|
|
298
|
+
break
|
|
299
|
+
# si hay errores
|
|
300
|
+
if command_args_errors:
|
|
301
|
+
return -1
|
|
302
|
+
# invoke
|
|
303
|
+
return command_to_execute.func(**command_args_dict)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .aes import aes_encrypt, aes_decrypt, password_generate
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from cryptography.fernet import Fernet # pip install cryptography
|
|
3
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
4
|
+
from cryptography.hazmat.primitives.hashes import SHA256
|
|
5
|
+
from cryptography.hazmat.primitives import hashes
|
|
6
|
+
from cryptography.hazmat.primitives import padding
|
|
7
|
+
from cryptography.hazmat.backends import default_backend
|
|
8
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
9
|
+
import base64
|
|
10
|
+
import io
|
|
11
|
+
import os
|
|
12
|
+
import struct
|
|
13
|
+
import random
|
|
14
|
+
import hashlib
|
|
15
|
+
import random
|
|
16
|
+
import string
|
|
17
|
+
|
|
18
|
+
# utils
|
|
19
|
+
def password_generate(min=10, max=32):
|
|
20
|
+
length = random.randint(min, max)
|
|
21
|
+
characters = string.ascii_letters + string.digits + string.punctuation
|
|
22
|
+
password = ''.join(random.choice(characters) for i in range(length))
|
|
23
|
+
return password
|
|
24
|
+
|
|
25
|
+
# utils
|
|
26
|
+
def aes_encrypt(text: str, password: str):
|
|
27
|
+
iterations = 0
|
|
28
|
+
saltLength = 16
|
|
29
|
+
ivLength = 16
|
|
30
|
+
blockSize = 16
|
|
31
|
+
paddingMode = "PKCS7"
|
|
32
|
+
cipherMode = "CBC"
|
|
33
|
+
keySize = 32
|
|
34
|
+
encoding = "base64"
|
|
35
|
+
separator = ','
|
|
36
|
+
version = ""
|
|
37
|
+
fold = 76
|
|
38
|
+
# salt
|
|
39
|
+
salt = os.urandom(saltLength)
|
|
40
|
+
# iterations
|
|
41
|
+
iterations = int(random.uniform(50000, 50000))
|
|
42
|
+
# derive key
|
|
43
|
+
key = hashlib.pbkdf2_hmac(
|
|
44
|
+
hash_name='sha256', # HMAC-SHA256
|
|
45
|
+
password=password.encode(), # Password as bytes
|
|
46
|
+
salt=salt, # Salt as bytes
|
|
47
|
+
iterations=iterations, # Iteration count
|
|
48
|
+
dklen=keySize # Length of the derived key
|
|
49
|
+
)
|
|
50
|
+
# iv
|
|
51
|
+
iv = os.urandom(ivLength)
|
|
52
|
+
# ms
|
|
53
|
+
body_ms = io.BytesIO();
|
|
54
|
+
# write iv
|
|
55
|
+
body_ms.write(iv)
|
|
56
|
+
# write salt
|
|
57
|
+
body_ms.write(salt)
|
|
58
|
+
# write iterations
|
|
59
|
+
body_ms.write(bytearray(iterations.to_bytes(4, byteorder='little')))
|
|
60
|
+
# cipher
|
|
61
|
+
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
|
|
62
|
+
# encript
|
|
63
|
+
encryptor = cipher.encryptor()
|
|
64
|
+
# Pad the plaintext to a multiple of 16 bytes
|
|
65
|
+
padder = padding.PKCS7(blockSize * 8).padder()
|
|
66
|
+
padded_plaintext = padder.update(base64.b64encode(salt) + text.encode()) + padder.finalize()
|
|
67
|
+
#print(padded_plaintext)
|
|
68
|
+
# crypt
|
|
69
|
+
ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
|
|
70
|
+
body_ms.write(ciphertext)
|
|
71
|
+
# read
|
|
72
|
+
body_ms.seek(0)
|
|
73
|
+
body = body_ms.read()
|
|
74
|
+
body_ms.close()
|
|
75
|
+
# result
|
|
76
|
+
result = "aes:" + separator + base64.b64encode(body).decode()
|
|
77
|
+
# fold
|
|
78
|
+
result = "\n".join(
|
|
79
|
+
result[i:i + fold] for i in range(0, len(result), fold)
|
|
80
|
+
)
|
|
81
|
+
# return
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
def aes_decrypt(text: str, password: str):
|
|
85
|
+
iterations = 0
|
|
86
|
+
saltLength = 16
|
|
87
|
+
ivLength = 16
|
|
88
|
+
blockSize = 16
|
|
89
|
+
paddingMode = "PKCS7"
|
|
90
|
+
cipherMode = "CBC"
|
|
91
|
+
keySize = 32
|
|
92
|
+
encoding = "base64"
|
|
93
|
+
separator = ','
|
|
94
|
+
version = ""
|
|
95
|
+
# header
|
|
96
|
+
header = text[:text.index(",")]
|
|
97
|
+
# body
|
|
98
|
+
body = text[text.index(",")+1:]
|
|
99
|
+
body_bytes = base64.b64decode(body)
|
|
100
|
+
stream = io.BytesIO(body_bytes)
|
|
101
|
+
# read iv
|
|
102
|
+
iv = stream.read(ivLength)
|
|
103
|
+
# read salt
|
|
104
|
+
salt = stream.read(saltLength)
|
|
105
|
+
# read iterations
|
|
106
|
+
iterations = struct.unpack('<I', stream.read(4))[0]
|
|
107
|
+
# derive key
|
|
108
|
+
key = hashlib.pbkdf2_hmac(
|
|
109
|
+
hash_name='sha256', # HMAC-SHA256
|
|
110
|
+
password=password.encode(), # Password as bytes
|
|
111
|
+
salt=salt, # Salt as bytes
|
|
112
|
+
iterations=iterations, # Iteration count
|
|
113
|
+
dklen=keySize # Length of the derived key
|
|
114
|
+
)
|
|
115
|
+
# cipher
|
|
116
|
+
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
|
|
117
|
+
# decryptor
|
|
118
|
+
decryptor = cipher.decryptor()
|
|
119
|
+
# decrypt
|
|
120
|
+
decrypted_result = decryptor.update(stream.read()) + decryptor.finalize()
|
|
121
|
+
# remove padding
|
|
122
|
+
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
|
123
|
+
decrypted_result = unpadder.update(decrypted_result) + unpadder.finalize()
|
|
124
|
+
# decode
|
|
125
|
+
result = decrypted_result.decode()
|
|
126
|
+
# skip salt
|
|
127
|
+
result = result[int(saltLength*1.5):]
|
|
128
|
+
# return
|
|
129
|
+
return result
|
|
130
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .secrets import SecretsManager
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import keyring
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
from ..commands import command
|
|
7
|
+
from ..crypto import aes_decrypt, aes_encrypt, password_generate
|
|
8
|
+
|
|
9
|
+
# consts
|
|
10
|
+
KEYRING_APP = "dprojectstools"
|
|
11
|
+
|
|
12
|
+
# class
|
|
13
|
+
class SecretsManager():
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# vars
|
|
17
|
+
_password = None
|
|
18
|
+
_path = ""
|
|
19
|
+
_dict = {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ctr
|
|
23
|
+
def __init__(self, name, password = None):
|
|
24
|
+
folder = Path(os.path.join(Path.home(), ".secrets"))
|
|
25
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
self._path = Path(folder, name + ".json")
|
|
27
|
+
if not password is None:
|
|
28
|
+
if password.startswith("keyring:"):
|
|
29
|
+
username = password[password.index(":") + 1:]
|
|
30
|
+
if username == "":
|
|
31
|
+
username = os.getlogin()
|
|
32
|
+
password = keyring.get_password(KEYRING_APP, username)
|
|
33
|
+
print(username)
|
|
34
|
+
print(password)
|
|
35
|
+
if password is None:
|
|
36
|
+
password = password_generate()
|
|
37
|
+
keyring.set_password(KEYRING_APP, username, password)
|
|
38
|
+
self._password = password
|
|
39
|
+
self._path = Path(folder, name + ".json.aes")
|
|
40
|
+
self._load()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# methods
|
|
44
|
+
def get(self, name):
|
|
45
|
+
value = self._dict.get(name)
|
|
46
|
+
if value is None:
|
|
47
|
+
value = self.set(name, value)
|
|
48
|
+
return str(value)
|
|
49
|
+
|
|
50
|
+
def keys(self):
|
|
51
|
+
return self._dict.keys()
|
|
52
|
+
|
|
53
|
+
def set(self, name, value=None):
|
|
54
|
+
if value is None:
|
|
55
|
+
value = input("Enter secret '{0}' value: ".format(name))
|
|
56
|
+
self._dict[name] = str(value)
|
|
57
|
+
self._save()
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
def delete(self, name):
|
|
61
|
+
del self._dict[name]
|
|
62
|
+
self._save()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# methods
|
|
66
|
+
def _load(self):
|
|
67
|
+
if os.path.isfile(self._path):
|
|
68
|
+
with open(self._path, "r") as file:
|
|
69
|
+
text = file.read()
|
|
70
|
+
if not self._password is None:
|
|
71
|
+
text = aes_decrypt(text, self._password);
|
|
72
|
+
self._dict = json.loads(text)
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
def _save(self):
|
|
76
|
+
text = json.dumps(self._dict, indent=4)
|
|
77
|
+
if not self._password is None:
|
|
78
|
+
text = aes_encrypt(text, self._password);
|
|
79
|
+
with open(self._path, "w") as file:
|
|
80
|
+
file.write(text)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ui method
|
|
84
|
+
@command("List secrets", index = 90)
|
|
85
|
+
def secrets_list(self):
|
|
86
|
+
for key in self.keys():
|
|
87
|
+
print("{0}: {1}".format(key, self.get(key)))
|
|
88
|
+
@command("Set secret")
|
|
89
|
+
def secrets_set(self,
|
|
90
|
+
name: Annotated[str, "Name"],
|
|
91
|
+
value: Annotated[str, "Value"]
|
|
92
|
+
):
|
|
93
|
+
self.set(name, value)
|
|
94
|
+
@command("Get secret")
|
|
95
|
+
def secrets_get(self,
|
|
96
|
+
name: Annotated[str, "Name"]
|
|
97
|
+
):
|
|
98
|
+
value = self.get(name)
|
|
99
|
+
print(value)
|
|
100
|
+
@command("Del secret")
|
|
101
|
+
def secrets_delete(self,
|
|
102
|
+
name: Annotated[str, "Name"]
|
|
103
|
+
):
|
|
104
|
+
self.delete(name)
|
|
105
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Marc Delòs Poch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: dprojectstools
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: A Set of development tools
|
|
5
|
+
Author-email: Marc Delos <marcdp@dprojects.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/marcdp/dprojectstools
|
|
7
|
+
Project-URL: Issues, https://github.com/marcdp/dprojectstools/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
|
|
15
|
+
# xmenu
|
|
16
|
+
A command line menu controller
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
dprojectstools/__init__.py,sha256=_RjYtKMfjjsymn9dV5noeotczZkhk928Fxo9h675EOE,164
|
|
2
|
+
dprojectstools/commands/__init__.py,sha256=VZmFrfX6F6NleK3z4pIA6GmuM9pygQxUOEXleVqJB3E,80
|
|
3
|
+
dprojectstools/commands/commands.py,sha256=0JyzR6oZMxzmSSEcx8UGiVGAcR8Jgfxfto2ubqhRGkE,12645
|
|
4
|
+
dprojectstools/crypto/__init__.py,sha256=CAPzDNcUXRa9zBM5CKzcMjtkuOCQojhaKp4GJ0JtcqE,62
|
|
5
|
+
dprojectstools/crypto/aes.py,sha256=qTJ-eHzRUB8UalnqJPkG62k3oxuy76QiEoZBFdSMpic,4186
|
|
6
|
+
dprojectstools/secrets/__init__.py,sha256=1-v3UV1A-FP_o1vcccii1T1wSXsj6nGWxWvxWzFmdBE,37
|
|
7
|
+
dprojectstools/secrets/secrets.py,sha256=8e-ELxbvt-2ZprTSj5_zdfYlWsjGFNDjkcEMCXee1Zw,3038
|
|
8
|
+
dprojectstools-0.0.3.dist-info/LICENSE,sha256=wog66J88bXIo-u2Axc3ia6UllVLpcW-TAsIoFX9DAr4,1094
|
|
9
|
+
dprojectstools-0.0.3.dist-info/METADATA,sha256=CTDW_G7Ji7Vb4YV-5VulNu7fVjVaiMIqa95eQeWLMS0,562
|
|
10
|
+
dprojectstools-0.0.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
11
|
+
dprojectstools-0.0.3.dist-info/top_level.txt,sha256=Rfed2TC6kuHWz0giVwl_Sc-ffhFooCeD4rlP84wr0hw,15
|
|
12
|
+
dprojectstools-0.0.3.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dprojectstools
|