Skip to content
Merged
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
68 changes: 68 additions & 0 deletions keepercommander/commands/helpers/team.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from ... import api, crypto, utils


class MissingTeamEncryptionKey(ValueError):
"""The enterprise tree key or data key needed to create a team is missing."""


def generate_team_add_key_fields(team_key, allow_rsa=True):
# type: (bytes, bool) -> dict
"""Generate the EC (and, when allowed, RSA) key fields for a team_add request.

The EC private key is encrypted with AES-v2 and the RSA private key with AES-v1,
both under ``team_key``. Every value is base64url-encoded. Returned names match
the request fields: ``ecc_private_key``, ``ecc_public_key``, and when RSA is
allowed ``private_key`` and ``public_key``.
"""
ec_private_key, ec_public_key = crypto.generate_ec_key()
fields = {
'ecc_private_key': utils.base64_url_encode(
crypto.encrypt_aes_v2(crypto.unload_ec_private_key(ec_private_key), team_key)),
'ecc_public_key': utils.base64_url_encode(crypto.unload_ec_public_key(ec_public_key)),
}
if allow_rsa:
rsa_private_key, rsa_public_key = crypto.generate_rsa_key()
fields['private_key'] = utils.base64_url_encode(
crypto.encrypt_aes_v1(crypto.unload_rsa_private_key(rsa_private_key), team_key))
fields['public_key'] = utils.base64_url_encode(crypto.unload_rsa_public_key(rsa_public_key))
return fields


def build_team_add_request(params, team_name, node_id,
restrict_edit=False, restrict_share=False, restrict_view=False,
manage_only=True, team_uid=None, team_key=None):
# type: (object, str, int, bool, bool, bool, bool, str, bytes) -> tuple
"""Build a team_add request and return ``(request, team_key)``.

``team_uid`` and the team AES key are generated when the caller does not
supply them. The team key is encrypted with the data key (AES-v1) and the
enterprise tree key (AES-v2). RSA key fields are omitted when
``params.forbid_rsa`` is set. Raises MissingTeamEncryptionKey when the tree
key or data key is missing. The returned team key is the raw AES key, for
callers that still need it to add members.
"""
tree_key = (getattr(params, 'enterprise', None) or {}).get('unencrypted_tree_key')
data_key = getattr(params, 'data_key', None)
if not tree_key or not data_key:
raise MissingTeamEncryptionKey('Enterprise tree key or data key is not available')

if team_uid is None:
team_uid = api.generate_record_uid()
if team_key is None:
team_key = api.generate_aes_key()

request = {
'command': 'team_add',
'team_uid': team_uid,
'team_name': team_name,
'restrict_edit': restrict_edit,
'restrict_share': restrict_share,
'restrict_view': restrict_view,
'node_id': node_id,
'team_key': utils.base64_url_encode(crypto.encrypt_aes_v1(team_key, data_key)),
'encrypted_team_key': utils.base64_url_encode(crypto.encrypt_aes_v2(team_key, tree_key)),
'manage_only': manage_only,
}
request.update(generate_team_add_key_fields(
team_key, allow_rsa=not getattr(params, 'forbid_rsa', False)))
return request, team_key
3 changes: 3 additions & 0 deletions keepercommander/importer/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ def _positive_int_arg(value):
help='Comma separated list of secret IDs to fetch (Thycotic)')
import_parser.add_argument('--add-users', dest='add_users', action='store_true',
help='provision source users as pre-activated Keeper enterprise users')
import_parser.add_argument('--add-teams', dest='add_teams', action='store_true',
help='create Keeper teams from source groups and add members '
'who already exist as Keeper users')
import_parser.add_argument('--target-node', '--node', dest='target_node', action='store',
help='node name or ID for CyberArk-provisioned users, teams, and roles (default: root node)')
import_parser.add_argument('--skip', dest='skip', action='store', type=_cyberark_skip_arg,
Expand Down
7 changes: 6 additions & 1 deletion keepercommander/importer/imp_exp.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,7 @@ def _import(params, file_format, filename, **kwargs):
skip_role = kwargs.get('skip_role') is True or 'role' in cyberark_skip
skip_user = kwargs.get('skip_user') is True or 'user' in cyberark_skip
add_users = kwargs.get('add_users') is True
add_teams = kwargs.get('add_teams') is True

import_into_raw = kwargs.get('import_into') or ''
import_into = import_into_raw
Expand All @@ -864,6 +865,9 @@ def _import(params, file_format, filename, **kwargs):
if add_users and not importer.support_add_users():
logging.warning('--add-users is not supported for --format=%s; ignoring', file_format)
add_users = False
if add_teams and not importer.support_add_teams():
logging.warning('--add-teams is not supported for --format=%s; ignoring', file_format)
add_teams = False
verbose_import_summary = importer.verbose_import_summary
show_skipped = show_skipped or verbose_import_summary

Expand All @@ -884,7 +888,8 @@ def _import(params, file_format, filename, **kwargs):
old_domain=old_domain, new_domain=new_domain, tmpdir=tmpdir, secret_ids=secret_ids,
dry_run=dry_run, target_node=target_node, use_nsf=use_nsf, record_type=record_type,
skip_team=skip_team, skip_role=skip_role, skip_user=skip_user,
add_users=add_users):
add_users=add_users,
add_teams=add_teams):
if isinstance(x, ImportRecord):
if filter_folder and not importer.support_folder_filter():
if not x.folders:
Expand Down
8 changes: 7 additions & 1 deletion keepercommander/importer/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ def support_add_users(self):
"""
return False

def support_add_teams(self):
"""
Indicates if this importer can create Keeper teams from source groups
"""
return False

@staticmethod
def import_field(field_type, field_value): # type: (str, str) -> any
if not field_value:
Expand Down Expand Up @@ -594,4 +600,4 @@ def download_membership(self, params, **kwargs): # type: (KeeperParams, ...)
class BaseDownloadRecordType(abc.ABC):
@abc.abstractmethod
def download_record_type(self, params, **kwargs): # type: (KeeperParams, ...) -> Iterable[RecordType]
pass
pass
Loading