Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release #1204

Merged
merged 6 commits into from
Mar 15, 2024
Merged

Release #1204

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion keepercommander/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
# Contact: ops@keepersecurity.com
#

__version__ = '16.10.8'
__version__ = '16.10.9'
10 changes: 10 additions & 0 deletions keepercommander/commands/audit_alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,15 @@

class AuditSettingMixin:
LAST_USERNAME = ""
LAST_ENTERPRISE_ID = 0
SETTINGS = None # type: Optional[dict]
EVENT_TYPES = None # type: Optional[List[Tuple[int, str]]]

@staticmethod
def load_settings(params, reload=False): # type: (KeeperParams, Optional[bool]) -> Optional[dict]
if params.enterprise is None:
AuditSettingMixin.SETTINGS = None
AuditSettingMixin.LAST_ENTERPRISE_ID = 0
return
if AuditSettingMixin.EVENT_TYPES is None:
rq = {
Expand All @@ -125,10 +128,16 @@ def load_settings(params, reload=False): # type: (KeeperParams, Optional[bool])
if event_name and isinstance(event_id, int):
AuditSettingMixin.EVENT_TYPES.append((event_id, event_name))

enterprise_id = 0
if isinstance(params.license, dict):
enterprise_id = params.license.get('enterprise_id')

if AuditSettingMixin.SETTINGS is None:
reload = True
elif AuditSettingMixin.LAST_USERNAME != params.user:
reload = True
elif enterprise_id == 0 or AuditSettingMixin.LAST_ENTERPRISE_ID != enterprise_id:
reload = True

if reload:
rq = {
Expand All @@ -137,6 +146,7 @@ def load_settings(params, reload=False): # type: (KeeperParams, Optional[bool])
}
AuditSettingMixin.SETTINGS = api.communicate(params, rq)
AuditSettingMixin.LAST_USERNAME = params.user
AuditSettingMixin.LAST_ENTERPRISE_ID = enterprise_id
return AuditSettingMixin.SETTINGS

@staticmethod
Expand Down
4 changes: 2 additions & 2 deletions keepercommander/commands/compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ def compile_user_report(user, access_events):
access_records = dict()
user_access_data = {user: access_records}
rec_uids = access_events.keys() if report_type == report_type_default \
else {r.record_uid for r in vault_records}
else {r for r in vault_records}

for uid in rec_uids:
access_event = access_events.get(uid, {})
Expand Down Expand Up @@ -579,7 +579,7 @@ def compile_report_data(rec_ids):

for name in usernames:
vault_records = sox_data.get_vault_records(name)
filter_by_recs = None if report_type == report_type_default else {r.record_uid for r in vault_records}
filter_by_recs = None if report_type == report_type_default else {r for r in vault_records}
user_access_events = get_records_accessed(name, filter_by_recs)
user_access_lookup.update(compile_user_report(name, user_access_events))

Expand Down
87 changes: 52 additions & 35 deletions keepercommander/commands/folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import re
import shutil
from collections import OrderedDict
from typing import Tuple, List, Optional, Dict, Set, Any
from typing import Tuple, List, Optional, Dict, Set, Any, Iterable

from asciitree import LeftAligned, BoxStyle, drawing
from colorama import Style
Expand Down Expand Up @@ -231,7 +231,7 @@ def execute(self, params, **kwargs):
folders.append(f)

if show_records and params.record_cache:
if folder_uid in params.subfolder_record_cache:
if folder_uid in params.subfolder_record_cache or recursive_search:
record_uids_by_folder = get_contained_record_uids(params, folder_uid, not recursive_search)
record_uids = {rec_uid for recs in record_uids_by_folder.values() for rec_uid in recs}
for uid in record_uids:
Expand Down Expand Up @@ -1265,14 +1265,24 @@ def get_folder_uid(path):
else:
raise CommandError('transform-folder', f'Folder {path} not found')

def validate(root_folders): # type: (List[BaseFolderNode]) -> None
def validate(root_folders): # type: (Iterable[BaseFolderNode]) -> None
SF_UID_KEY = 'shared_folder_uid'
shared_folders = []
contained_recs = []
for folder in root_folders:
shared_folders.extend(get_shared_folders(folder))
contained_recs.extend(get_contained_records(folder))

# Check for overlapping trees
root_uids = set()
contained_folder_uids = set()
for rf in root_folders:
root_uids.add(rf.uid)
contained_folder_uids.update(get_contained_folder_uids(params, rf.uid, children_only=False))
if contained_folder_uids.intersection(root_uids):
error_msg = 'Transformation of overlapping folder trees in the same command is not allowed.'
raise CommandError('transform-folder', error_msg)

# Check relevant shared folders in each tree: descendants, root, and/or ancestor shared-folders
def has_full_sf_privs(sf): # type: (Dict[str, Any]) -> bool
# Check if user has been directly granted full share-permissions for this folder
Expand Down Expand Up @@ -1318,7 +1328,7 @@ def has_full_sf_privs(sf): # type: (Dict[str, Any]) -> bool
msg += '\n'.join(sfs)
raise CommandError('transform-folder', msg)

def get_shared_folders(folder): # type: (BaseFolderNode) -> Dict[str, Dict[str, Any]]
def get_shared_folders(folder): # type: (BaseFolderNode) -> List[Dict[str, Any]]
api.sync_down(params)
shared_folders = []
# Folder is a shared-folder subfolder, return only the containing shared-folder
Expand Down Expand Up @@ -1368,6 +1378,9 @@ def new_mv_request(dst):
return {**rq_base, 'to_uid': dst.uid, 'to_type': dst.type}

for src_folder, r_uids in get_contained_records(folder):
# This folder has no records to move, skip to the next one
if not r_uids:
continue
dst_folder = get_copy(src_folder)
rq = new_mv_request(dst_folder)
transition_keys = []
Expand Down Expand Up @@ -1424,12 +1437,12 @@ def new_mv_request(dst):
raise KeeperApiError(result_code, error_msg)

def get_copy_name(folder, dest):
def get_path_tokens(folder_node:BaseFolderNode):
def get_path_tokens(f): # type: (BaseFolderNode) -> List[str]
result = []
while folder_node and folder_node is not params.root_folder:
result = [folder_node.name, *result]
folder_node = params.folder_cache.get(folder_node.parent_uid) or params.root_folder \
if folder_node is not params.root_folder \
while f and f is not params.root_folder:
result = [f.name, *result]
f = params.folder_cache.get(f.parent_uid) or params.root_folder \
if f is not params.root_folder \
else None
return result or []

Expand All @@ -1438,7 +1451,7 @@ def get_path_tokens(folder_node:BaseFolderNode):
path_tokens = get_path_tokens(folder)
name = ' - '.join(path_tokens)
copy_name = name
else :
else:
copy_name = folder.name + '(TRANSFORMED)' if folder.parent_uid == dest.uid else folder.name

while True:
Expand All @@ -1455,29 +1468,28 @@ def get_path_tokens(folder_node:BaseFolderNode):

copy_uid_lookup = dict() # type: Dict[str, str]

def copy_folder(folder, new_folder_type='user_folder', rebase=False):
def copy_folder(original_folder, new_folder_type='user_folder', rebase=False):
# type: (BaseFolderNode, Optional[str], Optional[bool]) -> BaseFolderNode
folders_cache = params.folder_cache
vault_root = params.root_folder
dest_uid = copy_uid_lookup.get(folder.parent_uid) or folder.parent_uid or ''
dest_uid = copy_uid_lookup.get(original_folder.parent_uid) or original_folder.parent_uid or ''
dest = vault_root if rebase else folders_cache.get(dest_uid) or vault_root
params.current_folder = dest.uid or ''

# Create folder copy of appropriate type
mkdir_cmd = FolderMakeCommand()
copy_name = get_copy_name(folder, dest)
copy_name = get_copy_name(original_folder, dest)
cmd_kwargs = {'folder': copy_name, new_folder_type: True}
mkdir_cmd.execute(params, **cmd_kwargs)
copy_uid = mkdir_cmd.execute(params, **cmd_kwargs)
api.sync_down(params)
copy_uid = get_folder_uid(copy_name)
copy_uid_lookup.update({folder.uid: copy_uid})
copy_uid_lookup.update({original_folder.uid: copy_uid})
return params.folder_cache.get(copy_uid)

def get_copy(folder): # type: (BaseFolderNode) -> BaseFolderNode
copy_uid = copy_uid_lookup.get(folder.uid)
def get_copy(original_folder): # type: (BaseFolderNode) -> BaseFolderNode
copy_uid = copy_uid_lookup.get(original_folder.uid)
return params.folder_cache.get(copy_uid)

def remove_trees(roots):
def remove_trees(roots): # type: (Iterable[BaseFolderNode]) -> None
if roots:
rmdir_cmd = FolderRemoveCommand()
rmdir_cmd.execute(params, pattern=[root.uid for root in roots], force=True, quiet=True)
Expand Down Expand Up @@ -1619,7 +1631,7 @@ def preview_transform(xform_pairs):
for root, xformed in xform_pairs:
params.current_folder = root.parent_uid or ''
tree_cmd = FolderTreeCommand()
tree_cmd.execute(params, folder=root.name, records=True, shares=True, hide_shares_key=hide_key,
tree_cmd.execute(params, folder=root.uid, records=True, shares=True, hide_shares_key=hide_key,
title='ORIGINAL:\n=========')
params.current_folder = xformed.parent_uid or ''
tree_cmd.execute(params, folder=xformed.uid, records=True, shares=True, hide_shares_key=True,
Expand Down Expand Up @@ -1658,32 +1670,37 @@ def on_abort(roots):
if not isinstance(folders, list):
folders = [folders]

targets = []
for f in folders:
folder_uid = f if f in params.folder_cache else get_folder_uid(f)
target_folder = params.folder_cache.get(folder_uid)
if not target_folder:
raise CommandError('transform-folder', f'Folder {f} not found')
children_transform = kwargs.get('children')
folder_uids = {(f if f in params.folder_cache else get_folder_uid(f)) for f in folders}
folder_nodes = dict()
targets = dict()
for f_uid in folder_uids:
folder_node = params.folder_cache.get(f_uid)
if not folder_node:
raise CommandError('transform-folder', f'Folder {f_uid} not found')
else:
targets.append(target_folder)
folder_map = {f_uid: folder_node}
folder_nodes.update(folder_map)
if not children_transform:
targets.update(folder_map)

if kwargs.get('children'):
targets = [params.folder_cache.get(sub_f) for t in targets for sub_f in t.subfolders]
if children_transform:
targets.update({sub_f: params.folder_cache.get(sub_f) for f in folder_nodes.values() for sub_f in f.subfolders})

validate(targets)
validate(targets.values())

transformed = []
for t in targets:
for t in targets.values():
try:
transformed.append(transform_tree(t))
except Error as e:
params.current_folder = current_folder
on_abort(targets)
on_abort(targets.values())
raise CommandError('transform-folder', f'Folder {t.name} could not be transformed.\n{e.message}')

transform_pairs = zip(targets, transformed)
transform_pairs = zip(targets.values(), transformed)
if force:
finalize_transform(targets)
finalize_transform(targets.values())
elif dry_run:
logging.info('Executing command in "dry-run" mode...')
preview_transform(transform_pairs)
Expand All @@ -1693,7 +1710,7 @@ def on_abort(roots):
inp = user_choice('Are you sure you want to proceed with this/these transformation(s)?', 'yn', default='n')
if inp.lower() == 'y':
logging.info('Executing transformation(s)...')
finalize_transform(targets)
finalize_transform(targets.values())
else:
logging.info('Transformation cancelled by user.')
remove_trees(transformed)
Expand Down
16 changes: 8 additions & 8 deletions keepercommander/importer/lastpass/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ def parse_SHAR(chunk, encryption_key, rsa_key): # type: (Any, bytes, bytes) ->
def parse_ATTA(chunk, accounts):
attachment = None
io = BytesIO(chunk.payload)
id = read_item(io).decode('utf-8')
parent_id = read_item(io).decode('utf-8')
mimetype = read_item(io).decode('utf-8')
storagekey = read_item(io).decode('utf-8')
id = read_item(io).decode('utf-8', 'ignore')
parent_id = read_item(io).decode('utf-8', 'ignore')
mimetype = read_item(io).decode('utf-8', 'ignore')
storagekey = read_item(io).decode('utf-8', 'ignore')
size = read_item(io)
try:
lastpass_size = int(size)
Expand All @@ -188,14 +188,14 @@ def parse_ATTA(chunk, accounts):

def parse_ACFL(chunk, encryption_key):
io = BytesIO(chunk.payload)
field_name = read_item(io).decode('utf-8')
field_type = read_item(io).decode('utf-8')
field_name = read_item(io).decode('utf-8', 'ignore')
field_type = read_item(io).decode('utf-8', 'ignore')
if field_type in {'email', 'tel', 'text', 'password', 'textarea'}:
field_value = decode_aes256_plain_auto(read_item(io), encryption_key)
else:
field_value = read_item(io)
field_value = field_value.decode('utf-8')
checked = read_item(io).decode('utf-8')
field_value = field_value.decode('utf-8', 'ignore')
checked = read_item(io).decode('utf-8', 'ignore')
return CustomField(field_name, field_type, field_value, checked == '1')


Expand Down
2 changes: 1 addition & 1 deletion keepercommander/sox/sox_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def get_vault_records(self, user_ref):

# Get SF-shared records
# Shared to team
user_teams = {t for tuid, t in self._teams.items() if user_id in t.users}
user_teams = {tuid for tuid, t in self._teams.items() if user_id in t.users}
team_sf_uids = {sfuid for sfuid, sf in self._shared_folders.items() if sf.teams.intersection(user_teams)}
# Shared to user
user_sf_uids = {sfuid for sfuid, sf in self._shared_folders.items() if user_id in sf.users}
Expand Down
Loading