def __init__(self, config_file=None, use_plugins=True, use_style=True): super(KingPhisherClientApplication, self).__init__() if use_style: gtk_version = (Gtk.get_major_version(), Gtk.get_minor_version()) if gtk_version > (3, 18): self._theme_file = 'theme.v2.css' else: self._theme_file = 'theme.v1.css' else: self._theme_file = DISABLED self.logger = logging.getLogger('KingPhisher.Client.Application') # log version information for debugging purposes self.logger.debug("gi.repository GLib version: {0}".format('.'.join( map(str, GLib.glib_version)))) self.logger.debug("gi.repository GObject version: {0}".format('.'.join( map(str, GObject.pygobject_version)))) self.logger.debug("gi.repository Gtk version: {0}.{1}.{2}".format( Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version())) if rpc_terminal.has_vte: self.logger.debug("gi.repository VTE version: {0}".format( rpc_terminal.Vte._version)) if graphs.has_matplotlib: self.logger.debug("matplotlib version: {0}".format( graphs.matplotlib.__version__)) self.set_property('application-id', 'org.king-phisher.client') self.set_property('register-session', True) self.config_file = config_file or os.path.join(USER_DATA_PATH, 'config.json') """The file containing the King Phisher client configuration.""" if not os.path.isfile(self.config_file): self._create_config() self.config = None """The primary King Phisher client configuration.""" self.main_window = None """The primary top-level :py:class:`~.MainAppWindow` instance.""" self.rpc = None """The :py:class:`~.KingPhisherRPCClient` instance for the application.""" self._ssh_forwarder = None """The SSH forwarder responsible for tunneling RPC communications.""" self.style_provider = None try: self.emit('config-load', True) except IOError: self.logger.critical('failed to load the client configuration') raise self.connect('window-added', self.signal_window_added) self.actions = {} self._create_actions() if not use_plugins: self.logger.info('disabling all plugins') self.config['plugins.enabled'] = [] self.plugin_manager = plugins.ClientPluginManager([ os.path.join(USER_DATA_PATH, 'plugins'), find.find_data_directory('plugins') ], self) if use_plugins: self.plugin_manager.load_all()
def vte_child_routine(config): """ This is the method which is executed within the child process spawned by VTE. It expects additional values to be set in the *config* object so it can initialize a new :py:class:`.KingPhisherRPCClient` instance. It will then drop into an interpreter where the user may directly interact with the rpc object. :param str config: A JSON encoded client configuration. """ config = json.loads(config) try: import readline import rlcompleter except ImportError: pass else: readline.parse_and_bind('tab: complete') plugins_directory = find.find_data_directory('plugins') if plugins_directory: sys.path.append(plugins_directory) rpc = KingPhisherRPCClient(**config['rpc_data']) logged_in = False for _ in range(0, 3): rpc.password = getpass.getpass("{0}@{1}'s password: "******"Python {0} on {1}".format(sys.version, sys.platform) print(banner) information = "Campaign Name: '{0}' ID: {1}".format( config['campaign_name'], config['campaign_id']) print(information) console_vars = { 'CAMPAIGN_NAME': config['campaign_name'], 'CAMPAIGN_ID': config['campaign_id'], 'os': os, 'rpc': rpc, 'sys': sys } export_to_builtins = ['CAMPAIGN_NAME', 'CAMPAIGN_ID', 'rpc'] console = code.InteractiveConsole(console_vars) for var in export_to_builtins: console.push("__builtins__['{0}'] = {0}".format(var)) console.interact( 'The \'rpc\' object holds the connected KingPhisherRPCClient instance') return
def test_load_data_files(self): completion_dir = find.find_data_directory('completion') self.assertIsNotNone(completion_dir, 'failed to find the \'completion\' directory') # validate that completion definitions claiming to be json are loadable as json for json_file in glob.glob(os.path.join(completion_dir, '*.json')): json_file = os.path.abspath(json_file) with open(json_file, 'r') as file_h: try: json_ex.load(file_h, strict=True) except Exception: self.fail("failed to load file '{0}' as json data".format(json_file))
def vte_child_routine(config): """ This is the method which is executed within the child process spawned by VTE. It expects additional values to be set in the *config* object so it can initialize a new :py:class:`.KingPhisherRPCClient` instance. It will then drop into an interpreter where the user may directly interact with the rpc object. :param str config: A JSON encoded client configuration. """ config = json.loads(config) try: import readline import rlcompleter # pylint: disable=unused-variable except ImportError: pass else: readline.parse_and_bind('tab: complete') plugins_directory = find.find_data_directory('plugins') if plugins_directory: sys.path.append(plugins_directory) rpc = KingPhisherRPCClient(**config['rpc_data']) logged_in = False for _ in range(0, 3): rpc.password = getpass.getpass("{0}@{1}'s password: "******"Python {0} on {1}".format(sys.version, sys.platform) print(banner) # pylint: disable=C0325 information = "Campaign Name: '{0}' ID: {1}".format(config['campaign_name'], config['campaign_id']) print(information) # pylint: disable=C0325 console_vars = { 'CAMPAIGN_NAME': config['campaign_name'], 'CAMPAIGN_ID': config['campaign_id'], 'os': os, 'rpc': rpc, 'sys': sys } export_to_builtins = ['CAMPAIGN_NAME', 'CAMPAIGN_ID', 'rpc'] console = code.InteractiveConsole(console_vars) for var in export_to_builtins: console.push("__builtins__['{0}'] = {0}".format(var)) console.interact('The \'rpc\' object holds the connected KingPhisherRPCClient instance') return
def _get_path(self): path = [find.find_data_directory('plugins')] extra_dirs = self.config.get_if_exists('server.plugin_directories', []) if isinstance(extra_dirs, str): extra_dirs = [extra_dirs] elif not isinstance(extra_dirs, list): raise errors.KingPhisherInputValidationError('configuration setting server.plugin_directories must be a list') for directory in extra_dirs: if not os.path.isdir(directory): continue path.append(directory) return path
def __init__(self, config_file=None, use_plugins=True, use_style=True): super(KingPhisherClientApplication, self).__init__() if use_style: gtk_version = (Gtk.get_major_version(), Gtk.get_minor_version()) if gtk_version > (3, 18): self._theme_file = 'theme.v2.css' else: self._theme_file = 'theme.v1.css' else: self._theme_file = DISABLED self.logger = logging.getLogger('KingPhisher.Client.Application') # log version information for debugging purposes self.logger.debug("gi.repository GLib version: {0}".format('.'.join(map(str, GLib.glib_version)))) self.logger.debug("gi.repository GObject version: {0}".format('.'.join(map(str, GObject.pygobject_version)))) self.logger.debug("gi.repository Gtk version: {0}.{1}.{2}".format(Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version())) if rpc_terminal.has_vte: self.logger.debug("gi.repository VTE version: {0}".format(rpc_terminal.Vte._version)) if graphs.has_matplotlib: self.logger.debug("matplotlib version: {0}".format(graphs.matplotlib.__version__)) self.set_property('application-id', 'org.king-phisher.client') self.set_property('register-session', True) self.config_file = config_file or os.path.join(USER_DATA_PATH, 'config.json') """The file containing the King Phisher client configuration.""" if not os.path.isfile(self.config_file): self._create_config() self.config = None """The primary King Phisher client configuration.""" self.main_window = None """The primary top-level :py:class:`~.MainAppWindow` instance.""" self.rpc = None """The :py:class:`~.KingPhisherRPCClient` instance for the application.""" self._ssh_forwarder = None """The SSH forwarder responsible for tunneling RPC communications.""" self.style_provider = None try: self.emit('config-load', True) except IOError: self.logger.critical('failed to load the client configuration') raise self.connect('window-added', self.signal_window_added) self.actions = {} self._create_actions() if not use_plugins: self.logger.info('disabling all plugins') self.config['plugins.enabled'] = [] self.plugin_manager = plugins.ClientPluginManager( [os.path.join(USER_DATA_PATH, 'plugins'), find.find_data_directory('plugins')], self ) if use_plugins: self.plugin_manager.load_all()
def _get_path(self): path = [find.find_data_directory('plugins')] extra_dirs = self.config.get_if_exists('server.plugin_directories', []) if isinstance(extra_dirs, str): extra_dirs = [extra_dirs] elif not isinstance(extra_dirs, list): raise errors.KingPhisherInputValidationError( 'configuration setting server.plugin_directories must be a list' ) for directory in extra_dirs: if not os.path.isdir(directory): continue path.append(directory) return path
def vte_child_routine(config): """ This is the method which is executed within the child process spawned by VTE. It expects additional values to be set in the *config* object so it can initialize a new :py:class:`.KingPhisherRPCClient` instance. It will then drop into an interpreter where the user may directly interact with the rpc object. :param str config: A JSON encoded client configuration. """ config = serializers.JSON.loads(config) try: import readline import rlcompleter # pylint: disable=unused-variable except ImportError: pass else: readline.parse_and_bind('tab: complete') for plugins_directory in ('rpc_plugins', 'rpc-plugins'): plugins_directory = find.find_data_directory(plugins_directory) if not plugins_directory: continue sys.path.append(plugins_directory) headers = config['rpc_data'].pop('headers') rpc = KingPhisherRPCClient(**config['rpc_data']) if rpc.headers is None: rpc.headers = {} for name, value in headers.items(): rpc.headers[str(name)] = str(value) banner = "Python {0} on {1}".format(sys.version, sys.platform) print(banner) # pylint: disable=superfluous-parens information = "Campaign Name: '{0}' ID: {1}".format( config['campaign_name'], config['campaign_id']) print(information) # pylint: disable=superfluous-parens console_vars = { 'CAMPAIGN_NAME': config['campaign_name'], 'CAMPAIGN_ID': config['campaign_id'], 'os': os, 'rpc': rpc, 'sys': sys } export_to_builtins = ['CAMPAIGN_NAME', 'CAMPAIGN_ID', 'rpc'] console = code.InteractiveConsole(console_vars) for var in export_to_builtins: console.push("__builtins__['{0}'] = {0}".format(var)) console.interact( 'The \'rpc\' object holds the connected KingPhisherRPCClient instance') return
def vte_child_routine(config): """ This is the method which is executed within the child process spawned by VTE. It expects additional values to be set in the *config* object so it can initialize a new :py:class:`.KingPhisherRPCClient` instance. It will then drop into an interpreter where the user may directly interact with the rpc object. :param str config: A JSON encoded client configuration. """ config = json_ex.loads(config) try: import readline import rlcompleter # pylint: disable=unused-variable except ImportError: pass else: readline.parse_and_bind('tab: complete') for plugins_directory in ('rpc_plugins', 'rpc-plugins'): plugins_directory = find.find_data_directory(plugins_directory) if not plugins_directory: continue sys.path.append(plugins_directory) headers = config['rpc_data'].pop('headers') rpc = KingPhisherRPCClient(**config['rpc_data']) if rpc.headers is None: rpc.headers = {} for name, value in headers.items(): rpc.headers[str(name)] = str(value) banner = "Python {0} on {1}".format(sys.version, sys.platform) print(banner) # pylint: disable=superfluous-parens information = "Campaign Name: '{0}' ID: {1}".format(config['campaign_name'], config['campaign_id']) print(information) # pylint: disable=superfluous-parens console_vars = { 'CAMPAIGN_NAME': config['campaign_name'], 'CAMPAIGN_ID': config['campaign_id'], 'os': os, 'rpc': rpc, 'sys': sys } export_to_builtins = ['CAMPAIGN_NAME', 'CAMPAIGN_ID', 'rpc'] console = code.InteractiveConsole(console_vars) for var in export_to_builtins: console.push("__builtins__['{0}'] = {0}".format(var)) console.interact('The \'rpc\' object holds the connected KingPhisherRPCClient instance') return
def child_routine(config): config = json.loads(config) try: import readline import rlcompleter except ImportError: pass else: readline.parse_and_bind('tab: complete') plugins_directory = find.find_data_directory('plugins') if plugins_directory: sys.path.append(plugins_directory) rpc = KingPhisherRPCClient(**config['rpc_data']) logged_in = False for _ in range(0, 3): rpc.password = getpass.getpass("{0}@{1}'s password: "******"Python {0} on {1}".format(sys.version, sys.platform) print(banner) information = "Campaign Name: '{0}' ID: {1}".format(config['campaign_name'], config['campaign_id']) print(information) console_vars = { 'CAMPAIGN_NAME': config['campaign_name'], 'CAMPAIGN_ID': config['campaign_id'], 'os': os, 'rpc': rpc } export_to_builtins = ['CAMPAIGN_NAME', 'CAMPAIGN_ID', 'rpc'] console = code.InteractiveConsole(console_vars) for var in export_to_builtins: console.push("__builtins__['{0}'] = {0}".format(var)) console.interact('The \'rpc\' object holds the connected KingPhisherRPCClient instance') return
def init_alembic(engine, schema_version): """ Creates the alembic_version table and sets the value of the table according to the specified schema version. :param engine: The engine used to connect to the database. :type engine: :py:class:`sqlalchemy.engine.Engine` :param int schema_version: The MetaData schema_version to set the alembic version to. """ pattern = re.compile('[a-f0-9]{10,16}_schema_v\d+\.py') alembic_revision = None alembic_directory = find.find_data_directory('alembic') if not alembic_directory: raise errors.KingPhisherDatabaseError( 'cannot find the alembic data directory') alembic_versions_files = os.listdir( os.path.join(alembic_directory, 'versions')) for file in alembic_versions_files: if not pattern.match(file): continue if not file.endswith('_schema_v' + str(schema_version) + '.py'): continue alembic_revision = file.split('_', 1)[0] break if not alembic_revision: raise errors.KingPhisherDatabaseError( "cannot find current alembic version for schema version {0}". format(schema_version)) alembic_metadata = sqlalchemy.MetaData(engine) alembic_table = sqlalchemy.Table( 'alembic_version', alembic_metadata, sqlalchemy.Column('version_num', sqlalchemy.String, primary_key=True, nullable=False)) alembic_metadata.create_all() alembic_version_entry = alembic_table.insert().values( version_num=alembic_revision) engine.connect().execute(alembic_version_entry) logger.info( "alembic_version table initialized to {0}".format(alembic_revision))
def init_database(connection_url): """ Create and initialize the database engine. This must be done before the session object can be used. This will also attempt to perform any updates to the database schema if the backend support such operations. :param str connection_url: The url for the database connection. :return: The initialized database engine. """ connection_url = normalize_connection_url(connection_url) connection_url = sqlalchemy.engine.url.make_url(connection_url) logger.info("initializing database connection with driver {0}".format( connection_url.drivername)) if connection_url.drivername == 'sqlite': engine = sqlalchemy.create_engine( connection_url, connect_args={'check_same_thread': False}, poolclass=sqlalchemy.pool.StaticPool) sqlalchemy.event.listens_for( engine, 'begin')(lambda conn: conn.execute('BEGIN')) elif connection_url.drivername == 'postgresql': engine = sqlalchemy.create_engine(connection_url) else: raise errors.KingPhisherDatabaseError( 'only sqlite and postgresql database drivers are supported') Session.remove() Session.configure(bind=engine) try: models.Base.metadata.create_all(engine) except sqlalchemy.exc.SQLAlchemyError as error: error_lines = map(lambda line: line.strip(), error.message.split('\n')) raise errors.KingPhisherDatabaseError('SQLAlchemyError: ' + ' '.join(error_lines).strip()) session = Session() set_meta_data('database_driver', connection_url.drivername, session=session) schema_version = (get_meta_data('schema_version', session=session) or models.SCHEMA_VERSION) session.commit() session.close() logger.debug("current database schema version: {0} ({1}current)".format( schema_version, ('' if schema_version == models.SCHEMA_VERSION else 'not '))) if schema_version > models.SCHEMA_VERSION: raise errors.KingPhisherDatabaseError( 'the database schema is for a newer version, automatic downgrades are not supported' ) elif schema_version < models.SCHEMA_VERSION: alembic_config_file = find.find_data_file('alembic.ini') if not alembic_config_file: raise errors.KingPhisherDatabaseError( 'cannot find the alembic.ini configuration file') alembic_directory = find.find_data_directory('alembic') if not alembic_directory: raise errors.KingPhisherDatabaseError( 'cannot find the alembic data directory') config = alembic.config.Config(alembic_config_file) config.config_file_name = alembic_config_file config.set_main_option('script_location', alembic_directory) config.set_main_option('skip_logger_config', 'True') config.set_main_option('sqlalchemy.url', str(connection_url)) logger.warning( "automatically updating the database schema to version {0}".format( models.SCHEMA_VERSION)) try: alembic.command.upgrade(config, 'head') except Exception as error: logger.critical( "database schema upgrade failed with exception: {0}.{1} {2}". format(error.__class__.__module__, error.__class__.__name__, getattr(error, 'message', '')).rstrip()) raise errors.KingPhisherDatabaseError( 'failed to upgrade to the latest database schema') set_meta_data('schema_version', models.SCHEMA_VERSION) logger.debug("connected to {0} database: {1}".format( connection_url.drivername, connection_url.database)) return engine
def init_database(connection_url): """ Create and initialize the database engine. This must be done before the session object can be used. This will also attempt to perform any updates to the database schema if the backend supports such operations. :param str connection_url: The url for the database connection. :return: The initialized database engine. """ connection_url = normalize_connection_url(connection_url) connection_url = sqlalchemy.engine.url.make_url(connection_url) logger.info("initializing database connection with driver {0}".format(connection_url.drivername)) if connection_url.drivername == 'sqlite': engine = sqlalchemy.create_engine(connection_url, connect_args={'check_same_thread': False}, poolclass=sqlalchemy.pool.StaticPool) sqlalchemy.event.listens_for(engine, 'begin')(lambda conn: conn.execute('BEGIN')) elif connection_url.drivername == 'postgresql': engine = sqlalchemy.create_engine(connection_url) else: raise errors.KingPhisherDatabaseError('only sqlite and postgresql database drivers are supported') Session.remove() Session.configure(bind=engine) inspector = sqlalchemy.inspect(engine) if not 'meta_data' in inspector.get_table_names(): logger.debug('meta_data table not found, creating all new tables') try: models.Base.metadata.create_all(engine) except sqlalchemy.exc.SQLAlchemyError as error: error_lines = (line.strip() for line in error.message.split('\n')) raise errors.KingPhisherDatabaseError('SQLAlchemyError: ' + ' '.join(error_lines).strip()) session = Session() set_meta_data('database_driver', connection_url.drivername, session=session) schema_version = (get_meta_data('schema_version', session=session) or models.SCHEMA_VERSION) session.commit() session.close() logger.debug("current database schema version: {0} ({1}current)".format(schema_version, ('' if schema_version == models.SCHEMA_VERSION else 'not '))) if schema_version > models.SCHEMA_VERSION: raise errors.KingPhisherDatabaseError('the database schema is for a newer version, automatic downgrades are not supported') elif schema_version < models.SCHEMA_VERSION: alembic_config_file = find.find_data_file('alembic.ini') if not alembic_config_file: raise errors.KingPhisherDatabaseError('cannot find the alembic.ini configuration file') alembic_directory = find.find_data_directory('alembic') if not alembic_directory: raise errors.KingPhisherDatabaseError('cannot find the alembic data directory') config = alembic.config.Config(alembic_config_file) config.config_file_name = alembic_config_file config.set_main_option('script_location', alembic_directory) config.set_main_option('skip_logger_config', 'True') config.set_main_option('sqlalchemy.url', str(connection_url)) logger.warning("automatically updating the database schema to version {0}".format(models.SCHEMA_VERSION)) try: alembic.command.upgrade(config, 'head') except Exception as error: logger.critical("database schema upgrade failed with exception: {0}.{1} {2}".format(error.__class__.__module__, error.__class__.__name__, getattr(error, 'message', '')).rstrip(), exc_info=True) raise errors.KingPhisherDatabaseError('failed to upgrade to the latest database schema') # reset it because it may have been altered by alembic Session.remove() Session.configure(bind=engine) session = Session() set_meta_data('schema_version', models.SCHEMA_VERSION) logger.debug("connected to {0} database: {1}".format(connection_url.drivername, connection_url.database)) return engine