Skip to content
Open
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
4 changes: 4 additions & 0 deletions keepercommander/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ def register_enterprise_commands(commands, aliases, command_info):
device_management.register_enterprise_commands(commands)
device_management.register_enterprise_command_info(aliases, command_info)

from . import sso_cloud
sso_cloud.register_commands(commands)
sso_cloud.register_command_info(aliases, command_info)

if sys.version_info.major > 3 or (sys.version_info.major == 3 and sys.version_info.minor >= 9):
from.pedm import pedm_admin
pedm_command = pedm_admin.PedmCommand()
Expand Down
74 changes: 74 additions & 0 deletions keepercommander/commands/sso_cloud/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# _ __
# | |/ /___ ___ _ __ ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
# |_|
#
# Keeper Commander
# Copyright 2024 Keeper Security Inc.
# Contact: ops@keepersecurity.com
#

"""sso_cloud package – CLI surface for SSO Cloud Connect configuration.

Package layout
--------------
constants.py – IdP enum mappings, setup guidance, setting groups
parsers.py – All argparse parser definitions
mixin.py – SsoCloudMixin (shared SP/config resolution and display helpers)
sp_commands.py – List, ConfigList, Create, Delete (SP lifecycle)
config_commands.py – Get, Set, Validate, Guide (configuration inspection/modification)
metadata_commands.py – Upload, Download (SAML metadata exchange)
log_commands.py – Log, LogClear (SAML diagnostics)
__init__.py – SsoCloudCommand router, register_commands / register_command_info
"""

from ..base import GroupCommand

from .sp_commands import (
SsoCloudListCommand, SsoCloudConfigListCommand,
SsoCloudCreateCommand, SsoCloudDeleteCommand,
)
from .config_commands import (
SsoCloudGetCommand, SsoCloudSetCommand,
SsoCloudValidateCommand, SsoCloudGuideCommand,
)
from .metadata_commands import SsoCloudUploadMetadataCommand, SsoCloudDownloadMetadataCommand
from .log_commands import SsoCloudLogCommand, SsoCloudLogClearCommand


class SsoCloudCommand(GroupCommand):
def __init__(self):
super(SsoCloudCommand, self).__init__()
self.register_command('create', SsoCloudCreateCommand(),
'Create a new SSO Cloud service provider and configuration.')
self.register_command('get', SsoCloudGetCommand(), 'View SSO Cloud configuration details.')
self.register_command('guide', SsoCloudGuideCommand(),
'Show IdP-specific setup guide.')
self.register_command('list', SsoCloudListCommand(), 'List SSO Cloud service providers.')
self.register_command('config-list', SsoCloudConfigListCommand(),
'List configurations for an SSO service provider.')
self.register_command('upload', SsoCloudUploadMetadataCommand(),
'Upload IdP metadata XML to an SSO configuration.')
self.register_command('download', SsoCloudDownloadMetadataCommand(),
'Download Keeper SP metadata XML.')
self.register_command('set', SsoCloudSetCommand(),
'Update SSO configuration settings.')
self.register_command('validate', SsoCloudValidateCommand(),
'Validate an SSO configuration.')
self.register_command('delete', SsoCloudDeleteCommand(),
'Delete an SSO configuration.')
self.register_command('log', SsoCloudLogCommand(),
'View SAML log entries.')
self.register_command('log-clear', SsoCloudLogClearCommand(),
'Clear SAML log entries.')
self.default_verb = 'list'


def register_commands(commands):
commands['sso-cloud'] = SsoCloudCommand()


def register_command_info(aliases, command_info):
command_info['sso-cloud'] = 'Manage SSO Cloud Connect service providers and configurations'
aliases['sso'] = 'sso-cloud'
161 changes: 161 additions & 0 deletions keepercommander/commands/sso_cloud/config_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# _ __
# | |/ /___ ___ _ __ ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
# |_|
#
# Keeper Commander
# Copyright 2024 Keeper Security Inc.
# Contact: ops@keepersecurity.com
#

"""Configuration commands: get, set, validate, guide."""

import logging

from typing import Any

from ... import api
from ...error import CommandError
from ...proto import ssocloud_pb2 as ssocloud
from ..enterprise_common import EnterpriseCommand

from .parsers import (
sso_cloud_get_parser, sso_cloud_set_parser,
sso_cloud_validate_parser, sso_cloud_guide_parser,
)
from .mixin import SsoCloudMixin


class SsoCloudGetCommand(EnterpriseCommand, SsoCloudMixin):
def get_parser(self):
return sso_cloud_get_parser

def execute(self, params, **kwargs):
target = kwargs.get('target')
svc = self.find_sso_service(params, target)
sp_id = svc['sso_service_provider_id']
self.ensure_cloud_sso(svc, target)

config_rs = self.get_selected_configuration(params, sp_id, config_target=kwargs.get('config'))
self.dump_configuration(config_rs, fmt=kwargs.get('format'), filename=kwargs.get('output'))


class SsoCloudGuideCommand(EnterpriseCommand, SsoCloudMixin):
def get_parser(self):
return sso_cloud_guide_parser

def execute(self, params, **kwargs):
target = kwargs.get('target')
svc = self.find_sso_service(params, target)
sp_id = svc['sso_service_provider_id']
self.ensure_cloud_sso(svc, target)

config_rs = self.get_selected_configuration(params, sp_id, config_target=kwargs.get('config'))
self.show_idp_guidance(config_rs, sp_name=svc.get('name', target))


class SsoCloudSetCommand(EnterpriseCommand, SsoCloudMixin):
def get_parser(self):
return sso_cloud_set_parser

def execute(self, params, **kwargs):
# type: (Any, **Any) -> Any
target = kwargs.get('target')
svc = self.find_sso_service(params, target)
sp_id = svc['sso_service_provider_id']
self.ensure_cloud_sso(svc, target)

settings_to_set = kwargs.get('setting') or []
settings_to_reset = kwargs.get('reset') or []

if not settings_to_set and not settings_to_reset:
raise CommandError('sso-cloud', 'Provide at least one --set KEY=VALUE or --reset KEY argument.')

config_rs = self.get_selected_configuration(params, sp_id, config_target=kwargs.get('config'))
config_id = config_rs.ssoSpConfigurationId

available_settings = {}
for sv in config_rs.ssoCloudSettingValue:
available_settings[sv.settingName.lower()] = sv

rq = ssocloud.SsoCloudConfigurationRequest()
rq.ssoServiceProviderId = sp_id
rq.ssoSpConfigurationId = config_id

for setting_str in settings_to_set:
pos = setting_str.find('=')
if pos < 1:
raise CommandError('sso-cloud', f'Invalid setting format "{setting_str}". Expected KEY=VALUE.')

key = setting_str[:pos].strip()
value = setting_str[pos + 1:].strip()

existing = available_settings.get(key.lower())
if not existing:
raise CommandError('sso-cloud', f'Unknown setting: "{key}". '
f'Use "sso-cloud get" to see available settings.')
if not existing.isEditable:
raise CommandError('sso-cloud', f'Setting "{key}" is read-only.')

action = ssocloud.SsoCloudSettingAction()
action.settingName = existing.settingName
action.operation = ssocloud.SET
action.value = value
rq.ssoCloudSettingAction.append(action)

for key in settings_to_reset:
existing = available_settings.get(key.strip().lower())
if not existing:
raise CommandError('sso-cloud', f'Unknown setting: "{key}".')
if not existing.isEditable:
raise CommandError('sso-cloud', f'Setting "{key}" is read-only.')

action = ssocloud.SsoCloudSettingAction()
action.settingName = existing.settingName
action.operation = ssocloud.RESET_TO_DEFAULT
rq.ssoCloudSettingAction.append(action)

updated_rs = api.communicate_rest(
params, rq, 'sso/config/sso_cloud_configuration_setting_set',
rs_type=ssocloud.SsoCloudConfigurationResponse)

logging.info('Configuration updated successfully.')
self.dump_configuration(updated_rs)


class SsoCloudValidateCommand(EnterpriseCommand, SsoCloudMixin):
def get_parser(self):
return sso_cloud_validate_parser

def execute(self, params, **kwargs):
# type: (Any, **Any) -> Any
target = kwargs.get('target')
svc = self.find_sso_service(params, target)
sp_id = svc['sso_service_provider_id']
self.ensure_cloud_sso(svc, target)

config_rs = self.get_selected_configuration(params, sp_id, config_target=kwargs.get('config'))
config_id = config_rs.ssoSpConfigurationId

rq = ssocloud.SsoCloudConfigurationValidationRequest()
rq.ssoSpConfigurationId.append(config_id)

rs = api.communicate_rest(
params, rq, 'sso/config/sso_cloud_configuration_validate',
rs_type=ssocloud.SsoCloudConfigurationValidationResponse)

all_valid = True
for vc in rs.validationContent:
if vc.isSuccessful:
logging.info('Configuration "%s" (ID: %s) is valid.',
config_rs.name, vc.ssoSpConfigurationId)
else:
all_valid = False
logging.warning('Configuration "%s" (ID: %s) has validation errors:',
config_rs.name, vc.ssoSpConfigurationId)
for msg in vc.errorMessage:
logging.warning(' - %s', msg)

if all_valid:
logging.info('SSO Cloud configuration is ready for use.')
Loading
Loading