chapps.policy module
Policy managers
All email policy managers inherit from EmailPolicy
, which provides a
fair amount of base functionality useful to its subclasses. So far, all but
the SPFEnforcementPolicy
are contained here. That one has
extra dependencies which are thus kept isolated. Find it in
spf_policy
.
- class chapps.policy.EmailPolicy[source]
Bases:
object
Abstract policy manager
- Subclasses must:
set class attribute
redis_key_prefix
to a unique value.define an instance method called
approve_policy_request()
.
This abstract superclass provides a standard framework for constructing Redis keys, since the main purpose of the policy manager is to make decisions about email by consulting Redis.
Instance attributes:
- config
chapps.config.CHAPPSConfig
either passed in during initialization or inherited from the environment- params
chapps.util.AttrDict
corresponding to the config for the policy manager- sentinel
a
redis.Sentinel
handle, orNone
if using only Redis- redis
a
redis.Redis
handle
- redis_key_prefix = 'chapps'
A placeholder value, since this class is abstract
Subclasses should set this to a unique prefix identifying the policy manager. For examples, see the included subclasses.
- static rediskey(prefix, *args)[source]
Format a string to serve as a Redis key for arbitrary data
- Parameters
In CHAPPS, each policy has its own prefix. What other data the policy uses to construct the key is not relevant to any other entities, though it must be sent as a string.
This routine simply joins up all the tokens with colon (
:
) characters, so it is not recommended to use them as part of the key-components (although it should ‘just work’).
- classmethod _fmtkey(*args)[source]
Convenience classmethod for Redis key construction
- Parameters
args (List[str]) – a list of key components
Subclasses may use this method which automatically discovers the prefix.
- __init__(cfg=None)[source]
Sets up a new policy manager
- Parameters
cfg (chapps.config.CHAPPSConfig) – optional
CHAPPSConfig
object for config override
Store the config and get the params for the specific policy class, which are in a config block named for the class. Using that config, set the policy manager up with a Redis handle, and an instance cache (from
expiring_dict.ExpiringDict
)
- _adapter_handle()[source]
Context manager for obtaining a database handle
In order to acquire policy configuration data, the policy manager must be able to reach the RDBMS or other policy-config data store. One of the policy manager’s priciple jobs is to obtain this data from the database and stuff it into Redis for future reference.
Adapter configuration, in terms of how to access the database, is obtained from the config object.
- approve_policy_request(ppr, **opts)[source]
Determine a policy outcome based on the PPR provided
This routine may return a boolean PASS/FAIL response, or it may for some policy classes return a string, which represents the policy outcome and is suitable for sending to Postfix.
The result of the policy approval is cached based on the instance value provided by Postfix. The memoization is done here in the superclass in order to avoid duplication of memoization code.
- Return type
- Parameters
ppr (chapps.util.PostfixPolicyRequest) –
- class chapps.policy.PostfixActions[source]
Bases:
object
Superclass for Postfix action adapters
- static defer_if_permit(msg, *args, **kwargs)[source]
Return the Postfix
DEFER_IF_PERMIT
directive with the provided message
- static reject(msg, *args, **kwargs)[source]
Return the Postfix
REJECT
directive along with the provided message
- static prepend(*args, **kwargs)[source]
Return the Postfix
PREPEND
directive. Include the header to prepend as keyword-argumentprepend
- __init__(cfg=None)[source]
Optionally supply a
chapps.config.CHAPPSConfig
instance as the first argument.
- action_for(*args, **kwargs)[source]
Abstract method which must be implemented in subclasses.
This method is intended to map responses from a policy manager onto Postfix directives.
Not all policy managers return only yes/no answers. Some, like
chapps.spf_policy.SPFEnforcementPolicy
, return a handful of different possible values, and so there must be a mechanism for allowing sites to determine what happens when each of those different outcomes occurs.
- class chapps.policy.PostfixPassfailActions[source]
Bases:
chapps.policy.PostfixActions
Postfix Actions adapter for PASS/FAIL policy responses.
Many policies return
True
if the email should be accepted/forwarded and returnFalse
if the email should be rejected/dropped. This class encapsulates the common case, and includes some logic to extract precise instructions from the config.- __init__(cfg=None)[source]
Optionally supply a
chapps.config.CHAPPSConfig
instance as the first argument.
- action_for(pf_result)[source]
Return an action closure for a pass/fail policy
Evaluates its argument
pf_result
as a boolean and returns the action closure for ‘passing’ if True, otherwise the action closure for ‘fail’. To provide backwards-compatibility with older versions, and to allow for more descriptive configuration elements, the actions may be attached to keys namedacceptance_message
orrejection_message
instead ofpassing
andfail
respectively. This is only true of policies with action factories inheriting fromPostfixPassfailActions
- class chapps.policy.PostfixOQPActions[source]
Bases:
chapps.policy.PostfixPassfailActions
Postfix Action translator for
chapps.policy.OutboundQuotaPolicy
- __init__(cfg=None)[source]
Optionally provide an instance of :py::class`chapps.config.CHAPPSConfig`.
All this class does is wire up
self.config
to point at thechapps.policy.OutboundQuotaPolicy
config block.
- class chapps.policy.PostfixGRLActions[source]
Bases:
chapps.policy.PostfixPassfailActions
Postfix Action translator for
chapps.policy.GreylistingPolicy
- __init__(cfg=None)[source]
Optionally provide an instance of
chapps.config.CHAPPSConfig
.All this class does is wire up
self.config
to point at thechapps.policy.GreylistingPolicy
config block.
- class chapps.policy.InboundPolicy[source]
Bases:
chapps.policy.EmailPolicy
- adapter_class
- domain_option_key(ppr)[source]
Return the Redis key for the domain’s Greylisting option
Uses the first of the list of tokenized recipients. Generally, inbound mail is expected to contain only one recipient per email.
- Parameters
ppr (chapps.inbound.InboundPPR) –
- class chapps.policy.GreylistingPolicy[source]
Bases:
chapps.policy.InboundPolicy
Policy manager which implements greylisting
Greylisting is a well-defined and frequently-implemented pattern. This implementation stores the tracking information in Redis.
Instance attributes (in addition to those of
EmailPolicy
):- min_defer
minimum time between retries, in seconds
- cache_ttl
how long to store tracking data, in seconds
- allow_after
success threshold, after which the client may be whitelisted
- redis_key_prefix = 'grl'
Greylisting Redis key prefix
- __init__(cfg=None, *, minimum_deferral=60, cache_ttl=86400, auto_allow_after=None)[source]
Initialize a greylisting policy manager
- Parameters
cfg (chapps.config.CHAPPSConfig) – optional config override
minimum_deferral (int) – min time between retries, in seconds
cache_ttl (int) – tracking data cache expiration time in seconds
auto_allow_after (int) – number of successful attempts needed before source client is considered trusted, and no longer incurs deferrals
- tuple_key(ppr)[source]
Return the greylisting tuple as a Redis key
The names of the values taken from
ppr
are as follows (in order):client_address
sender
recipient
- Return type
- Parameters
ppr (chapps.util.PostfixPolicyRequest) –
- client_key(ppr)[source]
Return the greylisting client key
This key indicates whether the client has enough successful resubmissions to be whitelisted.
- Parameters
ppr (chapps.util.PostfixPolicyRequest) –
- acquire_policy_for(ppr)[source]
- Parameters
ppr (chapps.inbound.InboundPPR) –
- _approve_policy_request(ppr, **opts)[source]
Perform greylisting
- Parameters
ppr (chapps.inbound.InboundPPR) –
- class chapps.policy.OutboundQuotaPolicy[source]
Bases:
chapps.policy.EmailPolicy
Policy manager which implements an outbound quota limitation
Outbound email is controlled based on the count of (attempted) transmissions in the last 24 hours. Some parameters are provided to fine-tune the behavior of the limiting algorithm.
Instance attributes (in addition to those of
EmailPolicy
):- interval
number of seconds to store transmission attemps, and to use for quota evaluation; defaults to one day
- counting_recipients
a boolean determined from the config; whether to count each recipient of a multi-recipient email as a separate transmission for quota purposes
- min_delta
defaults to 0; if set, the number of seconds which must elapse between send attempts. Currently experimental
- adapter_class
alias of
chapps.sqla_adapter.SQLAQuotaAdapter
- redis_key_prefix = 'oqp'
OutboundQuotaPolicy Redis prefix
- __init__(cfg=None, *, enforcement_interval=None, min_delta=0)[source]
Set up an outbound quota policy manager
- Parameters
cfg (chapps.config.CHAPPSConfig) – optional config override
enforcement_interval (int) – number of seconds to store transmission attemps, and to use for quota evaluation; defaults to one day
min_delta (int) – Minimum time which must pass between transmission attempts; defaults to 5 seconds to prevent spamming. Set to 0 to disable
- current_quota(user, quota=None)[source]
Provide real-time remaining quota for a user
- Parameters
user (str) – user-identifier
quota (chapps.models.Quota) – optional
Quota
record
- Returns
(remaining quota count, [remarks,…])
- Return type
The caller is anticipated to be the API. The User and Quota are both available, so the Quota may be provided, but it is not required. The
user
parameter is expected to contain a string at present, though this may change as thepydantic
data models become more tightly integrated into the codebase.The return value, intended for wrapping by the API and transmission to a client, is a tuple composed of:
the number of transmission attempts remaining to the user at the moment the query executed
a list of remarks created by the inspection routine
- reset_quota(user)[source]
Reset quota for user
- Parameters
user (str) – user-identifier
- Returns
(number of records dropped, [remarks,…])
- Return type
This method is intended for real-time management of the Redis configuration mirror. It will drop all the attempts from the outbound-quota transmission-tracking list for the named user.
- refresh_policy_cache(user, quota)[source]
API adapter method for refreshing policy config cache
- Parameters
user (str) –
quota (chapps.models.Quota) –
- acquire_policy_for(user, quota=None)[source]
Populate Redis with policy config data for a user
- Parameters
Go get the policy for a sender from the policy adapter.
If the margin needs to be configured on a per-sender basis, this is the place to adjust that. Right now, the margin is set in the config file, and applied to each user as policy config is loaded.
- _approve_policy_request(ppr)[source]
Determine whether this email falls within the quota
- Parameters
ppr (chapps.outbound.OutboundPPR) – the Postfix payload
Returns True if this email is within the quota.
This routine implements memoization on
ppr.instance
in order to overcome the Postfix double-checking weirdness. Sometimes, Postfix sends a request about a given email twice, but this is easy to spot because they will have the same value forppr.instance
.- Parameters
ppr (chapps.outbound.OutboundPPR) –
- class chapps.policy.SenderDomainAuthPolicy[source]
Bases:
chapps.policy.EmailPolicy
Policy manager implementing domain and whole-email matching for senders
This class encapsulates explicit policy regarding what sorts of masquerading authenticated users are allowed to do. Currently, two sorts of matches are handled, in succession.
First, the domain part of the email address, the entire string after the
@
, is matched against Domain entries linked to the User.If there is no Domain match, then Email entries linked to the User are checked. Email entries must match the entirety of a policy request’s
sender
attribute in order to pass.- adapter_class
- redis_key_prefix = 'sda'
Sender domain auth Redis key prefix
- __init__(cfg=None)[source]
Set up a new sender domain authorization policy manager
- Parameters
cfg (chapps.config.CHAPPSConfig) – optional config override
- sender_domain_key(ppr)[source]
Create a Redis key for a user->domain mapping
- Parameters
ppr (chapps.outbound.OutboundPPR) – a Postfix payload
- Returns
the sender domain key, by obtaining the domain part of the email address from
ppr.sender
- Return type
- sender_email_key(ppr)[source]
Create a Redis key for a user->email mapping
- Parameters
ppr (chapps.outbound.OutboundPPR) – a Postfix payload
- Returns
the sender email key, by obtaining the email address from
ppr.sender
- Return type
- _sender_domain_key(user, domain)[source]
Passes its two string params to _fmtkey
- Parameters
- Returns
a Redis key
- Return type
Should be called
_sender_auth_key
since it works with both domains and email addresses.
- _get_sender_domain(ppr)[source]
Returns the domain portion of
ppr.sender
- Parameters
ppr (chapps.outbound.OutboundPPR) – a Postfix payload
- Returns
the domain part of
ppr.sender
- Return type
- Raises
chapps.signals.TooManyAtsException – if there are more than one
@
inppr.sender
chapps.signals.NotAnEmailAddressException – if there is no
@
inppr.sender
chapps.signals.NullSenderException – if
ppr.sender
isNone
- acquire_policy_for(ppr)[source]
Populate Redis with policy config
- Parameters
ppr (chapps.outbound.OutboundPPR) – a Postfix payload
- Returns
whether the policy allows
ppr
- Return type
Populates Redis and return the policy result for
ppr
.
- check_policy_cache(user, domain)[source]
Check a particular policy cache entry for the API
- Parameters
- Returns
the cached policy
- Return type
- bulk_clear_policy_cache(users, domains=None, emails=None)[source]
Clear SDA policy cache for Users x [Domains + Users]
- bulk_check_policy_cache(users, domains=None, emails=None)[source]
Map auth subject onto user status
- Parameters
- Returns
an auth subject => user => status map as described below
- Return type
Builds a map keyed on auth subject (Domain and/or Email), full of maps from username to status. It looks a bit like this:
Mainly intended for use by the API.