def test_schema_file_names(self): alembic_directory = find.data_directory('alembic') versions = os.listdir(os.path.join(alembic_directory, 'versions')) for schema_file in versions: if not schema_file.endswith('.py'): continue self.assertRegex(schema_file, r'[a-f0-9]{10,16}_schema_v\d+\.py', schema_file)
def test_schema_file_names(self): alembic_directory = find.data_directory('alembic') versions = os.listdir(os.path.join(alembic_directory, 'versions')) for schema_file in versions: if not schema_file.endswith('.py'): continue self.assertRegex(schema_file, r'[a-f0-9]{10,16}_schema_v\d+\.py', schema_file)
def test_load_data_files(self): completion_dir = 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: serializers.JSON.load(file_h, strict=True) except Exception: self.fail("failed to load file '{0}' as json data".format(json_file))
def test_load_data_files(self): completion_dir = 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: serializers.JSON.load(file_h, strict=True) except Exception: self.fail("failed to load file '{0}' as json data".format(json_file))
def _get_path(self): path = [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 _get_path(self): path = [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 test_json_schema_directories(self): find.init_data_path() directory = find.data_directory(os.path.join('schemas', 'json')) self.assertIsNotNone(directory) for schema_file in os.listdir(directory): self.assertTrue(schema_file.endswith('.json')) schema_file = os.path.join(directory, schema_file) with open(schema_file, 'r') as file_h: schema_data = json.load(file_h) self.assertIsInstance(schema_data, dict) self.assertEqual(schema_data.get('$schema'), 'http://json-schema.org/draft-04/schema#') self.assertEqual(schema_data.get('id'), os.path.basename(schema_file)[:-5])
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.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 = 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.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 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(r'[a-f0-9]{10,16}_schema_v\d+\.py') alembic_revision = None alembic_directory = 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_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(r'[a-f0-9]{10,16}_schema_v\d+\.py') alembic_revision = None alembic_directory = 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 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: has_readline = False else: has_readline = True try: import IPython.terminal.embed except ImportError: has_ipython = False else: has_ipython = True for plugins_directory in ('rpc_plugins', 'rpc-plugins'): plugins_directory = 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) user_data_path = config['user_data_path'] sys.path.append(config['user_library_path']) print("Python {0} on {1}".format(sys.version, sys.platform)) # pylint: disable=superfluous-parens print("Campaign Name: '{0}' ID: {1}".format(config['campaign_name'], config['campaign_id'])) # pylint: disable=superfluous-parens print( 'The \'rpc\' object holds the connected KingPhisherRPCClient instance') console_vars = { 'CAMPAIGN_NAME': config['campaign_name'], 'CAMPAIGN_ID': config['campaign_id'], 'os': os, 'rpc': rpc, 'sys': sys } if has_ipython: console = IPython.terminal.embed.InteractiveShellEmbed( ipython_dir=os.path.join(user_data_path, 'ipython')) console.register_magic_function( functools.partial(_magic_graphql, rpc, 'query'), 'line', 'graphql') console.register_magic_function( functools.partial(_magic_graphql, rpc, 'file'), 'line', 'graphql_file') console.mainloop(console_vars) else: if has_readline: readline.parse_and_bind('tab: complete') console = code.InteractiveConsole(console_vars) for var in tuple(console_vars.keys()): console.push("__builtins__['{0}'] = {0}".format(var)) console.interact('') return
def test_find_data_directory(self): self.assertIsNotNone(find.data_directory('schemas'))
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.references = [] """A list to store references to arbitrary objects in for avoiding garbage collection.""" self.rpc = None """The :py:class:`~.KingPhisherRPCClient` instance for the application.""" self._rpc_ping_event = None # this will be populated when the RPC object is authenticated to ping # the server periodically and keep the session alive self.server_events = None """The :py:class:`~.ServerEventSubscriber` instance for the application to receive server events.""" 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.data_directory('plugins') ], self) if use_plugins: self.plugin_manager.load_all()
def init_database(connection_url, extra_init=False): """ 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. :param bool extra_init: Run optional extra dbms-specific initialization logic. :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': if extra_init: init_database_postgresql(connection_url) 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})".format(schema_version, ('latest' if schema_version == models.SCHEMA_VERSION else 'obsolete'))) if not 'alembic_version' in inspector.get_table_names(): logger.debug('alembic version table not found, attempting to create and set version') init_alembic(engine, schema_version) 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.data_file('alembic.ini') if not alembic_config_file: raise errors.KingPhisherDatabaseError('cannot find the alembic.ini configuration file') alembic_directory = 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 from version {0} to {1}".format(schema_version, 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') logger.info("successfully updated the database schema from version {0} to {1}".format(schema_version, models.SCHEMA_VERSION)) # 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)) signals.db_initialized.send(connection_url) return engine
def __init__(self, config_file=None, use_plugins=True, use_style=True): super(KingPhisherClientApplication, self).__init__() if use_style: # 3.20+ use theme.v2.css if Gtk.check_version(3, 20, 0) or its.on_windows: self._theme_file = 'theme.v1.css' else: self._theme_file = 'theme.v2.css' else: self._theme_file = DISABLED self.user_data_path = os.path.join(GLib.get_user_config_dir(), USER_DATA_PATH) """ The path to a directory where user data files can be stored. This path must be writable by the current user. The default value is platform dependant: :Linux: ``~/.config/king-phisher`` :Windows: ``%LOCALAPPDATA%\\king-phisher`` """ 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__)) # do not negotiate a single instance application # https://developer.gnome.org/gio/unstable/GApplication.html#G-APPLICATION-NON-UNIQUE:CAPS self.set_flags(Gio.ApplicationFlags.NON_UNIQUE) self.set_property('application-id', 'org.king-phisher.client') self.set_property('register-session', True) self.config_file = config_file or os.path.join(self.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.references = [] """A list to store references to arbitrary objects in for avoiding garbage collection.""" self.rpc = None """The :py:class:`~.KingPhisherRPCClient` instance for the application.""" self._rpc_ping_event = None # this will be populated when the RPC object is authenticated to ping # the server periodically and keep the session alive self.server_events = None """The :py:class:`~.ServerEventSubscriber` instance for the application to receive server events.""" self.server_user = None """The :py:class:`~.ServerUser` instance for the authenticated user.""" 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'] = [] if not os.path.exists(os.path.join(self.user_data_path, 'plugins')): try: os.mkdir(os.path.join(self.user_data_path, 'plugins')) except OSError: self.logger.warning('Failed to create user plugins folder') self.plugin_manager = plugins.ClientPluginManager([ os.path.join(self.user_data_path, 'plugins'), find.data_directory('plugins') ] + self.config.get('plugins.path', []), self) """The :py:class:`~king_phisher.client.plugins.ClientPluginManager` instance to manage the installed client plugins.""" if use_plugins: self.plugin_manager.load_all()
def init_database(connection_url, extra_init=False): """ 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. :param bool extra_init: Run optional extra dbms-specific initialization logic. :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': if extra_init: init_database_postgresql(connection_url) engine = sqlalchemy.create_engine( connection_url, connect_args={'client_encoding': 'utf8'}) else: raise errors.KingPhisherDatabaseError( 'only sqlite and postgresql database drivers are supported') Session.remove() Session.configure(bind=engine) inspector = sqlalchemy.inspect(engine) if 'campaigns' not in inspector.get_table_names(): logger.debug('campaigns 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()) schema_version = get_schema_version(engine) logger.debug("current database schema version: {0} ({1})".format( schema_version, ('latest' if schema_version == models.SCHEMA_VERSION else 'obsolete'))) if 'alembic_version' not in inspector.get_table_names(): logger.debug( 'alembic version table not found, attempting to create and set version' ) init_alembic(engine, schema_version) 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.data_file('alembic.ini') if not alembic_config_file: raise errors.KingPhisherDatabaseError( 'cannot find the alembic.ini configuration file') alembic_directory = 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 from version {0} to {1}" .format(schema_version, 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') logger.info( "successfully updated the database schema from version {0} to {1}". format(schema_version, models.SCHEMA_VERSION)) # reset it because it may have been altered by alembic Session.remove() Session.configure(bind=engine) set_metadata('database_driver', connection_url.drivername) set_metadata('last_started', datetime.datetime.utcnow()) set_metadata('schema_version', models.SCHEMA_VERSION) logger.debug("connected to {0} database: {1}".format( connection_url.drivername, connection_url.database)) signals.db_initialized.send(connection_url) return engine
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: has_readline = False else: has_readline = True try: import IPython.terminal.embed except ImportError: has_ipython = False else: has_ipython = True for plugins_directory in ('rpc_plugins', 'rpc-plugins'): plugins_directory = 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) user_data_path = config['user_data_path'] sys.path.append(config['user_library_path']) print("Python {0} on {1}".format(sys.version, sys.platform)) # pylint: disable=superfluous-parens print("Campaign Name: '{0}' ID: {1}".format(config['campaign_name'], config['campaign_id'])) # pylint: disable=superfluous-parens print('The \'rpc\' object holds the connected KingPhisherRPCClient instance') console_vars = { 'CAMPAIGN_NAME': config['campaign_name'], 'CAMPAIGN_ID': config['campaign_id'], 'os': os, 'rpc': rpc, 'sys': sys } if has_ipython: console = IPython.terminal.embed.InteractiveShellEmbed(ipython_dir=os.path.join(user_data_path, 'ipython')) console.register_magic_function(functools.partial(_magic_graphql, rpc, 'query'), 'line', 'graphql') console.register_magic_function(functools.partial(_magic_graphql, rpc, 'file'), 'line', 'graphql_file') console.mainloop(console_vars) else: if has_readline: readline.parse_and_bind('tab: complete') console = code.InteractiveConsole(console_vars) for var in tuple(console_vars.keys()): console.push("__builtins__['{0}'] = {0}".format(var)) console.interact('') return
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__)) # do not negotiate a single instance application # https://developer.gnome.org/gio/unstable/GApplication.html#G-APPLICATION-NON-UNIQUE:CAPS self.set_flags(Gio.ApplicationFlags.NON_UNIQUE) 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.references = [] """A list to store references to arbitrary objects in for avoiding garbage collection.""" self.rpc = None """The :py:class:`~.KingPhisherRPCClient` instance for the application.""" self._rpc_ping_event = None # this will be populated when the RPC object is authenticated to ping # the server periodically and keep the session alive self.server_events = None """The :py:class:`~.ServerEventSubscriber` instance for the application to receive server events.""" 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.data_directory('plugins')], self ) if use_plugins: self.plugin_manager.load_all()