1
0
مراية لـ https://github.com/postalserver/postal.git تم المزامنة 2025-12-01 05:43:04 +00:00

Add IP-based SMTP authentication (#1149)

هذا الالتزام موجود في:
Charlie Smurthwaite
2020-07-17 11:43:51 +01:00
ملتزم من قبل GitHub
الأصل 5c7802d218
التزام 9a3d568b27
9 ملفات معدلة مع 103 إضافات و14 حذوفات

عرض الملف

@@ -53,3 +53,21 @@ $ ->
$(element, $link.parent()).toggle() $(element, $link.parent()).toggle()
false false
toggleCredentialInputs = (type)->
$('[data-credential-key-type]').hide()
$('[data-credential-key-type] input').attr('disabled', true)
if type == 'SMTP-IP'
$('[data-credential-key-type=smtp-ip]').show()
$('[data-credential-key-type=smtp-ip] input').attr('disabled', false)
else
$('[data-credential-key-type=all]').show()
$(document).on 'change', 'select#credential_type', ->
value = $(this).val()
toggleCredentialInputs(value)
$(document).on 'turbolinks:load', ->
credentialTypeInput = $('select#credential_type')
if credentialTypeInput.length
toggleCredentialInputs(credentialTypeInput.val())

عرض الملف

@@ -95,6 +95,10 @@
background-color:$turquoise; background-color:$turquoise;
} }
.label--credentialType-smtp_ip {
background-color:$orange;
}
.label--spamStatus-not_checked { .label--spamStatus-not_checked {
background:#aaa; background:#aaa;
} }

عرض الملف

@@ -3,7 +3,7 @@ class CredentialsController < ApplicationController
include WithinOrganization include WithinOrganization
before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) } before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) }
before_action { params[:id] && @credential = @server.credentials.find_by_key!(params[:id]) } before_action { params[:id] && @credential = @server.credentials.find_by_uuid!(params[:id]) }
def index def index
@credentials = @server.credentials.order(:name).to_a @credentials = @server.credentials.order(:name).to_a
@@ -14,7 +14,7 @@ class CredentialsController < ApplicationController
end end
def create def create
@credential = @server.credentials.build(params.require(:credential).permit(:type, :name, :hold)) @credential = @server.credentials.build(params.require(:credential).permit(:type, :name, :key, :hold))
if @credential.save if @credential.save
redirect_to_with_json [organization, @server, :credentials] redirect_to_with_json [organization, @server, :credentials]
else else
@@ -23,7 +23,7 @@ class CredentialsController < ApplicationController
end end
def update def update
if @credential.update(params.require(:credential).permit(:name, :hold)) if @credential.update(params.require(:credential).permit(:name, :key, :hold))
redirect_to_with_json [organization, @server, :credentials] redirect_to_with_json [organization, @server, :credentials]
else else
render_form_errors 'edit', @credential render_form_errors 'edit', @credential

عرض الملف

@@ -12,24 +12,37 @@
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# hold :boolean default(FALSE) # hold :boolean default(FALSE)
# uuid :string(255)
# #
class Credential < ApplicationRecord class Credential < ApplicationRecord
include HasUUID
belongs_to :server belongs_to :server
TYPES = ['SMTP', 'API'] TYPES = ['SMTP', 'API', 'SMTP-IP']
validates :key, :presence => true, :uniqueness => true validates :key, :presence => true, :uniqueness => true
validates :type, :inclusion => {:in => TYPES} validates :type, :inclusion => {:in => TYPES}
validates :name, :presence => true validates :name, :presence => true
validate :validate_key_cannot_be_changed
random_string :key, :type => :chars, :length => 24, :unique => true validate :validate_key_for_smtp_ip
serialize :options, Hash serialize :options, Hash
before_validation :generate_key
def generate_key
return if self.type == 'SMTP-IP'
return if self.persisted?
self.key = SecureRandomString.new(24)
end
def to_param def to_param
key uuid
end end
def use def use
@@ -54,4 +67,30 @@ class Credential < ApplicationRecord
Base64.encode64("\0XX\0#{self.key}").strip Base64.encode64("\0XX\0#{self.key}").strip
end end
def ipaddr
return unless type == 'SMTP-IP'
@ipaddr ||= IPAddr.new(self.key)
rescue IPAddr::InvalidAddressError
nil
end
private
def validate_key_cannot_be_changed
return if new_record?
return unless key_changed?
return if type == 'SMTP-IP'
errors.add :key, "cannot be changed"
end
def validate_key_for_smtp_ip
return unless type == 'SMTP-IP'
IPAddr.new(self.key.to_s)
rescue IPAddr::InvalidAddressError
errors.add :key, "must be a valid IPv4 or IPv6 address"
end
end end

عرض الملف

@@ -15,14 +15,23 @@
%p.fieldSet__text %p.fieldSet__text
This is a friendly name so you can identify this credential later. You can enter anything This is a friendly name so you can identify this credential later. You can enter anything
you want here, the more descriptive the better. you want here, the more descriptive the better.
.fieldSet__field
.fieldSet__field{data: {credential_key_type: 'all'}}
= f.label :key, :class => 'fieldSet__label' = f.label :key, :class => 'fieldSet__label'
.fieldSet__input .fieldSet__input
= f.text_field :key, :readonly => true, :class => 'input input--text input--code', :placeholder => "Automatically generated", :tabindex => 1000, :value => (@credential.new_record? ? '' : @credential.key) = f.text_field :key, :readonly => false, :class => 'input input--text input--code', :placeholder => "Automatically generated", :tabindex => 1000, :value => (@credential.new_record? ? '' : @credential.key)
%p.fieldSet__text %p.fieldSet__text
This is the unique key which will be used to authenticate any requests to the API or our SMTP servers. This is the unique key which will be used to authenticate any requests to the API or the SMTP servers.
It will be generated randomly and cannot be changed. If you need a new token, you can create a new one and then It will be generated randomly and cannot be changed. If you need a new token, you can create a new one and then
delete the old one when you're ready. delete the old one when you're ready.
.fieldSet__field{data: {credential_key_type: 'smtp-ip'}}
= f.label :key, "Network", :class => 'fieldSet__label'
.fieldSet__input
= f.text_field :key, :class => 'input input--text input--code'
%p.fieldSet__text
This is the IP address or network that you wish to allow to authenticate to this mail server.
.fieldSet__field .fieldSet__field
= f.label :hold, :class => 'fieldSet__label' = f.label :hold, :class => 'fieldSet__label'
.fieldSet__input .fieldSet__input

عرض الملف

@@ -23,7 +23,7 @@
%li.credentialList__item %li.credentialList__item
= link_to [:edit, organization, @server, credential], :class => 'credentialList__link' do = link_to [:edit, organization, @server, credential], :class => 'credentialList__link' do
.credentialList__type .credentialList__type
%span.label{:class => "label--credentialType-#{credential.type.underscore}"}= credential.type %span.label{:class => "label--credentialType-#{credential.type.underscore}"}= credential.type.split('-').last
.credentialList__properties .credentialList__properties
%p.credentialList__name %p.credentialList__name
= credential.name = credential.name

عرض الملف

@@ -0,0 +1,8 @@
class AddUUIDToCredentials < ActiveRecord::Migration[5.2]
def change
add_column :credentials, :uuid, :string
Credential.find_each do |c|
c.update_column(:uuid, SecureRandom.uuid)
end
end
end

عرض الملف

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2018_02_16_114344) do ActiveRecord::Schema.define(version: 2020_07_17_083943) do
create_table "additional_route_endpoints", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "additional_route_endpoints", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.integer "route_id" t.integer "route_id"
@@ -68,6 +68,7 @@ ActiveRecord::Schema.define(version: 2018_02_16_114344) do
t.datetime "created_at", precision: 6 t.datetime "created_at", precision: 6
t.datetime "updated_at", precision: 6 t.datetime "updated_at", precision: 6
t.boolean "hold", default: false t.boolean "hold", default: false
t.string "uuid"
end end
create_table "domains", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "domains", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|

عرض الملف

@@ -312,10 +312,20 @@ module Postal
end end
else else
# This is unaccepted mail # User is trying to relay but is not authenticated. Try to authenticate by IP address
@credential = Credential.where(:type => 'SMTP-IP').all.sort_by { |c| c.ipaddr&.prefix || 0 }.reverse.find do |credential|
credential.ipaddr.include?(@ip_address)
end
if @credential
# Retry with credential
@credential.use
rcpt_to(data)
else
'530 Authentication required' '530 Authentication required'
end end
end end
end
def data(data) def data(data)
unless in_state(:rcpt_to_received) unless in_state(:rcpt_to_received)