SpamExperts/OrangeAssassin

View on GitHub
oa/plugins/base.py

Summary

Maintainability
A
1 hr
Test Coverage
"""Base for PAD plugins."""

from __future__ import absolute_import

from builtins import tuple
from builtins import object

try:
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
except ImportError:
    create_engine = None
    sessionmaker = None

from collections import defaultdict

import oa.conf


def dbi_to_mysql(dsn, user, password):
    conection_dates = defaultdict(int)
    dummy, driver, connection = dsn.split(":", 2)
    if driver.lower() == "mysql":
        driver = "mysql"
        db_name, hostname = connection.split(":", 1)
        conection_dates["driver"] = driver
        conection_dates["hostname"] = hostname
        conection_dates["db_name"] = db_name
        if not user or not password:
            return conection_dates
        conection_dates["user"] = user
        conection_dates["password"] = password
        return conection_dates


def dbi_to_alchemy(dsn, user, password):
    """Convert perl DBI setting to SQLAlchemy settings."""
    dummy, driver, connection = dsn.split(":", 2)
    if driver.lower() == "mysql":
        driver = "mysql+pymysql"
        db_name, hostname = connection.split(":", 1)
    elif driver.lower() == "pg":
        driver = "postgresql"
        values = dict(item.split("=") for item in connection.split(";"))
        db_name = values.get("dbname", "spamassassin")
        hostname = values.get("host", "localhost")
        if "port" in values:
            hostname = "%s:%s" % (hostname, values["port"])
    elif driver.lower() == "sqlite":
        driver = "sqlite"
        user, password, hostname = "", "", ""
        values = dict(item.split("=") for item in connection.split(";"))
        db_name = values["dbname"]
    else:
        return ""
    if not user or not password:
        return "%s://%s/%s" % (driver, hostname, db_name)
    return "%s://%s:%s@%s/%s" % (driver, user, password, hostname, db_name)


class BasePlugin(oa.conf.Conf, object):
    """Abstract class for plugins. All plugins must inherit from this class.

    This exposes methods to methods to store data and configuration options
    in the "global" context and the "local" context.

     * The "global" context is loaded once when the configuration is parsed
       and persists throughout until the plugin is reloaded.
     * The "local" context is stored per message and each new message parsed
       has its one context.

    The methods automatically stores the data under the plugin names to ensure
    that there are no name clashes between plugins.

    The plugin can also define eval rules by implementing a method and adding
    it to the eval_rules list. These will be registered after the plugin has
    been initialized.
    """
    eval_rules = tuple()
    # Defines any new rules that the plugins implements.
    cmds = None
    # See oa.conf.Conf for details on options.
    options = None

    # Database connection fields, each plugin should set their own if they need them
    dsn = None
    sql_username = ""
    sql_password = ""

    def __init__(self, ctxt):
        self.path_to_plugin = None
        super(BasePlugin, self).__init__(ctxt)

    def finish_parsing_start(self, results):
        """Called when the configuration parsing has finished but before
        the has actually been initialized from the parsed data.

        This can be used to insert new data after parsing.

        :param results: A dictionary that maps the rule names to the
          rest of the data extracted from the configuration (e.g. the
          score, description etc.)
        :return: Nothing

        """

    # XXX The name method for this is horrible, but it's likely better to have
    # XXX it the same as SA.
    def finish_parsing_end(self, ruleset):
        """Called when the configuration parsing has finished, but before the
        post-parsing. This hook can be used for e.g. to add rules to the
        ruleset.

        By default this prepares the SQLAlchemy engine if the plugin has any
        set.
        """
        connect_string = None
        self["engine"] = None
        if self.dsn:
            if self.dsn.upper().startswith("DBI"):
                # Convert from SA format.
                user = self.sql_username
                password = self.sql_password
                if not create_engine:
                    self["engine"] = dbi_to_mysql(self.dsn, user, password)
                else:
                    connect_string = dbi_to_alchemy(self.dsn, user, password)
            elif self.dsn:
                # The connect string is already in the correct format
                connect_string = self.dsn
        if connect_string is not None:
            self["engine"] = create_engine(connect_string)

    def get_engine(self):
        return self["engine"]

    def get_session(self):
        """Open a new SQLAlchemy session."""
        engine = self["engine"]
        return sessionmaker(bind=engine)()

    def check_start(self, msg):
        """Called before the metadata is extracted from the message. The
        message object passed will only have raw_msg and msg available.

        May be overridden.
        """

    def extract_metadata(self, msg, payload, text, part):
        """Called while the message metadata is extracted for every message
        part. If the part contains text, corresponding payload is provided,
        else it will be None.

        May be overridden.
        """

    def parsed_metadata(self, msg):
        """The message has been parsed and all the information can be accessed
        by the plugin.

        May be overridden.
        """

    def check_end(self, ruleset, msg):
        """The message check operation has just finished, and the results are
        about to be returned to the caller

        May be overridden.
        """

    def auto_learn_discriminator(self, ruleset, msg):
        """All message operations have finished and it can be checked for
        submission to autolearning systems

        May be overridden.
        """

    def plugin_report(self, msg):
        """Called when a message should be reported as spam.

        May be overridden.
        """

    def plugin_revoke(self, msg):
        """Called when a message should be reported as ham.

        May be overridden.
        """

    def parse_config(self, key, value):
        """Parse a config line that the normal parses doesn't know how to
        interpret.

        Use self.inhibit_further_callbacks to stop other plugins from
        processing this line.

        May be overridden.
        """
        super(BasePlugin, self).parse_config(key, value)