Пример #1
0
    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()
Пример #2
0
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
Пример #3
0
	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
Пример #5
0
	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
Пример #6
0
	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()
Пример #7
0
 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
Пример #8
0
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
Пример #9
0
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
Пример #11
0
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))
Пример #12
0
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
Пример #13
0
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