Add migration command

This commit is contained in:
Antonio J. Delgado 2024-11-11 08:16:32 +02:00
parent 679a4300b7
commit 3a9c124707
2 changed files with 115 additions and 34 deletions

130
nc_password_client/nc_password_client.py Normal file → Executable file
View file

@ -19,6 +19,7 @@ from xml.dom import minidom
import binascii
import click
import click_config_file
import passpy
try:
import requests
HAS_REQUESTS_LIB = True
@ -99,18 +100,18 @@ class NextcloudErrorHandler:
def status_code_error(self, status):
'''Process status code error???'''
try:
if status > 399:
self._log.error(
'Nextcloud returned with status code %s',
'Nextcloud returned status code %s',
status
)
except Exception:
self._log.error(
'Nextcloud returned with status code %s',
sys.exit(1)
else:
self._log.debug(
'Nextcloud returned status %s',
status
)
def parameter_spects(spec_arguments):
'''Specification of parameters???'''
argument_spec = dict(
@ -144,6 +145,7 @@ class NextcloudHandler:
self._init_log(params)
self.exit = NextcloudErrorHandler()
self.http = 'https'
self.timeout = params.get('timeout', 3)
self.ssl = True
if params.get('ssl_mode') == 'http':
self.http = 'http'
@ -261,7 +263,8 @@ class NextcloudHandler:
'''Do a GET request'''
r = requests.get(
f'{self.http}://{self.host}/{path}',
auth=(self.user, self.token), verify=self.ssl, headers=self.headers()
auth=(self.user, self.token), verify=self.ssl, headers=self.headers(),
timeout=self.timeout
)
if r.status_code == 200:
@ -344,12 +347,12 @@ class NextcloudHandler:
if src:
r = requests.put(
f'{self.http}://{self.host}/{path}',
data=open(src, 'rb'), auth=(self.user, self.token), verify=self.ssl
data=open(src, 'rb'), auth=(self.user, self.token), verify=self.ssl, timeout=self.timeout
)
else:
r = requests.put(
f'{self.http}://{self.host}/{path}',
headers=self.headers, auth=(self.user, self.token), verify=self.ssl
headers=self.headers, auth=(self.user, self.token), verify=self.ssl, timeout=self.timeout
)
if r.status_code in [200, 201, 204]:
@ -361,7 +364,7 @@ class NextcloudHandler:
'''Do a DELETE request'''
r = requests.delete(
f'{self.http}://{self.host}/{path}',
auth=(self.user, self.token), verify=self.ssl
auth=(self.user, self.token), verify=self.ssl, timeout=self.timeout
)
if r.status_code in [200, 204]:
@ -385,7 +388,7 @@ class NextcloudHandler:
data=body,
headers=self.headers(),
auth=(self.user, self.token),
verify=self.ssl
verify=self.ssl, timeout=self.timeout
)
if r.status_code == 201:
@ -412,7 +415,7 @@ class NextcloudHandler:
data=post_obj,
headers=self.headers(),
auth=(self.user, self.token),
verify=self.ssl
verify=self.ssl, timeout=self.timeout
)
if r.status_code == 200:
self.x_api_session = r.headers.get('X-API-SESSION')
@ -464,7 +467,8 @@ class NextcloudHandler:
data=post_obj,
headers=self.headers(),
auth=(self.user, self.token),
verify=self.ssl
verify=self.ssl,
timeout=self.timeout
)
if r.status_code == 201:
return r.json()
@ -494,7 +498,8 @@ class NextcloudHandler:
data=post_obj,
headers=self.headers(),
auth=(self.user, self.token),
verify=self.ssl
verify=self.ssl,
timeout=self.timeout
)
if r.status_code == 201:
@ -549,20 +554,33 @@ class NextcloudHandler:
else:
self.exit.status_code_error(r.status_code)
def exists_password(self, name):
for password in self.list_passwords():
if password['label'] == name:
return True
return False
def create_password(self, post_obj):
'''Create/add a password'''
if not self.exists_password(post_obj['label']):
r = requests.post(
f'{self.http}://{self.host}/index.php/apps/passwords/api/1.0/password/create',
data=post_obj,
headers=self.headers(),
auth=(self.user, self.token),
verify=self.ssl
verify=self.ssl, timeout=self.timeout
)
if r.status_code == 201:
return r.json()
else:
self._log.error(r.json())
self.exit.status_code_error(r.status_code)
else:
self._log.warning(
"Password with name '%s' already exists",
post_obj['label']
)
def delete_password(self, post_obj):
'''Delete a password'''
@ -571,7 +589,7 @@ class NextcloudHandler:
data=post_obj,
headers=self.headers(),
auth=(self.user, self.token),
verify=self.ssl
verify=self.ssl, timeout=self.timeout
)
if r.status_code == 200:
@ -586,7 +604,7 @@ class NextcloudHandler:
data=post_obj,
headers=self.headers(),
auth=(self.user, self.token),
verify=self.ssl
verify=self.ssl, timeout=self.timeout
)
if r.status_code == 200:
@ -594,14 +612,10 @@ class NextcloudHandler:
else:
self.exit.status_code_error(r.status_code)
def user(self):
'''Get user'''
return self.user
class NcPasswordClient:
'''Nextcloud Password Client'''
def __init__(self, debug_level, log_file, host, user, api_token, cse_password):
def __init__(self, debug_level, log_file, host, user, api_token, cse_password, timeout):
self.config = {}
self.config['debug_level'] = debug_level
if log_file is None:
@ -621,7 +635,8 @@ class NcPasswordClient:
"host": host,
"user": user,
"api_token": api_token,
"cse_password": cse_password
"cse_password": cse_password,
"timeout": timeout,
}
self.nc = NextcloudHandler(params)
@ -639,8 +654,27 @@ class NcPasswordClient:
def create_password(self, obj):
'''Create a password with an object'''
self._log.debug(
"Creating password %s",
obj
)
print(json.dumps(self.nc.create_password(obj), indent=2))
def delete_password(self, name):
'''Delete a password'''
for password in self.nc.list_passwords():
if password['label'] == name:
self._log.debug(
"Deleting password:%s",
json.dumps(password)
)
print(json.dumps(self.nc.delete_password(password), indent=2))
return True
self._log.warning(
"Password with name '%s' doesn't exist",
name
)
def create_passwords_folder(self, name):
'''Create a passwords folder'''
print(json.dumps(self.nc.create_passwords_folder(name), indent=2))
@ -649,6 +683,31 @@ class NcPasswordClient:
'''Delete a passwords folder'''
print(json.dumps(self.nc.delete_passwords_folder(name), indent=2))
def migrate_pass(self):
'''Migrate password store to Nextcloud pass'''
store = passpy.store.Store()
for item in store.find(''):
obj = {
"label": os.path.basename(item),
}
folder = os.path.dirname(item)
if folder != '':
obj['folder'] = folder
raw_values = store.get_key(item).split('\n')
obj['password'] = raw_values[0]
raw_values.pop(0)
for line in raw_values:
if ': ' in line:
split_line = line.split(': ')
field = split_line[0]
value = split_line[1]
else:
field = line
value = ''
if field != '' and value != '':
obj[field] = value
print(json.dumps(self.nc.create_password(obj), indent=2))
def _init_log(self):
''' Initialize log object '''
self._log = logging.getLogger("nc_password_client")
@ -722,13 +781,19 @@ class NcPasswordClient:
'--cse-password', '-p',
help='Nextcloud user\'s end-to-end encryption password'
)
@click.option(
'--timeout', '-t',
default=3,
type=click.INT,
help='Nextcloud user\'s end-to-end encryption password'
)
@click_config_file.configuration_option()
@click.pass_context
def cli(ctx, debug_level, log_file, host, user, api_token, cse_password):
def cli(ctx, debug_level, log_file, host, user, api_token, cse_password, timeout):
'''Client function to pass context'''
ctx.ensure_object(dict)
ctx.obj['NcPasswordClient'] = NcPasswordClient(
debug_level, log_file, host, user, api_token, cse_password
debug_level, log_file, host, user, api_token, cse_password, timeout
)
@cli.command()
@ -754,6 +819,14 @@ def create_password(ctx, obj):
'''Create a password'''
ctx.obj['NcPasswordClient'].create_password(json.loads(obj))
@cli.command()
@click.option('--name', '-n', required=True, help='Name of the password to delete')
@click_config_file.configuration_option()
@click.pass_context
def delete_password(ctx, name):
'''Delete a password'''
ctx.obj['NcPasswordClient'].delete_password(name)
@cli.command()
@click.option('--name', '-n', required=True, help='Name of the passwords folder to create')
@click_config_file.configuration_option()
@ -777,5 +850,12 @@ def delete_passwords_folder(ctx, name):
'''Delete a password folder'''
ctx.obj['NcPasswordClient'].delete_passwords_folder(name)
@cli.command()
@click_config_file.configuration_option()
@click.pass_context
def migrate_pass(ctx):
'''Migrate password store passwords to Nextcloud Passwords'''
ctx.obj['NcPasswordClient'].migrate_pass()
if __name__ == "__main__":
cli(obj={})

View file

@ -2,3 +2,4 @@ click
click_config_file
requests
pysodium
passpy