Skip to content

Commit cf2ab28

Browse files
Add secrets app share command (KC-851) (#1395)
* Add `secrets app share` command * `secrets app share`: minor refactor * `secrets app share`: more minor refactors
1 parent 94350d9 commit cf2ab28

File tree

1 file changed

+72
-4
lines changed
  • keepercommander/commands

1 file changed

+72
-4
lines changed

keepercommander/commands/ksm.py

+72-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
from keeper_secrets_manager_core.utils import bytes_to_base64, url_safe_str_to_bytes
2222

23-
from typing import Any, Sequence
23+
from typing import Sequence, List
2424

2525
from .base import Command, dump_report_data, user_choice, as_boolean
2626
from . import record
@@ -54,6 +54,12 @@
5454
--purge : Remove the application and purge it from the trash
5555
--force : Do not prompt for confirmation
5656
57+
{bcolors.BOLD}Grant User Access to Application (Share Application):{bcolors.ENDC}
58+
{bcolors.OKGREEN}secrets-manager app share {bcolors.OKBLUE}[APP NAME OR UID]{bcolors.OKGREEN} --email {bcolors.OKBLUE}[USERNAME] {bcolors.ENDC}
59+
60+
{bcolors.BOLD}Revoke User Access to Application (Unshare Application):{bcolors.ENDC}
61+
{bcolors.OKGREEN}secrets-manager app unshare {bcolors.OKBLUE}[APP NAME OR UID]{bcolors.OKGREEN} --email {bcolors.OKBLUE}[USERNAME]{bcolors.ENDC}
62+
5763
{bcolors.BOLD}Add Client Device:{bcolors.ENDC}
5864
{bcolors.OKGREEN}secrets-manager client add --app {bcolors.OKBLUE}[APP NAME OR UID] {bcolors.OKGREEN}--unlock-ip{bcolors.ENDC}
5965
Options:
@@ -91,8 +97,8 @@
9197
ksm_parser = argparse.ArgumentParser(prog='secrets-manager', description='Keeper Secrets Management (KSM) Commands',
9298
add_help=False)
9399
ksm_parser.add_argument('command', type=str, action='store', nargs="*",
94-
help='One of: "app list", "app get", "app create", "app remove", "client add", ' +
95-
'"client remove", "share add" or "share remove"')
100+
help='One of: "app list", "app get", "app create", "app remove", "app share", "app unshare", ' +
101+
'"client add", "client remove", "share add" or "share remove"')
96102
ksm_parser.add_argument('--secret', '-s', type=str, action='append', required=False,
97103
help='Record UID')
98104
ksm_parser.add_argument('--app', '-a', type=str, action='store', required=False,
@@ -121,6 +127,10 @@
121127
ksm_parser.add_argument('-f', '--force', dest='force', action='store_true', help='do not prompt')
122128
ksm_parser.add_argument('--config-init', type=str, dest='config_init', action='store',
123129
help='Initialize client config') # json, b64, file
130+
# Application sharing options
131+
ksm_parser.add_argument('--email', action='store', type=str, dest='email', help='Email of user to grant / remove application access to / from')
132+
ksm_parser.add_argument('--admin', action='store_true', help='Allow share recipient to manage application')
133+
124134

125135

126136
def ms_to_str(ms, frmt='%Y-%m-%d %H:%M:%S'):
@@ -139,7 +149,7 @@ def get_parser(self):
139149

140150
def execute(self, params, **kwargs):
141151

142-
ksm_command = kwargs.get('command')
152+
ksm_command = kwargs.get('command') # type: List[str]
143153
ksm_helpflag = kwargs.get('helpflag')
144154

145155
if len(ksm_command) == 0 or ksm_helpflag:
@@ -208,6 +218,27 @@ def execute(self, params, **kwargs):
208218

209219
return
210220

221+
if ksm_obj in ['app', 'apps'] and ksm_action in ['share', 'unshare']:
222+
app_name_or_uid = kwargs.get('app') or ksm_command[2] if len(ksm_command) == 3 else None
223+
if not app_name_or_uid:
224+
print(
225+
f'''{bcolors.WARNING}Application name is missing.{bcolors.ENDC}\n'''
226+
f'''\tEx: {bcolors.OKGREEN}secrets-manager app {ksm_action} {bcolors.OKBLUE}--app=MyApp{bcolors.OKGREEN} [email protected]{bcolors.ENDC}'''
227+
)
228+
return
229+
email = kwargs.get('email')
230+
unshare = 'un' in ksm_action
231+
is_admin = kwargs.get('admin', False)
232+
if not email:
233+
print(
234+
f'''{bcolors.WARNING}Email is missing.{bcolors.ENDC}\n'''
235+
f'''\tEx: {bcolors.OKGREEN}secrets-manager app {ksm_action} --app=MyApp {bcolors.OKBLUE}[email protected]{bcolors.ENDC}'''
236+
)
237+
return
238+
239+
KSMCommand.share_app(params, app_name_or_uid, email, is_admin=is_admin, unshare=unshare)
240+
return
241+
211242
if ksm_obj in ['share', 'secret'] and ksm_action is None:
212243
print(" Add Secret to the App\n\n"
213244
+ bcolors.OKGREEN + " secrets-manager share add --app " + bcolors.OKBLUE + "[APP NAME or APP UID]"
@@ -293,6 +324,43 @@ def execute(self, params, **kwargs):
293324
print(f"{bcolors.WARNING}Unknown combination of KSM commands. " +
294325
f"Type 'secrets-manager' for more details'{bcolors.ENDC}")
295326

327+
@staticmethod
328+
def share_app(params, app_name_or_uid, email, is_admin=False, unshare=False):
329+
app_rec = KSMCommand.get_app_record(params, app_name_or_uid)
330+
if app_rec is None:
331+
logging.warning('Application "%s" not found.' % app_name_or_uid)
332+
return
333+
app_uid = app_rec.get('record_uid', '')
334+
app_info = KSMCommand.get_app_info(params, app_uid)
335+
share_action = unshare and 'remove' or 'grant'
336+
sf_perm_keys = ('manage_users', 'manage_records', 'can_edit', 'can_share')
337+
sf_perms = {k: is_admin and not unshare and 'on' or 'off' for k in sf_perm_keys}
338+
rec_perms = dict(can_edit=is_admin and not unshare, can_share=is_admin and not unshare)
339+
users = [email]
340+
share_folder_args = dict(**sf_perms, action=share_action, user=users)
341+
share_rec_args = dict(**rec_perms, action=share_action, email=users)
342+
343+
from keepercommander.commands.register import ShareRecordCommand
344+
from keepercommander.commands.register import ShareFolderCommand
345+
346+
# (Un)Share application record
347+
share_rec_cmd = ShareRecordCommand()
348+
share_rec_cmd.execute(params, record=app_uid, **share_rec_args)
349+
350+
get_share_uid = lambda share: utils.base64_url_encode(share.secretUid)
351+
352+
# (Un)Share shared-folders associated w/ application
353+
is_sf = lambda share: APIRequest_pb2.ApplicationShareType.Name(share.shareType) == 'SHARE_TYPE_FOLDER'
354+
shared_folders = [get_share_uid(s) for ai in app_info for s in ai.shares or [] if is_sf(s)]
355+
share_folder_cmd = ShareFolderCommand()
356+
share_folder_cmd.execute(params, folder=shared_folders, **share_folder_args)
357+
358+
# (Un)Share records associated w/ the application
359+
is_rec = lambda share: APIRequest_pb2.ApplicationShareType.Name(share.shareType) == 'SHARE_TYPE_RECORD'
360+
shared_recs = [get_share_uid(s) for ai in app_info for s in ai.shares or [] if is_rec(s)]
361+
for rec in shared_recs:
362+
share_rec_cmd.execute(params, **share_rec_args, record=rec)
363+
296364
@staticmethod
297365
def add_app_share(params, secret_uids, app_name_or_uid, is_editable):
298366

0 commit comments

Comments
 (0)