コード例 #1
0
ファイル: database.py プロジェクト: yonghengshi/king-phisher
	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)
コード例 #2
0
ファイル: models.py プロジェクト: securestate/king-phisher
	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)
コード例 #3
0
	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))
コード例 #4
0
	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))
コード例 #5
0
ファイル: plugins.py プロジェクト: oJollyRogero/king-phisher
	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
コード例 #6
0
ファイル: plugins.py プロジェクト: xiaohanghang/king-phisher
	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
コード例 #7
0
	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])
コード例 #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.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 = 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
コード例 #10
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(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))
コード例 #11
0
ファイル: manager.py プロジェクト: oJollyRogero/king-phisher
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))
コード例 #12
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:
        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
コード例 #13
0
	def test_find_data_directory(self):
		self.assertIsNotNone(find.data_directory('schemas'))
コード例 #14
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.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()
コード例 #15
0
ファイル: manager.py プロジェクト: oJollyRogero/king-phisher
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
コード例 #16
0
ファイル: application.py プロジェクト: superf0sh/king-phisher
    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()
コード例 #17
0
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
コード例 #18
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:
		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
コード例 #19
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__))
		# 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()