Class: MailerToGo::SPF::Plan

Inherits:
Struct
  • Object
show all
Defined in:
lib/mailertogo/spf/plan.rb

Overview

What to tell a domain owner to publish, given what is already at that name.

action:

:publish     — nothing SPF-shaped is published at this name: hand them
             the standalone record. Also the fallback when DNS did not
             answer (resolved? == false), because guessing is worse.
:merge       — one record is already there and does not authorize the
             sender: they must REPLACE it with merged_record.
:deduplicate — two or more v=spf1 records are already published (the
             RFC 7208 §3.2 violation): replace ALL of them with
             merged_record.
:satisfied   — a single record already reaches the sender. Nothing to say.

over_lookup_limit is true only when the merged record was definitively measured past RFC 7208 §4.6.4's cap — never on an inconclusive DNS answer.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#actionObject

Returns the value of attribute action

Returns:

  • (Object)

    the current value of action



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def action
  @action
end

#all_qualifierObject

Returns the value of attribute all_qualifier

Returns:

  • (Object)

    the current value of all_qualifier



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def all_qualifier
  @all_qualifier
end

#existing_recordsObject

Returns the value of attribute existing_records

Returns:

  • (Object)

    the current value of existing_records



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def existing_records
  @existing_records
end

#include_nameObject

Returns the value of attribute include_name

Returns:

  • (Object)

    the current value of include_name



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def include_name
  @include_name
end

#lookup_limitObject

Returns the value of attribute lookup_limit

Returns:

  • (Object)

    the current value of lookup_limit



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def lookup_limit
  @lookup_limit
end

#lookupsObject

Returns the value of attribute lookups

Returns:

  • (Object)

    the current value of lookups



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def lookups
  @lookups
end

#merged_recordObject

Returns the value of attribute merged_record

Returns:

  • (Object)

    the current value of merged_record



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def merged_record
  @merged_record
end

#nameObject

Returns the value of attribute name

Returns:

  • (Object)

    the current value of name



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def name
  @name
end

#notesObject

Returns the value of attribute notes

Returns:

  • (Object)

    the current value of notes



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def notes
  @notes
end

#over_lookup_limitObject

Returns the value of attribute over_lookup_limit

Returns:

  • (Object)

    the current value of over_lookup_limit



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def over_lookup_limit
  @over_lookup_limit
end

#recordObject

Returns the value of attribute record

Returns:

  • (Object)

    the current value of record



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def record
  @record
end

#resolvedObject

Returns the value of attribute resolved

Returns:

  • (Object)

    the current value of resolved



23
24
25
# File 'lib/mailertogo/spf/plan.rb', line 23

def resolved
  @resolved
end

Class Method Details

.none(name: nil, record: nil) ⇒ Object

The "no instruction beyond the standalone record" plan, so no caller has to nil-check before asking a question. It answers everything the way a real :publish plan does: nothing to replace, nothing to warn about.



85
86
87
88
89
# File 'lib/mailertogo/spf/plan.rb', line 85

def self.none(name: nil, record: nil)
  new(action: :publish, name: name && Record.normalize_name(name), record: record,
      existing_records: [], notes: [], resolved: false,
      lookup_limit: Authorization::MAX_DNS_LOOKUPS, over_lookup_limit: false)
end

Instance Method Details

#deduplicate?Boolean

Returns:

  • (Boolean)


28
# File 'lib/mailertogo/spf/plan.rb', line 28

def deduplicate? = action == :deduplicate

#duplicated?Boolean

Already in the RFC-violating two-record state right now.

Returns:

  • (Boolean)


38
# File 'lib/mailertogo/spf/plan.rb', line 38

def duplicated? = deduplicate?

#merge?Boolean

Returns:

  • (Boolean)


27
# File 'lib/mailertogo/spf/plan.rb', line 27

def merge? = action == :merge

#offered_recordObject

The line to actually put in front of them. Withheld when merging would blow the lookup budget: a record we know permerrors is worse than the standalone instruction plus an explanation of what has to go first.



47
48
49
50
51
# File 'lib/mailertogo/spf/plan.rb', line 47

def offered_record
  return nil unless replacement?

  over_limit? ? nil : merged_record
end

#over_limit?Boolean

Returns:

  • (Boolean)


40
# File 'lib/mailertogo/spf/plan.rb', line 40

def over_limit? = over_lookup_limit == true

#publish?Boolean

Returns:

  • (Boolean)


26
# File 'lib/mailertogo/spf/plan.rb', line 26

def publish? = action == :publish

#replacement?Boolean

Are we asking them to REPLACE an existing record rather than add one? The difference matters enormously in a UI: "add this TXT record" next to an existing SPF record is exactly the instruction that produces the two-record permerror in the first place.

Returns:

  • (Boolean)


35
# File 'lib/mailertogo/spf/plan.rb', line 35

def replacement? = merge? || deduplicate?

#replaces?(name:, value:) ⇒ Boolean

Does this row carry the merged replacement instead of its own value? Only the SPF row at the name we resolved, and only when a merged line is actually on offer (never when it is withheld for the lookup cap).

Returns:

  • (Boolean)


70
71
72
73
74
75
76
# File 'lib/mailertogo/spf/plan.rb', line 70

def replaces?(name:, value:)
  offered = offered_record
  return false if offered.nil? || offered.empty?
  return false unless value.to_s.start_with?("v=spf1")

  Record.normalize_name(name) == self.name
end

#resolved?Boolean

Returns:

  • (Boolean)


42
# File 'lib/mailertogo/spf/plan.rb', line 42

def resolved? = resolved == true

#satisfied?Boolean

Returns:

  • (Boolean)


29
# File 'lib/mailertogo/spf/plan.rb', line 29

def satisfied? = action == :satisfied

#severityObject



59
# File 'lib/mailertogo/spf/plan.rb', line 59

def severity = warning? ? :warning : :info

#value_for(name:, value:) ⇒ Object



78
79
80
# File 'lib/mailertogo/spf/plan.rb', line 78

def value_for(name:, value:)
  replaces?(name: name, value: value) ? offered_record : value
end

#warning?Boolean

Is this state something to ALERT them about, or just an instruction? Already-duplicated and over-the-cap are defects they are living with today; a plain merge is just telling them how to add the sender correctly the first time. A fact about the plan, not about the view — two surfaces must not have to agree on it independently.

Returns:

  • (Boolean)


58
# File 'lib/mailertogo/spf/plan.rb', line 58

def warning? = duplicated? || over_limit?