From fd289c46fd7c01dafc32b91a5315b160c1443c35 Mon Sep 17 00:00:00 2001 From: Charlie Smurthwaite Date: Thu, 16 Mar 2023 15:50:53 +0000 Subject: [PATCH] style(rubocop): fix all safe auto correctable offenses --- Gemfile | 80 ++--- Rakefile | 2 +- api/authenticator.rb | 14 +- api/controllers/messages_api_controller.rb | 19 +- api/controllers/send_api_controller.rb | 74 ++--- api/structures/delivery_api_structure.rb | 8 +- api/structures/message_api_structure.rb | 76 ++--- .../address_endpoints_controller.rb | 4 +- app/controllers/application_controller.rb | 40 +-- app/controllers/credentials_controller.rb | 4 +- app/controllers/domains_controller.rb | 69 ++--- app/controllers/http_endpoints_controller.rb | 4 +- app/controllers/ip_addresses_controller.rb | 4 +- app/controllers/ip_pool_rules_controller.rb | 4 +- app/controllers/ip_pools_controller.rb | 12 +- app/controllers/messages_controller.rb | 100 +++---- .../organization_ip_pools_controller.rb | 4 +- app/controllers/organizations_controller.rb | 28 +- app/controllers/routes_controller.rb | 6 +- app/controllers/servers_controller.rb | 22 +- app/controllers/sessions_controller.rb | 56 ++-- app/controllers/smtp_endpoints_controller.rb | 5 +- app/controllers/track_domains_controller.rb | 13 +- app/controllers/user_controller.rb | 50 ++-- app/controllers/users_controller.rb | 21 +- app/controllers/webhooks_controller.rb | 9 +- app/helpers/application_helper.rb | 24 +- app/jobs/action_deletion_job.rb | 4 +- app/jobs/action_deletions_job.rb | 2 + app/jobs/check_all_dns_job.rb | 4 +- app/jobs/cleanup_authie_sessions_job.rb | 4 +- app/jobs/expire_held_messages_job.rb | 8 +- app/jobs/process_message_retention_job.rb | 2 + app/jobs/prune_suppression_lists_job.rb | 2 + app/jobs/prune_webhook_requests_job.rb | 2 + app/jobs/requeue_webhooks_job.rb | 2 + app/jobs/send_notifications_job.rb | 2 + app/jobs/send_webhook_job.rb | 22 +- app/jobs/sleep_job.rb | 2 + app/jobs/tidy_raw_messages_job.rb | 1 - app/jobs/unqueue_message_job.rb | 196 +++++++------ app/jobs/webhook_delivery_job.rb | 4 +- app/mailers/app_mailer.rb | 18 +- app/mailers/application_mailer.rb | 4 +- app/models/additional_route_endpoint.rb | 51 ++-- app/models/address_endpoint.rb | 12 +- app/models/application_record.rb | 4 +- app/models/concerns/has_message.rb | 9 +- app/models/concerns/has_soft_destroy.rb | 8 +- app/models/concerns/has_uuid.rb | 4 +- app/models/credential.rb | 35 ++- app/models/domain.rb | 64 ++-- app/models/domain/dns_checks.rb | 94 +++--- app/models/domain/dns_verification.rb | 11 +- app/models/http_endpoint.rb | 20 +- app/models/incoming_message_prototype.rb | 36 +-- app/models/ip_address.rb | 6 +- app/models/ip_pool.rb | 14 +- app/models/ip_pool_rule.rb | 33 ++- app/models/organization.rb | 56 ++-- app/models/organization_ip_pool.rb | 2 + app/models/organization_user.rb | 2 +- app/models/outgoing_message_prototype.rb | 64 ++-- app/models/queued_message.rb | 41 +-- app/models/route.rb | 162 +++++------ app/models/server.rb | 186 ++++++------ app/models/smtp_endpoint.rb | 16 +- app/models/track_domain.rb | 40 ++- app/models/user.rb | 28 +- app/models/user/authentication.rb | 15 +- app/models/user_invite.rb | 16 +- app/models/webhook.rb | 22 +- app/models/webhook_event.rb | 18 +- app/models/webhook_request.rb | 56 ++-- bin/bundle | 4 +- bin/rails | 6 +- bin/rake | 4 +- bin/setup | 18 +- bin/update | 18 +- config.ru | 4 +- config/application.rb | 8 +- config/boot.rb | 8 +- config/cron.rb | 10 +- config/environment.rb | 2 +- config/environments/development.rb | 4 +- config/environments/production.rb | 5 +- config/environments/test.rb | 2 +- config/initializers/assets.rb | 2 +- config/initializers/inflections.rb | 18 +- config/initializers/mail_extensions.rb | 100 ++++--- config/initializers/postal.rb | 4 +- config/initializers/record_key_for_dom.rb | 2 + config/initializers/secret_key.rb | 2 +- config/initializers/secure_headers.rb | 2 - config/initializers/sentry.rb | 12 +- config/initializers/session_store.rb | 2 +- config/initializers/smtp.rb | 4 +- config/initializers/smtp_extensions.rb | 8 +- config/initializers/trusted_proxies.rb | 4 + config/puma.rb | 8 +- config/routes.rb | 122 ++++---- ...003195209_create_authie_sessions.authie.rb | 2 + ...0_add_indexes_to_authie_sessions.authie.rb | 2 + ...add_parent_id_to_authie_sessions.authie.rb | 2 + ...two_factor_auth_fields_to_authie.authie.rb | 2 + db/migrate/20170418200606_initial_schema.rb | 65 +++-- ..._token_hashes_to_authie_sessions.authie.rb | 2 + ..._token_hashes_on_authie_sessions.authie.rb | 4 +- ...0170428153353_remove_type_from_ip_pools.rb | 2 + ...4344_add_host_to_authie_sessions.authie.rb | 2 + .../20200717083943_add_uuid_to_credentials.rb | 2 + ...0727210551_add_priority_to_ip_addresses.rb | 2 + db/schema.rb | 4 +- lib/postal/app_logger.rb | 25 +- lib/postal/bounce_message.rb | 8 +- lib/postal/config.rb | 94 +++--- lib/postal/countries.rb | 2 + lib/postal/dkim_header.rb | 30 +- lib/postal/error.rb | 2 + lib/postal/helpers.rb | 3 +- lib/postal/http.rb | 49 ++-- lib/postal/http_sender.rb | 90 +++--- lib/postal/job.rb | 14 +- lib/postal/message_db.rb | 2 + lib/postal/message_db/click.rb | 8 +- lib/postal/message_db/database.rb | 96 +++--- lib/postal/message_db/delivery.rb | 58 ++-- lib/postal/message_db/live_stats.rb | 15 +- lib/postal/message_db/load.rb | 6 +- lib/postal/message_db/message.rb | 274 +++++++++--------- lib/postal/message_db/migration.rb | 12 +- .../migrations/01_create_migrations.rb | 11 +- .../migrations/02_create_messages.rb | 93 +++--- .../migrations/03_create_deliveries.rb | 31 +- .../migrations/04_create_live_stats.rb | 17 +- .../migrations/05_create_raw_message_sizes.rb | 19 +- .../message_db/migrations/06_create_clicks.rb | 31 +- .../message_db/migrations/07_create_loads.rb | 27 +- .../message_db/migrations/08_create_stats.rb | 27 +- .../message_db/migrations/09_create_links.rb | 27 +- .../migrations/10_create_spam_checks.rb | 25 +- .../migrations/11_add_time_to_deliveries.rb | 2 + .../migrations/12_add_hold_expiry.rb | 2 + .../13_add_index_to_message_status.rb | 2 + .../migrations/14_create_suppressions.rb | 27 +- .../migrations/15_create_webhook_requests.rb | 35 +-- .../16_add_url_and_hook_to_webhooks.rb | 2 + .../17_add_replaced_link_count_to_messages.rb | 2 + .../18_add_endpoints_to_messages.rb | 2 + .../19_convert_database_to_utf8mb4.rb | 2 + .../migrations/20_increase_links_url_size.rb | 2 + lib/postal/message_db/mysql.rb | 12 +- lib/postal/message_db/provisioner.rb | 58 ++-- lib/postal/message_db/statistics.rb | 6 +- lib/postal/message_db/suppression_list.rb | 16 +- lib/postal/message_db/webhooks.rb | 32 +- lib/postal/message_inspection.rb | 2 + lib/postal/message_inspector.rb | 6 +- lib/postal/message_inspectors.rb | 2 + lib/postal/message_inspectors/clamav.rb | 16 +- lib/postal/message_inspectors/rspamd.rb | 34 +-- .../message_inspectors/spam_assassin.rb | 25 +- lib/postal/message_parser.rb | 52 ++-- lib/postal/message_requeuer.rb | 8 +- lib/postal/mx_lookup.rb | 2 +- lib/postal/query_string.rb | 6 +- lib/postal/rabbit_mq.rb | 26 +- lib/postal/reply_separator.rb | 31 +- lib/postal/rspec_helpers.rb | 8 +- lib/postal/send_result.rb | 2 + lib/postal/sender.rb | 2 + lib/postal/smtp_sender.rb | 97 ++++--- lib/postal/smtp_server.rb | 2 + lib/postal/smtp_server/client.rb | 195 +++++++------ lib/postal/smtp_server/server.rb | 119 ++++---- lib/postal/spam_check.rb | 6 +- lib/postal/tracking_middleware.rb | 81 +++--- lib/postal/user_creator.rb | 12 +- lib/postal/version.rb | 14 +- lib/postal/worker.rb | 114 ++++---- lib/tasks/auto_annotate_models.rake | 70 ++--- lib/tasks/postal.rake | 31 +- script/default_dkim_record.rb | 2 +- script/generate_initial_config.rb | 19 +- script/insert-bounce.rb | 10 +- script/make_user.rb | 9 +- script/queue_size.rb | 8 +- script/test_app_smtp.rb | 10 +- script/version.rb | 2 +- script/worker.rb | 2 +- spec/app/models/organization_spec.rb | 4 +- .../models/outgoing_message_prototype_spec.rb | 18 +- spec/app/models/server_spec.rb | 4 +- spec/app/models/user_spec.rb | 14 +- spec/factories/domain_factory.rb | 10 +- spec/factories/organization_factory.rb | 4 +- spec/factories/server_factory.rb | 2 - spec/factories/track_domain_factory.rb | 6 +- spec/factories/user_factory.rb | 2 - spec/lib/postal/dkim_header_spec.rb | 22 +- spec/lib/postal/message_db/database.rb | 4 +- spec/lib/postal/message_parser_spec.rb | 10 +- spec/lib/postal/query_string_spec.rb | 27 +- spec/rails_helper.rb | 17 +- 204 files changed, 2611 insertions(+), 2486 deletions(-) mode change 100644 => 100755 script/queue_size.rb mode change 100644 => 100755 script/version.rb mode change 100644 => 100755 script/worker.rb diff --git a/Gemfile b/Gemfile index d5f5946..0d8b0c9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,52 +1,52 @@ -source 'https://rubygems.org' -gem 'rails', '= 5.2.8.1' -gem 'mysql2' -gem 'puma' -gem 'turbolinks', '~> 5' -gem 'haml' -gem 'nifty-utils' -gem 'nilify_blanks' -gem 'kaminari' -gem 'bcrypt' -gem 'foreman' -gem 'hashie' -gem 'authie', '~> 3.0' -gem 'dynamic_form' -gem 'changey' -gem 'mail', :git => 'https://github.com/mikel/mail.git', :branch => '2-7-stable' -gem 'autoprefixer-rails' -gem 'bunny' -gem 'secure_headers' -gem 'chronic' -gem 'basic_ssl' -gem 'clockwork' -gem 'encrypto_signo' -gem 'nio4r' -gem 'sentry-raven' -gem 'gelf' -gem 'moonrope' -gem 'jwt' -gem 'highline', :require => false -gem 'resolv', '~> 0.2.1' -gem 'dotenv-rails' +source "https://rubygems.org" +gem "authie", "~> 3.0" +gem "autoprefixer-rails" +gem "basic_ssl" +gem "bcrypt" +gem "bunny" +gem "changey" +gem "chronic" +gem "clockwork" +gem "dotenv-rails" +gem "dynamic_form" +gem "encrypto_signo" +gem "foreman" +gem "gelf" +gem "haml" +gem "hashie" +gem "highline", require: false +gem "jwt" +gem "kaminari" +gem "mail", git: "https://github.com/mikel/mail.git", branch: "2-7-stable" +gem "moonrope" +gem "mysql2" +gem "nifty-utils" +gem "nilify_blanks" +gem "nio4r" +gem "puma" +gem "rails", "= 5.2.8.1" +gem "resolv", "~> 0.2.1" +gem "secure_headers" +gem "sentry-raven" +gem "turbolinks", "~> 5" group :development, :assets do - gem 'sass-rails', '~> 5.0' - gem 'uglifier', '>= 1.3.0' - gem 'coffee-rails', '~> 4.2' - gem 'jquery-rails' + gem "coffee-rails", "~> 4.2" + gem "jquery-rails" + gem "sass-rails", "~> 5.0" + gem "uglifier", ">= 1.3.0" end group :development, :test do - gem 'byebug' + gem "byebug" end group :development do - gem 'annotate' - gem 'rspec', require: false - gem 'rspec-rails', require: false - gem "factory_bot_rails", "~> 4.0", require: false + gem "annotate" gem "database_cleaner", require: false + gem "factory_bot_rails", "~> 4.0", require: false + gem "rspec", require: false + gem "rspec-rails", require: false gem "rubocop" gem "rubocop-rails" end diff --git a/Rakefile b/Rakefile index e85f913..9a5ea73 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require_relative 'config/application' +require_relative "config/application" Rails.application.load_tasks diff --git a/api/authenticator.rb b/api/authenticator.rb index 5625955..e42b863 100644 --- a/api/authenticator.rb +++ b/api/authenticator.rb @@ -1,19 +1,19 @@ authenticator :server do friendly_name "Server Authenticator" - header "X-Server-API-Key", "The API token for a server that you wish to authenticate with.", :example => 'f29a45f0d4e1744ebaee' - error 'InvalidServerAPIKey', "The API token provided in X-Server-API-Key was not valid.", :attributes => {:token => "The token that was looked up"} - error 'ServerSuspended', "The mail server has been suspended" + header "X-Server-API-Key", "The API token for a server that you wish to authenticate with.", example: "f29a45f0d4e1744ebaee" + error "InvalidServerAPIKey", "The API token provided in X-Server-API-Key was not valid.", attributes: { token: "The token that was looked up" } + error "ServerSuspended", "The mail server has been suspended" lookup do - if key = request.headers['X-Server-API-Key'] - if credential = Credential.where(:type => 'API', :key => key).first + if key = request.headers["X-Server-API-Key"] + if credential = Credential.where(type: "API", key: key).first if credential.server.suspended? - error 'ServerSuspended' + error "ServerSuspended" else credential.use credential end else - error 'InvalidServerAPIKey', :token => key + error "InvalidServerAPIKey", token: key end end end diff --git a/api/controllers/messages_api_controller.rb b/api/controllers/messages_api_controller.rb index d24c7cd..96404ca 100644 --- a/api/controllers/messages_api_controller.rb +++ b/api/controllers/messages_api_controller.rb @@ -6,35 +6,34 @@ controller :messages do action :message do title "Return message details" description "Returns all details about a message" - param :id, "The ID of the message", :type => Integer, :required => true - returns Hash, :structure => :message, :structure_opts => {:paramable => {:expansions => false}} - error 'MessageNotFound', "No message found matching provided ID", :attributes => {:id => "The ID of the message"} + param :id, "The ID of the message", type: Integer, required: true + returns Hash, structure: :message, structure_opts: { paramable: { expansions: false } } + error "MessageNotFound", "No message found matching provided ID", attributes: { id: "The ID of the message" } action do begin message = identity.server.message(params.id) rescue Postal::MessageDB::Message::NotFound => e - error 'MessageNotFound', :id => params.id + error "MessageNotFound", id: params.id end - structure :message, message, :return => true + structure :message, message, return: true end end action :deliveries do title "Return deliveries for a message" description "Returns an array of deliveries which have been attempted for this message" - param :id, "The ID of the message", :type => Integer, :required => true - returns Array, :structure => :delivery, :structure_opts => {:full => true} - error 'MessageNotFound', "No message found matching provided ID", :attributes => {:id => "The ID of the message"} + param :id, "The ID of the message", type: Integer, required: true + returns Array, structure: :delivery, structure_opts: { full: true } + error "MessageNotFound", "No message found matching provided ID", attributes: { id: "The ID of the message" } action do begin message = identity.server.message(params.id) rescue Postal::MessageDB::Message::NotFound => e - error 'MessageNotFound', :id => params.id + error "MessageNotFound", id: params.id end message.deliveries.map do |d| structure :delivery, d end end end - end diff --git a/api/controllers/send_api_controller.rb b/api/controllers/send_api_controller.rb index 0053f51..20da531 100644 --- a/api/controllers/send_api_controller.rb +++ b/api/controllers/send_api_controller.rb @@ -7,30 +7,30 @@ controller :send do title "Send a message" description "This action allows you to send a message by providing the appropriate options" # Acceptable Parameters - param :to, "The e-mail addresses of the recipients (max 50)", :type => Array - param :cc, "The e-mail addresses of any CC contacts (max 50)", :type => Array - param :bcc, "The e-mail addresses of any BCC contacts (max 50)", :type => Array - param :from, "The e-mail address for the From header", :type => String - param :sender, "The e-mail address for the Sender header", :type => String - param :subject, "The subject of the e-mail", :type => String - param :tag, "The tag of the e-mail", :type => String - param :reply_to, "Set the reply-to address for the mail", :type => String - param :plain_body, "The plain text body of the e-mail", :type => String - param :html_body, "The HTML body of the e-mail", :type => String - param :attachments, "An array of attachments for this e-mail", :type => Array - param :headers, "A hash of additional headers", :type => Hash - param :bounce, "Is this message a bounce?", :type => :boolean + param :to, "The e-mail addresses of the recipients (max 50)", type: Array + param :cc, "The e-mail addresses of any CC contacts (max 50)", type: Array + param :bcc, "The e-mail addresses of any BCC contacts (max 50)", type: Array + param :from, "The e-mail address for the From header", type: String + param :sender, "The e-mail address for the Sender header", type: String + param :subject, "The subject of the e-mail", type: String + param :tag, "The tag of the e-mail", type: String + param :reply_to, "Set the reply-to address for the mail", type: String + param :plain_body, "The plain text body of the e-mail", type: String + param :html_body, "The HTML body of the e-mail", type: String + param :attachments, "An array of attachments for this e-mail", type: Array + param :headers, "A hash of additional headers", type: Hash + param :bounce, "Is this message a bounce?", type: :boolean # Errors - error 'ValidationError', "The provided data was not sufficient to send an email", :attributes => {:errors => "A hash of error details"} - error 'NoRecipients', "There are no recipients defined to receive this message" - error 'NoContent', "There is no content defined for this e-mail" - error 'TooManyToAddresses', "The maximum number of To addresses has been reached (maximum 50)" - error 'TooManyCCAddresses', "The maximum number of CC addresses has been reached (maximum 50)" - error 'TooManyBCCAddresses', "The maximum number of BCC addresses has been reached (maximum 50)" - error 'FromAddressMissing', "The From address is missing and is required" - error 'UnauthenticatedFromAddress', "The From address is not authorised to send mail from this server" - error 'AttachmentMissingName', "An attachment is missing a name" - error 'AttachmentMissingData', "An attachment is missing data" + error "ValidationError", "The provided data was not sufficient to send an email", attributes: { errors: "A hash of error details" } + error "NoRecipients", "There are no recipients defined to receive this message" + error "NoContent", "There is no content defined for this e-mail" + error "TooManyToAddresses", "The maximum number of To addresses has been reached (maximum 50)" + error "TooManyCCAddresses", "The maximum number of CC addresses has been reached (maximum 50)" + error "TooManyBCCAddresses", "The maximum number of BCC addresses has been reached (maximum 50)" + error "FromAddressMissing", "The From address is missing and is required" + error "UnauthenticatedFromAddress", "The From address is not authorised to send mail from this server" + error "AttachmentMissingName", "An attachment is missing a name" + error "AttachmentMissingData", "An attachment is missing data" # Return returns Hash # Action @@ -51,13 +51,14 @@ controller :send do attributes[:attachments] = [] (params.attachments || []).each do |attachment| next unless attachment.is_a?(Hash) - attributes[:attachments] << {:name => attachment['name'], :content_type => attachment['content_type'], :data => attachment['data'], :base64 => true} + + attributes[:attachments] << { name: attachment["name"], content_type: attachment["content_type"], data: attachment["data"], base64: true } end - message = OutgoingMessagePrototype.new(identity.server, request.ip, 'api', attributes) + message = OutgoingMessagePrototype.new(identity.server, request.ip, "api", attributes) message.credential = identity if message.valid? result = message.create_messages - {:message_id => message.message_id, :messages => result} + { message_id: message.message_id, messages: result } else error message.errors.first end @@ -67,44 +68,43 @@ controller :send do action :raw do title "Send a raw RFC2882 message" description "This action allows you to send us a raw RFC2822 formatted message along with the recipients that it should be sent to. This is similar to sending a message through our SMTP service." - param :mail_from, "The address that should be logged as sending the message", :type => String, :required => true - param :rcpt_to, "The addresses this message should be sent to", :type => Array, :required => true - param :data, "A base64 encoded RFC2822 message to send", :type => String, :required => true - param :bounce, "Is this message a bounce?", :type => :boolean + param :mail_from, "The address that should be logged as sending the message", type: String, required: true + param :rcpt_to, "The addresses this message should be sent to", type: Array, required: true + param :data, "A base64 encoded RFC2822 message to send", type: String, required: true + param :bounce, "Is this message a bounce?", type: :boolean returns Hash - error 'UnauthenticatedFromAddress', "The From address is not authorised to send mail from this server" + error "UnauthenticatedFromAddress", "The From address is not authorised to send mail from this server" action do # Decode the raw message raw_message = Base64.decode64(params.data) # Parse through mail to get the from/sender headers mail = Mail.new(raw_message.split("\r\n\r\n", 2).first) - from_headers = {'from' => mail.from, 'sender' => mail.sender} + from_headers = { "from" => mail.from, "sender" => mail.sender } authenticated_domain = identity.server.find_authenticated_domain_from_headers(from_headers) # If we're not authenticated, don't continue if authenticated_domain.nil? - error 'UnauthenticatedFromAddress' + error "UnauthenticatedFromAddress" end # Store the result ready to return - result = {:message_id => nil, :messages => {}} + result = { message_id: nil, messages: {} } params.rcpt_to.uniq.each do |rcpt_to| message = identity.server.message_db.new_message message.rcpt_to = rcpt_to message.mail_from = params.mail_from message.raw_message = raw_message message.received_with_ssl = true - message.scope = 'outgoing' + message.scope = "outgoing" message.domain_id = authenticated_domain.id message.credential_id = identity.id message.bounce = params.bounce ? 1 : 0 message.save result[:message_id] = message.message_id if result[:message_id].nil? - result[:messages][rcpt_to] = {:id => message.id, :token => message.token} + result[:messages][rcpt_to] = { id: message.id, token: message.token } end result end end - end diff --git a/api/structures/delivery_api_structure.rb b/api/structures/delivery_api_structure.rb index 1f845a9..a92505b 100644 --- a/api/structures/delivery_api_structure.rb +++ b/api/structures/delivery_api_structure.rb @@ -2,9 +2,9 @@ structure :delivery do basic :id basic :status basic :details - basic :output, :value => proc { o.output&.strip } - basic :sent_with_ssl, :value => proc { o.sent_with_ssl == 1 } + basic :output, value: proc { o.output&.strip } + basic :sent_with_ssl, value: proc { o.sent_with_ssl == 1 } basic :log_id - basic :time, :value => proc { o.time&.to_f } - basic :timestamp, :value => proc { o.timestamp.to_f } + basic :time, value: proc { o.time&.to_f } + basic :timestamp, value: proc { o.timestamp.to_f } end diff --git a/api/structures/message_api_structure.rb b/api/structures/message_api_structure.rb index f9df28c..3794188 100644 --- a/api/structures/message_api_structure.rb +++ b/api/structures/message_api_structure.rb @@ -2,65 +2,65 @@ structure :message do basic :id basic :token - expansion(:status) { + expansion(:status) do { - :status => o.status, - :last_delivery_attempt => o.last_delivery_attempt ? o.last_delivery_attempt.to_f : nil, - :held => o.held == 1 ? true : false, - :hold_expiry => o.hold_expiry ? o.hold_expiry.to_f : nil + status: o.status, + last_delivery_attempt: o.last_delivery_attempt ? o.last_delivery_attempt.to_f : nil, + held: o.held == 1, + hold_expiry: o.hold_expiry ? o.hold_expiry.to_f : nil } - } + end - expansion(:details) { + expansion(:details) do { - :rcpt_to => o.rcpt_to, - :mail_from => o.mail_from, - :subject => o.subject, - :message_id => o.message_id, - :timestamp => o.timestamp.to_f, - :direction => o.scope, - :size => o.size, - :bounce => o.bounce, - :bounce_for_id => o.bounce_for_id, - :tag => o.tag, - :received_with_ssl => o.received_with_ssl + rcpt_to: o.rcpt_to, + mail_from: o.mail_from, + subject: o.subject, + message_id: o.message_id, + timestamp: o.timestamp.to_f, + direction: o.scope, + size: o.size, + bounce: o.bounce, + bounce_for_id: o.bounce_for_id, + tag: o.tag, + received_with_ssl: o.received_with_ssl } - } + end - expansion(:inspection) { + expansion(:inspection) do { - :inspected => o.inspected == 1 ? true : false, - :spam => o.spam == 1 ? true : false, - :spam_score => o.spam_score.to_f, - :threat => o.threat == 1 ? true : false, - :threat_details => o.threat_details + inspected: o.inspected == 1, + spam: o.spam == 1, + spam_score: o.spam_score.to_f, + threat: o.threat == 1, + threat_details: o.threat_details } - } + end expansion(:plain_body) { o.plain_body } expansion(:html_body) { o.html_body } - expansion(:attachments) { + expansion(:attachments) do o.attachments.map do |attachment| { - :filename => attachment.filename.to_s, - :content_type => attachment.mime_type, - :data => Base64.encode64(attachment.body.to_s), - :size => attachment.body.to_s.bytesize, - :hash => Digest::SHA1.hexdigest(attachment.body.to_s) + filename: attachment.filename.to_s, + content_type: attachment.mime_type, + data: Base64.encode64(attachment.body.to_s), + size: attachment.body.to_s.bytesize, + hash: Digest::SHA1.hexdigest(attachment.body.to_s) } end - } + end expansion(:headers) { o.headers } expansion(:raw_message) { Base64.encode64(o.raw_message) } - - expansion(:activity_entries) { + + expansion(:activity_entries) do { - :loads => o.loads, - :clicks => o.clicks + loads: o.loads, + clicks: o.clicks } - } + end end diff --git a/app/controllers/address_endpoints_controller.rb b/app/controllers/address_endpoints_controller.rb index 367ca0b..bd5ce72 100644 --- a/app/controllers/address_endpoints_controller.rb +++ b/app/controllers/address_endpoints_controller.rb @@ -19,7 +19,7 @@ class AddressEndpointsController < ApplicationController flash[:notice] = params[:return_notice] if params[:return_notice].present? redirect_to_with_json [:return_to, [organization, @server, :address_endpoints]] else - render_form_errors 'new', @address_endpoint + render_form_errors "new", @address_endpoint end end @@ -27,7 +27,7 @@ class AddressEndpointsController < ApplicationController if @address_endpoint.update(safe_params) redirect_to_with_json [organization, @server, :address_endpoints] else - render_form_errors 'edit', @address_endpoint + render_form_errors "edit", @address_endpoint end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 79b46b5..817938e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,4 @@ -require 'authie/session' +require "authie/session" class ApplicationController < ActionController::Base @@ -7,37 +7,37 @@ class ApplicationController < ActionController::Base before_action :login_required before_action :set_timezone - rescue_from Authie::Session::InactiveSession, :with => :auth_session_error - rescue_from Authie::Session::ExpiredSession, :with => :auth_session_error - rescue_from Authie::Session::BrowserMismatch, :with => :auth_session_error + rescue_from Authie::Session::InactiveSession, with: :auth_session_error + rescue_from Authie::Session::ExpiredSession, with: :auth_session_error + rescue_from Authie::Session::BrowserMismatch, with: :auth_session_error private def login_required - unless logged_in? - redirect_to login_path(:return_to => request.fullpath) - end + return if logged_in? + + redirect_to login_path(return_to: request.fullpath) end def admin_required if logged_in? unless current_user.admin? - render :plain => "Not permitted" + render plain: "Not permitted" end else - redirect_to login_path(:return_to => request.fullpath) + redirect_to login_path(return_to: request.fullpath) end end def require_organization_owner - unless organization.owner == current_user - redirect_to organization_root_path(organization), :alert => "This page can only be accessed by the organization's owner (#{organization.owner.name})" - end + return if organization.owner == current_user + + redirect_to organization_root_path(organization), alert: "This page can only be accessed by the organization's owner (#{organization.owner.name})" end def auth_session_error(exception) Rails.logger.info "AuthSessionError: #{exception.class}: #{exception.message}" - redirect_to login_path(:return_to => request.fullpath) + redirect_to login_path(return_to: request.fullpath) end def page_title @@ -46,7 +46,7 @@ class ApplicationController < ActionController::Base helper_method :page_title def redirect_to_with_return_to(url, *args) - if params[:return_to].blank? || !params[:return_to].starts_with?('/') + if params[:return_to].blank? || !params[:return_to].starts_with?("/") redirect_to url_with_return_to(url), *args else redirect_to url_with_return_to(url), *args @@ -54,7 +54,7 @@ class ApplicationController < ActionController::Base end def set_timezone - Time.zone = logged_in? ? current_user.time_zone : 'UTC' + Time.zone = logged_in? ? current_user.time_zone : "UTC" end def append_info_to_payload(payload) @@ -64,7 +64,7 @@ class ApplicationController < ActionController::Base end def url_with_return_to(url) - if params[:return_to].blank? || !params[:return_to].starts_with?('/') + if params[:return_to].blank? || !params[:return_to].starts_with?("/") url_for(url) else params[:return_to] @@ -83,14 +83,14 @@ class ApplicationController < ActionController::Base end respond_to do |wants| wants.html { redirect_to url } - wants.json { render :json => {:redirect_to => url} } + wants.json { render json: { redirect_to: url } } end end def render_form_errors(action_name, object) respond_to do |wants| wants.html { render action_name } - wants.json { render :json => {:form_errors => object.errors.full_messages}, :status => 422 } + wants.json { render json: { form_errors: object.errors.full_messages }, status: :unprocessable_entity } end end @@ -102,7 +102,7 @@ class ApplicationController < ActionController::Base render options[:render_action] end end - wants.json { render :json => {:flash => {type => message}} } + wants.json { render json: { flash: { type => message } } } end end @@ -111,7 +111,7 @@ class ApplicationController < ActionController::Base auth_session.invalidate! reset_session end - Authie::Session.start(self, :user => user) + Authie::Session.start(self, user: user) @current_user = user end diff --git a/app/controllers/credentials_controller.rb b/app/controllers/credentials_controller.rb index 449d839..736233e 100644 --- a/app/controllers/credentials_controller.rb +++ b/app/controllers/credentials_controller.rb @@ -18,7 +18,7 @@ class CredentialsController < ApplicationController if @credential.save redirect_to_with_json [organization, @server, :credentials] else - render_form_errors 'new', @credential + render_form_errors "new", @credential end end @@ -26,7 +26,7 @@ class CredentialsController < ApplicationController if @credential.update(params.require(:credential).permit(:name, :key, :hold)) redirect_to_with_json [organization, @server, :credentials] else - render_form_errors 'edit', @credential + render_form_errors "edit", @credential end end diff --git a/app/controllers/domains_controller.rb b/app/controllers/domains_controller.rb index 6ca9d25..c645944 100644 --- a/app/controllers/domains_controller.rb +++ b/app/controllers/domains_controller.rb @@ -28,7 +28,7 @@ class DomainsController < ApplicationController @domain = scope.build(params.require(:domain).permit(:name, :verification_method)) if current_user.admin? - @domain.verification_method = 'DNS' + @domain.verification_method = "DNS" @domain.verified_at = Time.now end @@ -39,7 +39,7 @@ class DomainsController < ApplicationController redirect_to_with_json [:verify, organization, @server, @domain] end else - render_form_errors 'new', @domain + render_form_errors "new", @domain end end @@ -50,56 +50,57 @@ class DomainsController < ApplicationController def verify if @domain.verified? - redirect_to [organization, @server, :domains], :alert => "#{@domain.name} has already been verified." + redirect_to [organization, @server, :domains], alert: "#{@domain.name} has already been verified." return end - if request.post? - case @domain.verification_method - when 'DNS' - if @domain.verify_with_dns - redirect_to_with_json [:setup, organization, @server, @domain], :notice => "#{@domain.name} has been verified successfully. You now need to configure your DNS records." + return unless request.post? + + case @domain.verification_method + when "DNS" + if @domain.verify_with_dns + redirect_to_with_json [:setup, organization, @server, @domain], notice: "#{@domain.name} has been verified successfully. You now need to configure your DNS records." + else + respond_to do |wants| + wants.html { flash.now[:alert] = "We couldn't verify your domain. Please double check you've added the TXT record correctly." } + wants.json { render json: { flash: { alert: "We couldn't verify your domain. Please double check you've added the TXT record correctly." } } } + end + end + when "Email" + if params[:code] + if @domain.verification_token == params[:code].to_s.strip + @domain.verify + redirect_to_with_json [:setup, organization, @server, @domain], notice: "#{@domain.name} has been verified successfully. You now need to configure your DNS records." else respond_to do |wants| - wants.html { flash.now[:alert] = "We couldn't verify your domain. Please double check you've added the TXT record correctly." } - wants.json { render :json => {:flash => {:alert => "We couldn't verify your domain. Please double check you've added the TXT record correctly."}}} + wants.html { flash.now[:alert] = "Invalid verification code. Please check and try again." } + wants.json { render json: { flash: { alert: "Invalid verification code. Please check and try again." } } } end end - when 'Email' - if params[:code] - if @domain.verification_token == params[:code].to_s.strip - @domain.verify - redirect_to_with_json [:setup, organization, @server, @domain], :notice => "#{@domain.name} has been verified successfully. You now need to configure your DNS records." - else - respond_to do |wants| - wants.html { flash.now[:alert] = "Invalid verification code. Please check and try again." } - wants.json { render :json => {:flash => {:alert => "Invalid verification code. Please check and try again."}}} - end - end - elsif params[:email_address].present? - raise Postal::Error, "Invalid email address" unless @domain.verification_email_addresses.include?(params[:email_address]) - AppMailer.verify_domain(@domain, params[:email_address], current_user).deliver - if @domain.owner.is_a?(Server) - redirect_to_with_json verify_organization_server_domain_path(organization, @server, @domain, :email_address => params[:email_address]) - else - redirect_to_with_json verify_organization_domain_path(organization, @domain, :email_address => params[:email_address]) - end + elsif params[:email_address].present? + raise Postal::Error, "Invalid email address" unless @domain.verification_email_addresses.include?(params[:email_address]) + + AppMailer.verify_domain(@domain, params[:email_address], current_user).deliver + if @domain.owner.is_a?(Server) + redirect_to_with_json verify_organization_server_domain_path(organization, @server, @domain, email_address: params[:email_address]) + else + redirect_to_with_json verify_organization_domain_path(organization, @domain, email_address: params[:email_address]) end end end end def setup - unless @domain.verified? - redirect_to [:verify, organization, @server, @domain], :alert => "You can't set up DNS for this domain until it has been verified." - end + return if @domain.verified? + + redirect_to [:verify, organization, @server, @domain], alert: "You can't set up DNS for this domain until it has been verified." end def check if @domain.check_dns(:manual) - redirect_to_with_json [organization, @server, :domains], :notice => "Your DNS records for #{@domain.name} look good!" + redirect_to_with_json [organization, @server, :domains], notice: "Your DNS records for #{@domain.name} look good!" else - redirect_to_with_json [:setup, organization, @server, @domain], :alert => "There seems to be something wrong with your DNS records. Check below for information." + redirect_to_with_json [:setup, organization, @server, @domain], alert: "There seems to be something wrong with your DNS records. Check below for information." end end diff --git a/app/controllers/http_endpoints_controller.rb b/app/controllers/http_endpoints_controller.rb index bfd3831..5f8558d 100644 --- a/app/controllers/http_endpoints_controller.rb +++ b/app/controllers/http_endpoints_controller.rb @@ -19,7 +19,7 @@ class HTTPEndpointsController < ApplicationController flash[:notice] = params[:return_notice] if params[:return_notice].present? redirect_to_with_json [:return_to, [organization, @server, :http_endpoints]] else - render_form_errors 'new', @http_endpoint + render_form_errors "new", @http_endpoint end end @@ -27,7 +27,7 @@ class HTTPEndpointsController < ApplicationController if @http_endpoint.update(safe_params) redirect_to_with_json [organization, @server, :http_endpoints] else - render_form_errors 'edit', @http_endpoint + render_form_errors "edit", @http_endpoint end end diff --git a/app/controllers/ip_addresses_controller.rb b/app/controllers/ip_addresses_controller.rb index b5c76e0..fe00579 100644 --- a/app/controllers/ip_addresses_controller.rb +++ b/app/controllers/ip_addresses_controller.rb @@ -13,7 +13,7 @@ class IPAddressesController < ApplicationController if @ip_address.save redirect_to_with_json [:edit, @ip_pool] else - render_form_errors 'new', @ip_address + render_form_errors "new", @ip_address end end @@ -21,7 +21,7 @@ class IPAddressesController < ApplicationController if @ip_address.update(safe_params) redirect_to_with_json [:edit, @ip_pool] else - render_form_errors 'edit', @ip_address + render_form_errors "edit", @ip_address end end diff --git a/app/controllers/ip_pool_rules_controller.rb b/app/controllers/ip_pool_rules_controller.rb index 75c8b27..043f004 100644 --- a/app/controllers/ip_pool_rules_controller.rb +++ b/app/controllers/ip_pool_rules_controller.rb @@ -29,7 +29,7 @@ class IPPoolRulesController < ApplicationController if @ip_pool_rule.save redirect_to_with_json [organization, @server, :ip_pool_rules] else - render_form_errors 'new', @ip_pool_rule + render_form_errors "new", @ip_pool_rule end end @@ -37,7 +37,7 @@ class IPPoolRulesController < ApplicationController if @ip_pool_rule.update(safe_params) redirect_to_with_json [organization, @server, :ip_pool_rules] else - render_form_errors 'edit', @ip_pool_rule + render_form_errors "edit", @ip_pool_rule end end diff --git a/app/controllers/ip_pools_controller.rb b/app/controllers/ip_pools_controller.rb index 4cbc910..d4476bf 100644 --- a/app/controllers/ip_pools_controller.rb +++ b/app/controllers/ip_pools_controller.rb @@ -14,25 +14,25 @@ class IPPoolsController < ApplicationController def create @ip_pool = IPPool.new(safe_params) if @ip_pool.save - redirect_to_with_json [:edit, @ip_pool], :notice => "IP Pool has been added successfully. You can now add IP addresses to it." + redirect_to_with_json [:edit, @ip_pool], notice: "IP Pool has been added successfully. You can now add IP addresses to it." else - render_form_errors 'new', @ip_pool + render_form_errors "new", @ip_pool end end def update if @ip_pool.update(safe_params) - redirect_to_with_json [:edit, @ip_pool], :notice => "IP Pool has been updated." + redirect_to_with_json [:edit, @ip_pool], notice: "IP Pool has been updated." else - render_form_errors 'edit', @ip_pool + render_form_errors "edit", @ip_pool end end def destroy @ip_pool.destroy - redirect_to_with_json :ip_pools, :notice => "IP pool has been removed successfully." + redirect_to_with_json :ip_pools, notice: "IP pool has been removed successfully." rescue ActiveRecord::DeleteRestrictionError => e - redirect_to_with_json [:edit, @ip_pool], :alert => "IP pool cannot be removed because it still has associated addresses or servers." + redirect_to_with_json [:edit, @ip_pool], alert: "IP pool cannot be removed because it still has associated addresses or servers." end private diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index 62eecfd..aee5491 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -1,4 +1,4 @@ - class MessagesController < ApplicationController +class MessagesController < ApplicationController include WithinOrganization @@ -6,12 +6,12 @@ before_action { params[:id] && @message = @server.message_db.message(params[:id].to_i) } def new - if params[:direction] == 'incoming' - @message = IncomingMessagePrototype.new(@server, request.ip, 'web-ui', {}) + if params[:direction] == "incoming" + @message = IncomingMessagePrototype.new(@server, request.ip, "web-ui", {}) @message.from = session[:test_in_from] || current_user.email_tag @message.to = @server.routes.order(:name).first&.description else - @message = OutgoingMessagePrototype.new(@server, request.ip, 'web-ui', {}) + @message = OutgoingMessagePrototype.new(@server, request.ip, "web-ui", {}) @message.to = session[:test_out_to] || current_user.email_address if domain = @server.domains.verified.order(:name).first @message.from = "test@#{domain.name}" @@ -22,28 +22,28 @@ end def create - if params[:direction] == 'incoming' + if params[:direction] == "incoming" session[:test_in_from] = params[:message][:from] if params[:message] - @message = IncomingMessagePrototype.new(@server, request.ip, 'web-ui', params[:message]) - @message.attachments = [{:name => "test.txt", :content_type => "text/plain", :data => "Hello world!"}] + @message = IncomingMessagePrototype.new(@server, request.ip, "web-ui", params[:message]) + @message.attachments = [{ name: "test.txt", content_type: "text/plain", data: "Hello world!" }] else session[:test_out_to] = params[:message][:to] if params[:message] - @message = OutgoingMessagePrototype.new(@server, request.ip, 'web-ui', params[:message]) + @message = OutgoingMessagePrototype.new(@server, request.ip, "web-ui", params[:message]) end if result = @message.create_messages if result.size == 1 - redirect_to_with_json organization_server_message_path(organization, @server, result.first.last[:id]), :notice => "Message was queued successfully" + redirect_to_with_json organization_server_message_path(organization, @server, result.first.last[:id]), notice: "Message was queued successfully" else - redirect_to_with_json [:queue, organization, @server], :notice => "Messages queued successfully " + redirect_to_with_json [:queue, organization, @server], notice: "Messages queued successfully " end else respond_to do |wants| wants.html do flash.now[:alert] = "Your message could not be sent. Ensure that all fields are completed fully. #{result.errors.inspect}" - render 'new' + render "new" end wants.json do - render :json => {:flash => {:alert => "Your message could not be sent. Please check all field are completed fully."}} + render json: { flash: { alert: "Your message could not be sent. Please check all field are completed fully." } } end end @@ -52,58 +52,62 @@ def outgoing @searchable = true - get_messages('outgoing') + get_messages("outgoing") respond_to do |wants| wants.html - wants.json { render :json => { - :flash => flash.each_with_object({}) { |(type, message), hash| hash[type] = message}, - :region_html => render_to_string(:partial => 'index', :formats => [:html]) - }} + wants.json do + render json: { + flash: flash.each_with_object({}) { |(type, message), hash| hash[type] = message }, + region_html: render_to_string(partial: "index", formats: [:html]) + } + end end end def incoming @searchable = true - get_messages('incoming') + get_messages("incoming") respond_to do |wants| wants.html - wants.json { render :json => { - :flash => flash.each_with_object({}) { |(type, message), hash| hash[type] = message}, - :region_html => render_to_string(:partial => 'index', :formats => [:html]) - }} + wants.json do + render json: { + flash: flash.each_with_object({}) { |(type, message), hash| hash[type] = message }, + region_html: render_to_string(partial: "index", formats: [:html]) + } + end end end def held - get_messages('held') + get_messages("held") end def deliveries - render :json => {:html => render_to_string(:partial => 'deliveries', :locals => {:message => @message})} + render json: { html: render_to_string(partial: "deliveries", locals: { message: @message }) } end def html_raw - render :html => @message.html_body_without_tracking_image.html_safe + render html: @message.html_body_without_tracking_image.html_safe end def spam_checks - @spam_checks = @message.spam_checks.sort_by { |s| s['score']}.reverse + @spam_checks = @message.spam_checks.sort_by { |s| s["score"] }.reverse end def attachment if @message.attachments.size > params[:attachment].to_i attachment = @message.attachments[params[:attachment].to_i] - send_data attachment.body, :content_type => attachment.mime_type, :disposition => 'download', :filename => attachment.filename + send_data attachment.body, content_type: attachment.mime_type, disposition: "download", filename: attachment.filename else - redirect_to attachments_organization_server_message_path(organization, @server, @message.id), :alert => "Attachment not found. Choose an attachment from the list below." + redirect_to attachments_organization_server_message_path(organization, @server, @message.id), alert: "Attachment not found. Choose an attachment from the list below." end end def download if @message.raw_message - send_data @message.raw_message, :filename => "Message-#{organization.permalink}-#{@server.permalink}-#{@message.id}.eml", :content_type => "text/plain" + send_data @message.raw_message, filename: "Message-#{organization.permalink}-#{@server.permalink}-#{@message.id}.eml", content_type: "text/plain" else - redirect_to organization_server_message_path(organization, @server, @message.id), :alert => "We no longer have the raw message stored for this message." + redirect_to organization_server_message_path(organization, @server, @message.id), alert: "We no longer have the raw message stored for this message." end end @@ -113,10 +117,10 @@ @message.queued_message.queue! flash[:notice] = "This message will be retried shortly." elsif @message.held? - @message.add_to_message_queue(:manual => true) + @message.add_to_message_queue(manual: true) flash[:notice] = "This message has been released. Delivery will be attempted shortly." else - @message.add_to_message_queue(:manual => true) + @message.add_to_message_queue(manual: true) flash[:notice] = "This message will be redelivered shortly." end else @@ -148,10 +152,10 @@ private def get_messages(scope) - if scope == 'held' - options = {:where => {:held => 1}} + if scope == "held" + options = { where: { held: 1 } } else - options = {:where => {:scope => scope, :spam => false}, :order => :timestamp, :direction => 'desc'} + options = { where: { scope: scope, spam: false }, order: :timestamp, direction: "desc" } if @query = (params[:query] || session["msg_query_#{@server.id}_#{scope}"]).presence session["msg_query_#{@server.id}_#{scope}"] = @query @@ -160,8 +164,8 @@ flash.now[:alert] = "It doesn't appear you entered anything to filter on. Please double check your query." else @queried = true - if qs[:order] == 'oldest-first' - options[:direction] = 'asc' + if qs[:order] == "oldest-first" + options[:direction] = "asc" end options[:where][:rcpt_to] = qs[:to] if qs[:to] @@ -176,7 +180,7 @@ end options[:where][:tag] = qs[:tag] if qs[:tag] options[:where][:id] = qs[:id] if qs[:id] - options[:where][:spam] = true if qs[:spam] == 'yes' || qs[:spam] == 'y' + options[:where][:spam] = true if qs[:spam] == "yes" || qs[:spam] == "y" if qs[:before] || qs[:after] options[:where][:timestamp] = {} if qs[:before] @@ -208,21 +212,19 @@ def get_time_from_string(string) begin - if string =~ /\A(\d{2,4})\-(\d{2})\-(\d{2}) (\d{2})\:(\d{2})\z/ - time = Time.new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i) - elsif string =~ /\A(\d{2,4})\-(\d{2})\-(\d{2})\z/ - time = Time.new($1.to_i, $2.to_i, $3.to_i, 0) + if string =~ /\A(\d{2,4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})\z/ + time = Time.new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i, ::Regexp.last_match(4).to_i, ::Regexp.last_match(5).to_i) + elsif string =~ /\A(\d{2,4})-(\d{2})-(\d{2})\z/ + time = Time.new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i, 0) else - time = Chronic.parse(string, :context => :past) + time = Chronic.parse(string, context: :past) end - rescue + rescue StandardError end - if time.nil? - raise TimeUndetermined, "Couldn't determine a suitable time from '#{string}'" - else - time - end + raise TimeUndetermined, "Couldn't determine a suitable time from '#{string}'" if time.nil? + + time end end diff --git a/app/controllers/organization_ip_pools_controller.rb b/app/controllers/organization_ip_pools_controller.rb index 0437dde..ace1182 100644 --- a/app/controllers/organization_ip_pools_controller.rb +++ b/app/controllers/organization_ip_pools_controller.rb @@ -1,7 +1,7 @@ class OrganizationIPPoolsController < ApplicationController include WithinOrganization - before_action :admin_required, :only => [:assignments] + before_action :admin_required, only: [:assignments] def index @ip_pools = organization.ip_pools.order(:name) @@ -10,7 +10,7 @@ class OrganizationIPPoolsController < ApplicationController def assignments organization.ip_pool_ids = params[:ip_pools] organization.save! - redirect_to [organization, :ip_pools], :notice => "Organization IP pools have been updated successfully" + redirect_to [organization, :ip_pools], notice: "Organization IP pools have been updated successfully" end end diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index 2afc012..8aaebc4 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -1,6 +1,6 @@ class OrganizationsController < ApplicationController - before_action :admin_required, :only => [:new, :create, :delete, :destroy] + before_action :admin_required, only: [:new, :create, :delete, :destroy] def index if current_user.admin? @@ -17,48 +17,48 @@ class OrganizationsController < ApplicationController @organization = Organization.new end + def edit + @organization_obj = current_user.organizations_scope.find(organization.id) + end + def create @organization = Organization.new(params.require(:organization).permit(:name, :permalink)) @organization.owner = current_user if @organization.save redirect_to_with_json organization_root_path(@organization) else - render_form_errors 'new', @organization + render_form_errors "new", @organization end end - def edit - @organization_obj = current_user.organizations_scope.find(organization.id) - end - def update @organization_obj = current_user.organizations_scope.find(organization.id) if @organization_obj.update(params.require(:organization).permit(:name, :time_zone)) - redirect_to_with_json organization_settings_path(@organization_obj), :notice => "Settings for #{@organization_obj.name} have been saved successfully." + redirect_to_with_json organization_settings_path(@organization_obj), notice: "Settings for #{@organization_obj.name} have been saved successfully." else - render_form_errors 'edit', @organization_obj + render_form_errors "edit", @organization_obj end end def destroy unless current_user.authenticate(params[:password]) respond_to do |wants| - wants.html { redirect_to organization_delete_path(@organization), :alert => "The password you entered was not valid. Please check and try again." } - wants.json { render :json => {:alert => "The password you entered was invalid. Please check and try again."} } + wants.html { redirect_to organization_delete_path(@organization), alert: "The password you entered was not valid. Please check and try again." } + wants.json { render json: { alert: "The password you entered was invalid. Please check and try again." } } end return end organization.soft_destroy - redirect_to_with_json root_path(:nrd => 1), :notice => "#{@organization.name} has been removed successfully." + redirect_to_with_json root_path(nrd: 1), notice: "#{@organization.name} has been removed successfully." end private def organization - if [:edit, :update, :delete, :destroy].include?(action_name.to_sym) - @organization ||= params[:org_permalink] ? current_user.organizations_scope.find_by_permalink!(params[:org_permalink]) : nil - end + return unless [:edit, :update, :delete, :destroy].include?(action_name.to_sym) + + @organization ||= params[:org_permalink] ? current_user.organizations_scope.find_by_permalink!(params[:org_permalink]) : nil end helper_method :organization diff --git a/app/controllers/routes_controller.rb b/app/controllers/routes_controller.rb index ea3d03d..e4c2eee 100644 --- a/app/controllers/routes_controller.rb +++ b/app/controllers/routes_controller.rb @@ -18,7 +18,7 @@ class RoutesController < ApplicationController if @route.save redirect_to_with_json [organization, @server, :routes] else - render_form_errors 'new', @route + render_form_errors "new", @route end end @@ -26,7 +26,7 @@ class RoutesController < ApplicationController if @route.update(safe_params) redirect_to_with_json [organization, @server, :routes] else - render_form_errors 'edit', @route + render_form_errors "edit", @route end end @@ -38,7 +38,7 @@ class RoutesController < ApplicationController private def safe_params - params.require(:route).permit(:name, :domain_id, :spam_mode, :_endpoint, :additional_route_endpoints_array => []) + params.require(:route).permit(:name, :domain_id, :spam_mode, :_endpoint, additional_route_endpoints_array: []) end end diff --git a/app/controllers/servers_controller.rb b/app/controllers/servers_controller.rb index ba0f7ec..69f0385 100644 --- a/app/controllers/servers_controller.rb +++ b/app/controllers/servers_controller.rb @@ -2,7 +2,7 @@ class ServersController < ApplicationController include WithinOrganization - before_action :admin_required, :only => [:advanced, :suspend, :unsuspend] + before_action :admin_required, only: [:advanced, :suspend, :unsuspend] before_action { params[:id] && @server = organization.servers.present.find_by_permalink!(params[:id]) } def index @@ -23,7 +23,7 @@ class ServersController < ApplicationController @first_date = graph_data.first.first @last_date = graph_data.last.first @graph_data = graph_data.map(&:last) - @messages = @server.message_db.messages(:order => 'id', :direction => 'desc', :limit => 6) + @messages = @server.message_db.messages(order: "id", direction: "desc", limit: 6) end def new @@ -35,7 +35,7 @@ class ServersController < ApplicationController if @server.save redirect_to_with_json organization_server_path(organization, @server) else - render_form_errors 'new', @server + render_form_errors "new", @server end end @@ -43,9 +43,9 @@ class ServersController < ApplicationController extra_params = [:spam_threshold, :spam_failure_threshold, :postmaster_address] extra_params += [:send_limit, :allow_sender, :log_smtp_data, :outbound_spam_threshold, :message_retention_days, :raw_message_retention_days, :raw_message_retention_size] if current_user.admin? if @server.update(safe_params(*extra_params)) - redirect_to_with_json organization_server_path(organization, @server), :notice => "Server settings have been updated" + redirect_to_with_json organization_server_path(organization, @server), notice: "Server settings have been updated" else - render_form_errors 'edit', @server + render_form_errors "edit", @server end end @@ -53,31 +53,31 @@ class ServersController < ApplicationController unless current_user.authenticate(params[:password]) respond_to do |wants| wants.html do - redirect_to [:delete, organization, @server], :alert => "The password you entered was not valid. Please check and try again." + redirect_to [:delete, organization, @server], alert: "The password you entered was not valid. Please check and try again." end wants.json do - render :json => {:alert => "The password you entere was invalid. Please check and try again"} + render json: { alert: "The password you entere was invalid. Please check and try again" } end end return end @server.soft_destroy - redirect_to_with_json organization_root_path(organization), :notice => "#{@server.name} has been deleted successfully" + redirect_to_with_json organization_root_path(organization), notice: "#{@server.name} has been deleted successfully" end def queue - @messages = @server.queued_messages.order(:id => :desc).page(params[:page]) + @messages = @server.queued_messages.order(id: :desc).page(params[:page]) @messages_with_message = @messages.include_message end def suspend @server.suspend(params[:reason]) - redirect_to_with_json [organization, @server], :notice => "Server has been suspended" + redirect_to_with_json [organization, @server], notice: "Server has been suspended" end def unsuspend @server.unsuspend - redirect_to_with_json [organization, @server], :notice => "Server has been unsuspended" + redirect_to_with_json [organization, @server], notice: "Server has been unsuspended" end private diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 59a29dd..36385aa 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,8 +1,8 @@ class SessionsController < ApplicationController - layout 'sub' + layout "sub" - skip_before_action :login_required, :only => [:new, :create, :create_with_token, :begin_password_reset, :finish_password_reset, :ip, :raise_error] + skip_before_action :login_required, only: [:new, :create, :create_with_token, :begin_password_reset, :finish_password_reset, :ip, :raise_error] def create login(User.authenticate(params[:email_address], params[:password])) @@ -10,13 +10,13 @@ class SessionsController < ApplicationController redirect_to_with_return_to root_path rescue Postal::Errors::AuthenticationError => e flash.now[:alert] = "The credentials you've provided are incorrect. Please check and try again." - render 'new' + render "new" end def create_with_token - result = JWT.decode(params[:token], Postal.signing_key.to_s, 'HS256')[0] - if result['timestamp'] > 1.minute.ago.to_f - login(User.find(result['user'].to_i)) + result = JWT.decode(params[:token], Postal.signing_key.to_s, "HS256")[0] + if result["timestamp"] > 1.minute.ago.to_f + login(User.find(result["user"].to_i)) redirect_to root_path else destroy @@ -33,42 +33,42 @@ class SessionsController < ApplicationController def persist auth_session.persist! if logged_in? - render :plain => "OK" + render plain: "OK" end def begin_password_reset - if request.post? - if user = User.where(:email_address => params[:email_address]).first - user.begin_password_reset(params[:return_to]) - redirect_to login_path(:return_to => params[:return_to]), :notice => "Please check your e-mail and click the link in the e-mail we've sent you." - else - redirect_to login_reset_path(:return_to => params[:return_to]), :alert => "No user exists with that e-mail address. Please check and try again." - end + return unless request.post? + + if user = User.where(email_address: params[:email_address]).first + user.begin_password_reset(params[:return_to]) + redirect_to login_path(return_to: params[:return_to]), notice: "Please check your e-mail and click the link in the e-mail we've sent you." + else + redirect_to login_reset_path(return_to: params[:return_to]), alert: "No user exists with that e-mail address. Please check and try again." end end def finish_password_reset - @user = User.where(:password_reset_token => params[:token]).where("password_reset_token_valid_until > ?", Time.now).first + @user = User.where(password_reset_token: params[:token]).where("password_reset_token_valid_until > ?", Time.now).first if @user.nil? - redirect_to login_path(:return_to => params[:return_to]), :alert => "This link has expired or never existed. Please choose reset password to try again." + redirect_to login_path(return_to: params[:return_to]), alert: "This link has expired or never existed. Please choose reset password to try again." end - if request.post? - if params[:password].blank? - flash.now[:alert] = "You must enter a new password" - return - end - @user.password = params[:password] - @user.password_confirmation = params[:password_confirmation] - if @user.save - login(@user) - redirect_to_with_return_to root_path, :notice => "Your new password has been set and you've been logged in." - end + return unless request.post? + + if params[:password].blank? + flash.now[:alert] = "You must enter a new password" + return end + @user.password = params[:password] + @user.password_confirmation = params[:password_confirmation] + return unless @user.save + + login(@user) + redirect_to_with_return_to root_path, notice: "Your new password has been set and you've been logged in." end def ip - render :plain => "ip: #{request.ip} remote ip: #{request.remote_ip}" + render plain: "ip: #{request.ip} remote ip: #{request.remote_ip}" end end diff --git a/app/controllers/smtp_endpoints_controller.rb b/app/controllers/smtp_endpoints_controller.rb index 55f2f0b..cdefbcc 100644 --- a/app/controllers/smtp_endpoints_controller.rb +++ b/app/controllers/smtp_endpoints_controller.rb @@ -1,4 +1,5 @@ class SMTPEndpointsController < ApplicationController + include WithinOrganization before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) } before_action { params[:id] && @smtp_endpoint = @server.smtp_endpoints.find_by_uuid!(params[:id]) } @@ -17,7 +18,7 @@ class SMTPEndpointsController < ApplicationController flash[:notice] = params[:return_notice] if params[:return_notice].present? redirect_to_with_json [:return_to, [organization, @server, :smtp_endpoints]] else - render_form_errors 'new', @smtp_endpoint + render_form_errors "new", @smtp_endpoint end end @@ -25,7 +26,7 @@ class SMTPEndpointsController < ApplicationController if @smtp_endpoint.update(safe_params) redirect_to_with_json [organization, @server, :smtp_endpoints] else - render_form_errors 'edit', @smtp_endpoint + render_form_errors "edit", @smtp_endpoint end end diff --git a/app/controllers/track_domains_controller.rb b/app/controllers/track_domains_controller.rb index 369182f..31d11e2 100644 --- a/app/controllers/track_domains_controller.rb +++ b/app/controllers/track_domains_controller.rb @@ -1,4 +1,5 @@ class TrackDomainsController < ApplicationController + include WithinOrganization before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) } before_action { params[:id] && @track_domain = @server.track_domains.find_by_uuid!(params[:id]) } @@ -16,7 +17,7 @@ class TrackDomainsController < ApplicationController if @track_domain.save redirect_to_with_json [:return_to, [organization, @server, :track_domains]] else - render_form_errors 'new', @track_domain + render_form_errors "new", @track_domain end end @@ -24,7 +25,7 @@ class TrackDomainsController < ApplicationController if @track_domain.update(params.require(:track_domain).permit(:track_loads, :track_clicks, :excluded_click_domains, :ssl_enabled)) redirect_to_with_json [organization, @server, :track_domains] else - render_form_errors 'edit', @track_domain + render_form_errors "edit", @track_domain end end @@ -35,15 +36,15 @@ class TrackDomainsController < ApplicationController def check if @track_domain.check_dns - redirect_to_with_json [organization, @server, :track_domains], :notice => "Your CNAME for #{@track_domain.full_name} looks good!" + redirect_to_with_json [organization, @server, :track_domains], notice: "Your CNAME for #{@track_domain.full_name} looks good!" else - redirect_to_with_json [organization, @server, :track_domains], :alert => "There seems to be something wrong with your DNS record. Check documentation for information." + redirect_to_with_json [organization, @server, :track_domains], alert: "There seems to be something wrong with your DNS record. Check documentation for information." end end def toggle_ssl - @track_domain.update(:ssl_enabled => !@track_domain.ssl_enabled) - redirect_to_with_json [organization, @server, :track_domains], :notice => "SSL settings for #{@track_domain.full_name} updated successfully." + @track_domain.update(ssl_enabled: !@track_domain.ssl_enabled) + redirect_to_with_json [organization, @server, :track_domains], notice: "SSL settings for #{@track_domain.full_name} updated successfully." end end diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index c2b622d..861c7ff 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -1,16 +1,20 @@ class UserController < ApplicationController - skip_before_action :login_required, :only => [:new, :create, :join] + skip_before_action :login_required, only: [:new, :create, :join] def new - @user_invite = UserInvite.active.find_by!(:uuid => params[:invite_token]) + @user_invite = UserInvite.active.find_by!(uuid: params[:invite_token]) @user = User.new @user.email_address = @user_invite.email_address - render :layout => 'sub' + render layout: "sub" + end + + def edit + @user = User.find(current_user.id) end def create - @user_invite = UserInvite.active.find_by!(:uuid => params[:invite_token]) + @user_invite = UserInvite.active.find_by!(uuid: params[:invite_token]) @user = User.new(params.require(:user).permit(:first_name, :last_name, :email_address, :password, :password_confirmation)) @user.email_verified_at = Time.now if @user.save @@ -18,19 +22,19 @@ class UserController < ApplicationController self.current_user = @user redirect_to root_path else - render 'new', :layout => 'sub' + render "new", layout: "sub" end end def join - if @invite = UserInvite.where(:uuid => params[:token]).where("expires_at > ?", Time.now).first + if @invite = UserInvite.where(uuid: params[:token]).where("expires_at > ?", Time.now).first if logged_in? if request.post? @invite.accept(current_user) - redirect_to_with_json root_path(:nrd => 1), :notice => "Invitation has been accepted successfully. You now have access to this organization." + redirect_to_with_json root_path(nrd: 1), notice: "Invitation has been accepted successfully. You now have access to this organization." elsif request.delete? @invite.reject - redirect_to_with_json root_path(:nrd => 1), :notice => "Invitation has been rejected successfully." + redirect_to_with_json root_path(nrd: 1), notice: "Invitation has been rejected successfully." else @organizations = @invite.organizations.order(:name).to_a end @@ -38,14 +42,10 @@ class UserController < ApplicationController redirect_to new_signup_path(params[:token]) end else - redirect_to_with_json root_path(:nrd => 1), :alert => "The invite URL you have has expired. Please ask the person who invited you to re-send your invitation." + redirect_to_with_json root_path(nrd: 1), alert: "The invite URL you have has expired. Please ask the person who invited you to re-send your invitation." end end - def edit - @user = User.find(current_user.id) - end - def update @user = User.find(current_user.id) @user.attributes = params.require(:user).permit(:first_name, :last_name, :time_zone, :email_address, :password, :password_confirmation) @@ -56,10 +56,10 @@ class UserController < ApplicationController respond_to do |wants| wants.html do flash.now[:alert] = "The current password you have entered is incorrect. Please check and try again." - render 'edit' + render "edit" end wants.json do - render :json => {:alert => "The current password you've entered is incorrect. Please check and try again"} + render json: { alert: "The current password you've entered is incorrect. Please check and try again" } end end return @@ -69,23 +69,23 @@ class UserController < ApplicationController if @user.save if email_changed - redirect_to_with_json verify_path(:return_to => settings_path), :notice => "Your settings have been updated successfully. As you've changed, your e-mail address you'll need to verify it before you can continue." + redirect_to_with_json verify_path(return_to: settings_path), notice: "Your settings have been updated successfully. As you've changed, your e-mail address you'll need to verify it before you can continue." else - redirect_to_with_json settings_path, :notice => "Your settings have been updated successfully." + redirect_to_with_json settings_path, notice: "Your settings have been updated successfully." end else - render_form_errors 'edit', @user + render_form_errors "edit", @user end end def verify - if request.post? - if params[:code].to_s.strip == current_user.email_verification_token.to_s || (Rails.env.development? && params[:code].to_s.strip == "123456") - current_user.verify! - redirect_to_with_json [:return_to, root_path], :notice => "Thanks - your e-mail address has been verified successfully." - else - flash_now :alert, "The code you've entered isn't correct. Please check and try again." - end + return unless request.post? + + if params[:code].to_s.strip == current_user.email_verification_token.to_s || (Rails.env.development? && params[:code].to_s.strip == "123456") + current_user.verify! + redirect_to_with_json [:return_to, root_path], notice: "Thanks - your e-mail address has been verified successfully." + else + flash_now :alert, "The code you've entered isn't correct. Please check and try again." end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index fdc0d0e..42dbbc8 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -11,45 +11,44 @@ class UsersController < ApplicationController @user = User.new(admin: true) end + def edit + end + def create @user = User.new(params.require(:user).permit(:email_address, :first_name, :last_name, :password, :password_confirmation, :admin, organization_ids: [])) if @user.save - redirect_to_with_json :users, :notice => "#{@user.name} has been created successfully." + redirect_to_with_json :users, notice: "#{@user.name} has been created successfully." else - render_form_errors 'new', @user + render_form_errors "new", @user end end - def edit - end - def update @user.attributes = params.require(:user).permit(:email_address, :first_name, :last_name, :admin, organization_ids: []) if @user == current_user && !@user.admin? respond_to do |wants| wants.html { redirect_to users_path, alert: "You cannot change your own admin status" } - wants.json { render :json => {:form_errors => ["You cannot change your own admin status"]}, :status => 422 } + wants.json { render json: { form_errors: ["You cannot change your own admin status"] }, status: :unprocessable_entity } end return end if @user.save - redirect_to_with_json :users, :notice => "Permissions for #{@user.name} have been updated successfully." + redirect_to_with_json :users, notice: "Permissions for #{@user.name} have been updated successfully." else - render_form_errors 'edit', @user + render_form_errors "edit", @user end end def destroy if @user == current_user - redirect_to_with_json :users, :alert => "You cannot delete your own user." + redirect_to_with_json :users, alert: "You cannot delete your own user." return end @user.destroy! - redirect_to_with_json :users, :notice => "#{@user.name} has been removed" + redirect_to_with_json :users, notice: "#{@user.name} has been removed" end - end diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index 12dcf91..6b949dc 100644 --- a/app/controllers/webhooks_controller.rb +++ b/app/controllers/webhooks_controller.rb @@ -1,4 +1,5 @@ class WebhooksController < ApplicationController + include WithinOrganization before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) } before_action { params[:id] && @webhook = @server.webhooks.find_by_uuid!(params[:id]) } @@ -8,7 +9,7 @@ class WebhooksController < ApplicationController end def new - @webhook = @server.webhooks.build(:all_events => true) + @webhook = @server.webhooks.build(all_events: true) end def create @@ -16,7 +17,7 @@ class WebhooksController < ApplicationController if @webhook.save redirect_to_with_json [organization, @server, :webhooks] else - render_form_errors 'new', @webhook + render_form_errors "new", @webhook end end @@ -24,7 +25,7 @@ class WebhooksController < ApplicationController if @webhook.update(safe_params) redirect_to_with_json [organization, @server, :webhooks] else - render_form_errors 'edit', @webhook + render_form_errors "edit", @webhook end end @@ -45,7 +46,7 @@ class WebhooksController < ApplicationController private def safe_params - params.require(:webhook).permit(:name, :url, :all_events, :enabled, :events => []) + params.require(:webhook).permit(:name, :url, :all_events, :enabled, events: []) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f80445f..0ddde9b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,9 +1,9 @@ module ApplicationHelper def format_delivery_details(server, text) - text.gsub!(/\/) do - id = $1.to_i - link_to("message ##{id}", organization_server_message_path(server.organization, server, id), :class => "u-link") + text.gsub!(//) do + id = ::Regexp.last_match(1).to_i + link_to("message ##{id}", organization_server_message_path(server.organization, server, id), class: "u-link") end text.html_safe end @@ -29,7 +29,7 @@ module ApplicationHelper unless server_domains.empty? s << "" for domain in server_domains - selected = domain == selected_domain ? "selected='selected'" : '' + selected = domain == selected_domain ? "selected='selected'" : "" s << "" end s << "" @@ -39,12 +39,11 @@ module ApplicationHelper unless organization_domains.empty? s << "" for domain in organization_domains - selected = domain == selected_domain ? "selected='selected'" : '' + selected = domain == selected_domain ? "selected='selected'" : "" s << "" end s << "" end - end.html_safe end @@ -57,19 +56,18 @@ module ApplicationHelper s << "" for endpoint in http_endpoints value = "#{endpoint.class}##{endpoint.uuid}" - selected = value == selected_value ? "selected='selected'" : '' + selected = value == selected_value ? "selected='selected'" : "" s << "" end s << "" end - smtp_endpoints = server.smtp_endpoints.order(:name).to_a if smtp_endpoints.present? s << "" for endpoint in smtp_endpoints value = "#{endpoint.class}##{endpoint.uuid}" - selected = value == selected_value ? "selected='selected'" : '' + selected = value == selected_value ? "selected='selected'" : "" s << "" end s << "" @@ -80,7 +78,7 @@ module ApplicationHelper s << "" for endpoint in address_endpoints value = "#{endpoint.class}##{endpoint.uuid}" - selected = value == selected_value ? "selected='selected'" : '' + selected = value == selected_value ? "selected='selected'" : "" s << "" end s << "" @@ -89,14 +87,14 @@ module ApplicationHelper unless options[:other] == false s << "" Route::MODES.each do |mode| - next if mode == 'Endpoint' - selected = (selected_value == mode ? "selected='selected'" : '') + next if mode == "Endpoint" + + selected = (selected_value == mode ? "selected='selected'" : "") text = t("route_modes.#{mode.underscore}") s << "" end s << "" end - end.html_safe end diff --git a/app/jobs/action_deletion_job.rb b/app/jobs/action_deletion_job.rb index 9b32e27..b39e877 100644 --- a/app/jobs/action_deletion_job.rb +++ b/app/jobs/action_deletion_job.rb @@ -1,6 +1,7 @@ class ActionDeletionJob < Postal::Job + def perform - object = params['type'].constantize.deleted.find_by_id(params['id']) + object = params["type"].constantize.deleted.find_by_id(params["id"]) if object log "Deleting #{params['type']}##{params['id']}" object.destroy @@ -9,4 +10,5 @@ class ActionDeletionJob < Postal::Job log "Couldn't find deleted object #{params['type']}##{params['id']}" end end + end diff --git a/app/jobs/action_deletions_job.rb b/app/jobs/action_deletions_job.rb index 53618a1..ebdab73 100644 --- a/app/jobs/action_deletions_job.rb +++ b/app/jobs/action_deletions_job.rb @@ -1,4 +1,5 @@ class ActionDeletionsJob < Postal::Job + def perform Organization.deleted.each do |org| log "Permanently removing organization #{org.id} (#{org.permalink})" @@ -10,4 +11,5 @@ class ActionDeletionsJob < Postal::Job server.destroy end end + end diff --git a/app/jobs/check_all_dns_job.rb b/app/jobs/check_all_dns_job.rb index e4db2f5..29427c5 100644 --- a/app/jobs/check_all_dns_job.rb +++ b/app/jobs/check_all_dns_job.rb @@ -1,6 +1,7 @@ class CheckAllDNSJob < Postal::Job + def perform - Domain.where.not(:dns_checked_at => nil).where("dns_checked_at <= ?", 1.hour.ago).each do |domain| + Domain.where.not(dns_checked_at: nil).where("dns_checked_at <= ?", 1.hour.ago).each do |domain| log "Checking DNS for domain: #{domain.name}" domain.check_dns(:auto) end @@ -10,4 +11,5 @@ class CheckAllDNSJob < Postal::Job domain.check_dns end end + end diff --git a/app/jobs/cleanup_authie_sessions_job.rb b/app/jobs/cleanup_authie_sessions_job.rb index 5090d46..6beaa36 100644 --- a/app/jobs/cleanup_authie_sessions_job.rb +++ b/app/jobs/cleanup_authie_sessions_job.rb @@ -1,7 +1,9 @@ -require 'authie/session' +require "authie/session" class CleanupAuthieSessionsJob < Postal::Job + def perform Authie::Session.cleanup end + end diff --git a/app/jobs/expire_held_messages_job.rb b/app/jobs/expire_held_messages_job.rb index 7c43eed..a65233f 100644 --- a/app/jobs/expire_held_messages_job.rb +++ b/app/jobs/expire_held_messages_job.rb @@ -1,12 +1,14 @@ class ExpireHeldMessagesJob < Postal::Job + def perform Server.all.each do |server| - messages = server.message_db.messages(:where => { - :status => 'Held', - :hold_expiry => {:less_than => Time.now.to_f} + messages = server.message_db.messages(where: { + status: "Held", + hold_expiry: { less_than: Time.now.to_f } }) messages.each(&:cancel_hold) end end + end diff --git a/app/jobs/process_message_retention_job.rb b/app/jobs/process_message_retention_job.rb index ce53c45..d4ab415 100644 --- a/app/jobs/process_message_retention_job.rb +++ b/app/jobs/process_message_retention_job.rb @@ -1,4 +1,5 @@ class ProcessMessageRetentionJob < Postal::Job + def perform Server.all.each do |server| if server.raw_message_retention_days @@ -18,4 +19,5 @@ class ProcessMessageRetentionJob < Postal::Job end end end + end diff --git a/app/jobs/prune_suppression_lists_job.rb b/app/jobs/prune_suppression_lists_job.rb index 8a85040..772b428 100644 --- a/app/jobs/prune_suppression_lists_job.rb +++ b/app/jobs/prune_suppression_lists_job.rb @@ -1,8 +1,10 @@ class PruneSuppressionListsJob < Postal::Job + def perform Server.all.each do |s| log "Pruning suppression lists for server #{s.id}" s.message_db.suppression_list.prune end end + end diff --git a/app/jobs/prune_webhook_requests_job.rb b/app/jobs/prune_webhook_requests_job.rb index deba1a0..2b424cb 100644 --- a/app/jobs/prune_webhook_requests_job.rb +++ b/app/jobs/prune_webhook_requests_job.rb @@ -1,8 +1,10 @@ class PruneWebhookRequestsJob < Postal::Job + def perform Server.all.each do |s| log "Pruning webhook requests for server #{s.id}" s.message_db.webhooks.prune end end + end diff --git a/app/jobs/requeue_webhooks_job.rb b/app/jobs/requeue_webhooks_job.rb index 4532656..21a4684 100644 --- a/app/jobs/requeue_webhooks_job.rb +++ b/app/jobs/requeue_webhooks_job.rb @@ -1,5 +1,7 @@ class RequeueWebhooksJob < Postal::Job + def perform WebhookRequest.requeue_all end + end diff --git a/app/jobs/send_notifications_job.rb b/app/jobs/send_notifications_job.rb index 00f4232..9caf795 100644 --- a/app/jobs/send_notifications_job.rb +++ b/app/jobs/send_notifications_job.rb @@ -1,5 +1,7 @@ class SendNotificationsJob < Postal::Job + def perform Server.send_send_limit_notifications end + end diff --git a/app/jobs/send_webhook_job.rb b/app/jobs/send_webhook_job.rb index 693ca26..b0a3eab 100644 --- a/app/jobs/send_webhook_job.rb +++ b/app/jobs/send_webhook_job.rb @@ -1,25 +1,25 @@ class SendWebhookJob < Postal::Job def perform - if server = Server.find(params['server_id']) + if server = Server.find(params["server_id"]) new_items = {} - if params['payload'] - for key, value in params['payload'] - if key.to_s =~ /\A\_(\w+)/ - begin - new_items[$1] = server.message_db.message(value.to_i).webhook_hash - rescue Postal::MessageDB::Message::NotFound - end + if params["payload"] + for key, value in params["payload"] + next unless key.to_s =~ /\A_(\w+)/ + + begin + new_items[::Regexp.last_match(1)] = server.message_db.message(value.to_i).webhook_hash + rescue Postal::MessageDB::Message::NotFound end end end new_items.each do |key, value| - params['payload'].delete("_#{key}") - params['payload'][key] = value + params["payload"].delete("_#{key}") + params["payload"][key] = value end - WebhookRequest.trigger(server, params['event'], params['payload']) + WebhookRequest.trigger(server, params["event"], params["payload"]) else log "Couldn't find server with ID #{params['server_id']}" end diff --git a/app/jobs/sleep_job.rb b/app/jobs/sleep_job.rb index ec71942..17da30c 100644 --- a/app/jobs/sleep_job.rb +++ b/app/jobs/sleep_job.rb @@ -1,5 +1,7 @@ class SleepJob < Postal::Job + def perform sleep 5 end + end diff --git a/app/jobs/tidy_raw_messages_job.rb b/app/jobs/tidy_raw_messages_job.rb index e6c6374..a9654e2 100644 --- a/app/jobs/tidy_raw_messages_job.rb +++ b/app/jobs/tidy_raw_messages_job.rb @@ -1,7 +1,6 @@ class TidyRawMessagesJob < Postal::Job def perform - end end diff --git a/app/jobs/unqueue_message_job.rb b/app/jobs/unqueue_message_job.rb index a24a5b5..a44e2b9 100644 --- a/app/jobs/unqueue_message_job.rb +++ b/app/jobs/unqueue_message_job.rb @@ -1,6 +1,7 @@ class UnqueueMessageJob < Postal::Job + def perform - if original_message = QueuedMessage.find_by_id(params['id']) + if original_message = QueuedMessage.find_by_id(params["id"]) if original_message.acquire_lock log "Lock acquired for queued message #{original_message.id}" @@ -22,7 +23,7 @@ class UnqueueMessageJob < Postal::Job begin other_messages = original_message.batchable_messages(100) log "Found #{other_messages.size} associated messages to process at the same time (batch key: #{original_message.batch_key})" - rescue + rescue StandardError original_message.unlock raise end @@ -45,7 +46,7 @@ class UnqueueMessageJob < Postal::Job # if queued_message.server.suspended? log "#{log_prefix} Server is suspended. Holding message." - queued_message.message.create_delivery('Held', :details => "Mail server has been suspended. No e-mails can be processed at present. Contact support for assistance.") + queued_message.message.create_delivery("Held", details: "Mail server has been suspended. No e-mails can be processed at present. Contact support for assistance.") queued_message.destroy next end @@ -53,19 +54,19 @@ class UnqueueMessageJob < Postal::Job # We might not be able to send this any more, check the attempts if queued_message.attempts >= Postal.config.general.maximum_delivery_attempts details = "Maximum number of delivery attempts (#{queued_message.attempts}) has been reached." - if queued_message.message.scope == 'incoming' + if queued_message.message.scope == "incoming" # Send bounces to incoming e-mails when they are hard failed if bounce_id = queued_message.send_bounce details += " Bounce sent to sender (see message )" end - elsif queued_message.message.scope == 'outgoing' + elsif queued_message.message.scope == "outgoing" # Add the recipient to the suppression list - if queued_message.server.message_db.suppression_list.add(:recipient, queued_message.message.rcpt_to, :reason => "too many soft fails") + if queued_message.server.message_db.suppression_list.add(:recipient, queued_message.message.rcpt_to, reason: "too many soft fails") log "Added #{queued_message.message.rcpt_to} to suppression list because maximum attempts has been reached" details += " Added #{queued_message.message.rcpt_to} to suppression list because delivery has failed #{queued_message.attempts} times." end end - queued_message.message.create_delivery('HardFail', :details => details) + queued_message.message.create_delivery("HardFail", details: details) queued_message.destroy log "#{log_prefix} Message has reached maximum number of attempts. Hard failing." next @@ -74,15 +75,15 @@ class UnqueueMessageJob < Postal::Job # If the raw message has been removed (removed by retention) unless queued_message.message.raw_message? log "#{log_prefix} Raw message has been removed. Not sending." - queued_message.message.create_delivery('HardFail', :details => "Raw message has been removed. Cannot send message.") + queued_message.message.create_delivery("HardFail", details: "Raw message has been removed. Cannot send message.") queued_message.destroy next end # - # Handle Incoming Messages + #  Handle Incoming Messages # - if queued_message.message.scope == 'incoming' + if queued_message.message.scope == "incoming" # # If this is a bounce, we need to handle it as such # @@ -91,8 +92,8 @@ class UnqueueMessageJob < Postal::Job original_messages = queued_message.message.original_messages unless original_messages.empty? for original_message in queued_message.message.original_messages - queued_message.message.update(:bounce_for_id => original_message.id, :domain_id => original_message.domain_id) - queued_message.message.create_delivery('Processed', :details => "This has been detected as a bounce message for .") + queued_message.message.update(bounce_for_id: original_message.id, domain_id: original_message.domain_id) + queued_message.message.create_delivery("Processed", details: "This has been detected as a bounce message for .") original_message.bounce!(queued_message.message) log "#{log_prefix} Bounce linked with message #{original_message.id}" end @@ -101,11 +102,11 @@ class UnqueueMessageJob < Postal::Job end # This message was sent to the return path but hasn't been matched - # to an original message. If we have a route for this, route it - # otherwise we'll drop at this point. + #  to an original message. If we have a route for this, route it + #  otherwise we'll drop at this point. if queued_message.message.route_id.nil? log "#{log_prefix} No source messages found. Hard failing." - queued_message.message.create_delivery('HardFail', :details => "This message was a bounce but we couldn't link it with any outgoing message and there was no route for it.") + queued_message.message.create_delivery("HardFail", details: "This message was a bounce but we couldn't link it with any outgoing message and there was no route for it.") queued_message.destroy next end @@ -124,7 +125,7 @@ class UnqueueMessageJob < Postal::Job queued_message.message.inspect_message if queued_message.message.inspected == 1 is_spam = queued_message.message.spam_score > queued_message.server.spam_threshold - queued_message.message.update(:spam => 1) if is_spam + queued_message.message.update(spam: 1) if is_spam queued_message.message.append_headers( "X-Postal-Spam: #{queued_message.message.spam == 1 ? 'yes' : 'no'}", "X-Postal-Spam-Threshold: #{queued_message.server.spam_threshold}", @@ -140,15 +141,15 @@ class UnqueueMessageJob < Postal::Job # if queued_message.message.spam_score >= queued_message.server.spam_failure_threshold log "#{log_prefix} Message has a spam score higher than the server's maxmimum. Hard failing." - queued_message.message.create_delivery('HardFail', :details => "Message's spam score is higher than the failure threshold for this server. Threshold is currently #{queued_message.server.spam_failure_threshold}.") + queued_message.message.create_delivery("HardFail", details: "Message's spam score is higher than the failure threshold for this server. Threshold is currently #{queued_message.server.spam_failure_threshold}.") queued_message.destroy next end # If the server is in development mode, hold it - if queued_message.server.mode == 'Development' && !queued_message.manual? + if queued_message.server.mode == "Development" && !queued_message.manual? log "Server is in development mode so holding." - queued_message.message.create_delivery('Held', :details => "Server is in development mode.") + queued_message.message.create_delivery("Held", details: "Server is in development mode.") queued_message.destroy log "#{log_prefix} Server is in development mode. Holding." next @@ -161,16 +162,16 @@ class UnqueueMessageJob < Postal::Job if route = queued_message.message.route # If the route says we're holding quananteed mail and this is spam, we'll hold this - if route.spam_mode == 'Quarantine' && queued_message.message.spam == 1 && !queued_message.manual? - queued_message.message.create_delivery('Held', :details => "Message placed into quarantine.") + if route.spam_mode == "Quarantine" && queued_message.message.spam == 1 && !queued_message.manual? + queued_message.message.create_delivery("Held", details: "Message placed into quarantine.") queued_message.destroy log "#{log_prefix} Route says to quarantine spam message. Holding." next end # If the route says we're holding quananteed mail and this is spam, we'll hold this - if route.spam_mode == 'Fail' && queued_message.message.spam == 1 && !queued_message.manual? - queued_message.message.create_delivery('HardFail', :details => "Message is spam and the route specifies it should be failed.") + if route.spam_mode == "Fail" && queued_message.message.spam == 1 && !queued_message.manual? + queued_message.message.create_delivery("HardFail", details: "Message is spam and the route specifies it should be failed.") queued_message.destroy log "#{log_prefix} Route says to fail spam message. Hard failing." next @@ -179,8 +180,8 @@ class UnqueueMessageJob < Postal::Job # # Messages that should be blindly accepted are blindly accepted # - if route.mode == 'Accept' - queued_message.message.create_delivery('Processed', :details => "Message has been accepted but not sent to any endpoints.") + if route.mode == "Accept" + queued_message.message.create_delivery("Processed", details: "Message has been accepted but not sent to any endpoints.") queued_message.destroy log "#{log_prefix} Route says to accept without endpoint. Marking as processed." next @@ -189,14 +190,14 @@ class UnqueueMessageJob < Postal::Job # # Messages that should be accepted and held should be held # - if route.mode == 'Hold' + if route.mode == "Hold" log "#{log_prefix} Route says to hold message." if queued_message.manual? log "#{log_prefix} Message was queued manually. Marking as processed." - queued_message.message.create_delivery('Processed', :details => "Message has been processed.") + queued_message.message.create_delivery("Processed", details: "Message has been processed.") else log "#{log_prefix} Message was not queued manually. Holding." - queued_message.message.create_delivery('Held', :details => "Message has been accepted but not sent to any endpoints.") + queued_message.message.create_delivery("Held", details: "Message has been accepted but not sent to any endpoints.") end queued_message.destroy next @@ -205,45 +206,43 @@ class UnqueueMessageJob < Postal::Job # # Messages that should be bounced should be bounced (or rejected if they got this far) # - if route.mode == 'Bounce' || route.mode == 'Reject' + if route.mode == "Bounce" || route.mode == "Reject" if id = queued_message.send_bounce - queued_message.message.create_delivery('HardFail', :details => "Message has been bounced because the route asks for this. See message ") + queued_message.message.create_delivery("HardFail", details: "Message has been bounced because the route asks for this. See message ") log "#{log_prefix} Route says to bounce. Hard failing and sent bounce (#{id})." end queued_message.destroy next end - begin - if @fixed_result - result = @fixed_result + if @fixed_result + result = @fixed_result + else + case queued_message.message.endpoint + when SMTPEndpoint + sender = cached_sender(Postal::SMTPSender, queued_message.message.recipient_domain, nil, servers: [queued_message.message.endpoint]) + when HTTPEndpoint + sender = cached_sender(Postal::HTTPSender, queued_message.message.endpoint) + when AddressEndpoint + sender = cached_sender(Postal::SMTPSender, queued_message.message.endpoint.domain, nil, force_rcpt_to: queued_message.message.endpoint.address) else - case queued_message.message.endpoint - when SMTPEndpoint - sender = cached_sender(Postal::SMTPSender, queued_message.message.recipient_domain, nil, :servers => [queued_message.message.endpoint]) - when HTTPEndpoint - sender = cached_sender(Postal::HTTPSender, queued_message.message.endpoint) - when AddressEndpoint - sender = cached_sender(Postal::SMTPSender, queued_message.message.endpoint.domain, nil, :force_rcpt_to => queued_message.message.endpoint.address) - else - log "#{log_prefix} Invalid endpoint for route (#{queued_message.message.endpoint_type})" - queued_message.message.create_delivery('HardFail', :details => "Invalid endpoint for route.") - queued_message.destroy - next - end - result = sender.send_message(queued_message.message) - if result.connect_error - @fixed_result = result - end + log "#{log_prefix} Invalid endpoint for route (#{queued_message.message.endpoint_type})" + queued_message.message.create_delivery("HardFail", details: "Invalid endpoint for route.") + queued_message.destroy + next + end + result = sender.send_message(queued_message.message) + if result.connect_error + @fixed_result = result end end # Log the result log_details = result.details - if result.type =='HardFail' && result.suppress_bounce + if result.type == "HardFail" && result.suppress_bounce # The delivery hard failed, but requested that no bounce be sent log "#{log_prefix} Suppressing bounce message after hard fail" - elsif result.type =='HardFail' && queued_message.message.send_bounces? + elsif result.type == "HardFail" && queued_message.message.send_bounces? # If the message is a hard fail, send a bounce message for this message. log "#{log_prefix} Sending a bounce because message hard failed" if bounce_id = queued_message.send_bounce @@ -252,7 +251,7 @@ class UnqueueMessageJob < Postal::Job end end - queued_message.message.create_delivery(result.type, :details => log_details, :output => result.output&.strip, :sent_with_ssl => result.secure, :log_id => result.log_id, :time => result.time) + queued_message.message.create_delivery(result.type, details: log_details, output: result.output&.strip, sent_with_ssl: result.secure, log_id: result.log_id, time: result.time) if result.retry log "#{log_prefix} Message requeued for trying later." @@ -266,7 +265,7 @@ class UnqueueMessageJob < Postal::Job end else log "#{log_prefix} No route and/or endpoint available for processing. Hard failing." - queued_message.message.create_delivery('HardFail', :details => "Message does not have a route and/or endpoint available for delivery.") + queued_message.message.create_delivery("HardFail", details: "Message does not have a route and/or endpoint available for delivery.") queued_message.destroy next end @@ -275,10 +274,10 @@ class UnqueueMessageJob < Postal::Job # # Handle Outgoing Messages # - if queued_message.message.scope == 'outgoing' + if queued_message.message.scope == "outgoing" if queued_message.message.domain.nil? log "#{log_prefix} Message has no domain. Hard failing." - queued_message.message.create_delivery('HardFail', :details => "Message's domain no longer exist") + queued_message.message.create_delivery("HardFail", details: "Message's domain no longer exist") queued_message.destroy next end @@ -288,7 +287,7 @@ class UnqueueMessageJob < Postal::Job # if queued_message.message.rcpt_to.blank? log "#{log_prefix} Message has no to address. Hard failing." - queued_message.message.create_delivery('HardFail', :details => "Message doesn't have an RCPT to") + queued_message.message.create_delivery("HardFail", details: "Message doesn't have an RCPT to") queued_message.destroy next end @@ -298,7 +297,7 @@ class UnqueueMessageJob < Postal::Job # if !queued_message.manual? && queued_message.message.credential && queued_message.message.credential.hold? log "#{log_prefix} Credential wants us to hold messages. Holding." - queued_message.message.create_delivery('Held', :details => "Credential is configured to hold all messages authenticated by it.") + queued_message.message.create_delivery("Held", details: "Credential is configured to hold all messages authenticated by it.") queued_message.destroy next end @@ -308,15 +307,15 @@ class UnqueueMessageJob < Postal::Job # if !queued_message.manual? && sl = queued_message.server.message_db.suppression_list.get(:recipient, queued_message.message.rcpt_to) log "#{log_prefix} Recipient is on the suppression list. Holding." - queued_message.message.create_delivery('Held', :details => "Recipient (#{queued_message.message.rcpt_to}) is on the suppression list (reason: #{sl['reason']})") + queued_message.message.create_delivery("Held", details: "Recipient (#{queued_message.message.rcpt_to}) is on the suppression list (reason: #{sl['reason']})") queued_message.destroy next end # Extract a tag and add it to the message if one doesn't exist - if queued_message.message.tag.nil? && tag = queued_message.message.headers['x-postal-tag'] + if queued_message.message.tag.nil? && tag = queued_message.message.headers["x-postal-tag"] log "#{log_prefix} Added tag #{tag.last}" - queued_message.message.update(:tag => tag.last) + queued_message.message.update(tag: tag.last) end # Parse the content of the message as appropriate @@ -331,61 +330,60 @@ class UnqueueMessageJob < Postal::Job queued_message.message.inspect_message if queued_message.message.inspected == 1 if queued_message.message.spam_score >= queued_message.server.outbound_spam_threshold - queued_message.message.update(:spam => 1) + queued_message.message.update(spam: 1) end log "#{log_prefix} Message inspected successfully" end end if queued_message.message.spam == 1 - queued_message.message.create_delivery("HardFail", :details => "Message is likely spam. Threshold is #{queued_message.server.outbound_spam_threshold} and the message scored #{queued_message.message.spam_score}.") + queued_message.message.create_delivery("HardFail", details: "Message is likely spam. Threshold is #{queued_message.server.outbound_spam_threshold} and the message scored #{queued_message.message.spam_score}.") queued_message.destroy log "#{log_prefix} Message is spam (#{queued_message.message.spam_score}). Hard failing." next end # Add outgoing headers - if !queued_message.message.has_outgoing_headers? + unless queued_message.message.has_outgoing_headers? queued_message.message.add_outgoing_headers end # Check send limits if queued_message.server.send_limit_exceeded? # If we're over the limit, we're going to be holding this message - queued_message.server.update_columns(:send_limit_exceeded_at => Time.now, :send_limit_approaching_at => nil) - queued_message.message.create_delivery('Held', :details => "Message held because send limit (#{queued_message.server.send_limit}) has been reached.") + queued_message.server.update_columns(send_limit_exceeded_at: Time.now, send_limit_approaching_at: nil) + queued_message.message.create_delivery("Held", details: "Message held because send limit (#{queued_message.server.send_limit}) has been reached.") queued_message.destroy log "#{log_prefix} Server send limit has been exceeded. Holding." next elsif queued_message.server.send_limit_approaching? # If we're approaching the limit, just say we are but continue to process the message - queued_message.server.update_columns(:send_limit_approaching_at => Time.now, :send_limit_exceeded_at => nil) + queued_message.server.update_columns(send_limit_approaching_at: Time.now, send_limit_exceeded_at: nil) else - queued_message.server.update_columns(:send_limit_approaching_at => nil, :send_limit_exceeded_at => nil) + queued_message.server.update_columns(send_limit_approaching_at: nil, send_limit_exceeded_at: nil) end # Update the live stats for this message. queued_message.message.database.live_stats.increment(queued_message.message.scope) # If the server is in development mode, hold it - if queued_message.server.mode == 'Development' && !queued_message.manual? + if queued_message.server.mode == "Development" && !queued_message.manual? log "Server is in development mode so holding." - queued_message.message.create_delivery('Held', :details => "Server is in development mode.") + queued_message.message.create_delivery("Held", details: "Server is in development mode.") queued_message.destroy log "#{log_prefix} Server is in development mode. Holding." next end # Send the outgoing message to the SMTP sender - begin - if @fixed_result - result = @fixed_result - else - sender = cached_sender(Postal::SMTPSender, queued_message.message.recipient_domain, queued_message.ip_address) - result = sender.send_message(queued_message.message) - if result.connect_error - @fixed_result = result - end + + if @fixed_result + result = @fixed_result + else + sender = cached_sender(Postal::SMTPSender, queued_message.message.recipient_domain, queued_message.ip_address) + result = sender.send_message(queued_message.message) + if result.connect_error + @fixed_result = result end end @@ -393,30 +391,26 @@ class UnqueueMessageJob < Postal::Job # If the message has been hard failed, check to see how many other recent hard fails we've had for the address # and if there are more than 2, suppress the address for 30 days. # - if result.type == 'HardFail' - recent_hard_fails = queued_message.server.message_db.select(:messages, :where => {:rcpt_to => queued_message.message.rcpt_to, :status => 'HardFail', :timestamp => {:greater_than => 24.hours.ago.to_f}}, :count => true) - if recent_hard_fails >= 1 - if queued_message.server.message_db.suppression_list.add(:recipient, queued_message.message.rcpt_to, :reason => "too many hard fails") - log "#{log_prefix} Added #{queued_message.message.rcpt_to} to suppression list because #{recent_hard_fails} hard fails in 24 hours" - result.details += "." if result.details =~ /\.\z/ - result.details += " Recipient added to suppression list (too many hard fails)." - end + if result.type == "HardFail" + recent_hard_fails = queued_message.server.message_db.select(:messages, where: { rcpt_to: queued_message.message.rcpt_to, status: "HardFail", timestamp: { greater_than: 24.hours.ago.to_f } }, count: true) + if recent_hard_fails >= 1 && queued_message.server.message_db.suppression_list.add(:recipient, queued_message.message.rcpt_to, reason: "too many hard fails") + log "#{log_prefix} Added #{queued_message.message.rcpt_to} to suppression list because #{recent_hard_fails} hard fails in 24 hours" + result.details += "." if result.details =~ /\.\z/ + result.details += " Recipient added to suppression list (too many hard fails)." end end # # If a message is sent successfully, remove the users from the suppression list # - if result.type == 'Sent' - if queued_message.server.message_db.suppression_list.remove(:recipient, queued_message.message.rcpt_to) - log "#{log_prefix} Removed #{queued_message.message.rcpt_to} from suppression list because success" - result.details += "." if result.details =~ /\.\z/ - result.details += " Recipient removed from suppression list." - end + if result.type == "Sent" && queued_message.server.message_db.suppression_list.remove(:recipient, queued_message.message.rcpt_to) + log "#{log_prefix} Removed #{queued_message.message.rcpt_to} from suppression list because success" + result.details += "." if result.details =~ /\.\z/ + result.details += " Recipient removed from suppression list." end # Log the result - queued_message.message.create_delivery(result.type, :details => result.details, :output => result.output, :sent_with_ssl => result.secure, :log_id => result.log_id, :time => result.time) + queued_message.message.create_delivery(result.type, details: result.details, output: result.output, sent_with_ssl: result.secure, log_id: result.log_id, time: result.time) if result.retry log "#{log_prefix} Message requeued for trying later." queued_message.retry_later(result.retry.is_a?(Integer) ? result.retry : nil) @@ -425,17 +419,16 @@ class UnqueueMessageJob < Postal::Job queued_message.destroy end end - - rescue => e + rescue StandardError => e log "#{log_prefix} Internal error: #{e.class}: #{e.message}" e.backtrace.each { |e| log("#{log_prefix} #{e}") } queued_message.retry_later log "#{log_prefix} Queued message was unlocked" if defined?(Raven) - Raven.capture_exception(e, :extra => {:job_id => self.id, :server_id => queued_message.server_id, :message_id => queued_message.message_id}) + Raven.capture_exception(e, extra: { job_id: self.id, server_id: queued_message.server_id, message_id: queued_message.message_id }) end if queued_message.message - queued_message.message.create_delivery("Error", :details => "An internal error occurred while sending this message. This message will be retried automatically. If this persists, contact support for assistance.", :output => "#{e.class}: #{e.message}", :log_id => "J-#{self.id}") + queued_message.message.create_delivery("Error", details: "An internal error occurred while sending this message. This message will be retried automatically. If this persists, contact support for assistance.", output: "#{e.class}: #{e.message}", log_id: "J-#{self.id}") end end end @@ -447,7 +440,11 @@ class UnqueueMessageJob < Postal::Job log "No queued message with ID #{params['id']} was available for processing." end ensure - @sender&.finish rescue nil + begin + @sender&.finish + rescue StandardError + nil + end end private @@ -459,4 +456,5 @@ class UnqueueMessageJob < Postal::Job sender end end + end diff --git a/app/jobs/webhook_delivery_job.rb b/app/jobs/webhook_delivery_job.rb index 595c725..d4217db 100644 --- a/app/jobs/webhook_delivery_job.rb +++ b/app/jobs/webhook_delivery_job.rb @@ -1,6 +1,7 @@ class WebhookDeliveryJob < Postal::Job + def perform - if webhook_request = WebhookRequest.find_by_id(params['id']) + if webhook_request = WebhookRequest.find_by_id(params["id"]) if webhook_request.deliver log "Succesfully delivered" else @@ -10,4 +11,5 @@ class WebhookDeliveryJob < Postal::Job log "No webhook request found with ID '#{params['id']}'" end end + end diff --git a/app/mailers/app_mailer.rb b/app/mailers/app_mailer.rb index 25e5fb6..61f366b 100644 --- a/app/mailers/app_mailer.rb +++ b/app/mailers/app_mailer.rb @@ -2,50 +2,50 @@ class AppMailer < ApplicationMailer def verify_email_address(user) @user = user - mail :to => @user.email_address, :subject => "Verify your new e-mail address" + mail to: @user.email_address, subject: "Verify your new e-mail address" end def new_user(user) @user = user - mail :to => @user.email_address, :subject => "Welcome to Postal" + mail to: @user.email_address, subject: "Welcome to Postal" end def user_invite(user_invite, organization) @user_invite = user_invite @organization = organization - mail :to => @user_invite.email_address, :subject => "Access the #{organization.name} organization on Postal" + mail to: @user_invite.email_address, subject: "Access the #{organization.name} organization on Postal" end def verify_domain(domain, email_address, user) @domain = domain @email_address = email_address @user = user - mail :to => email_address, :subject => "Verify your ownership of #{@domain.name}" + mail to: email_address, subject: "Verify your ownership of #{@domain.name}" end def password_reset(user, return_to = nil) @user = user @return_to = return_to - mail :to => @user.email_address, :subject => "Reset your Postal password" + mail to: @user.email_address, subject: "Reset your Postal password" end def server_send_limit_approaching(server) @server = server - mail :to => @server.organization.notification_addresses, :subject => "[#{server.full_permalink}] Mail server is approaching its send limit" + mail to: @server.organization.notification_addresses, subject: "[#{server.full_permalink}] Mail server is approaching its send limit" end def server_send_limit_exceeded(server) @server = server - mail :to => @server.organization.notification_addresses, :subject => "[#{server.full_permalink}] Mail server has exceeded its send limit" + mail to: @server.organization.notification_addresses, subject: "[#{server.full_permalink}] Mail server has exceeded its send limit" end def server_suspended(server) @server = server - mail :to => @server.organization.notification_addresses, :subject => "[#{server.full_permalink}] Your mail server has been suspended" + mail to: @server.organization.notification_addresses, subject: "[#{server.full_permalink}] Your mail server has been suspended" end def test_message(recipient) - mail :to => recipient, :subject => "Postal SMTP Test Message" + mail to: recipient, subject: "Postal SMTP Test Message" end end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 542129e..75864cb 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,6 @@ class ApplicationMailer < ActionMailer::Base - default :from => "#{Postal.smtp_from_name} <#{Postal.smtp_from_address}>" + + default from: "#{Postal.smtp_from_name} <#{Postal.smtp_from_address}>" layout false + end diff --git a/app/models/additional_route_endpoint.rb b/app/models/additional_route_endpoint.rb index d94ac14..1282d9e 100644 --- a/app/models/additional_route_endpoint.rb +++ b/app/models/additional_route_endpoint.rb @@ -13,22 +13,21 @@ class AdditionalRouteEndpoint < ApplicationRecord belongs_to :route - belongs_to :endpoint, :polymorphic => true + belongs_to :endpoint, polymorphic: true validate :validate_endpoint_belongs_to_server validate :validate_wildcard validate :validate_uniqueness def self.find_by_endpoint(endpoint) - class_name, id = endpoint.split('#', 2) + class_name, id = endpoint.split("#", 2) unless Route::ENDPOINT_TYPES.include?(class_name) raise Postal::Error, "Invalid endpoint class name '#{class_name}'" end - if uuid = class_name.constantize.find_by_uuid(id) - where(:endpoint_type => class_name, :endpoint_id => uuid).first - else - nil - end + + return unless uuid = class_name.constantize.find_by_uuid(id) + + where(endpoint_type: class_name, endpoint_id: uuid).first end def _endpoint @@ -38,39 +37,37 @@ class AdditionalRouteEndpoint < ApplicationRecord def _endpoint=(value) if value.blank? self.endpoint = nil - else - if value =~ /\#/ - class_name, id = value.split('#', 2) - unless Route::ENDPOINT_TYPES.include?(class_name) - raise Postal::Error, "Invalid endpoint class name '#{class_name}'" - end - self.endpoint = class_name.constantize.find_by_uuid(id) - else - self.endpoint = nil + elsif value =~ /\#/ + class_name, id = value.split("#", 2) + unless Route::ENDPOINT_TYPES.include?(class_name) + raise Postal::Error, "Invalid endpoint class name '#{class_name}'" end + + self.endpoint = class_name.constantize.find_by_uuid(id) + else + self.endpoint = nil end end private def validate_endpoint_belongs_to_server - if self.endpoint && self.endpoint&.server != self.route.server - errors.add :endpoint, :invalid - end + return unless endpoint && endpoint&.server != route.server + + errors.add :endpoint, :invalid end def validate_uniqueness - if self.endpoint == self.route.endpoint - errors.add :base, "You can only add an endpoint to a route once" - end + return unless endpoint == route.endpoint + + errors.add :base, "You can only add an endpoint to a route once" end def validate_wildcard - if self.route.wildcard? - if self.endpoint_type == 'SMTPEndpoint' || self.endpoint_type == 'AddressEndpoint' - errors.add :base, "SMTP or address endpoints are not permitted on wildcard routes" - end - end + return unless route.wildcard? + return unless endpoint_type == "SMTPEndpoint" || endpoint_type == "AddressEndpoint" + + errors.add :base, "SMTP or address endpoints are not permitted on wildcard routes" end end diff --git a/app/models/address_endpoint.rb b/app/models/address_endpoint.rb index dd3b193..a2609be 100644 --- a/app/models/address_endpoint.rb +++ b/app/models/address_endpoint.rb @@ -16,10 +16,10 @@ class AddressEndpoint < ApplicationRecord include HasUUID belongs_to :server - has_many :routes, :as => :endpoint - has_many :additional_route_endpoints, :dependent => :destroy, :as => :endpoint + has_many :routes, as: :endpoint + has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint - validates :address, :presence => true, :format => {:with => /@/}, :uniqueness => {:scope => [:server_id], :message => "has already been added"} + validates :address, presence: true, format: { with: /@/ }, uniqueness: { scope: [:server_id], message: "has already been added" } before_destroy :update_routes @@ -28,15 +28,15 @@ class AddressEndpoint < ApplicationRecord end def update_routes - self.routes.each { |r| r.update(:endpoint => nil, :mode => 'Reject') } + routes.each { |r| r.update(endpoint: nil, mode: "Reject") } end def description - self.address + address end def domain - address.split('@', 2).last + address.split("@", 2).last end end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 2f70d13..03c6a5b 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,5 +1,7 @@ class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true - self.inheritance_column = 'sti_type' + self.inheritance_column = "sti_type" nilify_blanks + end diff --git a/app/models/concerns/has_message.rb b/app/models/concerns/has_message.rb index 928df64..97a02a0 100644 --- a/app/models/concerns/has_message.rb +++ b/app/models/concerns/has_message.rb @@ -5,7 +5,7 @@ module HasMessage end def message - @message ||= self.server.message_db.message(self.message_id) + @message ||= server.message_db.message(message_id) end def message=(message) @@ -14,6 +14,7 @@ module HasMessage end module ClassMethods + def include_message queued_messages = all.to_a server_ids = queued_messages.map(&:server_id).uniq @@ -22,10 +23,11 @@ module HasMessage elsif server_ids.size > 1 raise Postal::Error, "'include_message' can only be used on collections of messages from the same server" end + message_ids = queued_messages.map(&:message_id).uniq server = queued_messages.first&.server - messages = server.message_db.messages(:where => {:id => message_ids}).each_with_object({}) do |message, hash| - hash[message.id] = message + messages = server.message_db.messages(where: { id: message_ids }).index_by do |message| + message.id end queued_messages.each do |queued_message| if m = messages[queued_message.message_id] @@ -33,6 +35,7 @@ module HasMessage end end end + end end diff --git a/app/models/concerns/has_soft_destroy.rb b/app/models/concerns/has_soft_destroy.rb index b2af5fe..7ad2865 100644 --- a/app/models/concerns/has_soft_destroy.rb +++ b/app/models/concerns/has_soft_destroy.rb @@ -3,16 +3,16 @@ module HasSoftDestroy def self.included(base) base.define_callbacks :soft_destroy base.class_eval do - scope :deleted, -> { where.not(:deleted_at => nil) } - scope :present, -> { where(:deleted_at => nil) } + scope :deleted, -> { where.not(deleted_at: nil) } + scope :present, -> { where(deleted_at: nil) } end end def soft_destroy run_callbacks :soft_destroy do self.deleted_at = Time.now - self.save! - ActionDeletionJob.queue(:main, :type => self.class.name, :id => self.id) + save! + ActionDeletionJob.queue(:main, type: self.class.name, id: id) end end diff --git a/app/models/concerns/has_uuid.rb b/app/models/concerns/has_uuid.rb index f981bcc..a9b87a7 100644 --- a/app/models/concerns/has_uuid.rb +++ b/app/models/concerns/has_uuid.rb @@ -1,11 +1,13 @@ module HasUUID + def self.included(base) base.class_eval do - random_string :uuid, :type => :uuid, :unique => true + random_string :uuid, type: :uuid, unique: true end end def to_param uuid end + end diff --git a/app/models/credential.rb b/app/models/credential.rb index cfc7f73..f65b483 100644 --- a/app/models/credential.rb +++ b/app/models/credential.rb @@ -21,11 +21,11 @@ class Credential < ApplicationRecord belongs_to :server - TYPES = ['SMTP', 'API', 'SMTP-IP'] + TYPES = ["SMTP", "API", "SMTP-IP"] - validates :key, :presence => true, :uniqueness => true - validates :type, :inclusion => {:in => TYPES} - validates :name, :presence => true + validates :key, presence: true, uniqueness: true + validates :type, inclusion: { in: TYPES } + validates :name, presence: true validate :validate_key_cannot_be_changed validate :validate_key_for_smtp_ip @@ -33,10 +33,9 @@ class Credential < ApplicationRecord before_validation :generate_key - def generate_key - return if self.type == 'SMTP-IP' - return if self.persisted? + return if type == "SMTP-IP" + return if persisted? self.key = SecureRandomString.new(24) end @@ -51,26 +50,26 @@ class Credential < ApplicationRecord def usage_type if last_used_at.nil? - 'Unused' + "Unused" elsif last_used_at < 1.year.ago - 'Inactive' + "Inactive" elsif last_used_at < 6.months.ago - 'Dormant' + "Dormant" elsif last_used_at < 1.month.ago - 'Quiet' + "Quiet" else - 'Active' + "Active" end end def to_smtp_plain - Base64.encode64("\0XX\0#{self.key}").strip + Base64.encode64("\0XX\0#{key}").strip end def ipaddr - return unless type == 'SMTP-IP' + return unless type == "SMTP-IP" - @ipaddr ||= IPAddr.new(self.key) + @ipaddr ||= IPAddr.new(key) rescue IPAddr::InvalidAddressError nil end @@ -80,15 +79,15 @@ class Credential < ApplicationRecord def validate_key_cannot_be_changed return if new_record? return unless key_changed? - return if type == 'SMTP-IP' + return if type == "SMTP-IP" errors.add :key, "cannot be changed" end def validate_key_for_smtp_ip - return unless type == 'SMTP-IP' + return unless type == "SMTP-IP" - IPAddr.new(self.key.to_s) + IPAddr.new(key.to_s) rescue IPAddr::InvalidAddressError errors.add :key, "must be a valid IPv4 or IPv6 address" end diff --git a/app/models/domain.rb b/app/models/domain.rb index 9ec16e6..6bd195a 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -34,39 +34,39 @@ # index_domains_on_uuid (uuid) # -require 'resolv' +require "resolv" class Domain < ApplicationRecord include HasUUID - require_dependency 'domain/dns_checks' - require_dependency 'domain/dns_verification' + require_dependency "domain/dns_checks" + require_dependency "domain/dns_verification" - VERIFICATION_EMAIL_ALIASES = ['webmaster', 'postmaster', 'admin', 'administrator', 'hostmaster'] + VERIFICATION_EMAIL_ALIASES = ["webmaster", "postmaster", "admin", "administrator", "hostmaster"] - belongs_to :server, :optional => true - belongs_to :owner, :optional => true, :polymorphic => true - has_many :routes, :dependent => :destroy - has_many :track_domains, :dependent => :destroy + belongs_to :server, optional: true + belongs_to :owner, optional: true, polymorphic: true + has_many :routes, dependent: :destroy + has_many :track_domains, dependent: :destroy - VERIFICATION_METHODS = ['DNS', 'Email'] + VERIFICATION_METHODS = ["DNS", "Email"] - validates :name, :presence => true, :format => {:with => /\A[a-z0-9\-\.]*\z/}, :uniqueness => {:scope => [:owner_type, :owner_id], :message => "is already added"} - validates :verification_method, :inclusion => {:in => VERIFICATION_METHODS} + validates :name, presence: true, format: { with: /\A[a-z0-9\-.]*\z/ }, uniqueness: { scope: [:owner_type, :owner_id], message: "is already added" } + validates :verification_method, inclusion: { in: VERIFICATION_METHODS } - random_string :dkim_identifier_string, :type => :chars, :length => 6, :unique => true, :upper_letters_only => true + random_string :dkim_identifier_string, type: :chars, length: 6, unique: true, upper_letters_only: true before_create :generate_dkim_key - scope :verified, -> { where.not(:verified_at => nil) } + scope :verified, -> { where.not(verified_at: nil) } - when_attribute :verification_method, :changes_to => :anything do + when_attribute :verification_method, changes_to: :anything do before_save do - if self.verification_method == 'DNS' - self.verification_token = Nifty::Utils::RandomString.generate(:length => 32) - elsif self.verification_method == 'Email' - self.verification_token = rand(999999).to_s.ljust(6, '0') + if verification_method == "DNS" + self.verification_token = Nifty::Utils::RandomString.generate(length: 32) + elsif verification_method == "Email" + self.verification_token = rand(999_999).to_s.ljust(6, "0") else self.verification_token = nil end @@ -79,13 +79,13 @@ class Domain < ApplicationRecord def verify self.verified_at = Time.now - self.save! + save! end def parent_domains - parts = self.name.split('.') - parts[0,parts.size-1].each_with_index.map do |p, i| - parts[i..-1].join('.') + parts = name.split(".") + parts[0, parts.size - 1].each_with_index.map do |p, i| + parts[i..-1].join(".") end end @@ -94,7 +94,7 @@ class Domain < ApplicationRecord end def dkim_key - @dkim_key ||= OpenSSL::PKey::RSA.new(self.dkim_private_key) + @dkim_key ||= OpenSSL::PKey::RSA.new(dkim_private_key) end def to_param @@ -114,12 +114,12 @@ class Domain < ApplicationRecord end def dkim_record - public_key = dkim_key.public_key.to_s.gsub(/\-+[A-Z ]+\-+\n/, '').gsub(/\n/, '') + public_key = dkim_key.public_key.to_s.gsub(/-+[A-Z ]+-+\n/, "").gsub(/\n/, "") "v=DKIM1; t=s; h=sha256; p=#{public_key};" end def dkim_identifier - Postal.config.dns.dkim_identifier + "-#{self.dkim_identifier_string}" + Postal.config.dns.dkim_identifier + "-#{dkim_identifier_string}" end def dkim_record_name @@ -127,7 +127,7 @@ class Domain < ApplicationRecord end def return_path_domain - "#{Postal.config.dns.custom_return_path_prefix}.#{self.name}" + "#{Postal.config.dns.custom_return_path_prefix}.#{name}" end def nameservers @@ -135,7 +135,7 @@ class Domain < ApplicationRecord end def resolver - @resolver ||= Postal.config.general.use_local_ns_for_domains? ? Resolv::DNS.new : Resolv::DNS.new(:nameserver => nameservers) + @resolver ||= Postal.config.general.use_local_ns_for_domains? ? Resolv::DNS.new : Resolv::DNS.new(nameserver: nameservers) end private @@ -143,15 +143,17 @@ class Domain < ApplicationRecord def get_nameservers local_resolver = Resolv::DNS.new ns_records = [] - parts = name.split('.') + parts = name.split(".") (parts.size - 1).times do |n| - d = parts[n, parts.size - n + 1].join('.') + d = parts[n, parts.size - n + 1].join(".") ns_records = local_resolver.getresources(d, Resolv::DNS::Resource::IN::NS).map { |s| s.name.to_s } - break unless ns_records.blank? + break if ns_records.present? end return [] if ns_records.blank? - ns_records = ns_records.map{|r| local_resolver.getresources(r, Resolv::DNS::Resource::IN::A).map { |s| s.address.to_s} }.flatten + + ns_records = ns_records.map { |r| local_resolver.getresources(r, Resolv::DNS::Resource::IN::A).map { |s| s.address.to_s } }.flatten return [] if ns_records.blank? + ns_records end diff --git a/app/models/domain/dns_checks.rb b/app/models/domain/dns_checks.rb index de89b12..07f44a7 100644 --- a/app/models/domain/dns_checks.rb +++ b/app/models/domain/dns_checks.rb @@ -1,9 +1,9 @@ -require 'resolv' +require "resolv" class Domain def dns_ok? - spf_status == 'OK' && dkim_status == 'OK' && ['OK', 'Missing'].include?(self.mx_status) && ['OK', 'Missing'].include?(self.return_path_status) + spf_status == "OK" && dkim_status == "OK" && ["OK", "Missing"].include?(mx_status) && ["OK", "Missing"].include?(return_path_status) end def dns_checked? @@ -16,21 +16,21 @@ class Domain check_mx_records check_return_path_record self.dns_checked_at = Time.now - self.save! - if source == :auto && !dns_ok? && self.owner.is_a?(Server) - WebhookRequest.trigger(self.owner, 'DomainDNSError', { - :server => self.owner.webhook_hash, - :domain => self.name, - :uuid => self.uuid, - :dns_checked_at => self.dns_checked_at.to_f, - :spf_status => self.spf_status, - :spf_error => self.spf_error, - :dkim_status => self.dkim_status, - :dkim_error => self.dkim_error, - :mx_status => self.mx_status, - :mx_error => self.mx_error, - :return_path_status => self.return_path_status, - :return_path_error => self.return_path_error + save! + if source == :auto && !dns_ok? && owner.is_a?(Server) + WebhookRequest.trigger(owner, "DomainDNSError", { + server: owner.webhook_hash, + domain: name, + uuid: uuid, + dns_checked_at: dns_checked_at.to_f, + spf_status: spf_status, + spf_error: spf_error, + dkim_status: dkim_status, + dkim_error: dkim_error, + mx_status: mx_status, + mx_error: mx_error, + return_path_status: return_path_status, + return_path_error: return_path_error }) end dns_ok? @@ -41,19 +41,19 @@ class Domain # def check_spf_record - result = resolver.getresources(self.name, Resolv::DNS::Resource::IN::TXT) - spf_records = result.map(&:data).select { |d| d =~ /\Av=spf1/} + result = resolver.getresources(name, Resolv::DNS::Resource::IN::TXT) + spf_records = result.map(&:data).select { |d| d =~ /\Av=spf1/ } if spf_records.empty? - self.spf_status = 'Missing' - self.spf_error = 'No SPF record exists for this domain' + self.spf_status = "Missing" + self.spf_error = "No SPF record exists for this domain" else - suitable_spf_records = spf_records.select { |d| d =~ /include\:\s*#{Regexp.escape(Postal.config.dns.spf_include)}/} + suitable_spf_records = spf_records.select { |d| d =~ /include:\s*#{Regexp.escape(Postal.config.dns.spf_include)}/ } if suitable_spf_records.empty? - self.spf_status = 'Invalid' + self.spf_status = "Invalid" self.spf_error = "An SPF record exists but it doesn't include #{Postal.config.dns.spf_include}" false else - self.spf_status = 'OK' + self.spf_status = "OK" self.spf_error = nil true end @@ -68,24 +68,24 @@ class Domain # # DKIM # - + def check_dkim_record domain = "#{dkim_record_name}.#{name}" result = resolver.getresources(domain, Resolv::DNS::Resource::IN::TXT) records = result.map(&:data) if records.empty? - self.dkim_status = 'Missing' + self.dkim_status = "Missing" self.dkim_error = "No TXT records were returned for #{domain}" else - sanitised_dkim_record = records.first.strip.ends_with?(';') ? records.first.strip : "#{records.first.strip};" + sanitised_dkim_record = records.first.strip.ends_with?(";") ? records.first.strip : "#{records.first.strip};" if records.size > 1 - self.dkim_status = 'Invalid' + self.dkim_status = "Invalid" self.dkim_error = "There are #{records.size} records for at #{domain}. There should only be one." - elsif sanitised_dkim_record != self.dkim_record - self.dkim_status = 'Invalid' + elsif sanitised_dkim_record != dkim_record + self.dkim_status = "Invalid" self.dkim_error = "The DKIM record at #{domain} does not match the record we have provided. Please check it has been copied correctly." else - self.dkim_status = 'OK' + self.dkim_status = "OK" self.dkim_error = nil true end @@ -102,21 +102,21 @@ class Domain # def check_mx_records - result = resolver.getresources(self.name, Resolv::DNS::Resource::IN::MX) + result = resolver.getresources(name, Resolv::DNS::Resource::IN::MX) records = result.map(&:exchange) if records.empty? - self.mx_status = 'Missing' - self.mx_error = "There are no MX records for #{self.name}" + self.mx_status = "Missing" + self.mx_error = "There are no MX records for #{name}" else missing_records = Postal.config.dns.mx_records.dup - records.map { |r| r.to_s.downcase } if missing_records.empty? - self.mx_status = 'OK' + self.mx_status = "OK" self.mx_error = nil elsif missing_records.size == Postal.config.dns.mx_records.size - self.mx_status = 'Missing' - self.mx_error = 'You have MX records but none of them point to us.' + self.mx_status = "Missing" + self.mx_error = "You have MX records but none of them point to us." else - self.mx_status = 'Invalid' + self.mx_status = "Invalid" self.mx_error = "MX #{missing_records.size == 1 ? 'record' : 'records'} for #{missing_records.to_sentence} are missing and are required." end end @@ -132,19 +132,17 @@ class Domain # def check_return_path_record - result = resolver.getresources(self.return_path_domain, Resolv::DNS::Resource::IN::CNAME) + result = resolver.getresources(return_path_domain, Resolv::DNS::Resource::IN::CNAME) records = result.map { |r| r.name.to_s.downcase } if records.empty? - self.return_path_status = 'Missing' - self.return_path_error = "There is no return path record at #{self.return_path_domain}" + self.return_path_status = "Missing" + self.return_path_error = "There is no return path record at #{return_path_domain}" + elsif records.size == 1 && records.first == Postal.config.dns.return_path + self.return_path_status = "OK" + self.return_path_error = nil else - if records.size == 1 && records.first == Postal.config.dns.return_path - self.return_path_status = 'OK' - self.return_path_error = nil - else - self.return_path_status = 'Invalid' - self.return_path_error = "There is a CNAME record at #{self.return_path_domain} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.return_path}." - end + self.return_path_status = "Invalid" + self.return_path_error = "There is a CNAME record at #{return_path_domain} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.return_path}." end end diff --git a/app/models/domain/dns_verification.rb b/app/models/domain/dns_verification.rb index b9831cb..957d368 100644 --- a/app/models/domain/dns_verification.rb +++ b/app/models/domain/dns_verification.rb @@ -1,4 +1,4 @@ -require 'resolv' +require "resolv" class Domain @@ -7,11 +7,12 @@ class Domain end def verify_with_dns - return false unless self.verification_method == 'DNS' - result = resolver.getresources(self.name, Resolv::DNS::Resource::IN::TXT) - if result.map { |d| d.data.to_s.strip}.include?(self.dns_verification_string) + return false unless verification_method == "DNS" + + result = resolver.getresources(name, Resolv::DNS::Resource::IN::TXT) + if result.map { |d| d.data.to_s.strip }.include?(dns_verification_string) self.verified_at = Time.now - self.save + save else false end diff --git a/app/models/http_endpoint.rb b/app/models/http_endpoint.rb index d12be05..b87266a 100644 --- a/app/models/http_endpoint.rb +++ b/app/models/http_endpoint.rb @@ -26,19 +26,19 @@ class HTTPEndpoint < ApplicationRecord include HasUUID belongs_to :server - has_many :routes, :as => :endpoint - has_many :additional_route_endpoints, :dependent => :destroy, :as => :endpoint + has_many :routes, as: :endpoint + has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint - ENCODINGS = ['BodyAsJSON', 'FormData'] - FORMATS = ['Hash', 'RawMessage'] + ENCODINGS = ["BodyAsJSON", "FormData"] + FORMATS = ["Hash", "RawMessage"] before_destroy :update_routes - validates :name, :presence => true - validates :url, :presence => true - validates :encoding, :inclusion => {:in => ENCODINGS} - validates :format, :inclusion => {:in => FORMATS} - validates :timeout, :numericality => {:greater_than_or_equal_to => 5, :less_than_or_equal_to => 60} + validates :name, presence: true + validates :url, presence: true + validates :encoding, inclusion: { in: ENCODINGS } + validates :format, inclusion: { in: FORMATS } + validates :timeout, numericality: { greater_than_or_equal_to: 5, less_than_or_equal_to: 60 } default_value :timeout, -> { DEFAULT_TIMEOUT } @@ -51,7 +51,7 @@ class HTTPEndpoint < ApplicationRecord end def update_routes - self.routes.each { |r| r.update(:endpoint => nil, :mode => 'Reject') } + routes.each { |r| r.update(endpoint: nil, mode: "Reject") } end end diff --git a/app/models/incoming_message_prototype.rb b/app/models/incoming_message_prototype.rb index c006081..c832904 100644 --- a/app/models/incoming_message_prototype.rb +++ b/app/models/incoming_message_prototype.rb @@ -18,27 +18,23 @@ class IncomingMessagePrototype end def from_address - @from.gsub(/.*.*/, '').strip + @from.gsub(/.*.*/, "").strip end def route - @routes ||= begin - if @to.present? - uname, domain = @to.split('@', 2) - uname, tag = uname.split('+', 2) - @server.routes.includes(:domain).where(:domains => {:name => domain}, :name => uname).first - else - nil - end - end + @routes ||= if @to.present? + uname, domain = @to.split("@", 2) + uname, tag = uname.split("+", 2) + @server.routes.includes(:domain).where(domains: { name: domain }, name: uname).first + end end def attachments (@attachments || []).map do |attachment| { - :name => attachment[:name], - :content_type => attachment[:content_type] || 'application/octet-stream', - :data => attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data] + name: attachment[:name], + content_type: attachment[:content_type] || "application/octet-stream", + data: attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data] } end end @@ -47,10 +43,10 @@ class IncomingMessagePrototype if valid? messages = route.create_messages do |message| message.rcpt_to = @to - message.mail_from = self.from_address - message.raw_message = self.raw_message + message.mail_from = from_address + message.raw_message = raw_message end - {route.description => {:id => messages.first.id, :token => messages.first.token}} + { route.description => { id: messages.first.id, token: messages.first.token } } else false end @@ -66,7 +62,7 @@ class IncomingMessagePrototype end def validate - @errors = Array.new + @errors = [] if route.nil? @errors << "NoRoutesFound" end @@ -91,11 +87,11 @@ class IncomingMessagePrototype mail.message_id = "<#{SecureRandom.uuid}@#{Postal.config.dns.return_path}>" attachments.each do |attachment| mail.attachments[attachment[:name]] = { - :mime_type => attachment[:content_type], - :content => attachment[:data] + mime_type: attachment[:content_type], + content: attachment[:data] } end - mail.header['Received'] = "from #{@source_type} (#{@ip} [#{@ip}]) by Postal with HTTP; #{Time.now.utc.rfc2822.to_s}" + mail.header["Received"] = "from #{@source_type} (#{@ip} [#{@ip}]) by Postal with HTTP; #{Time.now.utc.rfc2822}" mail.to_s end end diff --git a/app/models/ip_address.rb b/app/models/ip_address.rb index eac06e9..fd67e86 100644 --- a/app/models/ip_address.rb +++ b/app/models/ip_address.rb @@ -16,9 +16,9 @@ class IPAddress < ApplicationRecord belongs_to :ip_pool - validates :ipv4, :presence => true, :uniqueness => true - validates :hostname, :presence => true - validates :ipv6, :uniqueness => {:allow_blank => true} + validates :ipv4, presence: true, uniqueness: true + validates :hostname, presence: true + validates :ipv6, uniqueness: { allow_blank: true } validates :priority, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100, only_integer: true } scope :order_by_priority, -> { order(priority: :desc) } diff --git a/app/models/ip_pool.rb b/app/models/ip_pool.rb index 629929b..754d87c 100644 --- a/app/models/ip_pool.rb +++ b/app/models/ip_pool.rb @@ -18,16 +18,16 @@ class IPPool < ApplicationRecord include HasUUID - validates :name, :presence => true + validates :name, presence: true - has_many :ip_addresses, :dependent => :restrict_with_exception - has_many :servers, :dependent => :restrict_with_exception - has_many :organization_ip_pools, :dependent => :destroy - has_many :organizations, :through => :organization_ip_pools - has_many :ip_pool_rules, :dependent => :destroy + has_many :ip_addresses, dependent: :restrict_with_exception + has_many :servers, dependent: :restrict_with_exception + has_many :organization_ip_pools, dependent: :destroy + has_many :organizations, through: :organization_ip_pools + has_many :ip_pool_rules, dependent: :destroy def self.default - where(:default => true).order(:id).first + where(default: true).order(:id).first end end diff --git a/app/models/ip_pool_rule.rb b/app/models/ip_pool_rule.rb index 1bd8b8c..2f37ad4 100644 --- a/app/models/ip_pool_rule.rb +++ b/app/models/ip_pool_rule.rb @@ -17,24 +17,24 @@ class IPPoolRule < ApplicationRecord include HasUUID - belongs_to :owner, :polymorphic => true + belongs_to :owner, polymorphic: true belongs_to :ip_pool validate :validate_from_and_to_addresses validate :validate_ip_pool_belongs_to_organization def from - from_text ? from_text.gsub(/\r/, '').split(/\n/).map(&:strip) : [] + from_text ? from_text.gsub(/\r/, "").split(/\n/).map(&:strip) : [] end def to - to_text ? to_text.gsub(/\r/, '').split(/\n/).map(&:strip) : [] + to_text ? to_text.gsub(/\r/, "").split(/\n/).map(&:strip) : [] end def apply_to_message?(message) - if from.present? && message.headers['from'].present? + if from.present? && message.headers["from"].present? from.each do |condition| - if message.headers['from'].any? { |f| self.class.address_matches?(condition, f) } + if message.headers["from"].any? { |f| self.class.address_matches?(condition, f) } return true end end @@ -54,28 +54,29 @@ class IPPoolRule < ApplicationRecord private def validate_from_and_to_addresses - if self.from.empty? && self.to.empty? - errors.add :base, "At least one rule condition must be specified" - end + return unless from.empty? && to.empty? + + errors.add :base, "At least one rule condition must be specified" end def validate_ip_pool_belongs_to_organization - org = self.owner.is_a?(Organization) ? self.owner : self.owner.organization - if self.ip_pool && self.ip_pool_id_changed? && !org.ip_pools.include?(self.ip_pool) - errors.add :ip_pool_id, "must belong to the organization" - end + org = owner.is_a?(Organization) ? owner : owner.organization + return unless ip_pool && ip_pool_id_changed? && !org.ip_pools.include?(ip_pool) + + errors.add :ip_pool_id, "must belong to the organization" end def self.address_matches?(condition, address) address = Postal::Helpers.strip_name_from_address(address) if condition =~ /@/ - parts = address.split('@') - domain, uname = parts.pop, parts.join('@') - uname, _ = uname.split('+', 2) + parts = address.split("@") + domain = parts.pop + uname = parts.join("@") + uname, = uname.split("+", 2) condition == "#{uname}@#{domain}" else # Match as a domain - condition == address.split('@').last + condition == address.split("@").last end end diff --git a/app/models/organization.rb b/app/models/organization.rb index 4f6f125..6e78330 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -23,41 +23,41 @@ class Organization < ApplicationRecord - RESERVED_PERMALINKS = ['new', 'edit', 'remove', 'delete', 'destroy', 'admin', 'mail', 'org', 'server'] + RESERVED_PERMALINKS = ["new", "edit", "remove", "delete", "destroy", "admin", "mail", "org", "server"] INITIAL_QUOTA = 10 - INITIAL_SUPER_QUOTA = 10000 + INITIAL_SUPER_QUOTA = 10_000 include HasUUID include HasSoftDestroy - validates :name, :presence => true - validates :permalink, :presence => true, :format => {:with => /\A[a-z0-9\-]*\z/}, :uniqueness => true, :exclusion => {:in => RESERVED_PERMALINKS} - validates :time_zone, :presence => true + validates :name, presence: true + validates :permalink, presence: true, format: { with: /\A[a-z0-9-]*\z/ }, uniqueness: true, exclusion: { in: RESERVED_PERMALINKS } + validates :time_zone, presence: true - default_value :time_zone, -> { 'UTC' } - default_value :permalink, -> { Organization.find_unique_permalink(self.name) if self.name } + default_value :time_zone, -> { "UTC" } + default_value :permalink, -> { Organization.find_unique_permalink(name) if name } - belongs_to :owner, :class_name => 'User' - has_many :organization_users, :dependent => :destroy - has_many :users, :through => :organization_users, :source_type => 'User' - has_many :user_invites, :through => :organization_users, :source_type => 'UserInvite', :source => :user - has_many :servers, :dependent => :destroy - has_many :domains, :as => :owner, :dependent => :destroy - has_many :organization_ip_pools, :dependent => :destroy - has_many :ip_pools, :through => :organization_ip_pools - has_many :ip_pool_rules, :dependent => :destroy, :as => :owner + belongs_to :owner, class_name: "User" + has_many :organization_users, dependent: :destroy + has_many :users, through: :organization_users, source_type: "User" + has_many :user_invites, through: :organization_users, source_type: "UserInvite", source: :user + has_many :servers, dependent: :destroy + has_many :domains, as: :owner, dependent: :destroy + has_many :organization_ip_pools, dependent: :destroy + has_many :ip_pools, through: :organization_ip_pools + has_many :ip_pool_rules, dependent: :destroy, as: :owner after_create do if pool = IPPool.default - self.ip_pools << IPPool.default + ip_pools << IPPool.default end end def status - if self.suspended? - 'Suspended' + if suspended? + "Suspended" else - 'Active' + "Active" end end @@ -71,25 +71,25 @@ class Organization < ApplicationRecord def user_assignment(user) @user_assignments ||= {} - @user_assignments[user.id] ||= organization_users.where(:user => user).first + @user_assignments[user.id] ||= organization_users.where(user: user).first end def make_owner(new_owner) - user_assignment(new_owner).update(:admin => true, :all_servers => true) - update(:owner => new_owner) + user_assignment(new_owner).update(admin: true, all_servers: true) + update(owner: new_owner) end # This is an array of addresses that should receive notifications for this organization def notification_addresses - self.users.map(&:email_tag) + users.map(&:email_tag) end def self.find_unique_permalink(name) loop.each_with_index do |_, i| - i = i + 1 + i += 1 proposal = name.parameterize proposal += "-#{i}" if i > 1 - unless self.where(:permalink => proposal).exists? + unless where(permalink: proposal).exists? return proposal end end @@ -97,9 +97,9 @@ class Organization < ApplicationRecord def self.[](id) if id.is_a?(String) - where(:permalink => id).first + where(permalink: id).first else - where(:id => id.to_i).first + where(id: id.to_i).first end end diff --git a/app/models/organization_ip_pool.rb b/app/models/organization_ip_pool.rb index 5456c94..9db6f81 100644 --- a/app/models/organization_ip_pool.rb +++ b/app/models/organization_ip_pool.rb @@ -10,6 +10,8 @@ # class OrganizationIPPool < ApplicationRecord + belongs_to :organization belongs_to :ip_pool + end diff --git a/app/models/organization_user.rb b/app/models/organization_user.rb index 08a7545..3f173da 100644 --- a/app/models/organization_user.rb +++ b/app/models/organization_user.rb @@ -14,6 +14,6 @@ class OrganizationUser < ApplicationRecord belongs_to :organization - belongs_to :user, :polymorphic => true, :optional => true + belongs_to :user, polymorphic: true, optional: true end diff --git a/app/models/outgoing_message_prototype.rb b/app/models/outgoing_message_prototype.rb index 22791b1..b904fff 100644 --- a/app/models/outgoing_message_prototype.rb +++ b/app/models/outgoing_message_prototype.rb @@ -1,4 +1,4 @@ -require 'resolv' +require "resolv" class OutgoingMessagePrototype @@ -29,9 +29,7 @@ class OutgoingMessagePrototype end end - def message_id - @message_id - end + attr_reader :message_id def from_address Postal::Helpers.strip_name_from_address(@from) @@ -59,15 +57,15 @@ class OutgoingMessagePrototype end def to_addresses - @to.is_a?(String) ? @to.to_s.split(/\,\s*/) : @to.to_a + @to.is_a?(String) ? @to.to_s.split(/,\s*/) : @to.to_a end def cc_addresses - @cc.is_a?(String) ? @cc.to_s.split(/\,\s*/) : @cc.to_a + @cc.is_a?(String) ? @cc.to_s.split(/,\s*/) : @cc.to_a end def bcc_addresses - @bcc.is_a?(String) ? @bcc.to_s.split(/\,\s*/) : @bcc.to_a + @bcc.is_a?(String) ? @bcc.to_s.split(/,\s*/) : @bcc.to_a end def all_addresses @@ -98,30 +96,30 @@ class OutgoingMessagePrototype def attachments (@attachments || []).map do |attachment| { - :name => attachment[:name], - :content_type => attachment[:content_type] || 'application/octet-stream', - :data => attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data] + name: attachment[:name], + content_type: attachment[:content_type] || "application/octet-stream", + data: attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data] } end end def validate - @errors = Array.new + @errors = [] if to_addresses.empty? && cc_addresses.empty? && bcc_addresses.empty? @errors << "NoRecipients" end if to_addresses.size > 50 - @errors << 'TooManyToAddresses' + @errors << "TooManyToAddresses" end if cc_addresses.size > 50 - @errors << 'TooManyCCAddresses' + @errors << "TooManyCCAddresses" end if bcc_addresses.size > 50 - @errors << 'TooManyBCCAddresses' + @errors << "TooManyBCCAddresses" end if @plain_body.blank? && @html_body.blank? @@ -136,7 +134,7 @@ class OutgoingMessagePrototype @errors << "UnauthenticatedFromAddress" end - if attachments && !attachments.empty? + if attachments.present? attachments.each_with_index do |attachment, index| if attachment[:name].blank? @errors << "AttachmentMissingName" unless @errors.include?("AttachmentMissingName") @@ -154,18 +152,18 @@ class OutgoingMessagePrototype if @custom_headers.is_a?(Hash) @custom_headers.each { |key, value| mail[key.to_s] = value.to_s } end - mail.to = self.to_addresses.join(', ') if self.to_addresses.present? - mail.cc = self.cc_addresses.join(', ') if self.cc_addresses.present? + mail.to = to_addresses.join(", ") if to_addresses.present? + mail.cc = cc_addresses.join(", ") if cc_addresses.present? mail.from = @from mail.sender = @sender mail.subject = @subject mail.reply_to = @reply_to - mail.part :content_type => "multipart/alternative" do |p| - if !@plain_body.blank? + mail.part content_type: "multipart/alternative" do |p| + if @plain_body.present? p.text_part = Mail::Part.new p.text_part.body = @plain_body end - if !@html_body.blank? + if @html_body.present? p.html_part = Mail::Part.new p.html_part.content_type = "text/html; charset=UTF-8" p.html_part.body = @html_body @@ -173,11 +171,11 @@ class OutgoingMessagePrototype end attachments.each do |attachment| mail.attachments[attachment[:name]] = { - :mime_type => attachment[:content_type], - :content => attachment[:data] + mime_type: attachment[:content_type], + content: attachment[:data] } end - mail.header['Received'] = "from #{@source_type} (#{self.resolved_hostname} [#{@ip}]) by Postal with HTTP; #{Time.now.utc.rfc2822.to_s}" + mail.header["Received"] = "from #{@source_type} (#{resolved_hostname} [#{@ip}]) by Postal with HTTP; #{Time.now.utc.rfc2822}" mail.message_id = "<#{@message_id}>" mail.to_s end @@ -185,21 +183,25 @@ class OutgoingMessagePrototype def create_message(address) message = @server.message_db.new_message - message.scope = 'outgoing' + message.scope = "outgoing" message.rcpt_to = address - message.mail_from = self.from_address - message.domain_id = self.domain.id - message.raw_message = self.raw_message - message.tag = self.tag - message.credential_id = self.credential&.id + message.mail_from = from_address + message.domain_id = domain.id + message.raw_message = raw_message + message.tag = tag + message.credential_id = credential&.id message.received_with_ssl = true message.bounce = @bounce ? 1 : 0 message.save - {:id => message.id, :token => message.token} + { id: message.id, token: message.token } end def resolved_hostname - @resolved_hostname ||= Resolv.new.getname(@ip) rescue @ip + @resolved_hostname ||= begin + Resolv.new.getname(@ip) + rescue StandardError + @ip + end end end diff --git a/app/models/queued_message.rb b/app/models/queued_message.rb index 43a2976..d34c07e 100644 --- a/app/models/queued_message.rb +++ b/app/models/queued_message.rb @@ -29,21 +29,21 @@ class QueuedMessage < ApplicationRecord include HasMessage belongs_to :server - belongs_to :ip_address, :optional => true - belongs_to :user, :optional => true + belongs_to :ip_address, optional: true + belongs_to :user, optional: true before_create :allocate_ip_address - after_commit :queue, :on => :create + after_commit :queue, on: :create - scope :unlocked, -> { where(:locked_at => nil) } + scope :unlocked, -> { where(locked_at: nil) } scope :retriable, -> { where("retry_after IS NULL OR retry_after <= ?", 30.seconds.from_now) } def retriable? - self.retry_after.nil? || self.retry_after <= 30.seconds.from_now + retry_after.nil? || retry_after <= 30.seconds.from_now end def queue - UnqueueMessageJob.queue(queue_name, :id => self.id) + UnqueueMessageJob.queue(queue_name, id: id) end def queue! @@ -56,21 +56,21 @@ class QueuedMessage < ApplicationRecord end def send_bounce - if self.message.send_bounces? - Postal::BounceMessage.new(self.server, self.message).queue - end + return unless message.send_bounces? + + Postal::BounceMessage.new(server, message).queue end def allocate_ip_address - if Postal.ip_pools? && self.message && pool = self.server.ip_pool_for_message(self.message) - self.ip_address = pool.ip_addresses.select_by_priority - end + return unless Postal.ip_pools? && message && pool = server.ip_pool_for_message(message) + + self.ip_address = pool.ip_addresses.select_by_priority end def acquire_lock time = Time.now locker = Postal.locker_name - rows = self.class.where(:id => self.id, :locked_by => nil, :locked_at => nil).update_all(:locked_by => locker, :locked_at => time) + rows = self.class.where(id: id, locked_by: nil, locked_at: nil).update_all(locked_by: locker, locked_at: time) if rows == 1 self.locked_by = locker self.locked_at = time @@ -81,20 +81,20 @@ class QueuedMessage < ApplicationRecord end def retry_later(time = nil) - retry_time = time || self.class.calculate_retry_time(self.attempts, 5.minutes) + retry_time = time || self.class.calculate_retry_time(attempts, 5.minutes) self.locked_by = nil self.locked_at = nil - update_columns(:locked_by => nil, :locked_at => nil, :retry_after => Time.now + retry_time, :attempts => self.attempts + 1) + update_columns(locked_by: nil, locked_at: nil, retry_after: Time.now + retry_time, attempts: attempts + 1) end def unlock self.locked_by = nil self.locked_at = nil - update_columns(:locked_by => nil, :locked_at => nil) + update_columns(locked_by: nil, locked_at: nil) end def self.calculate_retry_time(attempts, initial_period) - (1.3 ** attempts) * initial_period + (1.3**attempts) * initial_period end def locked? @@ -105,13 +105,14 @@ class QueuedMessage < ApplicationRecord unless locked? raise Postal::Error, "Must lock current message before locking any friends" end - if self.batch_key.nil? + + if batch_key.nil? [] else time = Time.now locker = Postal.locker_name - self.class.retriable.where(:batch_key => self.batch_key, :ip_address_id => self.ip_address_id, :locked_by => nil, :locked_at => nil).limit(limit).update_all(:locked_by => locker, :locked_at => time) - QueuedMessage.where(:batch_key => self.batch_key, :ip_address_id => self.ip_address_id, :locked_by => locker, :locked_at => time).where.not(id: self.id) + self.class.retriable.where(batch_key: batch_key, ip_address_id: ip_address_id, locked_by: nil, locked_at: nil).limit(limit).update_all(locked_by: locker, locked_at: time) + QueuedMessage.where(batch_key: batch_key, ip_address_id: ip_address_id, locked_by: locker, locked_at: time).where.not(id: id) end end diff --git a/app/models/route.rb b/app/models/route.rb index 3cb9a7b..8661d50 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -22,22 +22,22 @@ class Route < ApplicationRecord - MODES = ['Endpoint', 'Accept', 'Hold', 'Bounce', 'Reject'] + MODES = ["Endpoint", "Accept", "Hold", "Bounce", "Reject"] include HasUUID belongs_to :server - belongs_to :domain, :optional => true - belongs_to :endpoint, :polymorphic => true, :optional => true - has_many :additional_route_endpoints, :dependent => :destroy + belongs_to :domain, optional: true + belongs_to :endpoint, polymorphic: true, optional: true + has_many :additional_route_endpoints, dependent: :destroy - SPAM_MODES = ['Mark', 'Quarantine', 'Fail'] - ENDPOINT_TYPES = ['SMTPEndpoint', 'HTTPEndpoint', 'AddressEndpoint'] + SPAM_MODES = ["Mark", "Quarantine", "Fail"] + ENDPOINT_TYPES = ["SMTPEndpoint", "HTTPEndpoint", "AddressEndpoint"] - validates :name, :presence => true, :format => /\A(([a-z0-9\-\.]*)|(\*)|(__returnpath__))\z/ - validates :spam_mode, :inclusion => {:in => SPAM_MODES} - validates :endpoint, :presence => {:if => proc { self.mode == 'Endpoint' }} - validates :domain_id, :presence => {:unless => :return_path?} + validates :name, presence: true, format: /\A(([a-z0-9\-.]*)|(\*)|(__returnpath__))\z/ + validates :spam_mode, inclusion: { in: SPAM_MODES } + validates :endpoint, presence: { if: proc { mode == "Endpoint" } } + validates :domain_id, presence: { unless: :return_path? } validate :validate_route_is_routed validate :validate_domain_belongs_to_server validate :validate_endpoint_belongs_to_server @@ -47,7 +47,7 @@ class Route < ApplicationRecord after_save :save_additional_route_endpoints - random_string :token, :type => :chars, :length => 8, :unique => true + random_string :token, type: :chars, length: 8, unique: true def return_path? name == "__returnpath__" @@ -62,12 +62,10 @@ class Route < ApplicationRecord end def _endpoint - @endpoint ||= begin - if self.mode == 'Endpoint' - endpoint ? "#{endpoint.class}##{endpoint.uuid}" : nil - else - self.mode - end + if mode == "Endpoint" + @endpoint ||= endpoint ? "#{endpoint.class}##{endpoint.uuid}" : nil + else + @endpoint ||= mode end end @@ -75,18 +73,17 @@ class Route < ApplicationRecord if value.blank? self.endpoint = nil self.mode = nil - else - if value =~ /\#/ - class_name, id = value.split('#', 2) - unless ENDPOINT_TYPES.include?(class_name) - raise Postal::Error, "Invalid endpoint class name '#{class_name}'" - end - self.endpoint = class_name.constantize.find_by_uuid(id) - self.mode = 'Endpoint' - else - self.endpoint = nil - self.mode = value + elsif value =~ /\#/ + class_name, id = value.split("#", 2) + unless ENDPOINT_TYPES.include?(class_name) + raise Postal::Error, "Invalid endpoint class name '#{class_name}'" end + + self.endpoint = class_name.constantize.find_by_uuid(id) + self.mode = "Endpoint" + else + self.endpoint = nil + self.mode = value end end @@ -95,7 +92,7 @@ class Route < ApplicationRecord end def wildcard? - self.name == '*' + name == "*" end def additional_route_endpoints_array @@ -107,50 +104,51 @@ class Route < ApplicationRecord end def save_additional_route_endpoints - if @additional_route_endpoints_array - seen = [] - @additional_route_endpoints_array.each do |item| - if existing = additional_route_endpoints.find_by_endpoint(item) - seen << existing.id + return unless @additional_route_endpoints_array + + seen = [] + @additional_route_endpoints_array.each do |item| + if existing = additional_route_endpoints.find_by_endpoint(item) + seen << existing.id + else + route = additional_route_endpoints.build(_endpoint: item) + if route.save + seen << route.id else - route = additional_route_endpoints.build(:_endpoint => item) - if route.save - seen << route.id - else - route.errors.each do |field, message| - errors.add :base, message - end - raise ActiveRecord::RecordInvalid + route.errors.each do |field, message| + errors.add :base, message end + raise ActiveRecord::RecordInvalid end end - additional_route_endpoints.where.not(:id => seen).destroy_all end + additional_route_endpoints.where.not(id: seen).destroy_all end # # This message will create a suitable number of message objects for messages that - # are destined for this route. It receives a block which can set the message content + #  are destined for this route. It receives a block which can set the message content # but most information is specified already. # # Returns an array of created messages. # def create_messages(&block) messages = [] - message = self.build_message - if self.mode == 'Endpoint' && self.server.message_db.schema_version >= 18 - message.endpoint_type = self.endpoint_type - message.endpoint_id = self.endpoint_id + message = build_message + if mode == "Endpoint" && server.message_db.schema_version >= 18 + message.endpoint_type = endpoint_type + message.endpoint_id = endpoint_id end block.call(message) message.save messages << message # Also create any messages for additional endpoints that might exist - if self.mode == 'Endpoint' && self.server.message_db.schema_version >= 18 - self.additional_route_endpoints.each do |endpoint| + if mode == "Endpoint" && server.message_db.schema_version >= 18 + additional_route_endpoints.each do |endpoint| next unless endpoint.endpoint - message = self.build_message + + message = build_message message.endpoint_id = endpoint.endpoint_id message.endpoint_type = endpoint.endpoint_type block.call(message) @@ -163,69 +161,67 @@ class Route < ApplicationRecord end def build_message - message = self.server.message_db.new_message - message.scope = 'incoming' - message.rcpt_to = self.description - message.domain_id = self.domain&.id - message.route_id = self.id + message = server.message_db.new_message + message.scope = "incoming" + message.rcpt_to = description + message.domain_id = domain&.id + message.route_id = id message end private def validate_route_is_routed - if self.mode.nil? - errors.add :endpoint, "must be chosen" - end + return unless mode.nil? + + errors.add :endpoint, "must be chosen" end def validate_domain_belongs_to_server - if self.domain && ![self.server, self.server.organization].include?(self.domain.owner) + if domain && ![server, server.organization].include?(domain.owner) errors.add :domain, :invalid end - if self.domain && !self.domain.verified? - errors.add :domain, "has not been verified yet" - end + return unless domain && !domain.verified? + + errors.add :domain, "has not been verified yet" end def validate_endpoint_belongs_to_server - if self.endpoint && self.endpoint&.server != self.server - errors.add :endpoint, :invalid - end + return unless endpoint && endpoint&.server != server + + errors.add :endpoint, :invalid end def validate_name_uniqueness - return if self.server.nil? - if self.domain - if route = Route.includes(:domain).where(:domains => {:name => self.domain.name}, :name => self.name).where.not(:id => self.id).first + return if server.nil? + + if domain + if route = Route.includes(:domain).where(domains: { name: domain.name }, name: name).where.not(id: id).first errors.add :name, "is configured on the #{route.server.full_permalink} mail server" end - else - if route = Route.where(:name => "__returnpath__").where.not(:id => self.id).exists? - errors.add :base, "A return path route already exists for this server" - end + elsif route = Route.where(name: "__returnpath__").where.not(id: id).exists? + errors.add :base, "A return path route already exists for this server" end end def validate_return_path_route_endpoints - if return_path? - if self.mode != 'Endpoint' || self.endpoint_type != 'HTTPEndpoint' - errors.add :base, "Return path routes must point to an HTTP endpoint" - end - end + return unless return_path? + return unless mode != "Endpoint" || endpoint_type != "HTTPEndpoint" + + errors.add :base, "Return path routes must point to an HTTP endpoint" end def validate_no_additional_routes_on_non_endpoint_route - if self.mode != 'Endpoint' && !self.additional_route_endpoints_array.empty? - errors.add :base, "Additional routes are not permitted unless the primary route is an actual endpoint" - end + return unless mode != "Endpoint" && !additional_route_endpoints_array.empty? + + errors.add :base, "Additional routes are not permitted unless the primary route is an actual endpoint" end def self.find_by_name_and_domain(name, domain) - route = Route.includes(:domain).where(:name => name, :domains => {:name => domain}).first + route = Route.includes(:domain).where(name: name, domains: { name: domain }).first if route.nil? - route = Route.includes(:domain).where(:name => '*', :domains => {:name => domain}).first + route = Route.includes(:domain).where(name: "*", domains: { name: domain }).first end route end diff --git a/app/models/server.rb b/app/models/server.rb index c2b7319..614922f 100644 --- a/app/models/server.rb +++ b/app/models/server.rb @@ -41,7 +41,7 @@ class Server < ApplicationRecord - RESERVED_PERMALINKS = ['new', 'all', 'search', 'stats', 'edit', 'manage', 'delete', 'destroy', 'remove'] + RESERVED_PERMALINKS = ["new", "all", "search", "stats", "edit", "manage", "delete", "destroy", "remove"] include HasUUID include HasSoftDestroy @@ -49,55 +49,55 @@ class Server < ApplicationRecord attr_accessor :provision_database belongs_to :organization - belongs_to :ip_pool, :optional => true - has_many :domains, :dependent => :destroy, :as => :owner - has_many :credentials, :dependent => :destroy - has_many :smtp_endpoints, :dependent => :destroy - has_many :http_endpoints, :dependent => :destroy - has_many :address_endpoints, :dependent => :destroy - has_many :routes, :dependent => :destroy - has_many :queued_messages, :dependent => :delete_all - has_many :webhooks, :dependent => :destroy - has_many :webhook_requests, :dependent => :destroy - has_many :track_domains, :dependent => :destroy - has_many :ip_pool_rules, :dependent => :destroy, :as => :owner + belongs_to :ip_pool, optional: true + has_many :domains, dependent: :destroy, as: :owner + has_many :credentials, dependent: :destroy + has_many :smtp_endpoints, dependent: :destroy + has_many :http_endpoints, dependent: :destroy + has_many :address_endpoints, dependent: :destroy + has_many :routes, dependent: :destroy + has_many :queued_messages, dependent: :delete_all + has_many :webhooks, dependent: :destroy + has_many :webhook_requests, dependent: :destroy + has_many :track_domains, dependent: :destroy + has_many :ip_pool_rules, dependent: :destroy, as: :owner - MODES = ['Live', 'Development'] + MODES = ["Live", "Development"] - random_string :token, :type => :chars, :length => 6, :unique => true, :upper_letters_only => true - default_value :permalink, -> { name ? name.parameterize : nil} + random_string :token, type: :chars, length: 6, unique: true, upper_letters_only: true + default_value :permalink, -> { name ? name.parameterize : nil } default_value :raw_message_retention_days, -> { 30 } default_value :raw_message_retention_size, -> { 2048 } default_value :message_retention_days, -> { 60 } default_value :spam_threshold, -> { Postal.config.general.default_spam_threshold } default_value :spam_failure_threshold, -> { Postal.config.general.default_spam_failure_threshold } - validates :name, :presence => true, :uniqueness => {:scope => :organization_id} - validates :mode, :inclusion => {:in => MODES} - validates :permalink, :presence => true, :uniqueness => {:scope => :organization_id}, :format => {:with => /\A[a-z0-9\-]*\z/}, :exclusion => {:in => RESERVED_PERMALINKS} + validates :name, presence: true, uniqueness: { scope: :organization_id } + validates :mode, inclusion: { in: MODES } + validates :permalink, presence: true, uniqueness: { scope: :organization_id }, format: { with: /\A[a-z0-9-]*\z/ }, exclusion: { in: RESERVED_PERMALINKS } validate :validate_ip_pool_belongs_to_organization - before_validation(:on => :create) do - self.token = self.token.downcase if self.token + before_validation(on: :create) do + self.token = token.downcase if token end after_create do - unless self.provision_database == false + unless provision_database == false message_db.provisioner.provision end end - after_commit(:on => :destroy) do - unless self.provision_database == false + after_commit(on: :destroy) do + unless provision_database == false message_db.provisioner.drop end end def status - if self.suspended? - 'Suspended' + if suspended? + "Suspended" else - self.mode + mode end end @@ -110,12 +110,12 @@ class Server < ApplicationRecord end def actual_suspension_reason - if suspended? - if suspended_at.nil? - organization.suspension_reason - else - self.suspension_reason - end + return unless suspended? + + if suspended_at.nil? + organization.suspension_reason + else + suspension_reason end end @@ -124,30 +124,28 @@ class Server < ApplicationRecord end def message_db - @message_db ||= Postal::MessageDB::Database.new(self.organization_id, self.id) + @message_db ||= Postal::MessageDB::Database.new(organization_id, id) end - def message(id) - message_db.message(id) - end + delegate :message, to: :message_db def message_rate - @message_rate ||= message_db.live_stats.total(60, :types => [:incoming, :outgoing]) / 60.0 + @message_rate ||= message_db.live_stats.total(60, types: [:incoming, :outgoing]) / 60.0 end def held_messages - @held_messages ||= message_db.messages(:where => {:held => 1}, :count => true) + @held_messages ||= message_db.messages(where: { held: 1 }, count: true) end def throughput_stats @throughput_stats ||= begin - incoming = message_db.live_stats.total(60, :types => [:incoming]) - outgoing = message_db.live_stats.total(60, :types => [:outgoing]) + incoming = message_db.live_stats.total(60, types: [:incoming]) + outgoing = message_db.live_stats.total(60, types: [:outgoing]) outgoing_usage = send_limit ? (outgoing / send_limit.to_f) * 100 : 0 { - :incoming => incoming, - :outgoing => outgoing, - :outgoing_usage => outgoing_usage + incoming: incoming, + outgoing: outgoing, + outgoing_usage: outgoing_usage } end end @@ -166,8 +164,10 @@ class Server < ApplicationRecord end def domain_stats - domains = Domain.where(:owner_id => self.id, :owner_type => 'Server').to_a - total, unverified, bad_dns = 0, 0, 0 + domains = Domain.where(owner_id: id, owner_type: "Server").to_a + total = 0 + unverified = 0 + bad_dns = 0 domains.each do |domain| total += 1 unverified += 1 unless domain.verified? @@ -178,29 +178,29 @@ class Server < ApplicationRecord def webhook_hash { - :uuid => self.uuid, - :name => self.name, - :permalink => self.permalink, - :organization => self.organization&.permalink + uuid: uuid, + name: name, + permalink: permalink, + organization: organization&.permalink } end def send_volume - @send_volume ||= message_db.live_stats.total(60, :types => [:outgoing]) + @send_volume ||= message_db.live_stats.total(60, types: [:outgoing]) end def send_limit_approaching? - self.send_limit && (send_volume >= self.send_limit * 0.90) + send_limit && (send_volume >= send_limit * 0.90) end def send_limit_exceeded? - self.send_limit && send_volume >= self.send_limit + send_limit && send_volume >= send_limit end def send_limit_warning(type) AppMailer.send("server_send_limit_#{type}", self).deliver - self.update_column("send_limit_#{type}_notified_at", Time.now) - WebhookRequest.trigger(self, "SendLimit#{type.to_s.capitalize}", :server => webhook_hash, :volume => self.send_volume, :limit => self.send_limit) + update_column("send_limit_#{type}_notified_at", Time.now) + WebhookRequest.trigger(self, "SendLimit#{type.to_s.capitalize}", server: webhook_hash, volume: send_volume, limit: send_limit) end def queue_size @@ -209,36 +209,38 @@ class Server < ApplicationRecord def stats { - :queue => queue_size, - :held => self.held_messages, - :bounce_rate => self.bounce_rate, - :message_rate => self.message_rate, - :throughput => self.throughput_stats, - :size => self.message_db.total_size + queue: queue_size, + held: held_messages, + bounce_rate: bounce_rate, + message_rate: message_rate, + throughput: throughput_stats, + size: message_db.total_size } end def authenticated_domain_for_address(address) return nil if address.blank? + address = Postal::Helpers.strip_name_from_address(address) - uname, domain_name = address.split('@', 2) + uname, domain_name = address.split("@", 2) return nil unless uname return nil unless domain_name - uname, _ = uname.split('+', 2) + + uname, = uname.split("+", 2) # Check the server's domain - if domain = Domain.verified.order(:owner_type => :desc).where("(owner_type = 'Organization' AND owner_id = ?) OR (owner_type = 'Server' AND owner_id = ?)", self.organization_id, self.id).where(:name => domain_name).first + if domain = Domain.verified.order(owner_type: :desc).where("(owner_type = 'Organization' AND owner_id = ?) OR (owner_type = 'Server' AND owner_id = ?)", organization_id, id).where(name: domain_name).first return domain end - if any_domain = self.domains.verified.where(:use_for_any => true).order(:name).first - return any_domain - end + return unless any_domain = domains.verified.where(use_for_any: true).order(:name).first + + any_domain end def find_authenticated_domain_from_headers(headers) - header_to_check = ['from'] - header_to_check << 'sender' if self.allow_sender? + header_to_check = ["from"] + header_to_check << "sender" if allow_sender? header_to_check.each do |header_name| if headers[header_name].is_a?(Array) values = headers[header_name] @@ -257,36 +259,34 @@ class Server < ApplicationRecord def suspend(reason) self.suspended_at = Time.now self.suspension_reason = reason - self.save! + save! AppMailer.server_suspended(self).deliver end def unsuspend self.suspended_at = nil self.suspension_reason = nil - self.save! + save! end def validate_ip_pool_belongs_to_organization - if self.ip_pool && self.ip_pool_id_changed? && !self.organization.ip_pools.include?(self.ip_pool) - errors.add :ip_pool_id, "must belong to the organization" - end + return unless ip_pool && ip_pool_id_changed? && !organization.ip_pools.include?(ip_pool) + + errors.add :ip_pool_id, "must belong to the organization" end def ip_pool_for_message(message) - if message.scope == 'outgoing' - [self, self.organization].each do |scope| - rules = scope.ip_pool_rules.order(:created_at => :desc) - rules.each do |rule| - if rule.apply_to_message?(message) - return rule.ip_pool - end + return unless message.scope == "outgoing" + + [self, organization].each do |scope| + rules = scope.ip_pool_rules.order(created_at: :desc) + rules.each do |rule| + if rule.apply_to_message?(message) + return rule.ip_pool end end - self.ip_pool - else - nil end + ip_pool end def self.triggered_send_limit(type) @@ -297,12 +297,12 @@ class Server < ApplicationRecord def self.send_send_limit_notifications [:approaching, :exceeded].each_with_object({}) do |type, hash| hash[type] = 0 - servers = self.triggered_send_limit(type) - unless servers.empty? - servers.each do |server| - hash[type] += 1 - server.send_limit_warning(type) - end + servers = triggered_send_limit(type) + next if servers.empty? + + servers.each do |server| + hash[type] += 1 + server.send_limit_warning(type) end end end @@ -311,15 +311,15 @@ class Server < ApplicationRecord server = nil if id.is_a?(String) if id =~ /\A(\w+)\/(\w+)\z/ - server = includes(:organization).where(:organizations => {:permalink => $1}, :permalink => $2).first + server = includes(:organization).where(organizations: { permalink: ::Regexp.last_match(1) }, permalink: ::Regexp.last_match(2)).first end else - server = where(:id => id).first + server = where(id: id).first end if extra if extra.is_a?(String) - server.domains.where(:name => extra.to_s).first + server.domains.where(name: extra.to_s).first else server.message(extra.to_i) end diff --git a/app/models/smtp_endpoint.rb b/app/models/smtp_endpoint.rb index ffbb7dc..5c0f094 100644 --- a/app/models/smtp_endpoint.rb +++ b/app/models/smtp_endpoint.rb @@ -21,17 +21,17 @@ class SMTPEndpoint < ApplicationRecord include HasUUID belongs_to :server - has_many :routes, :as => :endpoint - has_many :additional_route_endpoints, :dependent => :destroy, :as => :endpoint + has_many :routes, as: :endpoint + has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint - SSL_MODES = ['None', 'Auto', 'STARTTLS', 'TLS'] + SSL_MODES = ["None", "Auto", "STARTTLS", "TLS"] before_destroy :update_routes - validates :name, :presence => true - validates :hostname, :presence => true, :format => /\A[a-z0-9\.\-]*\z/ - validates :ssl_mode, :inclusion => {:in => SSL_MODES} - validates :port, :numericality => {:only_integer => true, :allow_blank => true} + validates :name, presence: true + validates :hostname, presence: true, format: /\A[a-z0-9.-]*\z/ + validates :ssl_mode, inclusion: { in: SSL_MODES } + validates :port, numericality: { only_integer: true, allow_blank: true } def description "#{name} (#{hostname})" @@ -42,7 +42,7 @@ class SMTPEndpoint < ApplicationRecord end def update_routes - self.routes.each { |r| r.update(:endpoint => nil, :mode => 'Reject') } + routes.each { |r| r.update(endpoint: nil, mode: "Reject") } end end diff --git a/app/models/track_domain.rb b/app/models/track_domain.rb index 0ab8328..42b5821 100644 --- a/app/models/track_domain.rb +++ b/app/models/track_domain.rb @@ -18,7 +18,7 @@ # excluded_click_domains :text(65535) # -require 'resolv' +require "resolv" class TrackDomain < ApplicationRecord @@ -27,16 +27,16 @@ class TrackDomain < ApplicationRecord belongs_to :server belongs_to :domain - validates :name, :presence => true, :format => {:with => /\A[a-z0-9\-]+\z/}, :uniqueness => {:scope => :domain_id, :message => "is already added"} - validates :domain_id, :uniqueness => {:scope => :server_id, :message => "already has a track domain for this server"} + validates :name, presence: true, format: { with: /\A[a-z0-9-]+\z/ }, uniqueness: { scope: :domain_id, message: "is already added" } + validates :domain_id, uniqueness: { scope: :server_id, message: "already has a track domain for this server" } validate :validate_domain_belongs_to_server - scope :ok, -> { where(:dns_status => 'OK')} + scope :ok, -> { where(dns_status: "OK") } - after_create :check_dns, :unless => :dns_status + after_create :check_dns, unless: :dns_status before_validation do - self.server = self.domain.server if self.domain && self.server.nil? + self.server = domain.server if domain && server.nil? end def full_name @@ -48,26 +48,24 @@ class TrackDomain < ApplicationRecord end def dns_ok? - self.dns_status == 'OK' + dns_status == "OK" end def check_dns - result = self.domain.resolver.getresources(self.full_name, Resolv::DNS::Resource::IN::CNAME) + result = domain.resolver.getresources(full_name, Resolv::DNS::Resource::IN::CNAME) records = result.map { |r| r.name.to_s.downcase } if records.empty? - self.dns_status = 'Missing' - self.dns_error = "There is no record at #{self.full_name}" + self.dns_status = "Missing" + self.dns_error = "There is no record at #{full_name}" + elsif records.size == 1 && records.first == Postal.config.dns.track_domain + self.dns_status = "OK" + self.dns_error = nil else - if records.size == 1 && records.first == Postal.config.dns.track_domain - self.dns_status = 'OK' - self.dns_error = nil - else - self.dns_status = 'Invalid' - self.dns_error = "There is a CNAME record at #{self.full_name} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.track_domain}." - end + self.dns_status = "Invalid" + self.dns_error = "There is a CNAME record at #{full_name} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.track_domain}." end self.dns_checked_at = Time.now - self.save! + save! dns_ok? end @@ -76,9 +74,9 @@ class TrackDomain < ApplicationRecord end def validate_domain_belongs_to_server - if self.domain && ![self.server, self.server.organization].include?(self.domain.owner) - errors.add :domain, "does not belong to the server or the server's organization" - end + return unless domain && ![server, server.organization].include?(domain.owner) + + errors.add :domain, "does not belong to the server or the server's organization" end end diff --git a/app/models/user.rb b/app/models/user.rb index 6e03a2f..dc86e5c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,25 +27,23 @@ class User < ApplicationRecord include HasUUID - require_dependency 'user/authentication' + require_dependency "user/authentication" - validates :first_name, :presence => true - validates :last_name, :presence => true - validates :email_address, :presence => true, :uniqueness => true, :format => {:with => /@/, allow_blank: true} - validates :time_zone, :presence => true + validates :first_name, presence: true + validates :last_name, presence: true + validates :email_address, presence: true, uniqueness: true, format: { with: /@/, allow_blank: true } + validates :time_zone, presence: true - default_value :time_zone, -> { 'UTC' } + default_value :time_zone, -> { "UTC" } - has_many :organization_users, :dependent => :destroy, :as => :user - has_many :organizations, :through => :organization_users + has_many :organization_users, dependent: :destroy, as: :user + has_many :organizations, through: :organization_users def organizations_scope - @organizations_scope ||= begin - if self.admin? - Organization.present - else - self.organizations.present - end + if admin? + @organizations_scope ||= Organization.present + else + @organizations_scope ||= organizations.present end end @@ -70,7 +68,7 @@ class User < ApplicationRecord end def self.[](email) - where(:email_address => email).first + where(email_address: email).first end end diff --git a/app/models/user/authentication.rb b/app/models/user/authentication.rb index 0db7cdd..3a9ff87 100644 --- a/app/models/user/authentication.rb +++ b/app/models/user/authentication.rb @@ -2,9 +2,9 @@ class User has_secure_password - validates :password, :length => {:minimum => 8, :allow_blank => true} + validates :password, length: { minimum: 8, allow_blank: true } - when_attribute :password_digest, :changes_to => :anything do + when_attribute :password_digest, changes_to: :anything do before_save do self.password_reset_token = nil self.password_reset_token_valid_until = nil @@ -12,9 +12,10 @@ class User end def self.authenticate(email_address, password) - user = where(:email_address => email_address).first - raise Postal::Errors::AuthenticationError.new('InvalidEmailAddress') if user.nil? - raise Postal::Errors::AuthenticationError.new('InvalidPassword') unless user.authenticate(password) + user = where(email_address: email_address).first + raise Postal::Errors::AuthenticationError, "InvalidEmailAddress" if user.nil? + raise Postal::Errors::AuthenticationError, "InvalidPassword" unless user.authenticate(password) + user end @@ -27,9 +28,9 @@ class User end def begin_password_reset(return_to = nil) - self.password_reset_token = Nifty::Utils::RandomString.generate(:length => 24) + self.password_reset_token = Nifty::Utils::RandomString.generate(length: 24) self.password_reset_token_valid_until = 1.day.from_now - self.save! + save! AppMailer.password_reset(self, return_to).deliver end diff --git a/app/models/user_invite.rb b/app/models/user_invite.rb index 019c4f8..4db0863 100644 --- a/app/models/user_invite.rb +++ b/app/models/user_invite.rb @@ -18,10 +18,10 @@ class UserInvite < ApplicationRecord include HasUUID - validates :email_address, :presence => true, :uniqueness => true, :format => {:with => /@/, :allow_blank => true} + validates :email_address, presence: true, uniqueness: true, format: { with: /@/, allow_blank: true } - has_many :organization_users, :dependent => :destroy, :as => :user - has_many :organizations, :through => :organization_users + has_many :organization_users, dependent: :destroy, as: :user + has_many :organizations, through: :organization_users default_value :expires_at, -> { 7.days.from_now } @@ -41,16 +41,16 @@ class UserInvite < ApplicationRecord def accept(user) transaction do - self.organization_users.each do |ou| - ou.update(:user => user) || ou.destroy + organization_users.each do |ou| + ou.update(user: user) || ou.destroy end - self.organization_users.reload - self.destroy + organization_users.reload + destroy end end def reject - self.destroy + destroy end end diff --git a/app/models/webhook.rb b/app/models/webhook.rb index ea5e0f3..e5b6989 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -24,19 +24,19 @@ class Webhook < ApplicationRecord include HasUUID belongs_to :server - has_many :webhook_events, :dependent => :destroy + has_many :webhook_events, dependent: :destroy has_many :webhook_requests - validates :name, :presence => true - validates :url, :presence => true, :format => {:with => /\Ahttps?\:\/\/[a-z0-9\-\.\_\?\=\&\/\+:%@]+\z/i, :allow_blank => true} + validates :name, presence: true + validates :url, presence: true, format: { with: /\Ahttps?:\/\/[a-z0-9\-._?=&\/+:%@]+\z/i, allow_blank: true } - scope :enabled, -> { where(:enabled => true) } + scope :enabled, -> { where(enabled: true) } after_save :save_events - when_attribute :all_events, :changes_to => true do + when_attribute :all_events, changes_to: true do after_save do - self.webhook_events.destroy_all + webhook_events.destroy_all end end @@ -49,12 +49,12 @@ class Webhook < ApplicationRecord end def save_events - if @events - @events.each do |event| - webhook_events.where(:event => event).first_or_create! - end - webhook_events.where.not(:event => @events).destroy_all + return unless @events + + @events.each do |event| + webhook_events.where(event: event).first_or_create! end + webhook_events.where.not(event: @events).destroy_all end end diff --git a/app/models/webhook_event.rb b/app/models/webhook_event.rb index 65c02df..81b7252 100644 --- a/app/models/webhook_event.rb +++ b/app/models/webhook_event.rb @@ -15,18 +15,18 @@ class WebhookEvent < ApplicationRecord EVENTS = [ - 'MessageSent', - 'MessageDelayed', - 'MessageDeliveryFailed', - 'MessageHeld', - 'MessageBounced', - 'MessageLinkClicked', - 'MessageLoaded', - 'DomainDNSError' + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError" ] belongs_to :webhook - validates :event, :presence => true + validates :event, presence: true end diff --git a/app/models/webhook_request.rb b/app/models/webhook_request.rb index bbe5cca..5fc3043 100644 --- a/app/models/webhook_request.rb +++ b/app/models/webhook_request.rb @@ -19,17 +19,17 @@ class WebhookRequest < ApplicationRecord include HasUUID - RETRIES = {1 => 2.minutes, 2 => 3.minutes, 3 => 6.minutes, 4 => 10.minutes, 5 => 15.minutes} + RETRIES = { 1 => 2.minutes, 2 => 3.minutes, 3 => 6.minutes, 4 => 10.minutes, 5 => 15.minutes } belongs_to :server - belongs_to :webhook, :optional => true + belongs_to :webhook, optional: true - validates :url, :presence => true - validates :event, :presence => true + validates :url, presence: true + validates :event, presence: true serialize :payload, Hash - after_commit :queue, :on => :create + after_commit :queue, on: :create def self.trigger(server, event, payload = {}) unless server.is_a?(Server) @@ -38,52 +38,52 @@ class WebhookRequest < ApplicationRecord webhooks = server.webhooks.enabled.includes(:webhook_events).references(:webhook_events).where("webhooks.all_events = ? OR webhook_events.event = ?", true, event) webhooks.each do |webhook| - server.webhook_requests.create!(:event => event, :payload => payload, :webhook => webhook, :url => webhook.url) + server.webhook_requests.create!(event: event, payload: payload, webhook: webhook, url: webhook.url) end end def self.requeue_all - where("retry_after < ?", Time.now).each(&:queue) + where("retry_after < ?", Time.now).find_each(&:queue) end def queue - WebhookDeliveryJob.queue(:main, :id => self.id) + WebhookDeliveryJob.queue(:main, id: id) end def deliver logger = Postal.logger_for(:webhooks) - payload = {:event => self.event, :timestamp => self.created_at.to_f, :payload => self.payload, :uuid => self.uuid}.to_json - logger.info "[#{id}] Sending webhook request to `#{self.url}`" - result = Postal::HTTP.post(self.url, :sign => true, :json => payload, :timeout => 5) + payload = { event: event, timestamp: created_at.to_f, payload: self.payload, uuid: uuid }.to_json + logger.info "[#{id}] Sending webhook request to `#{url}`" + result = Postal::HTTP.post(url, sign: true, json: payload, timeout: 5) self.attempts += 1 self.retry_after = RETRIES[self.attempts]&.from_now - self.server.message_db.webhooks.record( - :event => self.event, - :url => self.url, - :webhook_id => self.webhook_id, - :attempt => self.attempts, - :timestamp => Time.now.to_f, - :payload => self.payload.to_json, - :uuid => self.uuid, - :status_code => result[:code], - :body => result[:body], - :will_retry => (self.retry_after ? 0 : 1) + server.message_db.webhooks.record( + event: event, + url: url, + webhook_id: webhook_id, + attempt: self.attempts, + timestamp: Time.now.to_f, + payload: self.payload.to_json, + uuid: uuid, + status_code: result[:code], + body: result[:body], + will_retry: (retry_after ? 0 : 1) ) if result[:code] >= 200 && result[:code] < 300 logger.info "[#{id}] -> Received #{result[:code]} status code. That's OK." - self.destroy - self.webhook&.update_column(:last_used_at, Time.now) + destroy + webhook&.update_column(:last_used_at, Time.now) true else logger.error "[#{id}] -> Received #{result[:code]} status code. That's not OK." self.error = "Couldn't send to URL. Code received was #{result[:code]}" - if self.retry_after - logger.info "[#{id}] -> Will retry #{self.retry_after} (this was attempt #{self.attempts})" - self.save + if retry_after + logger.info "[#{id}] -> Will retry #{retry_after} (this was attempt #{self.attempts})" + save else logger.info "[#{id}] -> Have tried #{self.attempts} times. Giving up." - self.destroy + destroy end false end diff --git a/bin/bundle b/bin/bundle index 66e9889..67efc37 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -load Gem.bin_path('bundler', 'bundle') +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) +load Gem.bin_path("bundler", "bundle") diff --git a/bin/rails b/bin/rails index 0739660..efc0377 100755 --- a/bin/rails +++ b/bin/rails @@ -1,4 +1,4 @@ #!/usr/bin/env ruby -APP_PATH = File.expand_path('../config/application', __dir__) -require_relative '../config/boot' -require 'rails/commands' +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/bin/rake b/bin/rake index 1724048..4fbf10b 100755 --- a/bin/rake +++ b/bin/rake @@ -1,4 +1,4 @@ #!/usr/bin/env ruby -require_relative '../config/boot' -require 'rake' +require_relative "../config/boot" +require "rake" Rake.application.run diff --git a/bin/setup b/bin/setup index e620b4d..37be32e 100755 --- a/bin/setup +++ b/bin/setup @@ -1,10 +1,10 @@ #!/usr/bin/env ruby -require 'pathname' -require 'fileutils' +require "pathname" +require "fileutils" include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = Pathname.new File.expand_path("..", __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -14,9 +14,9 @@ chdir APP_ROOT do # This script is a starting point to setup your application. # Add necessary setup steps to this file. - puts '== Installing dependencies ==' - system! 'gem install bundler --conservative' - system('bundle check') || system!('bundle install') + puts "== Installing dependencies ==" + system! "gem install bundler --conservative" + system("bundle check") || system!("bundle install") # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') @@ -24,11 +24,11 @@ chdir APP_ROOT do # end puts "\n== Preparing database ==" - system! 'bin/rails db:setup' + system! "bin/rails db:setup" puts "\n== Removing old logs and tempfiles ==" - system! 'bin/rails log:clear tmp:clear' + system! "bin/rails log:clear tmp:clear" puts "\n== Restarting application server ==" - system! 'bin/rails restart' + system! "bin/rails restart" end diff --git a/bin/update b/bin/update index a8e4462..db28935 100755 --- a/bin/update +++ b/bin/update @@ -1,10 +1,10 @@ #!/usr/bin/env ruby -require 'pathname' -require 'fileutils' +require "pathname" +require "fileutils" include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = Pathname.new File.expand_path("..", __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -14,16 +14,16 @@ chdir APP_ROOT do # This script is a way to update your development environment automatically. # Add necessary update steps to this file. - puts '== Installing dependencies ==' - system! 'gem install bundler --conservative' - system('bundle check') || system!('bundle install') + puts "== Installing dependencies ==" + system! "gem install bundler --conservative" + system("bundle check") || system!("bundle install") puts "\n== Updating database ==" - system! 'bin/rails db:migrate' + system! "bin/rails db:migrate" puts "\n== Removing old logs and tempfiles ==" - system! 'bin/rails log:clear tmp:clear' + system! "bin/rails log:clear tmp:clear" puts "\n== Restarting application server ==" - system! 'bin/rails restart' + system! "bin/rails restart" end diff --git a/config.ru b/config.ru index 565a46c..826d2c9 100644 --- a/config.ru +++ b/config.ru @@ -1,5 +1,5 @@ # This file is used by Rack-based servers to start the application. -require_relative 'config/environment' -$0="[postal] #{ENV['PROC_NAME']}" +require_relative "config/environment" +$0 = "[postal] #{ENV.fetch('PROC_NAME', nil)}" run Rails.application diff --git a/config/application.rb b/config/application.rb index 175c804..46daf09 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,4 +1,4 @@ -require_relative 'boot' +require_relative "boot" require "rails" require "active_model/railtie" @@ -14,6 +14,7 @@ Bundler.require(*Rails.groups) module Postal class Application < Rails::Application + # Disable most generators config.generators do |g| g.orm :active_record @@ -27,12 +28,13 @@ module Postal config.eager_load_namespaces << Postal # Disable field_with_errors - config.action_view.field_error_proc = Proc.new { |t, i| t } + config.action_view.field_error_proc = proc { |t, i| t } # Load the tracking server middleware - require 'postal/tracking_middleware' + require "postal/tracking_middleware" config.middleware.use Postal::TrackingMiddleware config.logger = Postal.logger_for(:rails) + end end diff --git a/config/boot.rb b/config/boot.rb index 26c224b..6a4545b 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,11 +1,11 @@ -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) $stdout.sync = true $stderr.sync = true -require 'bundler/setup' # Set up gems listed in the Gemfile. +require "bundler/setup" # Set up gems listed in the Gemfile. -require_relative '../lib/postal/config' +require_relative "../lib/postal/config" Postal.check_config! -ENV['RAILS_ENV'] = Postal.config.rails&.environment || 'development' +ENV["RAILS_ENV"] = Postal.config.rails&.environment || "development" diff --git a/config/cron.rb b/config/cron.rb index c89c62b..b9def94 100644 --- a/config/cron.rb +++ b/config/cron.rb @@ -1,26 +1,26 @@ module Clockwork configure do |config| - config[:tz] = 'UTC' + config[:tz] = "UTC" config[:logger] = Postal.logger_for(:cron) end - every 1.minute, 'every-1-minutes' do + every 1.minute, "every-1-minutes" do RequeueWebhooksJob.queue(:main) SendNotificationsJob.queue(:main) end - every 1.hour, 'every-hour', :at => ['**:15'] do + every 1.hour, "every-hour", at: ["**:15"] do CheckAllDNSJob.queue(:main) ExpireHeldMessagesJob.queue(:main) CleanupAuthieSessionsJob.queue(:main) end - every 1.hour, 'every-hour', :at => ['**:45'] do + every 1.hour, "every-hour", at: ["**:45"] do PruneWebhookRequestsJob.queue(:main) end - every 1.day, 'every-day', :at => ['03:00'] do + every 1.day, "every-day", at: ["03:00"] do ProcessMessageRetentionJob.queue(:main) PruneSuppressionListsJob.queue(:main) end diff --git a/config/environment.rb b/config/environment.rb index 426333b..cac5315 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,5 @@ # Load the Rails application. -require_relative 'application' +require_relative "application" # Initialize the Rails application. Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 896ef8d..c3e7b0a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -13,12 +13,12 @@ Rails.application.configure do config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. - if Rails.root.join('tmp/caching-dev.txt').exist? + if Rails.root.join("tmp/caching-dev.txt").exist? config.action_controller.perform_caching = true config.cache_store = :memory_store config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=172800' + "Cache-Control" => "public, max-age=172800" } else config.action_controller.perform_caching = false diff --git a/config/environments/production.rb b/config/environments/production.rb index 42f2fee..8c12538 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -34,7 +34,6 @@ Rails.application.configure do # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true @@ -43,7 +42,7 @@ Rails.application.configure do config.log_level = :info # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -65,7 +64,7 @@ Rails.application.configure do config.active_support.deprecation = :notify # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new + config.log_formatter = Logger::Formatter.new # Use a different logger for distributed setups. # require 'syslog/logger' diff --git a/config/environments/test.rb b/config/environments/test.rb index 30587ef..a3a92fb 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -15,7 +15,7 @@ Rails.application.configure do # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=3600' + "Cache-Control" => "public, max-age=3600" } # Show full error reports and disable caching. diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 01ef3e6..0838ada 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -1,7 +1,7 @@ # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. -Rails.application.config.assets.version = '1.0' +Rails.application.config.assets.version = "1.0" # Add additional assets to the asset load path # Rails.application.config.assets.paths << Emoji.images_path diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 7cb810c..ee182ad 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -12,13 +12,13 @@ # These inflection rules are supported but not enabled by default: ActiveSupport::Inflector.inflections(:en) do |inflect| - inflect.acronym 'API' - inflect.acronym 'SMTP' - inflect.acronym 'IP' - inflect.acronym 'DNS' - inflect.acronym 'UUID' - inflect.acronym 'HTTP' - inflect.acronym 'DB' - inflect.acronym 'MX' - inflect.acronym 'DKIM' + inflect.acronym "API" + inflect.acronym "SMTP" + inflect.acronym "IP" + inflect.acronym "DNS" + inflect.acronym "UUID" + inflect.acronym "HTTP" + inflect.acronym "DB" + inflect.acronym "MX" + inflect.acronym "DKIM" end diff --git a/config/initializers/mail_extensions.rb b/config/initializers/mail_extensions.rb index 034cf2e..d6cff5e 100644 --- a/config/initializers/mail_extensions.rb +++ b/config/initializers/mail_extensions.rb @@ -1,37 +1,38 @@ -require 'mail' +require "mail" module Mail + module Encodings + # Handle windows-1258 as windows-1252 when decoding - def Encodings.q_value_decode(str) - str = str.sub(/\=\?windows-?1258\?/i, '\=?windows-1252?') + def self.q_value_decode(str) + str = str.sub(/=\?windows-?1258\?/i, '\=?windows-1252?') RubyVer.q_value_decode(str) end - def Encodings.b_value_decode(str) - str = str.sub(/\=\?windows-?1258\?/i, '\=?windows-1252?') + + def self.b_value_decode(str) + str = str.sub(/=\?windows-?1258\?/i, '\=?windows-1252?') RubyVer.b_value_decode(str) end + end class Message + ## Extract plain text body of message def plain_body - if self.multipart? and self.text_part - self.text_part.decoded - elsif self.mime_type == 'text/plain' || self.mime_type.nil? - self.decoded - else - nil + if multipart? and text_part + text_part.decoded + elsif mime_type == "text/plain" || mime_type.nil? + decoded end end ## Extract HTML text body of message def html_body - if self.multipart? and self.html_part - self.html_part.decoded - elsif self.mime_type == 'text/html' - self.decoded - else - nil + if multipart? and html_part + html_part.decoded + elsif mime_type == "text/html" + decoded end end @@ -46,9 +47,21 @@ module Mail # Returns the filename of the attachment (if it exists) or returns nil # Make up a filename for rfc822 attachments if it isn't specified def find_attachment - content_type_name = header[:content_type].filename rescue nil - content_disp_name = header[:content_disposition].filename rescue nil - content_loc_name = header[:content_location].location rescue nil + content_type_name = begin + header[:content_type].filename + rescue StandardError + nil + end + content_disp_name = begin + header[:content_disposition].filename + rescue StandardError + nil + end + content_loc_name = begin + header[:content_location].location + rescue StandardError + nil + end if content_type && content_type_name filename = content_type_name @@ -56,56 +69,73 @@ module Mail filename = content_disp_name elsif content_location && content_loc_name filename = content_loc_name - elsif self.mime_type == "message/rfc822" - filename = "#{rand(100000000)}.eml" + elsif mime_type == "message/rfc822" + filename = "#{rand(100_000_000)}.eml" else filename = nil end if filename # Normal decode - filename = Mail::Encodings.decode_encode(filename, :decode) rescue filename + filename = begin + Mail::Encodings.decode_encode(filename, :decode) + rescue StandardError + filename + end end filename end def decode_body_as_text body_text = decode_body - charset_tmp = Encoding.find(Ruby19.pick_encoding(charset)) rescue 'ASCII' - charset_tmp = 'Windows-1252' if charset_tmp.to_s =~ /windows-?1258/i - if charset_tmp == Encoding.find('UTF-7') - body_text.force_encoding('UTF-8') - decoded = body_text.gsub(/\+.*?\-/m) {|n|Base64.decode64(n[1..-2]+'===').force_encoding('UTF-16BE').encode('UTF-8')} + charset_tmp = begin + Encoding.find(Ruby19.pick_encoding(charset)) + rescue StandardError + "ASCII" + end + charset_tmp = "Windows-1252" if charset_tmp.to_s =~ /windows-?1258/i + if charset_tmp == Encoding.find("UTF-7") + body_text.force_encoding("UTF-8") + decoded = body_text.gsub(/\+.*?-/m) { |n| Base64.decode64(n[1..-2] + "===").force_encoding("UTF-16BE").encode("UTF-8") } else body_text.force_encoding(charset_tmp) - decoded = body_text.encode("utf-8", :invalid => :replace, :undef => :replace) + decoded = body_text.encode("utf-8", invalid: :replace, undef: :replace) end - decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :undef => :replace).encode("utf-8") + decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", invalid: :replace, undef: :replace).encode("utf-8") end + end # Handle attached emails as attachments class AttachmentsList < Array + def initialize(parts_list) @parts_list = parts_list - @content_disposition_type = 'attachment' - parts_list.map { |p| + @content_disposition_type = "attachment" + parts_list.map do |p| (p.parts.empty? and p.attachment?) ? p : p.attachments - }.flatten.compact.each { |a| self << a } + end.flatten.compact.each { |a| self << a } self end + end + end class Array + def decoded - return nil if self.empty? - return self.first.decoded + return nil if empty? + + first.decoded end + end class NilClass + def decoded nil end + end diff --git a/config/initializers/postal.rb b/config/initializers/postal.rb index 78a0487..ca8e222 100644 --- a/config/initializers/postal.rb +++ b/config/initializers/postal.rb @@ -1,2 +1,2 @@ -require 'postal' -require 'postal/message_db/mysql' +require "postal" +require "postal/message_db/mysql" diff --git a/config/initializers/record_key_for_dom.rb b/config/initializers/record_key_for_dom.rb index b2bd913..b9ff469 100644 --- a/config/initializers/record_key_for_dom.rb +++ b/config/initializers/record_key_for_dom.rb @@ -1,5 +1,6 @@ module ActionView module RecordIdentifier + def dom_id(record, prefix = nil) if record.new_record? dom_class(record, prefix || NEW) @@ -8,5 +9,6 @@ module ActionView "#{dom_class(record, prefix)}#{JOIN}#{id}" end end + end end diff --git a/config/initializers/secret_key.rb b/config/initializers/secret_key.rb index 0c90da4..b649767 100644 --- a/config/initializers/secret_key.rb +++ b/config/initializers/secret_key.rb @@ -1,6 +1,6 @@ if Postal.config.rails&.secret_key Rails.application.secrets.secret_key_base = Postal.config.rails.secret_key else - $stderr.puts "No secret key was specified in the Postal config file. Using one for just this session" + warn "No secret key was specified in the Postal config file. Using one for just this session" Rails.application.secrets.secret_key_base = SecureRandom.hex(128) end diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb index 17747dc..98422c8 100644 --- a/config/initializers/secure_headers.rb +++ b/config/initializers/secure_headers.rb @@ -1,10 +1,8 @@ SecureHeaders::Configuration.default do |config| - config.hsts = SecureHeaders::OPT_OUT config.csp[:default_src] = [] config.csp[:script_src] = ["'self'"] config.csp[:child_src] = ["'self'"] config.csp[:connect_src] = ["'self'"] - end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index b28e197..36aad0a 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -1,14 +1,14 @@ -require 'postal/config' +require "postal/config" if Postal.config.general&.exception_url - require 'raven' + require "raven" Raven.configure do |config| config.dsn = Postal.config.general.exception_url - config.environments = ['production'] - if ENV['DEV_EXCEPTIONS'] - config.environments << 'development' + config.environments = ["production"] + if ENV["DEV_EXCEPTIONS"] + config.environments << "development" end config.silence_ready = true - config.tags = {:process => ENV['PROC_NAME']} + config.tags = { process: ENV.fetch("PROC_NAME", nil) } end end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index f93e36c..d5ee351 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Rails.application.config.session_store :cookie_store, key: '_postal_session' +Rails.application.config.session_store :cookie_store, key: "_postal_session" diff --git a/config/initializers/smtp.rb b/config/initializers/smtp.rb index 1ccdb89..9380825 100644 --- a/config/initializers/smtp.rb +++ b/config/initializers/smtp.rb @@ -1,5 +1,5 @@ -require 'postal/config' +require "postal/config" if Postal.config&.smtp ActionMailer::Base.delivery_method = :smtp - ActionMailer::Base.smtp_settings = {:address => Postal.config.smtp.host, :user_name => Postal.config.smtp.username, :password => Postal.config.smtp.password, :port => Postal.config.smtp.port || 25} + ActionMailer::Base.smtp_settings = { address: Postal.config.smtp.host, user_name: Postal.config.smtp.username, password: Postal.config.smtp.password, port: Postal.config.smtp.port || 25 } end diff --git a/config/initializers/smtp_extensions.rb b/config/initializers/smtp_extensions.rb index 6508d60..609560f 100644 --- a/config/initializers/smtp_extensions.rb +++ b/config/initializers/smtp_extensions.rb @@ -1,10 +1,13 @@ class Net::SMTP::Response + def message @string end + end class Net::SMTP + attr_accessor :source_address def secure_socket? @@ -19,7 +22,7 @@ class Net::SMTP # def rset @error_occurred = false - getok('RSET') + getok("RSET") end def rset_errors @@ -29,6 +32,7 @@ class Net::SMTP private def tcp_socket(address, port) - TCPSocket.open(address, port, self.source_address) + TCPSocket.open(address, port, source_address) end + end diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb index c94ba2a..c4317e6 100644 --- a/config/initializers/trusted_proxies.rb +++ b/config/initializers/trusted_proxies.rb @@ -1,9 +1,13 @@ module Rack class Request + module Helpers + def trusted_proxy?(ip) ip =~ /^127\.0\.0\.1$|^localhost$|^unix$$/i end + end + end end diff --git a/config/puma.rb b/config/puma.rb index ed64fb4..98e3877 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,9 +1,9 @@ -require_relative '../lib/postal/config' +require_relative "../lib/postal/config" threads_count = Postal.config.web_server&.max_threads&.to_i || 5 threads threads_count, threads_count -bind_address = Postal.config.web_server&.bind_address || '127.0.0.1' -bind_port = Postal.config.web_server&.port&.to_i || ENV['PORT'] || 5000 +bind_address = Postal.config.web_server&.bind_address || "127.0.0.1" +bind_port = Postal.config.web_server&.port&.to_i || ENV["PORT"] || 5000 bind "tcp://#{bind_address}:#{bind_port}" -environment Postal.config.rails&.environment || 'development' +environment Postal.config.rails&.environment || "development" prune_bundler quiet false diff --git a/config/routes.rb b/config/routes.rb index d0a4f18..48877e8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,20 +1,19 @@ Rails.application.routes.draw do - - scope "org/:org_permalink", :as => 'organization' do - resources :domains, :only => [:index, :new, :create, :destroy] do - match :verify, :on => :member, :via => [:get, :post] - get :setup, :on => :member - post :check, :on => :member + scope "org/:org_permalink", as: "organization" do + resources :domains, only: [:index, :new, :create, :destroy] do + match :verify, on: :member, via: [:get, :post] + get :setup, on: :member + post :check, on: :member end - resources :servers, :except => [:index] do - resources :domains, :only => [:index, :new, :create, :destroy] do - match :verify, :on => :member, :via => [:get, :post] - get :setup, :on => :member - post :check, :on => :member + resources :servers, except: [:index] do + resources :domains, only: [:index, :new, :create, :destroy] do + match :verify, on: :member, via: [:get, :post] + get :setup, on: :member + post :check, on: :member end resources :track_domains do - post :toggle_ssl, :on => :member - post :check, :on => :member + post :toggle_ssl, on: :member + post :check, on: :member end resources :credentials resources :routes @@ -23,70 +22,69 @@ Rails.application.routes.draw do resources :address_endpoints resources :ip_pool_rules resources :messages do - get :incoming, :on => :collection - get :outgoing, :on => :collection - get :held, :on => :collection - get :activity, :on => :member - get :plain, :on => :member - get :html, :on => :member - get :html_raw, :on => :member - get :attachments, :on => :member - get :headers, :on => :member - get :attachment, :on => :member - get :download, :on => :member - get :spam_checks, :on => :member - post :retry, :on => :member - post :cancel_hold, :on => :member - get :suppressions, :on => :collection - delete :remove_from_queue, :on => :member - get :deliveries, :on => :member + get :incoming, on: :collection + get :outgoing, on: :collection + get :held, on: :collection + get :activity, on: :member + get :plain, on: :member + get :html, on: :member + get :html_raw, on: :member + get :attachments, on: :member + get :headers, on: :member + get :attachment, on: :member + get :download, on: :member + get :spam_checks, on: :member + post :retry, on: :member + post :cancel_hold, on: :member + get :suppressions, on: :collection + delete :remove_from_queue, on: :member + get :deliveries, on: :member end resources :webhooks do - get :history, :on => :collection - get 'history/:uuid', :on => :collection, :action => 'history_request', :as => 'history_request' + get :history, on: :collection + get "history/:uuid", on: :collection, action: "history_request", as: "history_request" end - get :limits, :on => :member - get :retention, :on => :member - get :queue, :on => :member - get :spam, :on => :member - get :delete, :on => :member - get 'help/outgoing' => 'help#outgoing' - get 'help/incoming' => 'help#incoming' - get :advanced, :on => :member - post :suspend, :on => :member - post :unsuspend, :on => :member + get :limits, on: :member + get :retention, on: :member + get :queue, on: :member + get :spam, on: :member + get :delete, on: :member + get "help/outgoing" => "help#outgoing" + get "help/incoming" => "help#incoming" + get :advanced, on: :member + post :suspend, on: :member + post :unsuspend, on: :member end resources :ip_pool_rules - resources :ip_pools, :controller => 'organization_ip_pools' do - put :assignments, :on => :collection + resources :ip_pools, controller: "organization_ip_pools" do + put :assignments, on: :collection end - root 'servers#index' - get 'settings' => 'organizations#edit' - patch 'settings' => 'organizations#update' - get 'delete' => 'organizations#delete' - delete 'delete' => 'organizations#destroy' + root "servers#index" + get "settings" => "organizations#edit" + patch "settings" => "organizations#update" + get "delete" => "organizations#delete" + delete "delete" => "organizations#destroy" end - resources :organizations, :except => [:index] + resources :organizations, except: [:index] resources :users resources :ip_pools do resources :ip_addresses end - get 'settings' => 'user#edit' - patch 'settings' => 'user#update' - post 'persist' => 'sessions#persist' + get "settings" => "user#edit" + patch "settings" => "user#update" + post "persist" => "sessions#persist" - get 'login' => 'sessions#new' - post 'login' => 'sessions#create' - get 'login/token' => 'sessions#create_with_token' - delete 'logout' => 'sessions#destroy' - match 'login/reset' => 'sessions#begin_password_reset', :via => [:get, :post] - match 'login/reset/:token' => 'sessions#finish_password_reset', :via => [:get, :post] + get "login" => "sessions#new" + post "login" => "sessions#create" + get "login/token" => "sessions#create_with_token" + delete "logout" => "sessions#destroy" + match "login/reset" => "sessions#begin_password_reset", :via => [:get, :post] + match "login/reset/:token" => "sessions#finish_password_reset", :via => [:get, :post] + get "ip" => "sessions#ip" - get 'ip' => 'sessions#ip' - - root 'organizations#index' + root "organizations#index" end diff --git a/db/migrate/20161003195209_create_authie_sessions.authie.rb b/db/migrate/20161003195209_create_authie_sessions.authie.rb index fadf717..4b41180 100644 --- a/db/migrate/20161003195209_create_authie_sessions.authie.rb +++ b/db/migrate/20161003195209_create_authie_sessions.authie.rb @@ -1,5 +1,7 @@ # This migration comes from authie (originally 20141012174250) class CreateAuthieSessions < ActiveRecord::Migration + def change end + end diff --git a/db/migrate/20161003195210_add_indexes_to_authie_sessions.authie.rb b/db/migrate/20161003195210_add_indexes_to_authie_sessions.authie.rb index a3f7127..38a25ee 100644 --- a/db/migrate/20161003195210_add_indexes_to_authie_sessions.authie.rb +++ b/db/migrate/20161003195210_add_indexes_to_authie_sessions.authie.rb @@ -1,5 +1,7 @@ # This migration comes from authie (originally 20141013115205) class AddIndexesToAuthieSessions < ActiveRecord::Migration + def change end + end diff --git a/db/migrate/20161003195211_add_parent_id_to_authie_sessions.authie.rb b/db/migrate/20161003195211_add_parent_id_to_authie_sessions.authie.rb index 224dd28..674dff8 100644 --- a/db/migrate/20161003195211_add_parent_id_to_authie_sessions.authie.rb +++ b/db/migrate/20161003195211_add_parent_id_to_authie_sessions.authie.rb @@ -1,5 +1,7 @@ # This migration comes from authie (originally 20150109144120) class AddParentIdToAuthieSessions < ActiveRecord::Migration + def change end + end diff --git a/db/migrate/20161003195212_add_two_factor_auth_fields_to_authie.authie.rb b/db/migrate/20161003195212_add_two_factor_auth_fields_to_authie.authie.rb index 2a016b4..f1204b1 100644 --- a/db/migrate/20161003195212_add_two_factor_auth_fields_to_authie.authie.rb +++ b/db/migrate/20161003195212_add_two_factor_auth_fields_to_authie.authie.rb @@ -1,5 +1,7 @@ # This migration comes from authie (originally 20150305135400) class AddTwoFactorAuthFieldsToAuthie < ActiveRecord::Migration + def change end + end diff --git a/db/migrate/20170418200606_initial_schema.rb b/db/migrate/20170418200606_initial_schema.rb index e8ffbc1..2d42ff1 100644 --- a/db/migrate/20170418200606_initial_schema.rb +++ b/db/migrate/20170418200606_initial_schema.rb @@ -1,6 +1,6 @@ class InitialSchema < ActiveRecord::Migration - def up + def up create_table "additional_route_endpoints", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t| t.integer "route_id" t.string "endpoint_type" @@ -22,8 +22,8 @@ class InitialSchema < ActiveRecord::Migration t.string "token" t.string "browser_id" t.integer "user_id" - t.boolean "active", default: true - t.text "data", limit: 65535 + t.boolean "active", default: true + t.text "data", limit: 65_535 t.datetime "expires_at" t.datetime "login_at" t.string "login_ip" @@ -37,7 +37,7 @@ class InitialSchema < ActiveRecord::Migration t.integer "parent_id" t.datetime "two_factored_at" t.string "two_factored_ip" - t.integer "requests", default: 0 + t.integer "requests", default: 0 t.datetime "password_seen_at" t.string "token_hash" t.index ["browser_id"], name: "index_authie_sessions_on_browser_id", length: { browser_id: 8 }, using: :btree @@ -50,11 +50,11 @@ class InitialSchema < ActiveRecord::Migration t.string "key" t.string "type" t.string "name" - t.text "options", limit: 65535 + t.text "options", limit: 65_535 t.datetime "last_used_at", precision: 6 t.datetime "created_at", precision: 6 t.datetime "updated_at", precision: 6 - t.boolean "hold", default: false + t.boolean "hold", default: false end create_table "domains", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t| @@ -64,7 +64,7 @@ class InitialSchema < ActiveRecord::Migration t.string "verification_token" t.string "verification_method" t.datetime "verified_at" - t.text "dkim_private_key", limit: 65535 + t.text "dkim_private_key", limit: 65_535 t.datetime "created_at", precision: 6 t.datetime "updated_at", precision: 6 t.datetime "dns_checked_at", precision: 6 @@ -93,13 +93,13 @@ class InitialSchema < ActiveRecord::Migration t.string "url" t.string "encoding" t.string "format" - t.boolean "strip_replies", default: false - t.text "error", limit: 65535 + t.boolean "strip_replies", default: false + t.text "error", limit: 65_535 t.datetime "disabled_until", precision: 6 t.datetime "last_used_at", precision: 6 t.datetime "created_at", precision: 6 t.datetime "updated_at", precision: 6 - t.boolean "include_attachments", default: true + t.boolean "include_attachments", default: true t.integer "timeout" end @@ -117,8 +117,8 @@ class InitialSchema < ActiveRecord::Migration t.string "owner_type" t.integer "owner_id" t.integer "ip_pool_id" - t.text "from_text", limit: 65535 - t.text "to_text", limit: 65535 + t.text "from_text", limit: 65_535 + t.text "to_text", limit: 65_535 t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -128,7 +128,7 @@ class InitialSchema < ActiveRecord::Migration t.string "uuid" t.datetime "created_at", precision: 6 t.datetime "updated_at", precision: 6 - t.boolean "default", default: false + t.boolean "default", default: false t.string "type" t.index ["uuid"], name: "index_ip_pools_on_uuid", length: { uuid: 8 }, using: :btree end @@ -143,7 +143,7 @@ class InitialSchema < ActiveRecord::Migration create_table "organization_users", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t| t.integer "organization_id" t.integer "user_id" - t.datetime "created_at", precision: 6 + t.datetime "created_at", precision: 6 t.boolean "admin", default: false t.boolean "all_servers", default: true t.string "user_type" @@ -170,14 +170,14 @@ class InitialSchema < ActiveRecord::Migration t.integer "message_id" t.string "domain" t.string "locked_by" - t.datetime "locked_at", precision: 6 + t.datetime "locked_at", precision: 6 t.datetime "retry_after" t.datetime "created_at", precision: 6 t.datetime "updated_at", precision: 6 t.integer "ip_address_id" - t.integer "attempts", default: 0 + t.integer "attempts", default: 0 t.integer "route_id" - t.boolean "manual", default: false + t.boolean "manual", default: false t.string "batch_key" t.index ["domain"], name: "index_queued_messages_on_domain", length: { domain: 8 }, using: :btree t.index ["message_id"], name: "index_queued_messages_on_message_id", using: :btree @@ -209,11 +209,11 @@ class InitialSchema < ActiveRecord::Migration t.datetime "updated_at", precision: 6 t.string "permalink" t.integer "send_limit" - t.datetime "deleted_at", precision: 6 + t.datetime "deleted_at", precision: 6 t.integer "message_retention_days" t.integer "raw_message_retention_days" t.integer "raw_message_retention_size" - t.boolean "allow_sender", default: false + t.boolean "allow_sender", default: false t.string "token" t.datetime "send_limit_approaching_at", precision: 6 t.datetime "send_limit_approaching_notified_at", precision: 6 @@ -224,9 +224,9 @@ class InitialSchema < ActiveRecord::Migration t.string "postmaster_address" t.datetime "suspended_at", precision: 6 t.decimal "outbound_spam_threshold", precision: 8, scale: 2 - t.text "domains_not_to_click_track", limit: 65535 + t.text "domains_not_to_click_track", limit: 65_535 t.string "suspension_reason" - t.boolean "log_smtp_data", default: false + t.boolean "log_smtp_data", default: false t.index ["organization_id"], name: "index_servers_on_organization_id", using: :btree t.index ["permalink"], name: "index_servers_on_permalink", length: { permalink: 6 }, using: :btree t.index ["token"], name: "index_servers_on_token", length: { token: 6 }, using: :btree @@ -240,7 +240,7 @@ class InitialSchema < ActiveRecord::Migration t.string "hostname" t.string "ssl_mode" t.integer "port" - t.text "error", limit: 65535 + t.text "error", limit: 65_535 t.datetime "disabled_until", precision: 6 t.datetime "last_used_at", precision: 6 t.datetime "created_at", precision: 6 @@ -255,9 +255,9 @@ class InitialSchema < ActiveRecord::Migration create_table "track_certificates", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t| t.string "domain" - t.text "certificate", limit: 65535 - t.text "intermediaries", limit: 65535 - t.text "key", limit: 65535 + t.text "certificate", limit: 65_535 + t.text "intermediaries", limit: 65_535 + t.text "key", limit: 65_535 t.datetime "expires_at" t.datetime "renew_after" t.string "verification_path" @@ -280,7 +280,7 @@ class InitialSchema < ActiveRecord::Migration t.boolean "ssl_enabled", default: true t.boolean "track_clicks", default: true t.boolean "track_loads", default: true - t.text "excluded_click_domains", limit: 65535 + t.text "excluded_click_domains", limit: 65_535 end create_table "user_invites", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t| @@ -305,7 +305,7 @@ class InitialSchema < ActiveRecord::Migration t.datetime "updated_at", precision: 6 t.string "password_reset_token" t.datetime "password_reset_token_valid_until" - t.boolean "admin", default: false + t.boolean "admin", default: false t.index ["email_address"], name: "index_users_on_email_address", length: { email_address: 8 }, using: :btree t.index ["uuid"], name: "index_users_on_uuid", length: { uuid: 8 }, using: :btree end @@ -323,11 +323,11 @@ class InitialSchema < ActiveRecord::Migration t.string "url" t.string "event" t.string "uuid" - t.text "payload", limit: 65535 - t.integer "attempts", default: 0 - t.datetime "retry_after", precision: 6 - t.text "error", limit: 65535 - t.datetime "created_at", precision: 6 + t.text "payload", limit: 65_535 + t.integer "attempts", default: 0 + t.datetime "retry_after", precision: 6 + t.text "error", limit: 65_535 + t.datetime "created_at", precision: 6 end create_table "webhooks", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t| @@ -344,4 +344,5 @@ class InitialSchema < ActiveRecord::Migration t.index ["server_id"], name: "index_webhooks_on_server_id", using: :btree end end + end diff --git a/db/migrate/20170421195414_add_token_hashes_to_authie_sessions.authie.rb b/db/migrate/20170421195414_add_token_hashes_to_authie_sessions.authie.rb index 03109c9..fc23ed9 100644 --- a/db/migrate/20170421195414_add_token_hashes_to_authie_sessions.authie.rb +++ b/db/migrate/20170421195414_add_token_hashes_to_authie_sessions.authie.rb @@ -1,5 +1,7 @@ # This migration comes from authie (originally 20170417170000) class AddTokenHashesToAuthieSessions < ActiveRecord::Migration + def change end + end diff --git a/db/migrate/20170421195415_add_index_to_token_hashes_on_authie_sessions.authie.rb b/db/migrate/20170421195415_add_index_to_token_hashes_on_authie_sessions.authie.rb index 26519e3..362c52a 100644 --- a/db/migrate/20170421195415_add_index_to_token_hashes_on_authie_sessions.authie.rb +++ b/db/migrate/20170421195415_add_index_to_token_hashes_on_authie_sessions.authie.rb @@ -1,6 +1,8 @@ # This migration comes from authie (originally 20170421174100) class AddIndexToTokenHashesOnAuthieSessions < ActiveRecord::Migration + def change - add_index :authie_sessions, :token_hash, :length => 8 + add_index :authie_sessions, :token_hash, length: 8 end + end diff --git a/db/migrate/20170428153353_remove_type_from_ip_pools.rb b/db/migrate/20170428153353_remove_type_from_ip_pools.rb index b3e0d0b..40a9b18 100644 --- a/db/migrate/20170428153353_remove_type_from_ip_pools.rb +++ b/db/migrate/20170428153353_remove_type_from_ip_pools.rb @@ -1,5 +1,7 @@ class RemoveTypeFromIPPools < ActiveRecord::Migration[5.0] + def change remove_column :ip_pools, :type, :string end + end diff --git a/db/migrate/20180216114344_add_host_to_authie_sessions.authie.rb b/db/migrate/20180216114344_add_host_to_authie_sessions.authie.rb index 93ffafb..df6bef7 100644 --- a/db/migrate/20180216114344_add_host_to_authie_sessions.authie.rb +++ b/db/migrate/20180216114344_add_host_to_authie_sessions.authie.rb @@ -1,6 +1,8 @@ # This migration comes from authie (originally 20180215152200) class AddHostToAuthieSessions < ActiveRecord::Migration[4.2] + def change add_column :authie_sessions, :host, :string end + end diff --git a/db/migrate/20200717083943_add_uuid_to_credentials.rb b/db/migrate/20200717083943_add_uuid_to_credentials.rb index b3bb1b6..efcb6d2 100644 --- a/db/migrate/20200717083943_add_uuid_to_credentials.rb +++ b/db/migrate/20200717083943_add_uuid_to_credentials.rb @@ -1,8 +1,10 @@ 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 diff --git a/db/migrate/20210727210551_add_priority_to_ip_addresses.rb b/db/migrate/20210727210551_add_priority_to_ip_addresses.rb index 3db1367..eae5cd7 100644 --- a/db/migrate/20210727210551_add_priority_to_ip_addresses.rb +++ b/db/migrate/20210727210551_add_priority_to_ip_addresses.rb @@ -1,6 +1,8 @@ class AddPriorityToIPAddresses < ActiveRecord::Migration[5.2] + def change add_column :ip_addresses, :priority, :integer IPAddress.where(priority: nil).update_all(priority: 100) end + end diff --git a/db/schema.rb b/db/schema.rb index 020e443..e815656 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_07_27_210551) do - +ActiveRecord::Schema.define(version: 20_210_727_210_551) do create_table "additional_route_endpoints", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "route_id" t.string "endpoint_type" @@ -357,5 +356,4 @@ ActiveRecord::Schema.define(version: 2021_07_27_210551) do t.datetime "updated_at", precision: 6 t.index ["server_id"], name: "index_webhooks_on_server_id" end - end diff --git a/lib/postal/app_logger.rb b/lib/postal/app_logger.rb index b41d0fb..4bac388 100644 --- a/lib/postal/app_logger.rb +++ b/lib/postal/app_logger.rb @@ -1,6 +1,7 @@ -require 'logger' +require "logger" module Postal + class AppLogger < Logger def initialize(log_name, *args) @@ -16,10 +17,14 @@ module Postal if message.nil? message = block_given? ? yield : progname end - message = message.to_s.force_encoding('UTF-8').scrub - message_without_ansi = message.gsub(/\e\[([\d\;]+)?m/, '') rescue message - n.notify!(:short_message => message_without_ansi, :log_name => @log_name, :facility => 'postal', :application_name => 'postal', :process_name => ENV['PROC_NAME'], :pid => Process.pid) - rescue => e + message = message.to_s.force_encoding("UTF-8").scrub + message_without_ansi = begin + message.gsub(/\e\[([\d;]+)?m/, "") + rescue StandardError + message + end + n.notify!(short_message: message_without_ansi, log_name: @log_name, facility: "postal", application_name: "postal", process_name: ENV.fetch("PROC_NAME", nil), pid: Process.pid) + rescue StandardError => e # Can't log this to GELF. Soz. end end @@ -33,21 +38,25 @@ module Postal def self.graylog_notifier @graylog_notifier ||= graylog? ? GELF::Notifier.new(Postal.config.logging.graylog.host, Postal.config.logging.graylog.port) : nil end + end class LogFormatter + TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%3N".freeze - COLORS = [32,34,35,31,32,33] + COLORS = [32, 34, 35, 31, 32, 33] def call(severity, datetime, progname, msg) time = datetime.strftime(TIME_FORMAT) - if number = ENV['PROC_NAME'] - id = number.split('.').last.to_i + if number = ENV["PROC_NAME"] + id = number.split(".").last.to_i proc_text = "\e[#{COLORS[id % COLORS.size]}m[#{ENV['PROC_NAME']}:#{Process.pid}]\e[0m" else proc_text = "[#{Process.pid}]" end "#{proc_text} [#{time}] #{severity} -- : #{msg}\n" end + end + end diff --git a/lib/postal/bounce_message.rb b/lib/postal/bounce_message.rb index 6f1251c..3834c61 100644 --- a/lib/postal/bounce_message.rb +++ b/lib/postal/bounce_message.rb @@ -12,18 +12,18 @@ module Postal mail.from = "Mail Delivery Service <#{@message.route.description}>" mail.subject = "Mail Delivery Failed (#{@message.subject})" mail.text_part = body - mail.attachments['Original Message.eml'] = {:mime_type => 'message/rfc822', :encoding => 'quoted-printable', :content => @message.raw_message} + mail.attachments["Original Message.eml"] = { mime_type: "message/rfc822", encoding: "quoted-printable", content: @message.raw_message } mail.message_id = "<#{SecureRandom.uuid}@#{Postal.config.dns.return_path}>" mail.to_s end def queue message = @server.message_db.new_message - message.scope = 'outgoing' + message.scope = "outgoing" message.rcpt_to = @message.mail_from message.mail_from = @message.route.description message.domain_id = @message.domain&.id - message.raw_message = self.raw_message + message.raw_message = raw_message message.bounce = 1 message.bounce_for_id = @message.id message.save @@ -37,7 +37,7 @@ module Postal private def body - <<-BODY.strip_heredoc + <<~BODY This is the mail delivery service responsible for delivering mail to #{@message.route.description}. The message you've sent cannot be delivered. Your original message is attached to this message. diff --git a/lib/postal/config.rb b/lib/postal/config.rb index ec43f45..402b3ef 100644 --- a/lib/postal/config.rb +++ b/lib/postal/config.rb @@ -1,10 +1,10 @@ -require 'yaml' -require 'pathname' -require 'cgi' -require 'openssl' -require 'fileutils' -require_relative 'error' -require_relative 'version' +require "yaml" +require "pathname" +require "cgi" +require "openssl" +require "fileutils" +require_relative "error" +require_relative "version" module Postal @@ -21,50 +21,44 @@ module Postal end def self.app_root - @app_root ||= Pathname.new(File.expand_path('../../../', __FILE__)) + @app_root ||= Pathname.new(File.expand_path("../..", __dir__)) end def self.config @config ||= begin - require 'hashie/mash' - config = Hashie::Mash.new(self.defaults) - config = config.deep_merge(self.yaml_config) - config.deep_merge(self.local_yaml_config) + require "hashie/mash" + config = Hashie::Mash.new(defaults) + config = config.deep_merge(yaml_config) + config.deep_merge(local_yaml_config) end end def self.config_root - @config_root ||= begin - if ENV['POSTAL_CONFIG_ROOT'] - Pathname.new(ENV['POSTAL_CONFIG_ROOT']) - else - Pathname.new(File.expand_path("../../../config/postal", __FILE__)) - end + if ENV["POSTAL_CONFIG_ROOT"] + @config_root ||= Pathname.new(ENV["POSTAL_CONFIG_ROOT"]) + else + @config_root ||= Pathname.new(File.expand_path("../../config/postal", __dir__)) end end def self.log_root - @log_root ||= begin - if config.logging.root - Pathname.new(config.logging.root) - else - app_root.join('log') - end + if config.logging.root + @log_root ||= Pathname.new(config.logging.root) + else + @log_root ||= app_root.join("log") end end def self.config_file_path - @config_file_path ||= begin - if env == 'default' - File.join(config_root, 'postal.yml') - else - File.join(config_root, "postal.#{env}.yml") - end + if env == "default" + @config_file_path ||= File.join(config_root, "postal.yml") + else + @config_file_path ||= File.join(config_root, "postal.#{env}.yml") end end def self.env - @env ||= ENV.fetch('POSTAL_ENV', 'default') + @env ||= ENV.fetch("POSTAL_ENV", "default") end def self.yaml_config @@ -72,7 +66,7 @@ module Postal end def self.local_config_file_path - @local_config_file_path ||= File.join(config_root, 'postal.local.yml') + @local_config_file_path ||= File.join(config_root, "postal.local.yml") end def self.local_yaml_config @@ -80,11 +74,11 @@ module Postal end def self.defaults_file_path - @defaults_file_path ||= app_root.join('config', 'postal.defaults.yml') + @defaults_file_path ||= app_root.join("config", "postal.defaults.yml") end def self.defaults - @defaults ||= YAML.load_file(self.defaults_file_path) + @defaults ||= YAML.load_file(defaults_file_path) end def self.database_url @@ -98,8 +92,8 @@ module Postal def self.logger_for(name) @loggers ||= {} @loggers[name.to_sym] ||= begin - require 'postal/app_logger' - if config.logging.stdout || ENV['LOG_TO_STDOUT'] + require "postal/app_logger" + if config.logging.stdout || ENV["LOG_TO_STDOUT"] Postal::AppLogger.new(name, STDOUT) else FileUtils.mkdir_p(log_root) @@ -111,9 +105,9 @@ module Postal def self.process_name @process_name ||= begin string = "host:#{Socket.gethostname} pid:#{Process.pid}" - string += " procname:#{ENV['PROC_NAME']}" if ENV['PROC_NAME'] + string += " procname:#{ENV['PROC_NAME']}" if ENV["PROC_NAME"] string - rescue + rescue StandardError "pid:#{Process.pid}" end end @@ -133,7 +127,7 @@ module Postal end def self.smtp_private_key_path - config.smtp_server.tls_private_key_path || config_root.join('smtp.key') + config.smtp_server.tls_private_key_path || config_root.join("smtp.key") end def self.smtp_private_key @@ -141,7 +135,7 @@ module Postal end def self.smtp_certificate_path - config.smtp_server.tls_certificate_path || config_root.join('smtp.cert') + config.smtp_server.tls_certificate_path || config_root.join("smtp.cert") end def self.smtp_certificate_data @@ -150,7 +144,7 @@ module Postal def self.smtp_certificates @smtp_certificates ||= begin - certs = self.smtp_certificate_data.scan(/-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m) + certs = smtp_certificate_data.scan(/-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m) certs.map do |c| OpenSSL::X509::Certificate.new(c) end.freeze @@ -158,7 +152,7 @@ module Postal end def self.signing_key_path - config_root.join('signing.key') + config_root.join("signing.key") end def self.signing_key @@ -166,7 +160,7 @@ module Postal end def self.rp_dkim_dns_record - public_key = signing_key.public_key.to_s.gsub(/\-+[A-Z ]+\-+\n/, '').gsub(/\n/, '') + public_key = signing_key.public_key.to_s.gsub(/-+[A-Z ]+-+\n/, "").gsub(/\n/, "") "v=DKIM1; t=s; h=sha256; p=#{public_key};" end @@ -174,19 +168,19 @@ module Postal end def self.check_config! - return if ENV['POSTAL_SKIP_CONFIG_CHECK'].to_i == 1 + return if ENV["POSTAL_SKIP_CONFIG_CHECK"].to_i == 1 - unless File.exist?(self.config_file_path) - raise ConfigError, "No config found at #{self.config_file_path}" + unless File.exist?(config_file_path) + raise ConfigError, "No config found at #{config_file_path}" end - unless File.exists?(self.signing_key_path) - raise ConfigError, "No signing key found at #{self.signing_key_path}" - end + return if File.exist?(signing_key_path) + + raise ConfigError, "No signing key found at #{signing_key_path}" end def self.ip_pools? - self.config.general.use_ip_pools? + config.general.use_ip_pools? end end diff --git a/lib/postal/countries.rb b/lib/postal/countries.rb index 1cd0944..0eeb4e3 100644 --- a/lib/postal/countries.rb +++ b/lib/postal/countries.rb @@ -1,5 +1,7 @@ module Postal module Countries + NAMES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia, Plurinational State of", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Christmas Island", "Cocos (keeling) Islands", "Colombia", "Comoros", "Congo", "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote D'ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (malvinas)", "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea-bissau", "Guyana", "Haiti", "Heard Island and Mcdonald Islands", "Holy See (Vatican City State)", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan", "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, the Former Yugoslav Republic of", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", "Rwanda", "Saint Barthelemy", "Saint Helena, Ascension and Tristan Da Cunha", "Saint Kitts and Nevis", "Saint Lucia", "Saint Martin", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic", "Taiwan", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela, Bolivarian Republic of", "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"] + end end diff --git a/lib/postal/dkim_header.rb b/lib/postal/dkim_header.rb index 2a6db56..a40489c 100644 --- a/lib/postal/dkim_header.rb +++ b/lib/postal/dkim_header.rb @@ -2,14 +2,14 @@ module Postal class DKIMHeader def initialize(domain, message) - if domain && domain.dkim_status == 'OK' + if domain && domain.dkim_status == "OK" @domain_name = domain.name @dkim_key = domain.dkim_key @dkim_identifier = domain.dkim_identifier else @domain_name = Postal.config.dns.return_path @dkim_key = Postal.signing_key - @dkim_identifier = 'postal' + @dkim_identifier = "postal" end @domain = domain @message = message @@ -23,15 +23,15 @@ module Postal private def headers - @headers ||= @raw_headers.to_s.gsub(/\r?\n\s/, ' ').split(/\r?\n/) + @headers ||= @raw_headers.to_s.gsub(/\r?\n\s/, " ").split(/\r?\n/) end def header_names - normalized_headers.map{ |h| h.split(':')[0].strip } + normalized_headers.map { |h| h.split(":")[0].strip } end def normalized_headers - Array.new.tap do |new_headers| + [].tap do |new_headers| headers.select { |h| h.match(/^(from|sender|reply-to|subject|date|message-id|to|cc|mime-version|content-type|content-transfer-encoding|resent-to|resent-cc|resent-from|resent-sender|resent-message-id|in-reply-to|references|list-id|list-help|list-owner|list-unsubscribe|list-subscribe|list-post):/i) }.each do |h| new_headers << normalize_header(h) end @@ -45,26 +45,26 @@ module Postal # https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.2 # Split the key and value. - key, value = content.split(':', 2) + key, value = content.split(":", 2) # Convert all header field names (not the header field values) to # lowercase. For example, convert "SUBJect: AbC" to "subject: AbC". key.downcase! # Unfold all header field continuation lines as described in [RFC5322] - value.gsub!(/\r?\n[ \t]+/, ' ') + value.gsub!(/\r?\n[ \t]+/, " ") # Convert all sequences of one or more WSP characters to a single SP character. - value.gsub!(/[ \t]+/, ' ') + value.gsub!(/[ \t]+/, " ") # Delete all WSP characters at the end of each unfolded header field value. - value.gsub!(/[ \t]*\z/, '') + value.gsub!(/[ \t]*\z/, "") # Delete any WSP characters remaining after the colon separating the header field name from the header field value. - value.gsub!(/\A[ \t]*/, '') + value.gsub!(/\A[ \t]*/, "") # Join together - key + ':' + value + key + ":" + value end def normalized_body @@ -77,14 +77,14 @@ module Postal # a. Reduce whitespace # # * Reduce all sequences of WSP within a line to a single SP character. - content.gsub!(/[ \t]+/, ' ') + content.gsub!(/[ \t]+/, " ") # * Ignore all whitespace at the end of lines. Implementations MUST NOT # remove the CRLF at the end of the line. content.gsub!(/ \r\n/, "\r\n") # b. Ignore all empty lines at the end of the message body. - content.gsub!(/[ \r\n]*\z/, '') + content.gsub!(/[ \r\n]*\z/, "") content += "\r\n" content @@ -96,7 +96,7 @@ module Postal end def dkim_properties - @dkim_properties ||= Array.new.tap do |header| + @dkim_properties ||= [].tap do |header| header << "a=rsa-sha256; c=relaxed/relaxed;" header << "d=#{@domain_name};" header << "s=#{@dkim_identifier}; t=#{Time.now.utc.to_i};" @@ -115,7 +115,7 @@ module Postal end def signature - Base64.encode64(@dkim_key.sign(OpenSSL::Digest::SHA256.new, signable_header_string)).gsub("\n", '') + Base64.encode64(@dkim_key.sign(OpenSSL::Digest.new("SHA256"), signable_header_string)).gsub("\n", "") end end diff --git a/lib/postal/error.rb b/lib/postal/error.rb index 6fc2450..d00a423 100644 --- a/lib/postal/error.rb +++ b/lib/postal/error.rb @@ -1,7 +1,9 @@ module Postal + module Errors end class Error < StandardError end + end diff --git a/lib/postal/helpers.rb b/lib/postal/helpers.rb index e4d795b..e4b7262 100644 --- a/lib/postal/helpers.rb +++ b/lib/postal/helpers.rb @@ -3,7 +3,8 @@ module Postal def self.strip_name_from_address(address) return nil if address.nil? - address.gsub(/.*.*/, '').gsub(/\(.+?\)/, '').strip + + address.gsub(/.*.*/, "").gsub(/\(.+?\)/, "").strip end end diff --git a/lib/postal/http.rb b/lib/postal/http.rb index 8e587c8..a3d1399 100644 --- a/lib/postal/http.rb +++ b/lib/postal/http.rb @@ -1,5 +1,5 @@ -require 'net/https' -require 'uri' +require "net/https" +require "uri" module Postal module HTTP @@ -16,7 +16,7 @@ module Postal options[:headers] ||= {} uri = URI.parse(url) request = method.new((uri.path.length == 0 ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")) - options[:headers].each { |k,v| request.add_field k, v } + options[:headers].each { |k, v| request.add_field k, v } if options[:username] || uri.user request.basic_auth(options[:username] || uri.user, options[:password] || uri.password) @@ -29,7 +29,7 @@ module Postal elsif options[:json].is_a?(String) # If we have a JSON string, set the content type and body to be the JSON # data - request.add_field 'Content-Type', 'application/json' + request.add_field "Content-Type", "application/json" request.body = options[:json] elsif options[:text_body] @@ -38,15 +38,15 @@ module Postal end if options[:sign] - signature = EncryptoSigno.sign(Postal.signing_key, request.body.to_s).gsub("\n", '') - request.add_field 'X-Postal-Signature', signature + signature = EncryptoSigno.sign(Postal.signing_key, request.body.to_s).gsub("\n", "") + request.add_field "X-Postal-Signature", signature end - request['User-Agent'] = options[:user_agent] || "Postal/#{Postal.version}" + request["User-Agent"] = options[:user_agent] || "Postal/#{Postal.version}" connection = Net::HTTP.new(uri.host, uri.port) - if uri.scheme == 'https' + if uri.scheme == "https" connection.use_ssl = true connection.verify_mode = OpenSSL::SSL::VERIFY_PEER ssl = true @@ -59,36 +59,35 @@ module Postal Timeout.timeout(timeout) do result = connection.request(request) { - :code => result.code.to_i, - :body => result.body, - :headers => result.to_hash, - :secure => @ssl + code: result.code.to_i, + body: result.body, + headers: result.to_hash, + secure: @ssl } end rescue OpenSSL::SSL::SSLError => e { - :code => -3, - :body => "Invalid SSL certificate", - :headers =>{}, - :secure => @ssl + code: -3, + body: "Invalid SSL certificate", + headers: {}, + secure: @ssl } rescue SocketError, Errno::ECONNRESET, EOFError, Errno::EINVAL, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Errno::ECONNREFUSED => e { - :code => -2, - :body => e.message, - :headers => {}, - :secure => @ssl + code: -2, + body: e.message, + headers: {}, + secure: @ssl } rescue Timeout::Error => e { - :code => -1, - :body => "Timed out after #{timeout}s", - :headers => {}, - :secure => @ssl + code: -1, + body: "Timed out after #{timeout}s", + headers: {}, + secure: @ssl } end end end end - diff --git a/lib/postal/http_sender.rb b/lib/postal/http_sender.rb index 46de166..6b66217 100644 --- a/lib/postal/http_sender.rb +++ b/lib/postal/http_sender.rb @@ -4,7 +4,7 @@ module Postal def initialize(endpoint, options = {}) @endpoint = endpoint @options = options - @log_id = Nifty::Utils::RandomString.generate(:length => 8).upcase + @log_id = Nifty::Utils::RandomString.generate(length: 8).upcase end def send_message(message) @@ -16,10 +16,10 @@ module Postal request_options[:sign] = true request_options[:timeout] = @endpoint.timeout || 5 case @endpoint.encoding - when 'BodyAsJSON' - request_options[:json] = parameters(message, :flat => false).to_json - when 'FormData' - request_options[:params] = parameters(message, :flat => true) + when "BodyAsJSON" + request_options[:json] = parameters(message, flat: false).to_json + when "FormData" + request_options[:params] = parameters(message, flat: true) end log "Sending request to #{@endpoint.url}" @@ -28,28 +28,28 @@ module Postal result.details = "Received a #{response[:code]} from #{@endpoint.url}" log " -> Received: #{response[:code]}" if response[:body] - log " -> Body: #{response[:body][0,255]}" + log " -> Body: #{response[:body][0, 255]}" result.output = response[:body].to_s[0, 500].strip end if response[:code] >= 200 && response[:code] < 300 - # This is considered a success - result.type = 'Sent' + #  This is considered a success + result.type = "Sent" elsif response[:code] >= 500 && response[:code] < 600 # This is temporary. They might fix their server so it should soft fail. - result.type = 'SoftFail' + result.type = "SoftFail" result.retry = true elsif response[:code] < 0 # Connection/SSL etc... errors - result.type = 'SoftFail' + result.type = "SoftFail" result.retry = true result.connect_error = true elsif response[:code] == 429 # Rate limit exceeded, treat as a hard fail and don't send bounces - result.type = 'HardFail' + result.type = "HardFail" result.suppress_bounce = true else # This is permanent. Any other error isn't cool with us. - result.type = 'HardFail' + result.type = "HardFail" end result.time = (Time.now - start_time).to_f.round(2) result @@ -63,29 +63,29 @@ module Postal def parameters(message, options = {}) case @endpoint.format - when 'Hash' + when "Hash" hash = { - :id => message.id, - :rcpt_to => message.rcpt_to, - :mail_from => message.mail_from, - :token => message.token, - :subject => message.subject, - :message_id => message.message_id, - :timestamp => message.timestamp.to_f, - :size => message.size, - :spam_status => message.spam_status, - :bounce => message.bounce == 1 ? true : false, - :received_with_ssl => message.received_with_ssl == 1, - :to => message.headers['to']&.last, - :cc => message.headers['cc']&.last, - :from => message.headers['from']&.last, - :date => message.headers['date']&.last, - :in_reply_to => message.headers['in-reply-to']&.last, - :references => message.headers['references']&.last, - :html_body => message.html_body, - :attachment_quantity => message.attachments.size, - :auto_submitted => message.headers['auto-submitted']&.last, - :reply_to => message.headers['reply-to'] + id: message.id, + rcpt_to: message.rcpt_to, + mail_from: message.mail_from, + token: message.token, + subject: message.subject, + message_id: message.message_id, + timestamp: message.timestamp.to_f, + size: message.size, + spam_status: message.spam_status, + bounce: message.bounce == 1, + received_with_ssl: message.received_with_ssl == 1, + to: message.headers["to"]&.last, + cc: message.headers["cc"]&.last, + from: message.headers["from"]&.last, + date: message.headers["date"]&.last, + in_reply_to: message.headers["in-reply-to"]&.last, + references: message.headers["references"]&.last, + html_body: message.html_body, + attachment_quantity: message.attachments.size, + auto_submitted: message.headers["auto-submitted"]&.last, + reply_to: message.headers["reply-to"] } if @endpoint.strip_replies @@ -105,24 +105,24 @@ module Postal else hash[:attachments] = message.attachments.map do |a| { - :filename => a.filename, - :content_type => a.mime_type, - :size => a.body.to_s.bytesize, - :data => Base64.encode64(a.body.to_s) + filename: a.filename, + content_type: a.mime_type, + size: a.body.to_s.bytesize, + data: Base64.encode64(a.body.to_s) } end end end hash - when 'RawMessage' + when "RawMessage" { - :id => message.id, - :rcpt_to => message.rcpt_to, - :mail_from => message.mail_from, - :message => Base64.encode64(message.raw_message), - :base64 => true, - :size => message.size.to_i + id: message.id, + rcpt_to: message.rcpt_to, + mail_from: message.mail_from, + message: Base64.encode64(message.raw_message), + base64: true, + size: message.size.to_i } else {} diff --git a/lib/postal/job.rb b/lib/postal/job.rb index 4ab3df4..5f4ce71 100644 --- a/lib/postal/job.rb +++ b/lib/postal/job.rb @@ -1,16 +1,15 @@ -require 'nifty/utils/random_string' +require "nifty/utils/random_string" module Postal class Job + def initialize(id, params = {}) @id = id @params = params.with_indifferent_access on_initialize end - def id - @id - end + attr_reader :id def params @params || {} @@ -33,14 +32,15 @@ module Postal end def self.queue(queue, params = {}) - job_id = Nifty::Utils::RandomString.generate(:length => 10).upcase - job_payload = {'params' => params, 'class_name' => self.name, 'id' => job_id, 'queue' => queue} - Postal::Worker.job_queue(queue).publish(job_payload.to_json, :persistent => false) + job_id = Nifty::Utils::RandomString.generate(length: 10).upcase + job_payload = { "params" => params, "class_name" => name, "id" => job_id, "queue" => queue } + Postal::Worker.job_queue(queue).publish(job_payload.to_json, persistent: false) job_id end def self.perform(params = {}) new(nil, params).perform end + end end diff --git a/lib/postal/message_db.rb b/lib/postal/message_db.rb index 2d422d0..b187e43 100644 --- a/lib/postal/message_db.rb +++ b/lib/postal/message_db.rb @@ -1,5 +1,6 @@ module Postal module MessageDB + extend ActiveSupport::Autoload eager_autoload do autoload :Click @@ -14,5 +15,6 @@ module Postal autoload :SuppressionList autoload :Webhooks end + end end diff --git a/lib/postal/message_db/click.rb b/lib/postal/message_db/click.rb index 334a21c..b476e4b 100644 --- a/lib/postal/message_db/click.rb +++ b/lib/postal/message_db/click.rb @@ -3,10 +3,10 @@ module Postal class Click def initialize(attributes, link) - @url = link['url'] - @ip_address = attributes['ip_address'] - @user_agent = attributes['user_agent'] - @timestamp = Time.zone.at(attributes['timestamp']) + @url = link["url"] + @ip_address = attributes["ip_address"] + @user_agent = attributes["user_agent"] + @timestamp = Time.zone.at(attributes["timestamp"]) end attr_reader :ip_address diff --git a/lib/postal/message_db/database.rb b/lib/postal/message_db/database.rb index 45fd39a..1c8561e 100644 --- a/lib/postal/message_db/database.rb +++ b/lib/postal/message_db/database.rb @@ -22,10 +22,10 @@ module Postal # def schema_version @schema_version ||= begin - last_migration = select(:migrations, :order => :version, :direction => 'DESC', :limit => 1).first - last_migration ? last_migration['version'] : 0 + last_migration = select(:migrations, order: :version, direction: "DESC", limit: 1).first + last_migration ? last_migration["version"] : 0 rescue Mysql2::Error => e - e.message =~ /doesn\'t exist/ ? 0 : raise + e.message =~ /doesn't exist/ ? 0 : raise end end @@ -48,8 +48,8 @@ module Postal end # - # Create a new message with the given attributes. This won't be saved to the database - # until it has been 'save'd. + #  Create a new message with the given attributes. This won't be saved to the database + #  until it has been 'save'd. # def new_message(attributes = {}) Message.new(self, attributes) @@ -59,39 +59,39 @@ module Postal # Return the total size of all stored messages # def total_size - query("SELECT SUM(size) AS size FROM `#{database_name}`.`raw_message_sizes`").first['size'] || 0 + query("SELECT SUM(size) AS size FROM `#{database_name}`.`raw_message_sizes`").first["size"] || 0 end # - # Return the live stats instance + #  Return the live stats instance # def live_stats @live_stats ||= LiveStats.new(self) end # - # Return the statistics instance + #  Return the statistics instance # def statistics @statistics ||= Statistics.new(self) end # - # Return the provisioner instance + #  Return the provisioner instance # def provisioner @provisioner ||= Provisioner.new(self) end # - # Return the provisioner instance + #  Return the provisioner instance # def suppression_list @suppression_list ||= SuppressionList.new(self) end # - # Return the provisioner instance + #  Return the provisioner instance # def webhooks @webhooks ||= Webhooks.new(self) @@ -111,15 +111,13 @@ module Postal table_name = raw_table_name_for_date(date) begin headers, body = data.split(/\r?\n\r?\n/, 2) - headers_id = insert(table_name, :data => headers) - body_id = insert(table_name, :data => body) + headers_id = insert(table_name, data: headers) + body_id = insert(table_name, data: body) rescue Mysql2::Error => e - if e.message =~ /doesn\'t exist/ - provisioner.create_raw_table(table_name) - retry - else - raise - end + raise unless e.message =~ /doesn't exist/ + + provisioner.create_raw_table(table_name) + retry end [table_name, headers_id, body_id] end @@ -142,17 +140,18 @@ module Postal if options[:count] sql_query << " COUNT(id) AS count" elsif options[:fields] - sql_query << " " + options[:fields].map { |f| "`#{f}`" }.join(', ') + sql_query << (" " + options[:fields].map { |f| "`#{f}`" }.join(", ")) else sql_query << " *" end sql_query << " FROM `#{database_name}`.`#{table}`" - if options[:where] && !options[:where].empty? - sql_query << " " + build_where_string(options[:where], ' AND ') + if options[:where].present? + sql_query << (" " + build_where_string(options[:where], " AND ")) end if options[:order] - direction = (options[:direction] || 'ASC').upcase - raise Postal::Error, "Invalid direction #{options[:direction]}" unless ['ASC', 'DESC'].include?(direction) + direction = (options[:direction] || "ASC").upcase + raise Postal::Error, "Invalid direction #{options[:direction]}" unless ["ASC", "DESC"].include?(direction) + sql_query << " ORDER BY `#{options[:order]}` #{direction}" end @@ -166,14 +165,14 @@ module Postal result = query(sql_query) if options[:count] - result.first['count'] + result.first["count"] else result.to_a end end # - # A paginated version of select + #  A paginated version of select # def select_with_pagination(table, page, options = {}) page = page.to_i @@ -183,8 +182,8 @@ module Postal offset = (page - 1) * per_page result = {} - result[:total] = select(table, options.merge(:count => true)) - result[:records] = select(table, options.merge(:limit => per_page, :offset => offset)) + result[:total] = select(table, options.merge(count: true)) + result[:records] = select(table, options.merge(limit: per_page, offset: offset)) result[:per_page] = per_page result[:total_pages], remainder = result[:total].divmod(per_page) result[:total_pages] += 1 if remainder > 0 @@ -204,7 +203,7 @@ module Postal sql_query = "UPDATE `#{database_name}`.`#{table}` SET" sql_query << " #{hash_to_sql(attributes)}" if options[:where] - sql_query << " " + build_where_string(options[:where]) + sql_query << (" " + build_where_string(options[:where])) end with_mysql do |mysql| query_on_connection(mysql, sql_query) @@ -218,8 +217,8 @@ module Postal # def insert(table, attributes) sql_query = "INSERT INTO `#{database_name}`.`#{table}`" - sql_query << " (" + attributes.keys.map { |k| "`#{k}`" }.join(', ') + ")" - sql_query << " VALUES (" + attributes.values.map { |v| escape(v) }.join(', ') + ")" + sql_query << (" (" + attributes.keys.map { |k| "`#{k}`" }.join(", ") + ")") + sql_query << (" VALUES (" + attributes.values.map { |v| escape(v) }.join(", ") + ")") with_mysql do |mysql| query_on_connection(mysql, sql_query) mysql.last_id @@ -234,16 +233,16 @@ module Postal nil else sql_query = "INSERT INTO `#{database_name}`.`#{table}`" - sql_query << " (" + keys.map { |k| "`#{k}`" }.join(', ') + ")" + sql_query << (" (" + keys.map { |k| "`#{k}`" }.join(", ") + ")") sql_query << " VALUES " - sql_query << values.map { |v| "(" + v.map { |v| escape(v) }.join(', ') + ")" }.join(', ') + sql_query << values.map { |v| "(" + v.map { |v| escape(v) }.join(", ") + ")" }.join(", ") query(sql_query) end end # # Deletes a in the database. Accepts a table name, and some options which - # are shown below: + #  are shown below: # # :where => The condition to apply to the query # @@ -251,7 +250,7 @@ module Postal # def delete(table, options = {}) sql_query = "DELETE FROM `#{database_name}`.`#{table}`" - sql_query << " " + build_where_string(options[:where], ' AND ') + sql_query << (" " + build_where_string(options[:where], " AND ")) with_mysql do |mysql| query_on_connection(mysql, sql_query) mysql.affected_rows @@ -259,7 +258,7 @@ module Postal end # - # Return the correct database name + #  Return the correct database name # def database_name @database_name ||= "#{Postal.config.message_db.prefix}-server-#{@server_id}" @@ -269,8 +268,10 @@ module Postal # Run a query, log it and return the result # class ResultForExplainPrinter + attr_reader :columns attr_reader :rows + def initialize(result) if result.first @columns = result.first.keys @@ -280,6 +281,7 @@ module Postal @rows = [] end end + end def stringify_keys(hash) @@ -291,17 +293,15 @@ module Postal def escape(value) with_mysql do |mysql| if value == true - '1' + "1" elsif value == false - '0' + "0" elsif value.nil? - 'NULL' + "NULL" + elsif value.to_s.length == 0 + "NULL" else - if value.to_s.length == 0 - 'NULL' - else - "'" + mysql.escape(value.to_s) + "'" - end + "'" + mysql.escape(value.to_s) + "'" end end end @@ -320,7 +320,7 @@ module Postal time = Time.now.to_f - start_time logger.debug " \e[4;34mMessageDB Query (#{time.round(2)}s) \e[0m \e[33m#{query}\e[0m" if time > 0.5 && query =~ /\A(SELECT|UPDATE|DELETE) / - id = Nifty::Utils::RandomString.generate(:length => 6).upcase + id = Nifty::Utils::RandomString.generate(length: 6).upcase explain_result = ResultForExplainPrinter.new(connection.query("EXPLAIN #{query}")) slow_query_logger.info "[#{id}] EXPLAIN #{query}" for line in ActiveRecord::ConnectionAdapters::MySQL::ExplainPrettyPrinter.new.pp(explain_result, time).split("\n") @@ -342,16 +342,16 @@ module Postal MessageDB::MySQL.client(&block) end - def build_where_string(attributes, joiner = ', ') + def build_where_string(attributes, joiner = ", ") "WHERE #{hash_to_sql(attributes, joiner)}" end - def hash_to_sql(hash, joiner = ', ') + def hash_to_sql(hash, joiner = ", ") hash.map do |key, value| if value.is_a?(Array) && value.all? { |v| v.is_a?(Integer) } "`#{key}` IN (#{value.join(', ')})" elsif value.is_a?(Array) - escaped_values = value.map { |v| escape(v) }.join(', ') + escaped_values = value.map { |v| escape(v) }.join(", ") "`#{key}` IN (#{escaped_values})" elsif value.is_a?(Hash) sql = [] diff --git a/lib/postal/message_db/delivery.rb b/lib/postal/message_db/delivery.rb index 93116a9..e5d0e8c 100644 --- a/lib/postal/message_db/delivery.rb +++ b/lib/postal/message_db/delivery.rb @@ -4,9 +4,9 @@ module Postal def self.create(message, attributes = {}) attributes = message.database.stringify_keys(attributes) - attributes = attributes.merge('message_id' => message.id, 'timestamp' => Time.now.to_f) - id = message.database.insert('deliveries', attributes) - delivery = Delivery.new(message, attributes.merge('id' => id)) + attributes = attributes.merge("message_id" => message.id, "timestamp" => Time.now.to_f) + id = message.database.insert("deliveries", attributes) + delivery = Delivery.new(message, attributes.merge("id" => id)) delivery.update_statistics delivery.send_webhooks delivery @@ -18,52 +18,50 @@ module Postal end def method_missing(name, value = nil, &block) - if @attributes.has_key?(name.to_s) - @attributes[name.to_s] - else - nil - end + return unless @attributes.has_key?(name.to_s) + + @attributes[name.to_s] end def timestamp - @timestamp ||= @attributes['timestamp'] ? Time.zone.at(@attributes['timestamp']) : nil + @timestamp ||= @attributes["timestamp"] ? Time.zone.at(@attributes["timestamp"]) : nil end def update_statistics - if self.status == 'Held' - @message.database.statistics.increment_all(self.timestamp, 'held') + if status == "Held" + @message.database.statistics.increment_all(timestamp, "held") end - if self.status == 'Bounced' || self.status == 'HardFail' - @message.database.statistics.increment_all(self.timestamp, 'bounces') - end + return unless status == "Bounced" || status == "HardFail" + + @message.database.statistics.increment_all(timestamp, "bounces") end def send_webhooks - if self.webhook_event - WebhookRequest.trigger(@message.database.server_id, self.webhook_event, self.webhook_hash) - end + return unless webhook_event + + WebhookRequest.trigger(@message.database.server_id, webhook_event, webhook_hash) end def webhook_hash { - :message => @message.webhook_hash, - :status => self.status, - :details => self.details, - :output => self.output.to_s.force_encoding('UTF-8').scrub.truncate(512), - :sent_with_ssl => self.sent_with_ssl, - :timestamp => @attributes['timestamp'], - :time => self.time + message: @message.webhook_hash, + status: status, + details: details, + output: output.to_s.force_encoding("UTF-8").scrub.truncate(512), + sent_with_ssl: sent_with_ssl, + timestamp: @attributes["timestamp"], + time: time } end def webhook_event - @webhook_event ||= case self.status - when 'Sent' then 'MessageSent' - when 'SoftFail' then 'MessageDelayed' - when 'HardFail' then 'MessageDeliveryFailed' - when 'Held' then 'MessageHeld' - end + @webhook_event ||= case status + when "Sent" then "MessageSent" + when "SoftFail" then "MessageDelayed" + when "HardFail" then "MessageDeliveryFailed" + when "Held" then "MessageHeld" + end end end diff --git a/lib/postal/message_db/live_stats.rb b/lib/postal/message_db/live_stats.rb index c782730..1ed9547 100644 --- a/lib/postal/message_db/live_stats.rb +++ b/lib/postal/message_db/live_stats.rb @@ -25,15 +25,14 @@ module Postal if minutes > 60 raise Postal::Error, "Live stats can only return data for the last 60 minutes." end + options[:types] ||= [:incoming, :outgoing] - if options[:types].empty? - raise Postal::Error, "You must provide at least one type to return" - else - time = minutes.minutes.ago.beginning_of_minute.utc.to_f - types = options[:types].map {|t| "#{@database.escape(t.to_s)}"}.join(', ') - result = @database.query("SELECT SUM(count) as count FROM `#{@database.database_name}`.`live_stats` WHERE `type` IN (#{types}) AND timestamp > #{time}").first - result['count'] || 0 - end + raise Postal::Error, "You must provide at least one type to return" if options[:types].empty? + + time = minutes.minutes.ago.beginning_of_minute.utc.to_f + types = options[:types].map { |t| "#{@database.escape(t.to_s)}" }.join(", ") + result = @database.query("SELECT SUM(count) as count FROM `#{@database.database_name}`.`live_stats` WHERE `type` IN (#{types}) AND timestamp > #{time}").first + result["count"] || 0 end end diff --git a/lib/postal/message_db/load.rb b/lib/postal/message_db/load.rb index eae8d78..a87df02 100644 --- a/lib/postal/message_db/load.rb +++ b/lib/postal/message_db/load.rb @@ -3,9 +3,9 @@ module Postal class Load def initialize(attributes) - @ip_address = attributes['ip_address'] - @user_agent = attributes['user_agent'] - @timestamp = Time.zone.at(attributes['timestamp']) + @ip_address = attributes["ip_address"] + @user_agent = attributes["user_agent"] + @timestamp = Time.zone.at(attributes["timestamp"]) end attr_reader :ip_address diff --git a/lib/postal/message_db/message.rb b/lib/postal/message_db/message.rb index 7a7587a..ea87b80 100644 --- a/lib/postal/message_db/message.rb +++ b/lib/postal/message_db/message.rb @@ -6,16 +6,14 @@ module Postal end def self.find_one(database, query) - query = {:id => query.to_i} if query.is_a?(Integer) - if message = database.select('messages', :where => query, :limit => 1).first - Message.new(database, message) - else - raise NotFound, "No message found matching provided query #{query}" - end + query = { id: query.to_i } if query.is_a?(Integer) + raise NotFound, "No message found matching provided query #{query}" unless message = database.select("messages", where: query, limit: 1).first + + Message.new(database, message) end def self.find(database, options = {}) - if messages = database.select('messages', options) + if messages = database.select("messages", options) if messages.is_a?(Array) messages.map { |m| Message.new(database, m) } else @@ -27,7 +25,7 @@ module Postal end def self.find_with_pagination(database, page, options = {}) - messages = database.select_with_pagination('messages', page, options) + messages = database.select_with_pagination("messages", page, options) messages[:records] = messages[:records].map { |m| Message.new(database, m) } messages end @@ -50,26 +48,24 @@ module Postal # Return the credential for this message # def credential - @credential ||= self.credential_id ? Credential.find_by_id(self.credential_id) : nil + @credential ||= credential_id ? Credential.find_by_id(credential_id) : nil end # # Return the route for this message # def route - @route ||= self.route_id ? Route.find_by_id(self.route_id) : nil + @route ||= route_id ? Route.find_by_id(route_id) : nil end # # Return the endpoint for this message # def endpoint - @endpoint ||= begin - if self.endpoint_type && self.endpoint_id - self.endpoint_type.constantize.find_by_id(self.endpoint_id) - elsif self.route && self.route.mode == 'Endpoint' - self.route.endpoint - end + if endpoint_type && endpoint_id + @endpoint ||= endpoint_type.constantize.find_by_id(endpoint_id) + elsif route && route.mode == "Endpoint" + @endpoint ||= route.endpoint end end @@ -77,41 +73,41 @@ module Postal # Return the credential for this message # def domain - @domain ||= self.domain_id ? Domain.find_by_id(self.domain_id) : nil + @domain ||= domain_id ? Domain.find_by_id(domain_id) : nil end # # Copy appropriate attributes from the raw message to the message itself # def copy_attributes_from_raw_message - if self.raw_message - self.subject = self.headers['subject']&.last.to_s[0,200] - self.message_id = self.headers['message-id']&.last - if self.message_id - self.message_id = self.message_id.gsub(/.*.*/, '').strip - end - end + return unless raw_message + + self.subject = headers["subject"]&.last.to_s[0, 200] + self.message_id = headers["message-id"]&.last + return unless message_id + + self.message_id = message_id.gsub(/.*.*/, "").strip end # # Return the timestamp for this message # def timestamp - @timestamp ||= @attributes['timestamp'] ? Time.zone.at(@attributes['timestamp']) : nil + @timestamp ||= @attributes["timestamp"] ? Time.zone.at(@attributes["timestamp"]) : nil end # - # Return the time that the last delivery was attempted + #  Return the time that the last delivery was attempted # def last_delivery_attempt - @last_delivery_attempt ||= @attributes['last_delivery_attempt'] ? Time.zone.at(@attributes['last_delivery_attempt']) : nil + @last_delivery_attempt ||= @attributes["last_delivery_attempt"] ? Time.zone.at(@attributes["last_delivery_attempt"]) : nil end # # Return the hold expiry for this message # def hold_expiry - @hold_expiry ||= @attributes['hold_expiry'] ? Time.zone.at(@attributes['hold_expiry']) : nil + @hold_expiry ||= @attributes["hold_expiry"] ? Time.zone.at(@attributes["hold_expiry"]) : nil end # @@ -125,9 +121,9 @@ module Postal # Add a delivery attempt for this message # def create_delivery(status, options = {}) - delivery = Delivery.create(self, options.merge(:status => status)) - hold_expiry = status == 'Held' ? Postal.config.general.maximum_hold_expiry_days.days.from_now.to_f : nil - self.update(:status => status, :last_delivery_attempt => delivery.timestamp.to_f, :held => status == 'Held' ? 1 : 0, :hold_expiry => hold_expiry) + delivery = Delivery.create(self, options.merge(status: status)) + hold_expiry = status == "Held" ? Postal.config.general.maximum_hold_expiry_days.days.from_now.to_f : nil + update(status: status, last_delivery_attempt: delivery.timestamp.to_f, held: status == "Held" ? 1 : 0, hold_expiry: hold_expiry) delivery end @@ -135,10 +131,8 @@ module Postal # Return all deliveries for this object # def deliveries - @deliveries ||= begin - @database.select('deliveries', :where => {:message_id => self.id}, :order => :timestamp).map do |hash| - Delivery.new(self, hash) - end + @deliveries ||= @database.select("deliveries", where: { message_id: id }, order: :timestamp).map do |hash| + Delivery.new(self, hash) end end @@ -147,13 +141,13 @@ module Postal # def clicks @clicks ||= begin - clicks = @database.select('clicks', :where => {:message_id => self.id}, :order => :timestamp) + clicks = @database.select("clicks", where: { message_id: id }, order: :timestamp) if clicks.empty? [] else - links = @database.select('links', :where => {:id => clicks.map { |c| c['link_id'].to_i }}).group_by { |l| l['id'] } + links = @database.select("links", where: { id: clicks.map { |c| c["link_id"].to_i } }).group_by { |l| l["id"] } clicks.map do |hash| - Click.new(hash, links[hash['link_id']].first) + Click.new(hash, links[hash["link_id"]].first) end end end @@ -164,7 +158,7 @@ module Postal # def loads @loads ||= begin - loads = @database.select('loads', :where => {:message_id => self.id}, :order => :timestamp) + loads = @database.select("loads", where: { message_id: id }, order: :timestamp) loads.map do |hash| Load.new(hash) end @@ -172,22 +166,20 @@ module Postal end # - # Return all activity entries + #  Return all activity entries # def activity_entries @activity_entries ||= (deliveries + clicks + loads).sort_by(&:timestamp) end # - # Provide access to set and get acceptable attributes + #  Provide access to set and get acceptable attributes # def method_missing(name, value = nil, &block) if @attributes.has_key?(name.to_s) @attributes[name.to_s] - elsif name.to_s =~ /\=\z/ - @attributes[name.to_s.gsub('=', '').to_s] = value - else - nil + elsif name.to_s =~ /=\z/ + @attributes[name.to_s.gsub("=", "").to_s] = value end end @@ -195,11 +187,11 @@ module Postal # Has this message been persisted to the database yet? # def persisted? - !@attributes['id'].nil? + !@attributes["id"].nil? end # - # Save this message + #  Save this message # def save save_raw_message @@ -213,38 +205,38 @@ module Postal def update(attributes_to_change) @attributes = @attributes.merge(database.stringify_keys(attributes_to_change)) if persisted? - @database.update('messages', attributes_to_change, :where => {:id => self.id}) + @database.update("messages", attributes_to_change, where: { id: id }) else _create end end # - # Delete the message from the database + #  Delete the message from the database # def delete - if persisted? - @database.delete('messages', :where => {:id => self.id}) - end + return unless persisted? + + @database.delete("messages", where: { id: id }) end # - # Return the headers + #  Return the headers # def raw_headers - if self.raw_table - @raw_headers ||= @database.select(self.raw_table, :where => {:id => self.raw_headers_id}).first&.send(:[], 'data') || "" + if raw_table + @raw_headers ||= @database.select(raw_table, where: { id: raw_headers_id }).first&.send(:[], "data") || "" else "" end end # - # Return the full raw message body for this message. + #  Return the full raw message body for this message. # def raw_body - if self.raw_table - @raw ||= @database.select(self.raw_table, :where => {:id => self.raw_body_id}).first&.send(:[], 'data') || "" + if raw_table + @raw ||= @database.select(raw_table, where: { id: raw_body_id }).first&.send(:[], "data") || "" else "" end @@ -261,35 +253,35 @@ module Postal # Set the raw message ready for saving later # def raw_message=(raw) - @pending_raw_message = raw.force_encoding('BINARY') + @pending_raw_message = raw.force_encoding("BINARY") end # # Save the raw message to the database as appropriate # def save_raw_message - if @pending_raw_message - self.size = @pending_raw_message.bytesize - date = Date.today - table_name, headers_id, body_id = @database.insert_raw_message(@pending_raw_message, date) - self.raw_table = table_name - self.raw_headers_id = headers_id - self.raw_body_id = body_id - @raw = nil - @raw_headers = nil - @headers = nil - @mail = nil - @pending_raw_message = nil - copy_attributes_from_raw_message - @database.query("UPDATE `#{@database.database_name}`.`raw_message_sizes` SET size = size + #{self.size} WHERE table_name = '#{table_name}'") - end + return unless @pending_raw_message + + self.size = @pending_raw_message.bytesize + date = Date.today + table_name, headers_id, body_id = @database.insert_raw_message(@pending_raw_message, date) + self.raw_table = table_name + self.raw_headers_id = headers_id + self.raw_body_id = body_id + @raw = nil + @raw_headers = nil + @headers = nil + @mail = nil + @pending_raw_message = nil + copy_attributes_from_raw_message + @database.query("UPDATE `#{@database.database_name}`.`raw_message_sizes` SET size = size + #{size} WHERE table_name = '#{table_name}'") end # # Is there a raw message? # def raw_message? - !!self.raw_table + !!raw_table end # @@ -300,7 +292,7 @@ module Postal end # - # Return the HTML body for this message + #  Return the HTML body for this message # def html_body mail&.html_body @@ -310,7 +302,7 @@ module Postal # Return the HTML body with any tracking links # def html_body_without_tracking_image - html_body.gsub(/\

/, '') + html_body.gsub(/

/, "") end # @@ -325,7 +317,7 @@ module Postal # def headers @headers ||= begin - mail = Mail.new(self.raw_headers) + mail = Mail.new(raw_headers) mail.header.fields.each_with_object({}) do |field, hash| hash[field.name.downcase] ||= [] begin @@ -341,27 +333,27 @@ module Postal # Return the recipient domain for this message # def recipient_domain - self.rcpt_to ? self.rcpt_to.split('@').last : nil + rcpt_to ? rcpt_to.split("@").last : nil end # # Create a new item in the message queue for this message # def add_to_message_queue(options = {}) - QueuedMessage.create!(:message => self, :server_id => @database.server_id, :batch_key => self.batch_key, :domain => self.recipient_domain, :route_id => self.route_id, :manual => options[:manual]).id + QueuedMessage.create!(message: self, server_id: @database.server_id, batch_key: batch_key, domain: recipient_domain, route_id: route_id, manual: options[:manual]).id end # # Return a suitable batch key for this message # def batch_key - case self.scope - when 'outgoing' + case scope + when "outgoing" key = "outgoing-" - key += self.recipient_domain.to_s - when 'incoming' + key += recipient_domain.to_s + when "incoming" key = "incoming-" - key += "rt:#{self.route_id}-ep:#{self.endpoint_id}-#{self.endpoint_type}" + key += "rt:#{route_id}-ep:#{endpoint_id}-#{endpoint_type}" else key = nil end @@ -372,29 +364,30 @@ module Postal # Return the queued message # def queued_message - @queued_message ||= self.id ? QueuedMessage.where(:message_id => self.id, :server_id => @database.server_id).first : nil + @queued_message ||= id ? QueuedMessage.where(message_id: id, server_id: @database.server_id).first : nil end # # Return the spam status # def spam_status - return 'NotChecked' unless inspected == 1 - spam == 1 ? 'Spam' : 'NotSpam' + return "NotChecked" unless inspected == 1 + + spam == 1 ? "Spam" : "NotSpam" end # # Has this message been held? # def held? - status == 'Held' + status == "Held" end # # Does this message have our DKIM header yet? # def has_outgoing_headers? - !!(raw_headers =~ /^X\-Postal\-MsgID\:/i) + !!(raw_headers =~ /^X-Postal-MsgID:/i) end # @@ -402,11 +395,11 @@ module Postal # def add_outgoing_headers headers = [] - if self.domain - dkim = Postal::DKIMHeader.new(self.domain, self.raw_message) + if domain + dkim = Postal::DKIMHeader.new(domain, raw_message) headers << dkim.dkim_header end - headers << "X-Postal-MsgID: #{self.token}" + headers << "X-Postal-MsgID: #{token}" append_headers(*headers) end @@ -415,8 +408,8 @@ module Postal # def append_headers(*headers) new_headers = headers.join("\r\n") - new_headers = "#{new_headers}\r\n#{self.raw_headers}" - @database.update(self.raw_table, {:data => new_headers}, :where => {:id => self.raw_headers_id}) + new_headers = "#{new_headers}\r\n#{raw_headers}" + @database.update(raw_table, { data: new_headers }, where: { id: raw_headers_id }) @raw_headers = new_headers @raw_message = nil @headers = nil @@ -427,16 +420,16 @@ module Postal # def webhook_hash @webhook_hash ||= { - :id => self.id, - :token => self.token, - :direction => self.scope, - :message_id => self.message_id, - :to => self.rcpt_to, - :from => self.mail_from, - :subject => self.subject, - :timestamp => self.timestamp.to_f, - :spam_status => self.spam_status, - :tag => self.tag + id: id, + token: token, + direction: scope, + message_id: message_id, + to: rcpt_to, + from: mail_from, + subject: subject, + timestamp: timestamp.to_f, + spam_status: spam_status, + tag: tag } end @@ -444,46 +437,47 @@ module Postal # Mark this message as bounced # def bounce!(bounce_message) - create_delivery('Bounced', :details => "We've received a bounce message for this e-mail. See for details.") - SendWebhookJob.queue(:main, :server_id => self.database.server_id, :event => "MessageBounced", :payload => {:_original_message => self.id, :_bounce => bounce_message.id}) + create_delivery("Bounced", details: "We've received a bounce message for this e-mail. See for details.") + SendWebhookJob.queue(:main, server_id: database.server_id, event: "MessageBounced", payload: { _original_message: id, _bounce: bounce_message.id }) end # # Should bounces be sent for this message? # def send_bounces? - self.bounce != 1 && self.mail_from.present? + bounce != 1 && mail_from.present? end # # Add a load for this message # def create_load(request) - update('loaded' => Time.now.to_f) if loaded.nil? - database.insert(:loads, {:message_id => self.id, :ip_address => request.ip, :user_agent => request.user_agent, :timestamp => Time.now.to_f}) - SendWebhookJob.queue(:main, :server_id => self.database.server_id, :event => 'MessageLoaded', :payload => {:_message => self.id, :ip_address => request.ip, :user_agent => request.user_agent}) + update("loaded" => Time.now.to_f) if loaded.nil? + database.insert(:loads, { message_id: id, ip_address: request.ip, user_agent: request.user_agent, timestamp: Time.now.to_f }) + SendWebhookJob.queue(:main, server_id: database.server_id, event: "MessageLoaded", payload: { _message: id, ip_address: request.ip, user_agent: request.user_agent }) end # - # Create a new link + #  Create a new link # def create_link(url) hash = Digest::SHA1.hexdigest(url.to_s) - token = Nifty::Utils::RandomString.generate(:length => 8) - database.insert(:links, {:message_id => self.id, :hash => hash, :url => url, :timestamp => Time.now.to_f, :token => token}) + token = Nifty::Utils::RandomString.generate(length: 8) + database.insert(:links, { message_id: id, hash: hash, url: url, timestamp: Time.now.to_f, token: token }) token end # - # Return a message object that this message is a reply to + #  Return a message object that this message is a reply to # def original_messages - return nil unless self.bounce == 1 - other_message_ids = raw_message.scan(/\X\-Postal\-MsgID\:\s*([a-z0-9]+)/i).flatten + return nil unless bounce == 1 + + other_message_ids = raw_message.scan(/\X-Postal-MsgID:\s*([a-z0-9]+)/i).flatten if other_message_ids.empty? [] else - database.messages(:where => {:token => other_message_ids}) + database.messages(where: { token: other_message_ids }) end end @@ -491,39 +485,39 @@ module Postal # Was thsi message sent to a return path? # def rcpt_to_return_path? - !!(rcpt_to =~ /\@#{Regexp.escape(Postal.config.dns.custom_return_path_prefix)}\./) + !!(rcpt_to =~ /@#{Regexp.escape(Postal.config.dns.custom_return_path_prefix)}\./) end # # Inspect this message # def inspect_message - result = MessageInspection.scan(self, self.scope&.to_sym) + result = MessageInspection.scan(self, scope&.to_sym) # Update the messages table with the results of our inspection - update(:inspected => 1, :spam_score => result.spam_score, :threat => result.threat?, :threat_details => result.threat_message) + update(inspected: 1, spam_score: result.spam_score, threat: result.threat?, threat_details: result.threat_message) # Add any spam details into the spam checks database - self.database.insert_multi(:spam_checks, [:message_id, :code, :score, :description], result.spam_checks.map { |d| [self.id, d.code, d.score, d.description] }) + database.insert_multi(:spam_checks, [:message_id, :code, :score, :description], result.spam_checks.map { |d| [id, d.code, d.score, d.description] }) # Return the result result end # - # Return all spam checks for this message + #  Return all spam checks for this message # def spam_checks - @spam_checks ||= self.database.select(:spam_checks, :where => {:message_id => self.id}) + @spam_checks ||= database.select(:spam_checks, where: { message_id: id }) end # # Cancel the hold on this message # def cancel_hold - if self.status == 'Held' - create_delivery('HoldCancelled', :details => "The hold on this message has been removed without action.") - end + return unless status == "Held" + + create_delivery("HoldCancelled", details: "The hold on this message has been removed without action.") end # @@ -532,43 +526,43 @@ module Postal def parse_content parse_result = Postal::MessageParser.new(self) if parse_result.actioned? - # Somethign was changed, update the raw message - @database.update(self.raw_table, {:data => parse_result.new_body}, :where => {:id => self.raw_body_id}) + #  Somethign was changed, update the raw message + @database.update(raw_table, { data: parse_result.new_body }, where: { id: raw_body_id }) @raw = parse_result.new_body @raw_message = nil end - update('parsed' => 1, 'tracked_links' => parse_result.tracked_links, 'tracked_images' => parse_result.tracked_images) + update("parsed" => 1, "tracked_links" => parse_result.tracked_links, "tracked_images" => parse_result.tracked_images) end # # Has this message been parsed? # def parsed? - self.parsed == 1 + parsed == 1 end # # Should this message be parsed? # def should_parse? - parsed? == false && headers['x-amp'] != 'skip' + parsed? == false && headers["x-amp"] != "skip" end private def _update - @database.update('messages', @attributes.reject {|k,v| k == :id }, :where => {:id => @attributes['id']}) + @database.update("messages", @attributes.reject { |k, v| k == :id }, where: { id: @attributes["id"] }) end def _create - self.timestamp = Time.now.to_f if self.timestamp.blank? - self.status = 'Pending' if self.status.blank? - self.token = Nifty::Utils::RandomString.generate(:length => 12) if self.token.blank? - last_id = @database.insert('messages', @attributes.reject {|k,v| k == :id }) - @attributes['id'] = last_id - @database.statistics.increment_all(self.timestamp, self.scope) + self.timestamp = Time.now.to_f if timestamp.blank? + self.status = "Pending" if status.blank? + self.token = Nifty::Utils::RandomString.generate(length: 12) if token.blank? + last_id = @database.insert("messages", @attributes.reject { |k, v| k == :id }) + @attributes["id"] = last_id + @database.statistics.increment_all(timestamp, scope) Statistic.global.increment!(:total_messages) - Statistic.global.increment!("total_#{self.scope}".to_sym) + Statistic.global.increment!("total_#{scope}".to_sym) add_to_message_queue end diff --git a/lib/postal/message_db/migration.rb b/lib/postal/message_db/migration.rb index fc1948f..e209151 100644 --- a/lib/postal/message_db/migration.rb +++ b/lib/postal/message_db/migration.rb @@ -10,8 +10,11 @@ module Postal end def self.run(database, start_from = database.schema_version) - files = Dir[Rails.root.join('lib', 'postal', 'message_db', 'migrations', '*.rb')] - files = files.map { |f| id, name = f.split('/').last.split('_', 2); [id.to_i, name] }.sort_by(&:first) + files = Dir[Rails.root.join("lib", "postal", "message_db", "migrations", "*.rb")] + files = files.map do |f| + id, name = f.split("/").last.split("_", 2) + [id.to_i, name] + end.sort_by(&:first) latest_version = files.last.first if latest_version > start_from puts "\e[32mMigrating #{database.database_name} from version #{start_from} => #{files.last.first}\e[0m" @@ -19,14 +22,15 @@ module Postal puts "Nothing to do." end files.each do |version, file| - klass_name = file.gsub(/\.rb\z/, '').camelize + klass_name = file.gsub(/\.rb\z/, "").camelize next if start_from >= version + puts "\e[45m++ Migrating #{klass_name} (#{version})\e[0m" require "postal/message_db/migrations/#{version.to_s.rjust(2, '0')}_#{file}" klass = Postal::MessageDB::Migrations.const_get(klass_name) instance = klass.new(database) instance.up - database.insert(:migrations, :version => version) + database.insert(:migrations, version: version) end end diff --git a/lib/postal/message_db/migrations/01_create_migrations.rb b/lib/postal/message_db/migrations/01_create_migrations.rb index fbbb2ce..af1358b 100644 --- a/lib/postal/message_db/migrations/01_create_migrations.rb +++ b/lib/postal/message_db/migrations/01_create_migrations.rb @@ -2,14 +2,15 @@ module Postal module MessageDB module Migrations class CreateMigrations < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:migrations, - :columns => { - :version => 'int(11) NOT NULL', - }, - :primary_key => '`version`' - ) + columns: { + version: "int(11) NOT NULL" + }, + primary_key: "`version`") end + end end end diff --git a/lib/postal/message_db/migrations/02_create_messages.rb b/lib/postal/message_db/migrations/02_create_messages.rb index c8f1cb2..85d0086 100644 --- a/lib/postal/message_db/migrations/02_create_messages.rb +++ b/lib/postal/message_db/migrations/02_create_messages.rb @@ -2,55 +2,56 @@ module Postal module MessageDB module Migrations class CreateMessages < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:messages, - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :token => 'varchar(255) DEFAULT NULL', - :scope => 'varchar(10) DEFAULT NULL', - :rcpt_to => 'varchar(255) DEFAULT NULL', - :mail_from => 'varchar(255) DEFAULT NULL', - :subject => 'varchar(255) DEFAULT NULL', - :message_id => 'varchar(255) DEFAULT NULL', - :timestamp => 'decimal(18,6) DEFAULT NULL', - :route_id => 'int(11) DEFAULT NULL', - :domain_id => 'int(11) DEFAULT NULL', - :credential_id => 'int(11) DEFAULT NULL', - :status => 'varchar(255) DEFAULT NULL', - :held => 'tinyint(1) DEFAULT 0', - :size => 'varchar(255) DEFAULT NULL', - :last_delivery_attempt => 'decimal(18,6) DEFAULT NULL', - :raw_table => 'varchar(255) DEFAULT NULL', - :raw_body_id => 'int(11) DEFAULT NULL', - :raw_headers_id => 'int(11) DEFAULT NULL', - :inspected => 'tinyint(1) DEFAULT 0', - :spam => 'tinyint(1) DEFAULT 0', - :spam_score => 'decimal(8,2) DEFAULT 0', - :threat => 'tinyint(1) DEFAULT 0', - :threat_details => 'varchar(255) DEFAULT NULL', - :bounce => 'tinyint(1) DEFAULT 0', - :bounce_for_id => 'int(11) DEFAULT 0', - :tag => 'varchar(255) DEFAULT NULL', - :loaded => 'decimal(18,6) DEFAULT NULL', - :clicked => 'decimal(18,6) DEFAULT NULL', - :received_with_ssl => 'tinyint(1) DEFAULT NULL', - }, - :indexes => { - :on_message_id => '`message_id`(8)', - :on_token => '`token`(6)', - :on_bounce_for_id => '`bounce_for_id`', - :on_held => '`held`', - :on_scope_and_status => '`scope`(1), `spam`, `status`(6), `timestamp`', - :on_scope_and_tag => '`scope`(1), `spam`, `tag`(8), `timestamp`', - :on_scope_and_spam => '`scope`(1), `spam`, `timestamp`', - :on_scope_and_thr_status => '`scope`(1), `threat`, `status`(6), `timestamp`', - :on_scope_and_threat => '`scope`(1), `threat`, `timestamp`', - :on_rcpt_to => '`rcpt_to`(12), `timestamp`', - :on_mail_from => '`mail_from`(12), `timestamp`', - :on_raw_table => '`raw_table`(14)', - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + token: "varchar(255) DEFAULT NULL", + scope: "varchar(10) DEFAULT NULL", + rcpt_to: "varchar(255) DEFAULT NULL", + mail_from: "varchar(255) DEFAULT NULL", + subject: "varchar(255) DEFAULT NULL", + message_id: "varchar(255) DEFAULT NULL", + timestamp: "decimal(18,6) DEFAULT NULL", + route_id: "int(11) DEFAULT NULL", + domain_id: "int(11) DEFAULT NULL", + credential_id: "int(11) DEFAULT NULL", + status: "varchar(255) DEFAULT NULL", + held: "tinyint(1) DEFAULT 0", + size: "varchar(255) DEFAULT NULL", + last_delivery_attempt: "decimal(18,6) DEFAULT NULL", + raw_table: "varchar(255) DEFAULT NULL", + raw_body_id: "int(11) DEFAULT NULL", + raw_headers_id: "int(11) DEFAULT NULL", + inspected: "tinyint(1) DEFAULT 0", + spam: "tinyint(1) DEFAULT 0", + spam_score: "decimal(8,2) DEFAULT 0", + threat: "tinyint(1) DEFAULT 0", + threat_details: "varchar(255) DEFAULT NULL", + bounce: "tinyint(1) DEFAULT 0", + bounce_for_id: "int(11) DEFAULT 0", + tag: "varchar(255) DEFAULT NULL", + loaded: "decimal(18,6) DEFAULT NULL", + clicked: "decimal(18,6) DEFAULT NULL", + received_with_ssl: "tinyint(1) DEFAULT NULL" + }, + indexes: { + on_message_id: "`message_id`(8)", + on_token: "`token`(6)", + on_bounce_for_id: "`bounce_for_id`", + on_held: "`held`", + on_scope_and_status: "`scope`(1), `spam`, `status`(6), `timestamp`", + on_scope_and_tag: "`scope`(1), `spam`, `tag`(8), `timestamp`", + on_scope_and_spam: "`scope`(1), `spam`, `timestamp`", + on_scope_and_thr_status: "`scope`(1), `threat`, `status`(6), `timestamp`", + on_scope_and_threat: "`scope`(1), `threat`, `timestamp`", + on_rcpt_to: "`rcpt_to`(12), `timestamp`", + on_mail_from: "`mail_from`(12), `timestamp`", + on_raw_table: "`raw_table`(14)" + }) end + end end end diff --git a/lib/postal/message_db/migrations/03_create_deliveries.rb b/lib/postal/message_db/migrations/03_create_deliveries.rb index fee20be..a1ff299 100644 --- a/lib/postal/message_db/migrations/03_create_deliveries.rb +++ b/lib/postal/message_db/migrations/03_create_deliveries.rb @@ -2,24 +2,25 @@ module Postal module MessageDB module Migrations class CreateDeliveries < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:deliveries, - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :message_id => 'int(11) DEFAULT NULL', - :status => 'varchar(255) DEFAULT NULL', - :code => 'int(11) DEFAULT NULL', - :output => 'varchar(512) DEFAULT NULL', - :details => 'varchar(512) DEFAULT NULL', - :sent_with_ssl => 'tinyint(1) DEFAULT 0', - :log_id => 'varchar(100) DEFAULT NULL', - :timestamp => 'decimal(18,6) DEFAULT NULL' - }, - :indexes => { - :on_message_id => '`message_id`' - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + message_id: "int(11) DEFAULT NULL", + status: "varchar(255) DEFAULT NULL", + code: "int(11) DEFAULT NULL", + output: "varchar(512) DEFAULT NULL", + details: "varchar(512) DEFAULT NULL", + sent_with_ssl: "tinyint(1) DEFAULT 0", + log_id: "varchar(100) DEFAULT NULL", + timestamp: "decimal(18,6) DEFAULT NULL" + }, + indexes: { + on_message_id: "`message_id`" + }) end + end end end diff --git a/lib/postal/message_db/migrations/04_create_live_stats.rb b/lib/postal/message_db/migrations/04_create_live_stats.rb index dc26ef7..51824b8 100644 --- a/lib/postal/message_db/migrations/04_create_live_stats.rb +++ b/lib/postal/message_db/migrations/04_create_live_stats.rb @@ -2,17 +2,18 @@ module Postal module MessageDB module Migrations class CreateLiveStats < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:live_stats, - :columns => { - :type => 'varchar(20) NOT NULL', - :minute => 'int(11) NOT NULL', - :count => 'int(11) DEFAULT NULL', - :timestamp => 'decimal(18,6) DEFAULT NULL', - }, - :primary_key => '`minute`, `type`(8)' - ) + columns: { + type: "varchar(20) NOT NULL", + minute: "int(11) NOT NULL", + count: "int(11) DEFAULT NULL", + timestamp: "decimal(18,6) DEFAULT NULL" + }, + primary_key: "`minute`, `type`(8)") end + end end end diff --git a/lib/postal/message_db/migrations/05_create_raw_message_sizes.rb b/lib/postal/message_db/migrations/05_create_raw_message_sizes.rb index 4483c02..ecba2a0 100644 --- a/lib/postal/message_db/migrations/05_create_raw_message_sizes.rb +++ b/lib/postal/message_db/migrations/05_create_raw_message_sizes.rb @@ -2,18 +2,19 @@ module Postal module MessageDB module Migrations class CreateRawMessageSizes < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:raw_message_sizes, - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :table_name => 'varchar(255) DEFAULT NULL', - :size => 'bigint DEFAULT NULL' - }, - :indexes => { - :on_table_name => '`table_name`(14)' - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + table_name: "varchar(255) DEFAULT NULL", + size: "bigint DEFAULT NULL" + }, + indexes: { + on_table_name: "`table_name`(14)" + }) end + end end end diff --git a/lib/postal/message_db/migrations/06_create_clicks.rb b/lib/postal/message_db/migrations/06_create_clicks.rb index 252073a..19d8c66 100644 --- a/lib/postal/message_db/migrations/06_create_clicks.rb +++ b/lib/postal/message_db/migrations/06_create_clicks.rb @@ -2,24 +2,25 @@ module Postal module MessageDB module Migrations class CreateClicks < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:clicks, - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :message_id => 'int(11) DEFAULT NULL', - :link_id => 'int(11) DEFAULT NULL', - :ip_address => 'varchar(255) DEFAULT NULL', - :country => 'varchar(255) DEFAULT NULL', - :city => 'varchar(255) DEFAULT NULL', - :user_agent => 'varchar(255) DEFAULT NULL', - :timestamp => 'decimal(18,6) DEFAULT NULL' - }, - :indexes => { - :on_message_id => '`message_id`', - :on_link_id => '`link_id`' - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + message_id: "int(11) DEFAULT NULL", + link_id: "int(11) DEFAULT NULL", + ip_address: "varchar(255) DEFAULT NULL", + country: "varchar(255) DEFAULT NULL", + city: "varchar(255) DEFAULT NULL", + user_agent: "varchar(255) DEFAULT NULL", + timestamp: "decimal(18,6) DEFAULT NULL" + }, + indexes: { + on_message_id: "`message_id`", + on_link_id: "`link_id`" + }) end + end end end diff --git a/lib/postal/message_db/migrations/07_create_loads.rb b/lib/postal/message_db/migrations/07_create_loads.rb index 82449be..4ebfde3 100644 --- a/lib/postal/message_db/migrations/07_create_loads.rb +++ b/lib/postal/message_db/migrations/07_create_loads.rb @@ -2,22 +2,23 @@ module Postal module MessageDB module Migrations class CreateLoads < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:loads, - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :message_id => 'int(11) DEFAULT NULL', - :ip_address => 'varchar(255) DEFAULT NULL', - :country => 'varchar(255) DEFAULT NULL', - :city => 'varchar(255) DEFAULT NULL', - :user_agent => 'varchar(255) DEFAULT NULL', - :timestamp => 'decimal(18,6) DEFAULT NULL' - }, - :indexes => { - :on_message_id => '`message_id`' - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + message_id: "int(11) DEFAULT NULL", + ip_address: "varchar(255) DEFAULT NULL", + country: "varchar(255) DEFAULT NULL", + city: "varchar(255) DEFAULT NULL", + user_agent: "varchar(255) DEFAULT NULL", + timestamp: "decimal(18,6) DEFAULT NULL" + }, + indexes: { + on_message_id: "`message_id`" + }) end + end end end diff --git a/lib/postal/message_db/migrations/08_create_stats.rb b/lib/postal/message_db/migrations/08_create_stats.rb index 609ae9d..b31955d 100644 --- a/lib/postal/message_db/migrations/08_create_stats.rb +++ b/lib/postal/message_db/migrations/08_create_stats.rb @@ -2,24 +2,25 @@ module Postal module MessageDB module Migrations class CreateStats < Postal::MessageDB::Migration + def up [:hourly, :daily, :monthly, :yearly].each do |table_name| @database.provisioner.create_table("stats_#{table_name}", - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :time => 'int(11) DEFAULT NULL', - :incoming => 'bigint DEFAULT NULL', - :outgoing => 'bigint DEFAULT NULL', - :spam => 'bigint DEFAULT NULL', - :bounces => 'bigint DEFAULT NULL', - :held => 'bigint DEFAULT NULL', - }, - :unique_indexes => { - :on_time => '`time`' - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + time: "int(11) DEFAULT NULL", + incoming: "bigint DEFAULT NULL", + outgoing: "bigint DEFAULT NULL", + spam: "bigint DEFAULT NULL", + bounces: "bigint DEFAULT NULL", + held: "bigint DEFAULT NULL" + }, + unique_indexes: { + on_time: "`time`" + }) end end + end end end diff --git a/lib/postal/message_db/migrations/09_create_links.rb b/lib/postal/message_db/migrations/09_create_links.rb index b5c5749..96ddf4b 100644 --- a/lib/postal/message_db/migrations/09_create_links.rb +++ b/lib/postal/message_db/migrations/09_create_links.rb @@ -2,22 +2,23 @@ module Postal module MessageDB module Migrations class CreateLinks < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:links, - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :message_id => 'int(11) DEFAULT NULL', - :token => 'varchar(255) DEFAULT NULL', - :hash => 'varchar(255) DEFAULT NULL', - :url => 'varchar(255) DEFAULT NULL', - :timestamp => 'decimal(18,6) DEFAULT NULL' - }, - :indexes => { - :on_message_id => '`message_id`', - :on_token => '`token`(8)', - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + message_id: "int(11) DEFAULT NULL", + token: "varchar(255) DEFAULT NULL", + hash: "varchar(255) DEFAULT NULL", + url: "varchar(255) DEFAULT NULL", + timestamp: "decimal(18,6) DEFAULT NULL" + }, + indexes: { + on_message_id: "`message_id`", + on_token: "`token`(8)" + }) end + end end end diff --git a/lib/postal/message_db/migrations/10_create_spam_checks.rb b/lib/postal/message_db/migrations/10_create_spam_checks.rb index f3602f5..4e5f272 100644 --- a/lib/postal/message_db/migrations/10_create_spam_checks.rb +++ b/lib/postal/message_db/migrations/10_create_spam_checks.rb @@ -2,21 +2,22 @@ module Postal module MessageDB module Migrations class CreateSpamChecks < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:spam_checks, - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :message_id => 'int(11) DEFAULT NULL', - :score => 'decimal(8,2) DEFAULT NULL', - :code => 'varchar(255) DEFAULT NULL', - :description => 'varchar(255) DEFAULT NULL' - }, - :indexes => { - :on_message_id => '`message_id`', - :on_code => '`code`(8)', - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + message_id: "int(11) DEFAULT NULL", + score: "decimal(8,2) DEFAULT NULL", + code: "varchar(255) DEFAULT NULL", + description: "varchar(255) DEFAULT NULL" + }, + indexes: { + on_message_id: "`message_id`", + on_code: "`code`(8)" + }) end + end end end diff --git a/lib/postal/message_db/migrations/11_add_time_to_deliveries.rb b/lib/postal/message_db/migrations/11_add_time_to_deliveries.rb index 68199da..f7fc4cc 100644 --- a/lib/postal/message_db/migrations/11_add_time_to_deliveries.rb +++ b/lib/postal/message_db/migrations/11_add_time_to_deliveries.rb @@ -2,9 +2,11 @@ module Postal module MessageDB module Migrations class AddTimeToDeliveries < Postal::MessageDB::Migration + def up @database.query("ALTER TABLE `#{@database.database_name}`.`deliveries` ADD COLUMN `time` decimal(8,2)") end + end end end diff --git a/lib/postal/message_db/migrations/12_add_hold_expiry.rb b/lib/postal/message_db/migrations/12_add_hold_expiry.rb index 05063ba..f7037ae 100644 --- a/lib/postal/message_db/migrations/12_add_hold_expiry.rb +++ b/lib/postal/message_db/migrations/12_add_hold_expiry.rb @@ -2,9 +2,11 @@ module Postal module MessageDB module Migrations class AddHoldExpiry < Postal::MessageDB::Migration + def up @database.query("ALTER TABLE `#{@database.database_name}`.`messages` ADD COLUMN `hold_expiry` decimal(18,6)") end + end end end diff --git a/lib/postal/message_db/migrations/13_add_index_to_message_status.rb b/lib/postal/message_db/migrations/13_add_index_to_message_status.rb index 0cdacab..94034bf 100644 --- a/lib/postal/message_db/migrations/13_add_index_to_message_status.rb +++ b/lib/postal/message_db/migrations/13_add_index_to_message_status.rb @@ -2,9 +2,11 @@ module Postal module MessageDB module Migrations class AddIndexToMessageStatus < Postal::MessageDB::Migration + def up @database.query("ALTER TABLE `#{@database.database_name}`.`messages` ADD INDEX `on_status` (`status`(8)) USING BTREE") end + end end end diff --git a/lib/postal/message_db/migrations/14_create_suppressions.rb b/lib/postal/message_db/migrations/14_create_suppressions.rb index 9b0ec21..79c272e 100644 --- a/lib/postal/message_db/migrations/14_create_suppressions.rb +++ b/lib/postal/message_db/migrations/14_create_suppressions.rb @@ -2,22 +2,23 @@ module Postal module MessageDB module Migrations class CreateSuppressions < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:suppressions, - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :type => 'varchar(255) DEFAULT NULL', - :address => 'varchar(255) DEFAULT NULL', - :reason => 'varchar(255) DEFAULT NULL', - :timestamp => 'decimal(18,6) DEFAULT NULL', - :keep_until => 'decimal(18,6) DEFAULT NULL', - }, - :indexes => { - :on_address => '`address`(6)', - :on_keep_until => '`keep_until`', - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + type: "varchar(255) DEFAULT NULL", + address: "varchar(255) DEFAULT NULL", + reason: "varchar(255) DEFAULT NULL", + timestamp: "decimal(18,6) DEFAULT NULL", + keep_until: "decimal(18,6) DEFAULT NULL" + }, + indexes: { + on_address: "`address`(6)", + on_keep_until: "`keep_until`" + }) end + end end end diff --git a/lib/postal/message_db/migrations/15_create_webhook_requests.rb b/lib/postal/message_db/migrations/15_create_webhook_requests.rb index 03987df..90857aa 100644 --- a/lib/postal/message_db/migrations/15_create_webhook_requests.rb +++ b/lib/postal/message_db/migrations/15_create_webhook_requests.rb @@ -2,26 +2,27 @@ module Postal module MessageDB module Migrations class CreateWebhookRequests < Postal::MessageDB::Migration + def up @database.provisioner.create_table(:webhook_requests, - :columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :uuid => 'varchar(255) DEFAULT NULL', - :event => 'varchar(255) DEFAULT NULL', - :attempt => 'int(11) DEFAULT NULL', - :timestamp => 'decimal(18,6) DEFAULT NULL', - :status_code => 'int(1) DEFAULT NULL', - :body => 'text DEFAULT NULL', - :payload => 'text DEFAULT NULL', - :will_retry => 'tinyint DEFAULT NULL' - }, - :indexes => { - :on_uuid => '`uuid`(8)', - :on_event => '`event`(8)', - :on_timestamp => '`timestamp`', - } - ) + columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + uuid: "varchar(255) DEFAULT NULL", + event: "varchar(255) DEFAULT NULL", + attempt: "int(11) DEFAULT NULL", + timestamp: "decimal(18,6) DEFAULT NULL", + status_code: "int(1) DEFAULT NULL", + body: "text DEFAULT NULL", + payload: "text DEFAULT NULL", + will_retry: "tinyint DEFAULT NULL" + }, + indexes: { + on_uuid: "`uuid`(8)", + on_event: "`event`(8)", + on_timestamp: "`timestamp`" + }) end + end end end diff --git a/lib/postal/message_db/migrations/16_add_url_and_hook_to_webhooks.rb b/lib/postal/message_db/migrations/16_add_url_and_hook_to_webhooks.rb index 5d03f03..49e9ef3 100644 --- a/lib/postal/message_db/migrations/16_add_url_and_hook_to_webhooks.rb +++ b/lib/postal/message_db/migrations/16_add_url_and_hook_to_webhooks.rb @@ -2,11 +2,13 @@ module Postal module MessageDB module Migrations class AddUrlAndHookToWebhooks < Postal::MessageDB::Migration + def up @database.query("ALTER TABLE `#{@database.database_name}`.`webhook_requests` ADD COLUMN `url` varchar(255)") @database.query("ALTER TABLE `#{@database.database_name}`.`webhook_requests` ADD COLUMN `webhook_id` int(11)") @database.query("ALTER TABLE `#{@database.database_name}`.`webhook_requests` ADD INDEX `on_webhook_id` (`webhook_id`) USING BTREE") end + end end end diff --git a/lib/postal/message_db/migrations/17_add_replaced_link_count_to_messages.rb b/lib/postal/message_db/migrations/17_add_replaced_link_count_to_messages.rb index 8496132..df1075d 100644 --- a/lib/postal/message_db/migrations/17_add_replaced_link_count_to_messages.rb +++ b/lib/postal/message_db/migrations/17_add_replaced_link_count_to_messages.rb @@ -2,11 +2,13 @@ module Postal module MessageDB module Migrations class AddReplacedLinkCountToMessages < Postal::MessageDB::Migration + def up @database.query("ALTER TABLE `#{@database.database_name}`.`messages` ADD COLUMN `tracked_links` int(11) DEFAULT 0") @database.query("ALTER TABLE `#{@database.database_name}`.`messages` ADD COLUMN `tracked_images` int(11) DEFAULT 0") @database.query("ALTER TABLE `#{@database.database_name}`.`messages` ADD COLUMN `parsed` tinyint DEFAULT 0") end + end end end diff --git a/lib/postal/message_db/migrations/18_add_endpoints_to_messages.rb b/lib/postal/message_db/migrations/18_add_endpoints_to_messages.rb index b693383..9d872ce 100644 --- a/lib/postal/message_db/migrations/18_add_endpoints_to_messages.rb +++ b/lib/postal/message_db/migrations/18_add_endpoints_to_messages.rb @@ -2,9 +2,11 @@ module Postal module MessageDB module Migrations class AddEndpointsToMessages < Postal::MessageDB::Migration + def up @database.query("ALTER TABLE `#{@database.database_name}`.`messages` ADD COLUMN `endpoint_id` int(11), ADD COLUMN `endpoint_type` varchar(255)") end + end end end diff --git a/lib/postal/message_db/migrations/19_convert_database_to_utf8mb4.rb b/lib/postal/message_db/migrations/19_convert_database_to_utf8mb4.rb index 0bef8e4..b3d249c 100644 --- a/lib/postal/message_db/migrations/19_convert_database_to_utf8mb4.rb +++ b/lib/postal/message_db/migrations/19_convert_database_to_utf8mb4.rb @@ -2,6 +2,7 @@ module Postal module MessageDB module Migrations class ConvertDatabaseToUtf8mb4 < Postal::MessageDB::Migration + def up @database.query("ALTER DATABASE `#{@database.database_name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") @database.query("ALTER TABLE `#{@database.database_name}`.`clicks` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") @@ -20,6 +21,7 @@ module Postal @database.query("ALTER TABLE `#{@database.database_name}`.`suppressions` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") @database.query("ALTER TABLE `#{@database.database_name}`.`webhook_requests` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") end + end end end diff --git a/lib/postal/message_db/migrations/20_increase_links_url_size.rb b/lib/postal/message_db/migrations/20_increase_links_url_size.rb index e98124c..3ed4f78 100644 --- a/lib/postal/message_db/migrations/20_increase_links_url_size.rb +++ b/lib/postal/message_db/migrations/20_increase_links_url_size.rb @@ -2,9 +2,11 @@ module Postal module MessageDB module Migrations class IncreaseLinksUrlSize < Postal::MessageDB::Migration + def up @database.query("ALTER TABLE `#{@database.database_name}`.`links` MODIFY `url` TEXT") end + end end end diff --git a/lib/postal/message_db/mysql.rb b/lib/postal/message_db/mysql.rb index 2ef81d2..f4d7680 100644 --- a/lib/postal/message_db/mysql.rb +++ b/lib/postal/message_db/mysql.rb @@ -7,23 +7,21 @@ module Postal # it would be undesirable as we'd just end up with lots of connections. def self.new_client - Mysql2::Client.new(:host => Postal.config.message_db.host, :username => Postal.config.message_db.username, :password => Postal.config.message_db.password, :port => Postal.config.message_db.port, :reconnect => true, :encoding => Postal.config.message_db.encoding || 'utf8mb4') + Mysql2::Client.new(host: Postal.config.message_db.host, username: Postal.config.message_db.username, password: Postal.config.message_db.password, port: Postal.config.message_db.port, reconnect: true, encoding: Postal.config.message_db.encoding || "utf8mb4") end @free_clients = [] def self.client(&block) - client = @free_clients.shift || self.new_client + client = @free_clients.shift || new_client return_value = nil tries = 2 begin return_value = block.call(client) rescue Mysql2::Error => e - if e.message =~ /(lost connection|gone away)/i && (tries -= 1) > 0 - retry - else - raise - end + raise unless e.message =~ /(lost connection|gone away)/i && (tries -= 1) > 0 + + retry ensure @free_clients << client end diff --git a/lib/postal/message_db/provisioner.rb b/lib/postal/message_db/provisioner.rb index 0aee27e..2953512 100644 --- a/lib/postal/message_db/provisioner.rb +++ b/lib/postal/message_db/provisioner.rb @@ -46,7 +46,7 @@ module Postal @database.query("DROP DATABASE `#{@database.database_name}`;") true rescue Mysql2::Error => e - e.message =~ /doesn\'t exist/ ? false : raise + e.message =~ /doesn't exist/ ? false : raise end # @@ -68,9 +68,9 @@ module Postal # environment and can be quite dangerous in production. # def clean - ['clicks', 'deliveries', 'links', 'live_stats', 'loads', 'messages', - 'raw_message_sizes', 'spam_checks', 'stats_daily', 'stats_hourly', - 'stats_monthly', 'stats_yearly', 'suppressions', 'webhook_requests'].each do |table| + ["clicks", "deliveries", "links", "live_stats", "loads", "messages", + "raw_message_sizes", "spam_checks", "stats_daily", "stats_hourly", + "stats_monthly", "stats_yearly", "suppressions", "webhook_requests"].each do |table| @database.query("TRUNCATE `#{@database.database_name}`.`#{table}`") end end @@ -79,18 +79,15 @@ module Postal # Creates a new empty raw message table for the given date. Returns nothing. # def create_raw_table(table) - begin - @database.query(create_table_query(table,:columns => { - :id => 'int(11) NOT NULL AUTO_INCREMENT', - :data => 'longblob DEFAULT NULL', - :next => 'int(11) DEFAULT NULL' - } - )) - @database.query("INSERT INTO `#{@database.database_name}`.`raw_message_sizes` (table_name, size) VALUES ('#{table}', 0)") - rescue Mysql2::Error => e - # Don't worry if the table already exists, another thread has already run this code. - raise unless e.message =~ /already exists/ - end + @database.query(create_table_query(table, columns: { + id: "int(11) NOT NULL AUTO_INCREMENT", + data: "longblob DEFAULT NULL", + next: "int(11) DEFAULT NULL" + })) + @database.query("INSERT INTO `#{@database.database_name}`.`raw_message_sizes` (table_name, size) VALUES ('#{table}', 0)") + rescue Mysql2::Error => e + # Don't worry if the table already exists, another thread has already run this code. + raise unless e.message =~ /already exists/ end # @@ -101,7 +98,7 @@ module Postal [].tap do |tables| @database.query("SHOW TABLES FROM `#{@database.database_name}` LIKE 'raw-%'").each do |tbl| tbl_name = tbl.to_a.first.last - date = Date.parse(tbl_name.gsub(/\Araw\-/, '')) + date = Date.parse(tbl_name.gsub(/\Araw-/, "")) if earliest_date.nil? || date < earliest_date tables << tbl_name end @@ -128,25 +125,25 @@ module Postal end # - # Remove messages from the messages table that are too old to retain + #  Remove messages from the messages table that are too old to retain # def remove_messages(max_age = 60) time = (Date.today - max_age.days).to_time.end_of_day - if newest_message_to_remove = @database.select(:messages, :where => {:timestamp => {:less_than_or_equal_to => time.to_f}}, :limit => 1, :order => :id, :direction => 'DESC', :fields => [:id]).first - id = newest_message_to_remove['id'] - @database.query("DELETE FROM `#{@database.database_name}`.`clicks` WHERE `message_id` <= #{id}") - @database.query("DELETE FROM `#{@database.database_name}`.`loads` WHERE `message_id` <= #{id}") - @database.query("DELETE FROM `#{@database.database_name}`.`deliveries` WHERE `message_id` <= #{id}") - @database.query("DELETE FROM `#{@database.database_name}`.`spam_checks` WHERE `message_id` <= #{id}") - @database.query("DELETE FROM `#{@database.database_name}`.`messages` WHERE `id` <= #{id}") - end + return unless newest_message_to_remove = @database.select(:messages, where: { timestamp: { less_than_or_equal_to: time.to_f } }, limit: 1, order: :id, direction: "DESC", fields: [:id]).first + + id = newest_message_to_remove["id"] + @database.query("DELETE FROM `#{@database.database_name}`.`clicks` WHERE `message_id` <= #{id}") + @database.query("DELETE FROM `#{@database.database_name}`.`loads` WHERE `message_id` <= #{id}") + @database.query("DELETE FROM `#{@database.database_name}`.`deliveries` WHERE `message_id` <= #{id}") + @database.query("DELETE FROM `#{@database.database_name}`.`spam_checks` WHERE `message_id` <= #{id}") + @database.query("DELETE FROM `#{@database.database_name}`.`messages` WHERE `id` <= #{id}") end # # Remove raw message tables in order order until size is under the given size (given in MB) # def remove_raw_tables_until_less_than_size(size) - tables = self.raw_tables(nil) + tables = raw_tables(nil) tables_removed = [] until @database.total_size <= size table = tables.shift @@ -166,18 +163,18 @@ module Postal s << "CREATE TABLE `#{@database.database_name}`.`#{table_name}` (" s << options[:columns].map do |column_name, column_options| "`#{column_name}` #{column_options}" - end.join(', ') + end.join(", ") if options[:indexes] s << ", " s << options[:indexes].map do |index_name, index_options| "KEY `#{index_name}` (#{index_options}) USING BTREE" - end.join(', ') + end.join(", ") end if options[:unique_indexes] s << ", " s << options[:unique_indexes].map do |index_name, index_options| "UNIQUE KEY `#{index_name}` (#{index_options})" - end.join(', ') + end.join(", ") end if options[:primary_key] s << ", PRIMARY KEY (#{options[:primary_key]})" @@ -189,7 +186,6 @@ module Postal end end - end end end diff --git a/lib/postal/message_db/statistics.rb b/lib/postal/message_db/statistics.rb index cd686e8..37976e0 100644 --- a/lib/postal/message_db/statistics.rb +++ b/lib/postal/message_db/statistics.rb @@ -6,7 +6,7 @@ module Postal @database = database end - STATS_GAPS = {:hourly => :hour, :daily => :day, :monthly => :month, :yearly => :year} + STATS_GAPS = { hourly: :hour, daily: :day, monthly: :month, yearly: :year } COUNTERS = [:incoming, :outgoing, :spam, :bounces, :held] # @@ -44,8 +44,8 @@ module Postal h[c] = 0 end end - @database.select("stats_#{type}", :where => {:time => items.keys.map(&:to_i)}, :fields => [:time] | counters).each do |data| - time = Time.zone.at(data.delete('time')) + @database.select("stats_#{type}", where: { time: items.keys.map(&:to_i) }, fields: [:time] | counters).each do |data| + time = Time.zone.at(data.delete("time")) data.each do |key, value| items[time][key.to_sym] = value end diff --git a/lib/postal/message_db/suppression_list.rb b/lib/postal/message_db/suppression_list.rb index a3aa77e..fe683d5 100644 --- a/lib/postal/message_db/suppression_list.rb +++ b/lib/postal/message_db/suppression_list.rb @@ -8,29 +8,29 @@ module Postal def add(type, address, options = {}) keep_until = (options[:days] || Postal.config.general.suppression_list_removal_delay).days.from_now.to_f - if existing = @database.select('suppressions', :where => {:type => type, :address => address}, :limit =>1).first - reason = options[:reason] || existing['reason'] - @database.update('suppressions', {:reason => reason, :keep_until => keep_until}, :where => {:id => existing['id']}) + if existing = @database.select("suppressions", where: { type: type, address: address }, limit: 1).first + reason = options[:reason] || existing["reason"] + @database.update("suppressions", { reason: reason, keep_until: keep_until }, where: { id: existing["id"] }) else - @database.insert('suppressions', {:type => type, :address => address, :reason => options[:reason], :timestamp => Time.now.to_f, :keep_until => keep_until}) + @database.insert("suppressions", { type: type, address: address, reason: options[:reason], timestamp: Time.now.to_f, keep_until: keep_until }) end true end def get(type, address) - @database.select('suppressions', :where => {:type => type, :address => address, :keep_until => {:greater_than_or_equal_to => Time.now.to_f}}, :limit => 1).first + @database.select("suppressions", where: { type: type, address: address, keep_until: { greater_than_or_equal_to: Time.now.to_f } }, limit: 1).first end def all_with_pagination(page) - @database.select_with_pagination(:suppressions, page, :order => :timestamp, :direction => 'desc') + @database.select_with_pagination(:suppressions, page, order: :timestamp, direction: "desc") end def remove(type, address) - @database.delete('suppressions', :where => {:type => type, :address => address}) > 0 + @database.delete("suppressions", where: { type: type, address: address }) > 0 end def prune - @database.delete('suppressions', :where => {:keep_until => {:less_than => Time.now.to_f}}) || 0 + @database.delete("suppressions", where: { keep_until: { less_than: Time.now.to_f } }) || 0 end end diff --git a/lib/postal/message_db/webhooks.rb b/lib/postal/message_db/webhooks.rb index b90430e..57855ee 100644 --- a/lib/postal/message_db/webhooks.rb +++ b/lib/postal/message_db/webhooks.rb @@ -11,26 +11,27 @@ module Postal end def list(page) - result = @database.select_with_pagination(:webhook_requests, page, :order => :timestamp, :direction => 'desc') + result = @database.select_with_pagination(:webhook_requests, page, order: :timestamp, direction: "desc") result[:records] = result[:records].map { |i| Request.new(i) } result end def find(uuid) - request = @database.select(:webhook_requests, :where => {:uuid => uuid}).first || raise(RequestNotFound, "No request found with UUID '#{uuid}'") + request = @database.select(:webhook_requests, where: { uuid: uuid }).first || raise(RequestNotFound, "No request found with UUID '#{uuid}'") Request.new(request) end def prune - if last = @database.select(:webhook_requests, :where => {:timestamp => {:less_than => 10.days.ago.to_f}}, :order => 'timestamp', :direction => 'desc', :limit => 1, :fields => ['id']).first - @database.delete(:webhook_requests, :where => {:id => {:less_than_or_equal_to => last['id']}}) - end + return unless last = @database.select(:webhook_requests, where: { timestamp: { less_than: 10.days.ago.to_f } }, order: "timestamp", direction: "desc", limit: 1, fields: ["id"]).first + + @database.delete(:webhook_requests, where: { id: { less_than_or_equal_to: last["id"] } }) end class RequestNotFound < Postal::Error end class Request + def initialize(attributes) @attributes = attributes end @@ -40,47 +41,48 @@ module Postal end def timestamp - Time.zone.at(@attributes['timestamp']) + Time.zone.at(@attributes["timestamp"]) end def event - @attributes['event'] + @attributes["event"] end def status_code - @attributes['status_code'] + @attributes["status_code"] end def url - @attributes['url'] + @attributes["url"] end def uuid - @attributes['uuid'] + @attributes["uuid"] end def payload - @attributes['payload'] + @attributes["payload"] end def pretty_payload @pretty_payload ||= begin - json = JSON.parse(self.payload) + json = JSON.parse(payload) JSON.pretty_unparse(json) end end def body - @attributes['body'] + @attributes["body"] end def attempt - @attributes['attempt'] + @attributes["attempt"] end def will_retry? - @attributes['will_retry'] == 1 + @attributes["will_retry"] == 1 end + end end diff --git a/lib/postal/message_inspection.rb b/lib/postal/message_inspection.rb index 4096cd7..5f24723 100644 --- a/lib/postal/message_inspection.rb +++ b/lib/postal/message_inspection.rb @@ -31,11 +31,13 @@ module Postal end class << self + def scan(message, scope) inspection = new(message, scope) inspection.scan inspection end + end end diff --git a/lib/postal/message_inspector.rb b/lib/postal/message_inspector.rb index 10224a3..f522eb3 100644 --- a/lib/postal/message_inspector.rb +++ b/lib/postal/message_inspector.rb @@ -17,11 +17,11 @@ module Postal end class << self + # Return an array of all inspectors that are available for this # installation. def inspectors - Array.new.tap do |inspectors| - + [].tap do |inspectors| if Postal.config.rspamd&.enabled inspectors << MessageInspectors::Rspamd.new(Postal.config.rspamd) elsif Postal.config.spamd&.enabled @@ -31,9 +31,9 @@ module Postal if Postal.config.clamav&.enabled inspectors << MessageInspectors::Clamav.new(Postal.config.clamav) end - end end + end end diff --git a/lib/postal/message_inspectors.rb b/lib/postal/message_inspectors.rb index 33b7565..2be9941 100644 --- a/lib/postal/message_inspectors.rb +++ b/lib/postal/message_inspectors.rb @@ -1,10 +1,12 @@ module Postal module MessageInspectors + extend ActiveSupport::Autoload eager_autoload do autoload :Clamav autoload :Rspamd autoload :SpamAssassin end + end end diff --git a/lib/postal/message_inspectors/clamav.rb b/lib/postal/message_inspectors/clamav.rb index dbbda7d..0890601 100644 --- a/lib/postal/message_inspectors/clamav.rb +++ b/lib/postal/message_inspectors/clamav.rb @@ -16,13 +16,13 @@ module Postal data = tcp_socket.read end - if data && data =~ /\Astream\:\s+(.*?)[\s\0]+?/ - if $1.upcase == 'OK' + if data && data =~ /\Astream:\s+(.*?)[\s\0]+?/ + if ::Regexp.last_match(1).upcase == "OK" inspection.threat = false inspection.threat_message = "No threats found" else inspection.threat = true - inspection.threat_message = $1 + inspection.threat_message = ::Regexp.last_match(1) end else inspection.threat = false @@ -31,13 +31,17 @@ module Postal rescue Timeout::Error inspection.threat = false inspection.threat_message = "Timed out scanning for threats" - rescue => e + rescue StandardError => e logger.error "Error talking to clamav: #{e.class} (#{e.message})" - logger.error e.backtrace[0,5] + logger.error e.backtrace[0, 5] inspection.threat = false inspection.threat_message = "Error when scanning for threats" ensure - tcp_socket.close rescue nil + begin + tcp_socket.close + rescue StandardError + nil + end end end diff --git a/lib/postal/message_inspectors/rspamd.rb b/lib/postal/message_inspectors/rspamd.rb index 914a780..f52db16 100644 --- a/lib/postal/message_inspectors/rspamd.rb +++ b/lib/postal/message_inspectors/rspamd.rb @@ -1,4 +1,4 @@ -require 'net/http' +require "net/http" module Postal module MessageInspectors @@ -10,12 +10,12 @@ module Postal def inspect_message(inspection) response = request(inspection.message, inspection.scope) response = JSON.parse(response.body) - return unless response['symbols'].is_a?(Hash) + return unless response["symbols"].is_a?(Hash) - response['symbols'].values.each do |symbol| - next if symbol['description'].blank? + response["symbols"].values.each do |symbol| + next if symbol["description"].blank? - inspection.spam_checks << SpamCheck.new(symbol['name'], symbol['score'], symbol['description']) + inspection.spam_checks << SpamCheck.new(symbol["name"], symbol["score"], symbol["description"]) end rescue Error => e inspection.spam_checks << SpamCheck.new("ERROR", 0, e.message) @@ -31,24 +31,24 @@ module Postal raw_message = message.raw_message - request = Net::HTTP::Post.new('/checkv2') + request = Net::HTTP::Post.new("/checkv2") request.body = raw_message - request['Content-Length'] = raw_message.bytesize.to_s - request['Password'] = @config.password if @config.password - request['Flags'] = @config.flags if @config.flags - request['User-Agent'] = 'Postal' - request['Deliver-To'] = message.rcpt_to - request['From'] = message.mail_from - request['Rcpt'] = message.rcpt_to - request['Queue-Id'] = message.token + request["Content-Length"] = raw_message.bytesize.to_s + request["Password"] = @config.password if @config.password + request["Flags"] = @config.flags if @config.flags + request["User-Agent"] = "Postal" + request["Deliver-To"] = message.rcpt_to + request["From"] = message.mail_from + request["Rcpt"] = message.rcpt_to + request["Queue-Id"] = message.token if scope == :outgoing - request['User'] = '' + request["User"] = "" # We don't actually know the IP but an empty input here will # still trigger rspamd to treat this as an outbound email # and disable certain checks. # https://rspamd.com/doc/tutorials/scanning_outbound.html - request['Ip'] = '' + request["Ip"] = "" end response = nil @@ -56,7 +56,7 @@ module Postal response = http.request(request) rescue Exception => e logger.error "Error talking to rspamd: #{e.class} (#{e.message})" - logger.error e.backtrace[0,5] + logger.error e.backtrace[0, 5] raise Error, "Error when scanning with rspamd (#{e.class})" end diff --git a/lib/postal/message_inspectors/spam_assassin.rb b/lib/postal/message_inspectors/spam_assassin.rb index e84a136..640d0dc 100644 --- a/lib/postal/message_inspectors/spam_assassin.rb +++ b/lib/postal/message_inspectors/spam_assassin.rb @@ -3,8 +3,8 @@ module Postal class SpamAssassin < MessageInspector EXCLUSIONS = { - :outgoing => ['NO_RECEIVED', 'NO_RELAYS', 'ALL_TRUSTED', 'FREEMAIL_FORGED_REPLYTO', 'RDNS_DYNAMIC', 'CK_HELO_GENERIC', /^SPF\_/, /^HELO\_/, /DKIM_/, /^RCVD_IN_/], - :incoming => [] + outgoing: ["NO_RECEIVED", "NO_RELAYS", "ALL_TRUSTED", "FREEMAIL_FORGED_REPLYTO", "RDNS_DYNAMIC", "CK_HELO_GENERIC", /^SPF_/, /^HELO_/, /DKIM_/, /^RCVD_IN_/], + incoming: [] } def inspect_message(inspection) @@ -24,11 +24,11 @@ module Postal total = 0.0 rules = data ? data.split(/^---(.*)\r?\n/).last.split(/\r?\n/) : [] while line = rules.shift - if line =~ /\A([\- ]?[\d\.]+)\s+(\w+)\s+(.*)/ - total += $1.to_f - spam_checks << SpamCheck.new($2, $1.to_f, $3) + if line =~ /\A([- ]?[\d.]+)\s+(\w+)\s+(.*)/ + total += ::Regexp.last_match(1).to_f + spam_checks << SpamCheck.new(::Regexp.last_match(2), ::Regexp.last_match(1).to_f, ::Regexp.last_match(3)) else - spam_checks.last.description << " " + line.strip + spam_checks.last.description << (" " + line.strip) end end @@ -36,17 +36,18 @@ module Postal checks.each do |check| inspection.spam_checks << check end - rescue Timeout::Error inspection.spam_checks << SpamCheck.new("TIMEOUT", 0, "Timed out when scanning for spam") - - rescue => e + rescue StandardError => e logger.error "Error talking to spamd: #{e.class} (#{e.message})" - logger.error e.backtrace[0,5] + logger.error e.backtrace[0, 5] inspection.spam_checks << SpamCheck.new("ERROR", 0, "Error when scanning for spam") - ensure - tcp_socket.close rescue nil + begin + tcp_socket.close + rescue StandardError + nil + end end end diff --git a/lib/postal/message_parser.rb b/lib/postal/message_parser.rb index 5a14fdc..06abf20 100644 --- a/lib/postal/message_parser.rb +++ b/lib/postal/message_parser.rb @@ -1,18 +1,18 @@ module Postal class MessageParser - URL_REGEX = /(?(?https?)\:\/\/(?[A-Za-z0-9\-\.\:]+)(?\/[A-Za-z0-9\.\/\+\?\&\-\_\%\=\~\:\;\(\)\[\]#]*)?+)/ + URL_REGEX = /(?(?https?):\/\/(?[A-Za-z0-9\-.:]+)(?\/[A-Za-z0-9.\/+?&\-_%=~:;()\[\]#]*)?+)/ def initialize(message) @message = message @actioned = false @tracked_links = 0 @tracked_images = 0 - @domain = @message.server.track_domains.where(:domain => @message.domain, :dns_status => "OK").first + @domain = @message.server.track_domains.where(domain: @message.domain, dns_status: "OK").first - if @domain - @parsed_output = generate - end + return unless @domain + + @parsed_output = generate end attr_reader :tracked_links @@ -36,29 +36,27 @@ module Postal if @mail.mime_type =~ /text\/plain/ @mail.body = parse(@mail.body.decoded.dup, :text) @mail.content_transfer_encoding = nil - @mail.charset = 'UTF-8' + @mail.charset = "UTF-8" elsif @mail.mime_type =~ /text\/html/ @mail.body = parse(@mail.body.decoded.dup, :html) @mail.content_transfer_encoding = nil - @mail.charset = 'UTF-8' + @mail.charset = "UTF-8" end end else parse_parts(@mail.parts) end @mail.to_s - rescue => e - if Rails.env.development? - raise - else - if defined?(Raven) - Raven.capture_exception(e) - end - @actioned = false - @tracked_links = 0 - @tracked_images = 0 - @original_message + rescue StandardError => e + raise if Rails.env.development? + + if defined?(Raven) + Raven.capture_exception(e) end + @actioned = false + @tracked_links = 0 + @tracked_images = 0 + @original_message end def parse_parts(parts) @@ -66,11 +64,11 @@ module Postal if part.content_type =~ /text\/html/ part.body = parse(part.body.decoded.dup, :html) part.content_transfer_encoding = nil - part.charset = 'UTF-8' + part.charset = "UTF-8" elsif part.content_type =~ /text\/plain/ part.body = parse(part.body.decoded.dup, :text) part.content_transfer_encoding = nil - part.charset = 'UTF-8' + part.charset = "UTF-8" elsif part.content_type =~ /multipart\/(alternative|related)/ unless part.parts.empty? parse_parts(part.parts) @@ -97,33 +95,33 @@ module Postal if track_domain?($~[:domain]) @tracked_links += 1 url = $~[:url] - while url =~ /[^\w]$/ do + while url =~ /[^\w]$/ theend = url.size - 2 url = url[0..theend] end token = @message.create_link(url) "#{domain}/#{@message.server.token}/#{token}" else - $& + ::Regexp.last_match(0) end end end if type == :html - part.gsub!(/href=([\'\"])(#{URL_REGEX})[\'\"]/) do + part.gsub!(/href=(['"])(#{URL_REGEX})['"]/) do if track_domain?($~[:domain]) @tracked_links += 1 token = @message.create_link($~[:url]) "href='#{domain}/#{@message.server.token}/#{token}'" else - $& + ::Regexp.last_match(0) end end end - part.gsub!(/(https?)\+notrack\:\/\//) do + part.gsub!(/(https?)\+notrack:\/\//) do @actioned = true - "#{$1}://" + "#{::Regexp.last_match(1)}://" end part @@ -132,7 +130,7 @@ module Postal def insert_tracking_image(part) @tracked_images += 1 container = "

" - if part =~ /\<\/body\>/ + if part =~ /<\/body>/ part.gsub("", "#{container}") else part + container diff --git a/lib/postal/message_requeuer.rb b/lib/postal/message_requeuer.rb index 89991a0..ec4d2e3 100644 --- a/lib/postal/message_requeuer.rb +++ b/lib/postal/message_requeuer.rb @@ -22,10 +22,10 @@ module Postal end def check_exit - if @exit - log "Exiting" - Process.exit(0) - end + return unless @exit + + log "Exiting" + Process.exit(0) end end diff --git a/lib/postal/mx_lookup.rb b/lib/postal/mx_lookup.rb index ed170ed..a859c04 100644 --- a/lib/postal/mx_lookup.rb +++ b/lib/postal/mx_lookup.rb @@ -23,7 +23,7 @@ module Postal def resolve(domain) Resolv::DNS.open do |dns| - dns.timeouts = [10,5] + dns.timeouts = [10, 5] dns.getresources(domain, Resolv::DNS::Resource::IN::MX).map { |m| [m.preference.to_i, m.exchange.to_s] } end end diff --git a/lib/postal/query_string.rb b/lib/postal/query_string.rb index dc2c04b..9546baa 100644 --- a/lib/postal/query_string.rb +++ b/lib/postal/query_string.rb @@ -9,12 +9,10 @@ module Postal to_hash[value.to_s] end - def empty? - to_hash.empty? - end + delegate :empty?, to: :to_hash def to_hash - @hash ||= @string.scan(/([a-z]+)\:\s*(?:(\d{2,4}\-\d{2}-\d{2}\s\d{2}\:\d{2})|\"(.*?)\"|(.*?))(\s|\z)/).each_with_object({}) do |(key, date, string_with_spaces, value), hash| + @hash ||= @string.scan(/([a-z]+):\s*(?:(\d{2,4}-\d{2}-\d{2}\s\d{2}:\d{2})|"(.*?)"|(.*?))(\s|\z)/).each_with_object({}) do |(key, date, string_with_spaces, value), hash| if date actual_value = date elsif string_with_spaces diff --git a/lib/postal/rabbit_mq.rb b/lib/postal/rabbit_mq.rb index b2c2f00..50ac3f9 100644 --- a/lib/postal/rabbit_mq.rb +++ b/lib/postal/rabbit_mq.rb @@ -1,34 +1,34 @@ -require 'postal/config' -require 'bunny' +require "postal/config" +require "bunny" module Postal module RabbitMQ def self.create_connection - bunny_host = [ 'localhost' ] + bunny_host = ["localhost"] if Postal.config.rabbitmq&.host.is_a?(Array) bunny_host = Postal.config.rabbitmq&.host elsif Postal.config.rabbitmq&.host.is_a?(String) - bunny_host = [ Postal.config.rabbitmq&.host ] + bunny_host = [Postal.config.rabbitmq&.host] end conn = Bunny.new( - :hosts => bunny_host, - :port => Postal.config.rabbitmq&.port || 5672, - :tls => Postal.config.rabbitmq&.tls || false, - :verify_peer => Postal.config.rabbitmq&.verify_peer || true, - :tls_ca_certificates => Postal.config.rabbitmq&.tls_ca_certificates || [ "/etc/ssl/certs/ca-certificates.crt" ], - :username => Postal.config.rabbitmq&.username || 'guest', - :password => Postal.config.rabbitmq&.password || 'guest', - :vhost => Postal.config.rabbitmq&.vhost || nil + hosts: bunny_host, + port: Postal.config.rabbitmq&.port || 5672, + tls: Postal.config.rabbitmq&.tls || false, + verify_peer: Postal.config.rabbitmq&.verify_peer || true, + tls_ca_certificates: Postal.config.rabbitmq&.tls_ca_certificates || ["/etc/ssl/certs/ca-certificates.crt"], + username: Postal.config.rabbitmq&.username || "guest", + password: Postal.config.rabbitmq&.password || "guest", + vhost: Postal.config.rabbitmq&.vhost || nil ) conn.start conn end def self.create_channel - conn = self.create_connection + conn = create_connection conn.create_channel(nil, Postal.config.workers.threads) end diff --git a/lib/postal/reply_separator.rb b/lib/postal/reply_separator.rb index 6f7d8ee..b27362e 100644 --- a/lib/postal/reply_separator.rb +++ b/lib/postal/reply_separator.rb @@ -3,30 +3,31 @@ module Postal RULES = [ /^-{2,10} $.*/m, - /^\>*\s*----- ?Original Message ?-----.*/m, - /^\>*\s*From\:[^\r\n]*[\r\n]+Sent\:.*/m, - /^\>*\s*From\:[^\r\n]*[\r\n]+Date\:.*/m, - /^\>*\s*-----Urspr.ngliche Nachricht----- .*/m, - /^\>*\s*Le[^\r\n]{10,200}a .crit ?\:\s*$.*/, - /^\>*\s*__________________.*/m, - /^\>*\s*On.{10,200}wrote:\s*$.*/m, - /^\>*\s*Sent from my.*/m, - /^\>*\s*=== Please reply above this line ===.*/m, - /(^\>.*\n?){10,}/, + /^>*\s*----- ?Original Message ?-----.*/m, + /^>*\s*From:[^\r\n]*[\r\n]+Sent:.*/m, + /^>*\s*From:[^\r\n]*[\r\n]+Date:.*/m, + /^>*\s*-----Urspr.ngliche Nachricht----- .*/m, + /^>*\s*Le[^\r\n]{10,200}a .crit ?:\s*$.*/, + /^>*\s*__________________.*/m, + /^>*\s*On.{10,200}wrote:\s*$.*/m, + /^>*\s*Sent from my.*/m, + /^>*\s*=== Please reply above this line ===.*/m, + /(^>.*\n?){10,}/ ] def self.separate(text) - return '' unless text.is_a?(String) - text = text.gsub("\r", '') + return "" unless text.is_a?(String) + + text = text.gsub("\r", "") stripped = "" RULES.each do |rule| text.gsub!(rule) do - stripped = $&.to_s + "\n" + stripped - '' + stripped = ::Regexp.last_match(0).to_s + "\n" + stripped + "" end end stripped = stripped.strip - [text.strip, stripped.blank? ? nil : stripped] + [text.strip, stripped.presence] end end diff --git a/lib/postal/rspec_helpers.rb b/lib/postal/rspec_helpers.rb index 5ec44ea..ddc875c 100644 --- a/lib/postal/rspec_helpers.rb +++ b/lib/postal/rspec_helpers.rb @@ -8,12 +8,12 @@ module Postal server.message_db.provisioner.clean end - def create_plain_text_message(server, text, to = 'test@example.com', override_attributes = {}) - domain = create(:domain, :owner => server) - attributes = {:from => "test@#{domain.name}", :subject => "Test Plain Text Message"}.merge(override_attributes) + def create_plain_text_message(server, text, to = "test@example.com", override_attributes = {}) + domain = create(:domain, owner: server) + attributes = { from: "test@#{domain.name}", subject: "Test Plain Text Message" }.merge(override_attributes) attributes[:to] = to attributes[:plain_body] = text - message = OutgoingMessagePrototype.new(server, '127.0.0.1', 'testsuite', attributes) + message = OutgoingMessagePrototype.new(server, "127.0.0.1", "testsuite", attributes) result = message.create_message(to) server.message_db.message(result[:id]) end diff --git a/lib/postal/send_result.rb b/lib/postal/send_result.rb index c1788d4..251e075 100644 --- a/lib/postal/send_result.rb +++ b/lib/postal/send_result.rb @@ -1,5 +1,6 @@ module Postal class SendResult + attr_accessor :type attr_accessor :details attr_accessor :retry @@ -9,5 +10,6 @@ module Postal attr_accessor :log_id attr_accessor :time attr_accessor :suppress_bounce + end end diff --git a/lib/postal/sender.rb b/lib/postal/sender.rb index df9020e..7c4914a 100644 --- a/lib/postal/sender.rb +++ b/lib/postal/sender.rb @@ -1,5 +1,6 @@ module Postal class Sender + def start end @@ -8,5 +9,6 @@ module Postal def finish end + end end diff --git a/lib/postal/smtp_sender.rb b/lib/postal/smtp_sender.rb index 0182517..1ae9457 100644 --- a/lib/postal/smtp_sender.rb +++ b/lib/postal/smtp_sender.rb @@ -1,4 +1,4 @@ -require 'resolv' +require "resolv" module Postal class SMTPSender < Sender @@ -10,7 +10,7 @@ module Postal @smtp_client = nil @connection_errors = [] @hostnames = [] - @log_id = Nifty::Utils::RandomString.generate(:length => 8).upcase + @log_id = Nifty::Utils::RandomString.generate(length: 8).upcase end def start @@ -22,16 +22,15 @@ module Postal elsif server.is_a?(Hash) hostname = server[:hostname] port = server[:port] || 25 - ssl_mode = server[:ssl_mode] || 'Auto' + ssl_mode = server[:ssl_mode] || "Auto" else hostname = server port = 25 - ssl_mode = 'Auto' + ssl_mode = "Auto" end @hostnames << hostname [:aaaa, :a].each do |ip_type| - if @source_ip_address && @source_ip_address.ipv6.blank? && ip_type == :aaaa # Don't try to use IPv6 if the IP address we're sending from doesn't support it. next @@ -57,11 +56,11 @@ module Postal end case ssl_mode - when 'Auto' + when "Auto" smtp_client.enable_starttls_auto(self.class.ssl_context_without_verify) - when 'STARTTLS' + when "STARTTLS" smtp_client.enable_starttls(self.class.ssl_context_with_verify) - when 'TLS' + when "TLS" smtp_client.enable_tls(self.class.ssl_context_with_verify) else # Nothing @@ -69,9 +68,8 @@ module Postal smtp_client.start(@source_ip_address ? @source_ip_address.hostname : self.class.default_helo_hostname) log "Connected to #{@remote_ip}:#{port} (#{hostname})" - - rescue => e - if e.is_a?(OpenSSL::SSL::SSLError) && ssl_mode == 'Auto' + rescue StandardError => e + if e.is_a?(OpenSSL::SSL::SSLError) && ssl_mode == "Auto" log "SSL error (#{e.message}), retrying without SSL" ssl_mode = nil retry @@ -79,7 +77,11 @@ module Postal log "Cannot connect to #{@remote_ip}:#{port} (#{hostname}) (#{e.class}: #{e.message})" @connection_errors << e.message unless @connection_errors.include?(e.message) - smtp_client.disconnect rescue nil + begin + smtp_client.disconnect + rescue StandardError + nil + end smtp_client = nil end @@ -95,17 +97,24 @@ module Postal def reconnect log "Reconnecting" - @smtp_client&.finish rescue nil + begin + @smtp_client&.finish + rescue StandardError + nil + end start end def safe_rset # Something went wrong sending the last email. Reset the connection if possible, else disconnect. + + @smtp_client.rset + rescue StandardError + # Don't reconnect, this would be rather rude if we don't have any more emails to send. begin - @smtp_client.rset - rescue - # Don't reconnect, this would be rather rude if we don't have any more emails to send. - @smtp_client.finish rescue nil + @smtp_client.finish + rescue StandardError + nil end end @@ -126,7 +135,7 @@ module Postal begin if message.bounce == 1 mail_from = "" - elsif message.domain.return_path_status == 'OK' + elsif message.domain.return_path_status == "OK" mail_from = "#{message.server.token}@#{message.domain.return_path_domain}" else mail_from = "#{message.server.token}@#{Postal.config.dns.return_path}" @@ -138,10 +147,10 @@ module Postal log "-> No SMTP server available for #{@domain}" log "-> Hostnames: #{@hostnames.inspect}" log "-> Errors: #{@connection_errors.inspect}" - result.type = 'SoftFail' + result.type = "SoftFail" result.retry = true result.details = "No SMTP servers were available for #{@domain}. Tried #{@hostnames.to_sentence}" - result.output = @connection_errors.join(', ') + result.output = @connection_errors.join(", ") result.connect_error = true return result else @@ -151,46 +160,43 @@ module Postal smtp_result = @smtp_client.send_message(raw_message, mail_from, [rcpt_to]) end rescue Errno::ECONNRESET, Errno::EPIPE, OpenSSL::SSL::SSLError - if (tries += 1) < 2 - reconnect - retry - else - raise - end + raise unless (tries += 1) < 2 + + reconnect + retry end - result.type = 'Sent' + result.type = "Sent" result.details = "Message for #{rcpt_to} accepted by #{destination_host_description}" if @smtp_client.source_address result.details += " (from #{@smtp_client.source_address})" end result.output = smtp_result.string log "Message sent ##{message.id} to #{destination_host_description} for #{rcpt_to}" - rescue Net::SMTPServerBusy, Net::SMTPAuthenticationError, Net::SMTPSyntaxError, Net::SMTPUnknownError, Net::ReadTimeout => e log "#{e.class}: #{e.message}" - result.type = 'SoftFail' + result.type = "SoftFail" result.retry = true result.details = "Temporary SMTP delivery error when sending to #{destination_host_description}" result.output = e.message if e.to_s =~ /(\d+) seconds/ - result.retry = $1.to_i + 10 + result.retry = ::Regexp.last_match(1).to_i + 10 elsif e.to_s =~ /(\d+) minutes/ - result.retry = ($1.to_i * 60) + 10 + result.retry = (::Regexp.last_match(1).to_i * 60) + 10 end safe_rset rescue Net::SMTPFatalError => e log "#{e.class}: #{e.message}" - result.type = 'HardFail' + result.type = "HardFail" result.details = "Permanent SMTP delivery error when sending to #{destination_host_description}" result.output = e.message safe_rset - rescue => e + rescue StandardError => e log "#{e.class}: #{e.message}" if defined?(Raven) - Raven.capture_exception(e, :extra => {:log_id => @log_id, :server_id => message.server.id, :message_id => message.id}) + Raven.capture_exception(e, extra: { log_id: @log_id, server_id: message.server.id, message_id: message.id }) end - result.type = 'SoftFail' + result.type = "SoftFail" result.retry = true result.details = "An error occurred while sending the message to #{destination_host_description}" result.output = e.message @@ -198,8 +204,7 @@ module Postal end result.time = (Time.now - start_time).to_f.round(2) - return result - ensure + result end def finish @@ -230,7 +235,7 @@ module Postal def lookup_ip_address(type, hostname) records = [] Resolv::DNS.open do |dns| - dns.timeouts = [10,5] + dns.timeouts = [10, 5] case type when :a records = dns.getresources(hostname, Resolv::DNS::Resource::IN::A) @@ -265,15 +270,13 @@ module Postal def self.relay_hosts hosts = Postal.config.smtp_relays.map do |relay| - if relay.hostname.present? - { - :hostname => relay.hostname, - :port => relay.port, - :ssl_mode => relay.ssl_mode - } - else - nil - end + next unless relay.hostname.present? + + { + hostname: relay.hostname, + port: relay.port, + ssl_mode: relay.ssl_mode + } end.compact hosts.empty? ? nil : hosts end diff --git a/lib/postal/smtp_server.rb b/lib/postal/smtp_server.rb index 9755e39..b9a02d1 100644 --- a/lib/postal/smtp_server.rb +++ b/lib/postal/smtp_server.rb @@ -1,9 +1,11 @@ module Postal module SMTPServer + extend ActiveSupport::Autoload eager_autoload do autoload :Client autoload :Server end + end end diff --git a/lib/postal/smtp_server/client.rb b/lib/postal/smtp_server/client.rb index 916bd5b..eeed9e0 100644 --- a/lib/postal/smtp_server/client.rb +++ b/lib/postal/smtp_server/client.rb @@ -1,11 +1,11 @@ -require 'resolv' -require 'nifty/utils/random_string' +require "resolv" +require "nifty/utils/random_string" module Postal module SMTPServer class Client - CRAM_MD5_DIGEST = OpenSSL::Digest.new('md5') + CRAM_MD5_DIGEST = OpenSSL::Digest.new("md5") LOG_REDACTION_STRING = "[redacted]".freeze attr_reader :logging_enabled @@ -23,9 +23,9 @@ module Postal end def check_ip_address - if @ip_address && Postal.config.smtp_server.log_exclude_ips && @ip_address =~ Regexp.new(Postal.config.smtp_server.log_exclude_ips) - @logging_enabled = false - end + return unless @ip_address && Postal.config.smtp_server.log_exclude_ips && @ip_address =~ Regexp.new(Postal.config.smtp_server.log_exclude_ips) + + @logging_enabled = false end def transaction_reset @@ -36,7 +36,7 @@ module Postal end def id - @id ||= Nifty::Utils::RandomString.generate(:length => 6).upcase + @id ||= Nifty::Utils::RandomString.generate(length: 6).upcase end def handle(data) @@ -56,13 +56,13 @@ module Postal def sanitize_input_for_log(data) if @password_expected_next @password_expected_next = false - if data =~ /\A[a-z0-9]{3,}\=*\z/i + if data =~ /\A[a-z0-9]{3,}=*\z/i return LOG_REDACTION_STRING end end data = data.dup - data.gsub!(/(.*AUTH \w+) (.*)\z/i) { "#{$1} #{LOG_REDACTION_STRING}" } + data.gsub!(/(.*AUTH \w+) (.*)\z/i) { "#{::Regexp.last_match(1)} #{LOG_REDACTION_STRING}" } data end @@ -74,9 +74,7 @@ module Postal @start_tls || false end - def start_tls=(value) - @start_tls = value - end + attr_writer :start_tls def handle_command(data) case data @@ -93,12 +91,13 @@ module Postal when /^RCPT TO/i then rcpt_to(data) when /^DATA/i then data(data) else - '502 Invalid/unsupported command' + "502 Invalid/unsupported command" end end def log(text) return false unless @logging_enabled + Postal.logger_for(:smtp_server).debug "[#{id}] #{text}" end @@ -106,8 +105,12 @@ module Postal def resolve_hostname Resolv::DNS.open do |dns| - dns.timeouts = [10,5] - @hostname = dns.getname(@ip_address) rescue @ip_address + dns.timeouts = [10, 5] + @hostname = begin + dns.getname(@ip_address) + rescue StandardError + @ip_address + end end end @@ -120,7 +123,7 @@ module Postal "220 #{Postal.config.dns.smtp_server_hostname} ESMTP Postal/#{id}" else @finished = true - '502 Proxy Error' + "502 Proxy Error" end end @@ -141,15 +144,15 @@ module Postal def ehlo(data) resolve_hostname - @helo_name = data.strip.split(' ', 2)[1] + @helo_name = data.strip.split(" ", 2)[1] transaction_reset @state = :welcomed - ["250-My capabilities are", Postal.config.smtp_server.tls_enabled? && !@tls ? "250-STARTTLS" : nil, "250 AUTH CRAM-MD5 PLAIN LOGIN", ] + ["250-My capabilities are", Postal.config.smtp_server.tls_enabled? && !@tls ? "250-STARTTLS" : nil, "250 AUTH CRAM-MD5 PLAIN LOGIN"] end def helo(data) resolve_hostname - @helo_name = data.strip.split(' ', 2)[1] + @helo_name = data.strip.split(" ", 2)[1] transaction_reset @state = :welcomed "250 #{Postal.config.dns.smtp_server_hostname}" @@ -158,59 +161,61 @@ module Postal def rset transaction_reset @state = :welcomed - '250 OK' + "250 OK" end def noop - '250 OK' + "250 OK" end def auth_plain(data) - handler = Proc.new do |data| + handler = proc do |data| @proc = nil data = Base64.decode64(data) parts = data.split("\0") - username, password = parts[-2], parts[-1] + username = parts[-2] + password = parts[-1] unless username && password - next '535 Authenticated failed - protocol error' + next "535 Authenticated failed - protocol error" end + authenticate(password) end - data = data.gsub(/AUTH PLAIN ?/i, '') - if data.strip == '' + data = data.gsub(/AUTH PLAIN ?/i, "") + if data.strip == "" @proc = handler @password_expected_next = true - '334' + "334" else handler.call(data) end end def auth_login(data) - password_handler = Proc.new do |data| + password_handler = proc do |data| @proc = nil password = Base64.decode64(data) authenticate(password) end - username_handler = Proc.new do |data| + username_handler = proc do |data| @proc = password_handler @password_expected_next = true - '334 UGFzc3dvcmQ6' # "Password:" + "334 UGFzc3dvcmQ6" # "Password:" end - data = data.gsub!(/AUTH LOGIN ?/i, '') - if data.strip == '' + data = data.gsub!(/AUTH LOGIN ?/i, "") + if data.strip == "" @proc = username_handler - '334 VXNlcm5hbWU6' # "Username:" + "334 VXNlcm5hbWU6" # "Username:" else username_handler.call(nil) end end def authenticate(password) - if @credential = Credential.where(:type => 'SMTP', :key => password).first + if @credential = Credential.where(type: "SMTP", key: password).first @credential.use "235 Granted for #{@credential.server.organization.permalink}/#{@credential.server.permalink}" else @@ -220,27 +225,27 @@ module Postal end def auth_cram_md5(data) - challenge = Digest::SHA1.hexdigest(Time.now.to_i.to_s + rand(100000).to_s) - challenge = "<#{challenge[0,20]}@#{Postal.config.dns.smtp_server_hostname}>" + challenge = Digest::SHA1.hexdigest(Time.now.to_i.to_s + rand(100_000).to_s) + challenge = "<#{challenge[0, 20]}@#{Postal.config.dns.smtp_server_hostname}>" - handler = Proc.new do |data| + handler = proc do |data| @proc = nil - username, password = Base64.decode64(data).split(' ', 2).map{ |a| a.chomp } - org_permlink, server_permalink = username.split(/[\/\_]/, 2) - server = ::Server.includes(:organization).where(:organizations => {:permalink => org_permlink}, :permalink => server_permalink).first + username, password = Base64.decode64(data).split(" ", 2).map { |a| a.chomp } + org_permlink, server_permalink = username.split(/[\/_]/, 2) + server = ::Server.includes(:organization).where(organizations: { permalink: org_permlink }, permalink: server_permalink).first if server.nil? log "\e[33m WARN: AUTH failure for #{@ip_address}\e[0m" - next '535 Denied' + next "535 Denied" end grant = nil - server.credentials.where(:type => 'SMTP').each do |credential| + server.credentials.where(type: "SMTP").each do |credential| correct_response = OpenSSL::HMAC.hexdigest(CRAM_MD5_DIGEST, credential.key, challenge) - if password == correct_response - @credential = credential - @credential.use - grant = "235 Granted for #{credential.server.organization.permalink}/#{credential.server.permalink}" - break - end + next unless password == correct_response + + @credential = credential + @credential.use + grant = "235 Granted for #{credential.server.organization.permalink}/#{credential.server.permalink}" + break end if grant.nil? log "\e[33m WARN: AUTH failure for #{@ip_address}\e[0m" @@ -250,12 +255,12 @@ module Postal end @proc = handler - "334 " + Base64.encode64(challenge).gsub(/[\r\n]/, '') + "334 " + Base64.encode64(challenge).gsub(/[\r\n]/, "") end def mail_from(data) unless in_state(:welcomed, :mail_from_received) - return '503 EHLO/HELO first please' + return "503 EHLO/HELO first please" end @state = :mail_from_received @@ -263,93 +268,93 @@ module Postal if data =~ /AUTH=/ # Discard AUTH= parameter and anything that follows. # We don't need this parameter as we don't trust any client to set it - mail_from_line = data.sub(/ *AUTH=.*/, '') + mail_from_line = data.sub(/ *AUTH=.*/, "") else mail_from_line = data end - @mail_from = mail_from_line.gsub(/MAIL FROM\s*:\s*/i, '').gsub(/.*.*/, '').strip - '250 OK' + @mail_from = mail_from_line.gsub(/MAIL FROM\s*:\s*/i, "").gsub(/.*.*/, "").strip + "250 OK" end def rcpt_to(data) unless in_state(:mail_from_received, :rcpt_to_received) - return '503 EHLO/HELO and MAIL FROM first please' + return "503 EHLO/HELO and MAIL FROM first please" end - rcpt_to = data.gsub(/RCPT TO\s*:\s*/i, '').gsub(/.*.*/, '').strip + rcpt_to = data.gsub(/RCPT TO\s*:\s*/i, "").gsub(/.*.*/, "").strip if rcpt_to.blank? - return '501 RCPT TO should not be empty' + return "501 RCPT TO should not be empty" end - uname, domain = rcpt_to.split('@', 2) + uname, domain = rcpt_to.split("@", 2) if domain.blank? - return '501 Invalid RCPT TO' + return "501 Invalid RCPT TO" end - uname, tag = uname.split('+', 2) + uname, tag = uname.split("+", 2) if domain == Postal.config.dns.return_path || domain =~ /\A#{Regexp.escape(Postal.config.dns.custom_return_path_prefix)}\./ # This is a return path @state = :rcpt_to_received - if server = ::Server.where(:token => uname).first + if server = ::Server.where(token: uname).first if server.suspended? - '535 Mail server has been suspended' + "535 Mail server has been suspended" else log "Added bounce on server #{server.id}" @recipients << [:bounce, rcpt_to, server] - '250 OK' + "250 OK" end else - '550 Invalid server token' + "550 Invalid server token" end elsif domain == Postal.config.dns.route_domain # This is an email direct to a route. This isn't actually supported yet. @state = :rcpt_to_received - if route = Route.where(:token => uname).first + if route = Route.where(token: uname).first if route.server.suspended? - '535 Mail server has been suspended' - elsif route.mode == 'Reject' - '550 Route does not accept incoming messages' + "535 Mail server has been suspended" + elsif route.mode == "Reject" + "550 Route does not accept incoming messages" else log "Added route #{route.id} to recipients (tag: #{tag.inspect})" actual_rcpt_to = "#{route.name}" + (tag ? "+#{tag}" : "") + "@#{route.domain.name}" - @recipients << [:route, actual_rcpt_to, route.server, :route => route] - '250 OK' + @recipients << [:route, actual_rcpt_to, route.server, { route: route }] + "250 OK" end else - '550 Invalid route token' + "550 Invalid route token" end elsif @credential # This is outgoing mail for an authenticated user @state = :rcpt_to_received if @credential.server.suspended? - '535 Mail server has been suspended' + "535 Mail server has been suspended" else log "Added external address '#{rcpt_to}'" @recipients << [:credential, rcpt_to, @credential.server] - '250 OK' + "250 OK" end elsif uname && domain && route = Route.find_by_name_and_domain(uname, domain) # This is incoming mail for a route @state = :rcpt_to_received if route.server.suspended? - '535 Mail server has been suspended' - elsif route.mode == 'Reject' - '550 Route does not accept incoming messages' + "535 Mail server has been suspended" + elsif route.mode == "Reject" + "550 Route does not accept incoming messages" else log "Added route #{route.id} to recipients (tag: #{tag.inspect})" - @recipients << [:route, rcpt_to, route.server, :route => route] - '250 OK' + @recipients << [:route, rcpt_to, route.server, { route: route }] + "250 OK" end else # 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 = Credential.where(type: "SMTP-IP").all.sort_by { |c| c.ipaddr&.prefix || 0 }.reverse.find do |credential| credential.ipaddr.include?(@ip_address) end @@ -358,33 +363,33 @@ module Postal @credential.use rcpt_to(data) else - '530 Authentication required' + "530 Authentication required" end end end def data(data) unless in_state(:rcpt_to_received) - return '503 HELO/EHLO, MAIL FROM and RCPT TO before sending data' + return "503 HELO/EHLO, MAIL FROM and RCPT TO before sending data" end @data = "".force_encoding("BINARY") @headers = {} @receiving_headers = true - received_header_content = "from #{@helo_name} (#{@hostname} [#{@ip_address}]) by #{Postal.config.dns.smtp_server_hostname} with SMTP; #{Time.now.utc.rfc2822.to_s}".force_encoding('BINARY') - if !Postal.config.smtp_server.strip_received_headers? + received_header_content = "from #{@helo_name} (#{@hostname} [#{@ip_address}]) by #{Postal.config.dns.smtp_server_hostname} with SMTP; #{Time.now.utc.rfc2822}".force_encoding("BINARY") + unless Postal.config.smtp_server.strip_received_headers? @data << "Received: #{received_header_content}\r\n" end - @headers['received'] = [received_header_content] + @headers["received"] = [received_header_content] - handler = Proc.new do |data| - if data == '.' + handler = proc do |data| + if data == "." @logging_enabled = true @proc = nil finished else - data = data.to_s.sub(/\A\.\./, '.') + data = data.to_s.sub(/\A\.\./, ".") if @credential && @credential.server.log_smtp_data? # We want to log if enabled @@ -405,7 +410,7 @@ module Postal # skip the append methods at the bottom of this loop. next if Postal.config.smtp_server.strip_received_headers? && @header_key && @header_key.downcase == "received" else - @header_key, value = data.split(/\:\s*/, 2) + @header_key, value = data.split(/:\s*/, 2) @headers[@header_key.downcase] ||= [] @headers[@header_key.downcase] << value # As above @@ -419,7 +424,7 @@ module Postal end @proc = handler - '354 Go ahead' + "354 Go ahead" end def finished @@ -429,10 +434,10 @@ module Postal return "552 Message too large (maximum size %dMB)" % Postal.config.smtp_server.max_message_size end - if @headers['received'].select { |r| r =~ /by #{Postal.config.dns.smtp_server_hostname}/ }.count > 4 + if @headers["received"].select { |r| r =~ /by #{Postal.config.dns.smtp_server_hostname}/ }.count > 4 transaction_reset @state = :welcomed - return '550 Loop detected' + return "550 Loop detected" end authenticated_domain = nil @@ -441,7 +446,7 @@ module Postal if authenticated_domain.nil? transaction_reset @state = :welcomed - return '530 From/Sender name is not valid' + return "530 From/Sender name is not valid" end end @@ -456,13 +461,13 @@ module Postal message.mail_from = @mail_from message.raw_message = @data message.received_with_ssl = @tls - message.scope = 'outgoing' + message.scope = "outgoing" message.domain_id = authenticated_domain&.id message.credential_id = @credential.id message.save when :bounce - if rp_route = server.routes.where(:name => "__returnpath__").first + if rp_route = server.routes.where(name: "__returnpath__").first # If there's a return path route, we can use this to create the message rp_route.create_messages do |message| message.rcpt_to = rcpt_to @@ -478,7 +483,7 @@ module Postal message.mail_from = @mail_from message.raw_message = @data message.received_with_ssl = @tls - message.scope = 'incoming' + message.scope = "incoming" message.bounce = 1 message.save end @@ -493,7 +498,7 @@ module Postal end transaction_reset @state = :welcomed - '250 OK' + "250 OK" end def in_state(*states) diff --git a/lib/postal/smtp_server/server.rb b/lib/postal/smtp_server/server.rb index 41c2096..94a830a 100644 --- a/lib/postal/smtp_server/server.rb +++ b/lib/postal/smtp_server/server.rb @@ -1,5 +1,5 @@ -require 'ipaddr' -require 'nio' +require "ipaddr" +require "nio" module Postal module SMTPServer @@ -18,11 +18,11 @@ module Postal trap("USR1") do STDOUT.puts "Received USR1 signal, respawning." fork do - if ENV['APP_ROOT'] - Dir.chdir(ENV['APP_ROOT']) + if ENV["APP_ROOT"] + Dir.chdir(ENV["APP_ROOT"]) end - ENV.delete('BUNDLE_GEMFILE') - exec("bundle exec --keep-file-descriptors rake postal:smtp_server", :close_others => false) + ENV.delete("BUNDLE_GEMFILE") + exec("bundle exec --keep-file-descriptors rake postal:smtp_server", close_others: false) end end @@ -30,7 +30,6 @@ module Postal STDOUT.puts "Received TERM signal, shutting down." unlisten end - end def ssl_context @@ -38,7 +37,7 @@ module Postal ssl_context = OpenSSL::SSL::SSLContext.new ssl_context.cert = Postal.smtp_certificates[0] ssl_context.extra_chain_cert = Postal.smtp_certificates[1..-1] - ssl_context.key = Postal.smtp_private_key + ssl_context.key = Postal.smtp_private_key ssl_context.ssl_version = Postal.config.smtp_server.ssl_version if Postal.config.smtp_server.ssl_version ssl_context.ciphers = Postal.config.smtp_server.tls_ciphers if Postal.config.smtp_server.tls_ciphers ssl_context @@ -46,8 +45,8 @@ module Postal end def listen - if ENV['SERVER_FD'] - @server = TCPServer.for_fd(ENV['SERVER_FD'].to_i) + if ENV["SERVER_FD"] + @server = TCPServer.for_fd(ENV["SERVER_FD"].to_i) else @server = TCPServer.open(Postal.config.smtp_server.bind_address, Postal.config.smtp_server.port) end @@ -61,7 +60,7 @@ module Postal @server.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, 10) @server.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, 5) end - ENV['SERVER_FD'] = @server.to_i.to_s + ENV["SERVER_FD"] = @server.to_i.to_s logger.info "Listening on #{Postal.config.smtp_server.bind_address}:#{Postal.config.smtp_server.port}" end @@ -72,7 +71,7 @@ module Postal end def kill_parent - Process.kill('TERM', Process.ppid) + Process.kill("TERM", Process.ppid) end def run_event_loop @@ -81,7 +80,7 @@ module Postal # Register the SMTP listener @io_selector.register(@server, :r) # Create a hash to contain a buffer for each client. - buffers = Hash.new { |h, k| h[k] = String.new.force_encoding('BINARY') } + buffers = Hash.new { |h, k| h[k] = String.new.force_encoding("BINARY") } loop do # Wait for an event to occur @io_selector.select do |monitor| @@ -112,17 +111,25 @@ module Postal # Register the client and its socket with nio4r monitor = @io_selector.register(new_io, :r) monitor.value = client - rescue => e + rescue StandardError => e # If something goes wrong, log as appropriate and disconnect the client if defined?(Raven) - Raven.capture_exception(e, :extra => {:log_id => (client.id rescue nil)}) + Raven.capture_exception(e, extra: { log_id: begin + client.id + rescue StandardError + nil + end }) end logger.error "An error occurred while accepting a new client." logger.error "#{e.class}: #{e.message}" e.backtrace.each do |line| logger.error line end - new_io.close rescue nil + begin + new_io.close + rescue StandardError + nil + end end else # This event is not an incoming connection so it must be data from a client @@ -135,7 +142,7 @@ module Postal if client.start_tls? begin # Can we accept the TLS connection at this time? - io.accept_nonblock() + io.accept_nonblock # We were able to accept the connection, the client is no longer handshaking client.start_tls = false rescue IO::WaitReadable, IO::WaitWritable => e @@ -153,12 +160,10 @@ module Postal # There is an extra step for SSL sockets case io when OpenSSL::SSL::SSLSocket - buffers[io] << io.readpartial(10240) - while(io.pending > 0) - buffers[io] << io.readpartial(10240) - end + buffers[io] << io.readpartial(10_240) + buffers[io] << io.readpartial(10_240) while io.pending > 0 else - buffers[io] << io.readpartial(10240) + buffers[io] << io.readpartial(10_240) end rescue EOFError, Errno::ECONNRESET, Errno::ETIMEDOUT # Client went away @@ -167,7 +172,7 @@ module Postal # Normalize all \r\n and \n to \r\n, but ignore only \r. # A \r\n may be split in 2 buffers (\n in one buffer and \r in the other) - buffers[io] = buffers[io].gsub(/\r/,"").encode(buffers[io].encoding, crlf_newline: true) + buffers[io] = buffers[io].gsub(/\r/, "").encode(buffers[io].encoding, crlf_newline: true) # We line buffer, so look to see if we have received a newline # and keep doing so until all buffered lines have been processed. @@ -178,17 +183,17 @@ module Postal # Send the received line to the client object for processing result = client.handle(line) # If the client object returned some data, write it back to the client - unless result.nil? - result = [result] unless result.is_a?(Array) - result.compact.each do |line| - client.log "\e[34m=> #{line.strip}\e[0m" - begin - io.write(line.to_s + "\r\n") - io.flush - rescue Errno::ECONNRESET - # Client disconnected before we could write response - eof = true - end + next if result.nil? + + result = [result] unless result.is_a?(Array) + result.compact.each do |line| + client.log "\e[34m=> #{line.strip}\e[0m" + begin + io.write(line.to_s + "\r\n") + io.flush + rescue Errno::ECONNRESET + # Client disconnected before we could write response + eof = true end end end @@ -219,11 +224,15 @@ module Postal Process.exit(0) end end - rescue => e + rescue StandardError => e # Something went wrong, log as appropriate - client_id = client ? client.id : '------' + client_id = client ? client.id : "------" if defined?(Raven) - Raven.capture_exception(e, :extra => {:log_id => (client.id rescue nil)}) + Raven.capture_exception(e, extra: { log_id: begin + client.id + rescue StandardError + nil + end }) end logger.error "[#{client_id}] An error occurred while processing data from a client." logger.error "[#{client_id}] #{e.class}: #{e.message}" @@ -231,9 +240,17 @@ module Postal logger.error "[#{client_id}] #{line}" end # Close all IO and forget this client - @io_selector.deregister(io) rescue nil + begin + @io_selector.deregister(io) + rescue StandardError + nil + end buffers.delete(io) - io.close rescue nil + begin + io.close + rescue StandardError + nil + end if @io_selector.empty? Process.exit(0) end @@ -241,27 +258,27 @@ module Postal end end # If unlisten has been called, stop listening - if $unlisten - @io_selector.deregister(@server) - @server.close - # If there's nothing left to do, shut down the process - if @io_selector.empty? - Process.exit(0) - end - # Clear the request - $unlisten = false + next unless $unlisten + + @io_selector.deregister(@server) + @server.close + # If there's nothing left to do, shut down the process + if @io_selector.empty? + Process.exit(0) end + # Clear the request + $unlisten = false end end def run # Write PID to file if path specified - if ENV['PID_FILE'] - File.open(ENV['PID_FILE'], 'w') { |f| f.write(Process.pid.to_s + "\n") } + if ENV["PID_FILE"] + File.write(ENV["PID_FILE"], Process.pid.to_s + "\n") end # If we have been spawned to replace an existing processm shut down the # parent after listening. - if ENV['SERVER_FD'] + if ENV["SERVER_FD"] listen kill_parent else diff --git a/lib/postal/spam_check.rb b/lib/postal/spam_check.rb index 799531b..b0ab968 100644 --- a/lib/postal/spam_check.rb +++ b/lib/postal/spam_check.rb @@ -11,9 +11,9 @@ module Postal def to_hash { - :code => code, - :score => score, - :description => description + code: code, + score: score, + description: description } end diff --git a/lib/postal/tracking_middleware.rb b/lib/postal/tracking_middleware.rb index 90c3cac..b02bc6c 100644 --- a/lib/postal/tracking_middleware.rb +++ b/lib/postal/tracking_middleware.rb @@ -1,27 +1,27 @@ module Postal class TrackingMiddleware - TRACKING_PIXEL = File.read(Rails.root.join('app', 'assets', 'images', 'tracking_pixel.png')) + TRACKING_PIXEL = File.read(Rails.root.join("app", "assets", "images", "tracking_pixel.png")) def initialize(app = nil) @app = app end def call(env) - unless env['HTTP_X_POSTAL_TRACK_HOST'].to_i == 1 + unless env["HTTP_X_POSTAL_TRACK_HOST"].to_i == 1 return @app.call(env) end request = Rack::Request.new(env) case request.path - when /\A\/img\/([a-z0-9\-]+)\/([a-z0-9\-]+)/i - server_token = $1 - message_token = $2 + when /\A\/img\/([a-z0-9-]+)\/([a-z0-9-]+)/i + server_token = ::Regexp.last_match(1) + message_token = ::Regexp.last_match(2) dispatch_image_request(request, server_token, message_token) - when /\A\/([a-z0-9\-]+)\/([a-z0-9\-]+)/i - server_token = $1 - link_token = $2 + when /\A\/([a-z0-9-]+)\/([a-z0-9-]+)/i + server_token = ::Regexp.last_match(1) + link_token = ::Regexp.last_match(2) dispatch_redirect_request(request, server_token, link_token) else [200, {}, ["Hello."]] @@ -33,72 +33,69 @@ module Postal def dispatch_image_request(request, server_token, message_token) message_db = get_message_db_from_server_token(server_token) if message_db.nil? - return [404, {}, ['Invalid Server Token']] + return [404, {}, ["Invalid Server Token"]] end begin - message = message_db.message(:token => message_token) + message = message_db.message(token: message_token) message.create_load(request) rescue Postal::MessageDB::Message::NotFound # This message has been removed, we'll just continue to serve the image - rescue => e + rescue StandardError => e # Somethign else went wrong. We don't want to stop the image loading though because # this is our problem. Log this exception though. Raven.capture_exception(e) if defined?(Raven) end - source_image = request.params['src'] + source_image = request.params["src"] case source_image when nil headers = {} - headers['Content-Type'] = "image/png" - headers['Content-Length'] = TRACKING_PIXEL.bytesize.to_s - return [200, headers, [TRACKING_PIXEL]] - when /\Ahttps?\:\/\// - response = Postal::HTTP.get(source_image, :timeout => 3) - if response[:code] == 200 - headers = {} - headers['Content-Type'] = response[:headers]['content-type']&.first - headers['Last-Modified'] = response[:headers]['last-modified']&.first - headers['Cache-Control'] = response[:headers]['cache-control']&.first - headers['Etag'] = response[:headers]['etag']&.first - headers['Content-Length'] = response[:body].bytesize.to_s - return [200, headers, [response[:body]]] - else - return [404, {}, ['Not found']] - end + headers["Content-Type"] = "image/png" + headers["Content-Length"] = TRACKING_PIXEL.bytesize.to_s + [200, headers, [TRACKING_PIXEL]] + when /\Ahttps?:\/\// + response = Postal::HTTP.get(source_image, timeout: 3) + return [404, {}, ["Not found"]] unless response[:code] == 200 + + headers = {} + headers["Content-Type"] = response[:headers]["content-type"]&.first + headers["Last-Modified"] = response[:headers]["last-modified"]&.first + headers["Cache-Control"] = response[:headers]["cache-control"]&.first + headers["Etag"] = response[:headers]["etag"]&.first + headers["Content-Length"] = response[:body].bytesize.to_s + [200, headers, [response[:body]]] + else - return [400, {}, ['Invalid/missing source image']] + [400, {}, ["Invalid/missing source image"]] end end def dispatch_redirect_request(request, server_token, link_token) message_db = get_message_db_from_server_token(server_token) if message_db.nil? - return [404, {}, ['Invalid Server Token']] + return [404, {}, ["Invalid Server Token"]] end - link = message_db.select(:links, :where => {:token => link_token}, :limit => 1).first + link = message_db.select(:links, where: { token: link_token }, limit: 1).first if link.nil? - return [404, {}, ['Link not found']] + return [404, {}, ["Link not found"]] end time = Time.now.to_f - if link['message_id'] - message_db.update(:messages, {:clicked => time}, :where => {:id => link['message_id']}) - message_db.insert(:clicks, {:message_id => link['message_id'], :link_id => link['id'], :ip_address => request.ip, :user_agent => request.user_agent, :timestamp => time}) - SendWebhookJob.queue(:main, :server_id => message_db.server_id, :event => 'MessageLinkClicked', :payload => {:_message => link['message_id'], :url => link['url'], :token => link['token'], :ip_address => request.ip, :user_agent => request.user_agent}) + if link["message_id"] + message_db.update(:messages, { clicked: time }, where: { id: link["message_id"] }) + message_db.insert(:clicks, { message_id: link["message_id"], link_id: link["id"], ip_address: request.ip, user_agent: request.user_agent, timestamp: time }) + SendWebhookJob.queue(:main, server_id: message_db.server_id, event: "MessageLinkClicked", payload: { _message: link["message_id"], url: link["url"], token: link["token"], ip_address: request.ip, user_agent: request.user_agent }) end - return [307, {'Location' => link['url']}, ["Redirected to: #{link['url']}"]] + [307, { "Location" => link["url"] }, ["Redirected to: #{link['url']}"]] end def get_message_db_from_server_token(token) - if server = ::Server.find_by_token(token) - server.message_db - else - nil - end + return unless server = ::Server.find_by_token(token) + + server.message_db end end diff --git a/lib/postal/user_creator.rb b/lib/postal/user_creator.rb index dba4d1f..a8b32d0 100644 --- a/lib/postal/user_creator.rb +++ b/lib/postal/user_creator.rb @@ -1,4 +1,4 @@ -require 'highline' +require "highline" module Postal module UserCreator @@ -10,10 +10,10 @@ module Postal puts "This tool is usually only used to create your initial admin user." puts user = User.new - user.email_address = cli.ask("E-Mail Address".ljust(20, ' ') + ": ") - user.first_name = cli.ask("First Name".ljust(20, ' ') + ": ") - user.last_name = cli.ask("Last Name".ljust(20, ' ') + ": ") - user.password = cli.ask("Initial Password".ljust(20, ' ') + ": ") { |value| value.echo = '*' } + user.email_address = cli.ask("E-Mail Address".ljust(20, " ") + ": ") + user.first_name = cli.ask("First Name".ljust(20, " ") + ": ") + user.last_name = cli.ask("Last Name".ljust(20, " ") + ": ") + user.password = cli.ask("Initial Password".ljust(20, " ") + ": ") { |value| value.echo = "*" } block.call(user) if block_given? if user.save @@ -28,7 +28,7 @@ module Postal end puts end - end + end end diff --git a/lib/postal/version.rb b/lib/postal/version.rb index 6fc24f9..d701fe5 100644 --- a/lib/postal/version.rb +++ b/lib/postal/version.rb @@ -1,12 +1,14 @@ module Postal - VERSION_PATH = File.expand_path('../../VERSION', __dir__) - VERSION = if File.file?(VERSION_PATH) - File.read(VERSION_PATH).strip.delete_prefix('v') - else - '0.0.0-dev' - end + + VERSION_PATH = File.expand_path("../../VERSION", __dir__) + if File.file?(VERSION_PATH) + VERSION = File.read(VERSION_PATH).strip.delete_prefix("v") + else + VERSION = "0.0.0-dev" + end def self.version VERSION end + end diff --git a/lib/postal/worker.rb b/lib/postal/worker.rb index 5340711..96b86fd 100644 --- a/lib/postal/worker.rb +++ b/lib/postal/worker.rb @@ -11,11 +11,17 @@ module Postal def work logger.info "Worker running with #{Postal.config.workers.threads} threads" - Signal.trap("INT") { @exit = true; set_process_name } - Signal.trap("TERM") { @exit = true; set_process_name } + Signal.trap("INT") do + @exit = true + set_process_name + end + Signal.trap("TERM") do + @exit = true + set_process_name + end self.class.job_channel.prefetch(Postal.config.workers.threads) - @initial_queues.each { |queue | join_queue(queue) } + @initial_queues.each { |queue| join_queue(queue) } exit_checks = 0 loop do @@ -42,49 +48,51 @@ module Postal private def receive_job(delivery_info, properties, body) - begin - message = JSON.parse(body) rescue nil - if message && message['class_name'] - @running_jobs << message['id'] - set_process_name - start_time = Time.now - Thread.current[:job_id] = message['id'] - logger.info "[#{message['id']}] Started processing \e[34m#{message['class_name']}\e[0m job" - begin - klass = message['class_name'].constantize.new(message['id'], message['params']) - klass.perform - GC.start - rescue => e - klass.on_error(e) if defined?(klass) - logger.warn "[#{message['id']}] \e[31m#{e.class}: #{e.message}\e[0m" - e.backtrace.each do |line| - logger.warn "[#{message['id']}] " + line - end - if defined?(Raven) - Raven.capture_exception(e, :extra => {:job_id => message['id']}) - end - ensure - logger.info "[#{message['id']}] Finished processing \e[34m#{message['class_name']}\e[0m job in #{Time.now - start_time}s" - end - end - ensure - Thread.current[:job_id] = nil - self.class.job_channel.ack(delivery_info.delivery_tag) - @running_jobs.delete(message['id']) if message['id'] + message = begin + JSON.parse(body) + rescue StandardError + nil + end + if message && message["class_name"] + @running_jobs << message["id"] set_process_name - - if @exit && @running_jobs.empty? - logger.info "Exiting because all jobs have finished." - exit 0 + start_time = Time.now + Thread.current[:job_id] = message["id"] + logger.info "[#{message['id']}] Started processing \e[34m#{message['class_name']}\e[0m job" + begin + klass = message["class_name"].constantize.new(message["id"], message["params"]) + klass.perform + GC.start + rescue StandardError => e + klass.on_error(e) if defined?(klass) + logger.warn "[#{message['id']}] \e[31m#{e.class}: #{e.message}\e[0m" + e.backtrace.each do |line| + logger.warn "[#{message['id']}] " + line + end + if defined?(Raven) + Raven.capture_exception(e, extra: { job_id: message["id"] }) + end + ensure + logger.info "[#{message['id']}] Finished processing \e[34m#{message['class_name']}\e[0m job in #{Time.now - start_time}s" end end + ensure + Thread.current[:job_id] = nil + self.class.job_channel.ack(delivery_info.delivery_tag) + @running_jobs.delete(message["id"]) if message["id"] + set_process_name + + if @exit && @running_jobs.empty? + logger.info "Exiting because all jobs have finished." + exit 0 + end end def join_queue(queue) if @active_queues[queue] logger.info "Attempted to join queue #{queue} but already joined." else - consumer = self.class.job_queue(queue).subscribe(:manual_ack => true) do |delivery_info, properties, body| + consumer = self.class.job_queue(queue).subscribe(manual_ack: true) do |delivery_info, properties, body| receive_job(delivery_info, properties, body) end @active_queues[queue] = consumer @@ -129,24 +137,22 @@ module Postal need = id elsif @unassigned_ips.include?(ip) # We know this IP isn't valid. We don't need to do anything - else + elsif !self.class.local_ip?(ip) && ip_address = IPAddress.where("ipv4 = ? OR ipv6 = ?", ip, ip).first # We need to look this up - if !self.class.local_ip?(ip) && ip_address = IPAddress.where("ipv4 = ? OR ipv6 = ?", ip, ip).first - @pairs[ip_address.ipv4] = ip_address.ipv6 - @ip_to_id_mapping[ip] = ip_address.id - need = ip_address.id - else - @unassigned_ips << ip - end + @pairs[ip_address.ipv4] = ip_address.ipv6 + @ip_to_id_mapping[ip] = ip_address.id + need = ip_address.id + else + @unassigned_ips << ip end - if need - pair = @pairs[ip] || @pairs.key(ip) - if pair.nil? || current_ip_addresses.include?(pair) - needed_ip_ids << @ip_to_id_mapping[ip] - else - logger.info "Host has '#{ip}' but its pair (#{pair}) isn't here. Cannot add now." - end + next unless need + + pair = @pairs[ip] || @pairs.key(ip) + if pair.nil? || current_ip_addresses.include?(pair) + needed_ip_ids << @ip_to_id_mapping[ip] + else + logger.info "Host has '#{ip}' but its pair (#{pair}) isn't here. Cannot add now." end end @@ -197,9 +203,7 @@ module Postal def self.job_queue(name) @job_queues ||= {} - @job_queues[name] ||= begin - job_channel.queue("deliver-jobs-#{name}", :durable => true, :arguments => {'x-message-ttl' => 60000}) - end + @job_queues[name] ||= job_channel.queue("deliver-jobs-#{name}", durable: true, arguments: { "x-message-ttl" => 60_000 }) end def self.local_ip?(ip) diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake index 0df6b27..1920150 100644 --- a/lib/tasks/auto_annotate_models.rake +++ b/lib/tasks/auto_annotate_models.rake @@ -6,41 +6,41 @@ if Rails.env.development? # You can override any of these by setting an environment variable of the # same name. Annotate.set_defaults( - 'routes' => 'false', - 'position_in_routes' => 'before', - 'position_in_class' => 'before', - 'position_in_test' => 'before', - 'position_in_fixture' => 'before', - 'position_in_factory' => 'before', - 'position_in_serializer' => 'before', - 'show_foreign_keys' => 'true', - 'show_indexes' => 'true', - 'simple_indexes' => 'false', - 'model_dir' => 'app/models', - 'root_dir' => '', - 'include_version' => 'false', - 'require' => '', - 'exclude_tests' => 'false', - 'exclude_fixtures' => 'false', - 'exclude_factories' => 'false', - 'exclude_serializers' => 'false', - 'exclude_scaffolds' => 'true', - 'exclude_controllers' => 'true', - 'exclude_helpers' => 'true', - 'ignore_model_sub_dir' => 'false', - 'ignore_columns' => nil, - 'ignore_routes' => nil, - 'ignore_unknown_models' => 'false', - 'hide_limit_column_types' => 'integer,boolean', - 'skip_on_db_migrate' => 'false', - 'format_bare' => 'true', - 'format_rdoc' => 'false', - 'format_markdown' => 'false', - 'sort' => 'false', - 'force' => 'false', - 'trace' => 'false', - 'wrapper_open' => nil, - 'wrapper_close' => nil + "routes" => "false", + "position_in_routes" => "before", + "position_in_class" => "before", + "position_in_test" => "before", + "position_in_fixture" => "before", + "position_in_factory" => "before", + "position_in_serializer" => "before", + "show_foreign_keys" => "true", + "show_indexes" => "true", + "simple_indexes" => "false", + "model_dir" => "app/models", + "root_dir" => "", + "include_version" => "false", + "require" => "", + "exclude_tests" => "false", + "exclude_fixtures" => "false", + "exclude_factories" => "false", + "exclude_serializers" => "false", + "exclude_scaffolds" => "true", + "exclude_controllers" => "true", + "exclude_helpers" => "true", + "ignore_model_sub_dir" => "false", + "ignore_columns" => nil, + "ignore_routes" => nil, + "ignore_unknown_models" => "false", + "hide_limit_column_types" => "integer,boolean", + "skip_on_db_migrate" => "false", + "format_bare" => "true", + "format_rdoc" => "false", + "format_markdown" => "false", + "sort" => "false", + "force" => "false", + "trace" => "false", + "wrapper_open" => nil, + "wrapper_close" => nil ) end diff --git a/lib/tasks/postal.rake b/lib/tasks/postal.rake index f7e5373..480f9f4 100644 --- a/lib/tasks/postal.rake +++ b/lib/tasks/postal.rake @@ -1,25 +1,27 @@ namespace :postal do - desc "Start the cron worker" - task :cron => :environment do - require 'clockwork' - require Rails.root.join('config', 'cron') - trap('TERM') { puts "Exiting..."; Process.exit(0) } + task cron: :environment do + require "clockwork" + require Rails.root.join("config", "cron") + trap("TERM") do + puts "Exiting..." + Process.exit(0) + end Clockwork.run end - desc 'Start SMTP Server' - task :smtp_server => :environment do - Postal::SMTPServer::Server.new(:debug => true).run + desc "Start SMTP Server" + task smtp_server: :environment do + Postal::SMTPServer::Server.new(debug: true).run end - desc 'Start the message requeuer' - task :requeuer => :environment do + desc "Start the message requeuer" + task requeuer: :environment do Postal::MessageRequeuer.new.run end - desc 'Run all migrations on message databases' - task :migrate_message_databases => :environment do + desc "Run all migrations on message databases" + task migrate_message_databases: :environment do Server.all.each do |server| puts "\e[35m-------------------------------------------------------------------\e[0m" puts "\e[35m#{server.id}: #{server.name} (#{server.permalink})\e[0m" @@ -27,9 +29,8 @@ namespace :postal do server.message_db.provisioner.migrate end end - end -Rake::Task['db:migrate'].enhance do - Rake::Task['postal:migrate_message_databases'].invoke +Rake::Task["db:migrate"].enhance do + Rake::Task["postal:migrate_message_databases"].invoke end diff --git a/script/default_dkim_record.rb b/script/default_dkim_record.rb index 49ec8d0..50c8a30 100644 --- a/script/default_dkim_record.rb +++ b/script/default_dkim_record.rb @@ -1,2 +1,2 @@ -require File.expand_path('../../lib/postal/config', __FILE__) +require File.expand_path("../lib/postal/config", __dir__) puts Postal.rp_dkim_dns_record diff --git a/script/generate_initial_config.rb b/script/generate_initial_config.rb index 64c3c7d..131b4cb 100755 --- a/script/generate_initial_config.rb +++ b/script/generate_initial_config.rb @@ -1,23 +1,22 @@ #!/usr/bin/env ruby -require File.expand_path('../../lib/postal/config', __FILE__) -require 'openssl' -require 'securerandom' -require 'fileutils' +require File.expand_path("../lib/postal/config", __dir__) +require "openssl" +require "securerandom" +require "fileutils" unless File.directory?(Postal.config_root) FileUtils.mkdir_p(Postal.config_root) end unless File.exist?(Postal.config_file_path) - content = File.read(Postal.app_root.join('config', 'postal.example.yml')) - content.gsub!('{{secretkey}}', SecureRandom.hex(128)) - File.open(Postal.config_file_path, 'w') { |f| f.write(content) } + content = File.read(Postal.app_root.join("config", "postal.example.yml")) + content.gsub!("{{secretkey}}", SecureRandom.hex(128)) + File.write(Postal.config_file_path, content) puts "Created example config file at #{Postal.config_file_path}" end -unless File.exists?(Postal.signing_key_path) +unless File.exist?(Postal.signing_key_path) key = OpenSSL::PKey::RSA.new(1024).to_s - File.open(Postal.signing_key_path, 'w') { |f| f.write(key) } + File.write(Postal.signing_key_path, key) puts "Created new signing key for DKIM & HTTP requests" end - diff --git a/script/insert-bounce.rb b/script/insert-bounce.rb index 35d8fc9..87cca6b 100755 --- a/script/insert-bounce.rb +++ b/script/insert-bounce.rb @@ -10,23 +10,23 @@ if ARGV[0].nil? || ARGV[1].nil? exit 1 end -require_relative '../config/environment' +require_relative "../config/environment" server = Server.find(ARGV[0]) puts "Got server #{server.name}" -template = File.read(Rails.root.join('resource/postfix-bounce.msg')) +template = File.read(Rails.root.join("resource/postfix-bounce.msg")) if ARGV[1].to_s =~ /\A(\d+)\z/ message = server.message_db.message(ARGV[1].to_i) puts "Got message #{message.id} with token #{message.token}" - template.gsub!('{{MSGID}}', message.token) + template.gsub!("{{MSGID}}", message.token) else - template.gsub!('{{MSGID}}', ARGV[1].to_s) + template.gsub!("{{MSGID}}", ARGV[1].to_s) end message = server.message_db.new_message -message.scope = 'incoming' +message.scope = "incoming" message.rcpt_to = "#{server.token}@#{Postal.config.dns.return_path}" message.mail_from = "MAILER-DAEMON@smtp.infra.atech.io" message.raw_message = template diff --git a/script/make_user.rb b/script/make_user.rb index 280a7b5..79db624 100755 --- a/script/make_user.rb +++ b/script/make_user.rb @@ -1,8 +1,11 @@ #!/usr/bin/env ruby -trap("INT") { puts ; exit } +trap("INT") do + puts + exit +end -require_relative '../config/environment' -require 'postal/user_creator' +require_relative "../config/environment" +require "postal/user_creator" Postal::UserCreator.start do |u| u.admin = true diff --git a/script/queue_size.rb b/script/queue_size.rb old mode 100644 new mode 100755 index 6ae20ce..d14a966 --- a/script/queue_size.rb +++ b/script/queue_size.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby -require_relative '../lib/postal/config' -require 'mysql2' +require_relative "../lib/postal/config" +require "mysql2" -client = Mysql2::Client.new(:host => Postal.config.main_db.host, :username => Postal.config.main_db.username, :password => Postal.config.main_db.password, :port => Postal.config.main_db.port, :database => Postal.config.main_db.database) +client = Mysql2::Client.new(host: Postal.config.main_db.host, username: Postal.config.main_db.username, password: Postal.config.main_db.password, port: Postal.config.main_db.port, database: Postal.config.main_db.database) result = client.query("SELECT COUNT(id) as size FROM `queued_messages` WHERE retry_after IS NULL OR retry_after <= ADDTIME(UTC_TIMESTAMP(), '30') AND locked_at IS NULL") -puts result.to_a.first['size'] +puts result.to_a.first["size"] diff --git a/script/test_app_smtp.rb b/script/test_app_smtp.rb index 1b3cc2a..a7b009d 100755 --- a/script/test_app_smtp.rb +++ b/script/test_app_smtp.rb @@ -1,13 +1,15 @@ #!/usr/bin/env ruby -trap("INT") { puts ; exit } - +trap("INT") do + puts + exit +end if ARGV[0].nil? || !(ARGV[0] =~ /@/) puts "usage: postal test-app-smtp [email address]" exit 1 end -require_relative '../config/environment' +require_relative "../config/environment" begin Timeout.timeout(10) do @@ -15,7 +17,7 @@ begin end puts "\e[32mMessage has been sent successfully.\e[0m" -rescue => e +rescue StandardError => e puts "\e[31mMessage was not delivered successfully to SMTP server.\e[0m" puts "Error: #{e.class} (#{e.message})" puts diff --git a/script/version.rb b/script/version.rb old mode 100644 new mode 100755 index 42c8317..0bd3a3c --- a/script/version.rb +++ b/script/version.rb @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.expand_path('../../lib/postal/version', __FILE__) +require File.expand_path("../lib/postal/version", __dir__) puts Postal.version diff --git a/script/worker.rb b/script/worker.rb old mode 100644 new mode 100755 index 85ba7e1..3642577 --- a/script/worker.rb +++ b/script/worker.rb @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require_relative '../config/environment' +require_relative "../config/environment" Postal::Worker.new([:main]).work diff --git a/spec/app/models/organization_spec.rb b/spec/app/models/organization_spec.rb index 7e9f814..bde3f5e 100644 --- a/spec/app/models/organization_spec.rb +++ b/spec/app/models/organization_spec.rb @@ -1,7 +1,6 @@ -require 'rails_helper' +require "rails_helper" describe Organization do - context "model" do subject(:organization) { create(:organization) } @@ -10,5 +9,4 @@ describe Organization do expect(organization.uuid.length).to eq 36 end end - end diff --git a/spec/app/models/outgoing_message_prototype_spec.rb b/spec/app/models/outgoing_message_prototype_spec.rb index a86fc2b..ff71716 100644 --- a/spec/app/models/outgoing_message_prototype_spec.rb +++ b/spec/app/models/outgoing_message_prototype_spec.rb @@ -1,23 +1,21 @@ -require 'rails_helper' +require "rails_helper" describe OutgoingMessagePrototype do - it "should create a new message" do with_global_server do |server| - domain = create(:domain, :owner => server) - prototype = OutgoingMessagePrototype.new(server, '127.0.0.1', 'TestSuite', { - :from => "test@#{domain.name}", - :to => "test@example.com", - :subject => "Test Message", - :plain_body => "A plain body!" + domain = create(:domain, owner: server) + prototype = OutgoingMessagePrototype.new(server, "127.0.0.1", "TestSuite", { + from: "test@#{domain.name}", + to: "test@example.com", + subject: "Test Message", + plain_body: "A plain body!" }) expect(prototype.valid?).to be true - message = prototype.create_message('test@example.com') + message = prototype.create_message("test@example.com") expect(message).to be_a Hash expect(message[:id]).to be_a Integer expect(message[:token]).to be_a String end end - end diff --git a/spec/app/models/server_spec.rb b/spec/app/models/server_spec.rb index 91a8b99..049dfff 100644 --- a/spec/app/models/server_spec.rb +++ b/spec/app/models/server_spec.rb @@ -1,7 +1,6 @@ -require 'rails_helper' +require "rails_helper" describe Server do - context "model" do subject(:server) { create(:server) } @@ -10,5 +9,4 @@ describe Server do expect(server.uuid.length).to eq 36 end end - end diff --git a/spec/app/models/user_spec.rb b/spec/app/models/user_spec.rb index 5b365ef..f955561 100644 --- a/spec/app/models/user_spec.rb +++ b/spec/app/models/user_spec.rb @@ -1,7 +1,6 @@ -require 'rails_helper' +require "rails_helper" describe User do - context "model" do subject(:user) { create(:user) } @@ -13,24 +12,23 @@ describe User do context ".authenticate" do it "should not authenticate users with invalid emails" do - expect { User.authenticate('nothing@nothing.com', 'hello') }.to raise_error(Postal::Errors::AuthenticationError) do |e| - expect(e.error).to eq 'InvalidEmailAddress' + expect { User.authenticate("nothing@nothing.com", "hello") }.to raise_error(Postal::Errors::AuthenticationError) do |e| + expect(e.error).to eq "InvalidEmailAddress" end end it "should not authenticate users with invalid passwords" do user = create(:user) - expect { User.authenticate(user.email_address, 'hello') }.to raise_error(Postal::Errors::AuthenticationError) do |e| - expect(e.error).to eq 'InvalidPassword' + expect { User.authenticate(user.email_address, "hello") }.to raise_error(Postal::Errors::AuthenticationError) do |e| + expect(e.error).to eq "InvalidPassword" end end it "should authenticate valid users" do user = create(:user) auth_user = nil - expect { auth_user = User.authenticate(user.email_address, 'passw0rd') }.to_not raise_error + expect { auth_user = User.authenticate(user.email_address, "passw0rd") }.to_not raise_error expect(auth_user).to eq user end end - end diff --git a/spec/factories/domain_factory.rb b/spec/factories/domain_factory.rb index aa51b96..0a61d55 100644 --- a/spec/factories/domain_factory.rb +++ b/spec/factories/domain_factory.rb @@ -35,16 +35,14 @@ # FactoryBot.define do - factory :domain do - association :owner, :factory => :user + association :owner, factory: :user sequence(:name) { |n| "example#{n}.com" } - verification_method { 'DNS' } + verification_method { "DNS" } verified_at { Time.now } end - factory :organization_domain, :parent => :domain do - association :owner, :factory => :organization + factory :organization_domain, parent: :domain do + association :owner, factory: :organization end - end diff --git a/spec/factories/organization_factory.rb b/spec/factories/organization_factory.rb index 86d9172..843c0f4 100644 --- a/spec/factories/organization_factory.rb +++ b/spec/factories/organization_factory.rb @@ -22,11 +22,9 @@ # FactoryBot.define do - factory :organization do name { "Acme Inc" } sequence(:permalink) { |n| "org#{n}" } - association :owner, :factory => :user + association :owner, factory: :user end - end diff --git a/spec/factories/server_factory.rb b/spec/factories/server_factory.rb index acff5b6..3397cd2 100644 --- a/spec/factories/server_factory.rb +++ b/spec/factories/server_factory.rb @@ -40,7 +40,6 @@ # FactoryBot.define do - factory :server do association :organization name { "Mail Server" } @@ -48,5 +47,4 @@ FactoryBot.define do provision_database { false } sequence(:permalink) { |n| "server#{n}" } end - end diff --git a/spec/factories/track_domain_factory.rb b/spec/factories/track_domain_factory.rb index 125c7ee..64a7d62 100644 --- a/spec/factories/track_domain_factory.rb +++ b/spec/factories/track_domain_factory.rb @@ -19,15 +19,13 @@ # FactoryBot.define do - factory :track_domain do name { "click" } - dns_status { 'OK' } + dns_status { "OK" } association :server after(:build) do |track_domain| - track_domain.domain ||= create(:domain, :owner => track_domain.server) + track_domain.domain ||= create(:domain, owner: track_domain.server) end end - end diff --git a/spec/factories/user_factory.rb b/spec/factories/user_factory.rb index 42d7b2a..a4a0f02 100644 --- a/spec/factories/user_factory.rb +++ b/spec/factories/user_factory.rb @@ -24,7 +24,6 @@ # FactoryBot.define do - factory :user do first_name { "John" } last_name { "Doe" } @@ -32,5 +31,4 @@ FactoryBot.define do email_verified_at { Time.now } sequence(:email_address) { |n| "user#{n}@example.com" } end - end diff --git a/spec/lib/postal/dkim_header_spec.rb b/spec/lib/postal/dkim_header_spec.rb index f5ae1c9..b2e5390 100644 --- a/spec/lib/postal/dkim_header_spec.rb +++ b/spec/lib/postal/dkim_header_spec.rb @@ -1,29 +1,28 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" describe Postal::DKIMHeader do - - examples = Rails.root.join('spec/examples/dkim_signing/*.msg') + examples = Rails.root.join("spec/examples/dkim_signing/*.msg") Dir[examples].each do |path| contents = File.read(path) frontmatter, email = contents.split(/^---\n/m, 2) frontmatter = YAML.load(frontmatter) email.strip it "works with #{path.split('/').last}" do - mocked_time = Time.at(frontmatter['time'].to_i) + mocked_time = Time.at(frontmatter["time"].to_i) allow(Time).to receive(:now).and_return(mocked_time) - domain = instance_double('Domain') - allow(domain).to receive(:dkim_status).and_return('OK') - allow(domain).to receive(:name).and_return(frontmatter['domain']) - allow(domain).to receive(:dkim_key).and_return(OpenSSL::PKey::RSA.new(frontmatter['private_key'])) - allow(domain).to receive(:dkim_identifier).and_return(frontmatter['dkim_identifier']) + domain = instance_double("Domain") + allow(domain).to receive(:dkim_status).and_return("OK") + allow(domain).to receive(:name).and_return(frontmatter["domain"]) + allow(domain).to receive(:dkim_key).and_return(OpenSSL::PKey::RSA.new(frontmatter["private_key"])) + allow(domain).to receive(:dkim_identifier).and_return(frontmatter["dkim_identifier"]) - expectation = "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" \ + expectation = "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" \ "\td=#{frontmatter['domain']};\r\n" \ "\ts=#{frontmatter['dkim_identifier']}; t=#{mocked_time.to_i};\r\n" \ - "\tbh=#{frontmatter['bh']};\r\n"\ + "\tbh=#{frontmatter['bh']};\r\n" \ "\th=#{frontmatter['headers']};\r\n" \ "\tb=#{frontmatter['b'].scan(/.{1,72}/).join("\r\n\t")}" @@ -32,5 +31,4 @@ describe Postal::DKIMHeader do expect(header.dkim_header).to eq expectation end end - end diff --git a/spec/lib/postal/message_db/database.rb b/spec/lib/postal/message_db/database.rb index a1aa80c..00995e1 100644 --- a/spec/lib/postal/message_db/database.rb +++ b/spec/lib/postal/message_db/database.rb @@ -1,7 +1,6 @@ -require 'rails_helper' +require "rails_helper" describe Postal::MessageDB::Database do - context "when provisioned" do subject(:database) { GLOBAL_SERVER.message_db } @@ -13,5 +12,4 @@ describe Postal::MessageDB::Database do expect(database.schema_version).to be_a Integer end end - end diff --git a/spec/lib/postal/message_parser_spec.rb b/spec/lib/postal/message_parser_spec.rb index 5e17cd9..c47a229 100644 --- a/spec/lib/postal/message_parser_spec.rb +++ b/spec/lib/postal/message_parser_spec.rb @@ -1,11 +1,10 @@ -require 'rails_helper' +require "rails_helper" describe Postal::MessageParser do - it "should not do anything when there are no tracking domains" do with_global_server do |server| expect(server.track_domains.size).to eq 0 - message = create_plain_text_message(server, 'Hello world!', 'test@example.com') + message = create_plain_text_message(server, "Hello world!", "test@example.com") parser = Postal::MessageParser.new(message) expect(parser.actioned?).to be false expect(parser.tracked_links).to eq 0 @@ -15,13 +14,12 @@ describe Postal::MessageParser do it "should replace links in messages" do with_global_server do |server| - message = create_plain_text_message(server, 'Hello world! http://github.com/atech/postal', 'test@example.com') - track_domain = create(:track_domain, :server => server, :domain => message.domain) + message = create_plain_text_message(server, "Hello world! http://github.com/atech/postal", "test@example.com") + track_domain = create(:track_domain, server: server, domain: message.domain) parser = Postal::MessageParser.new(message) expect(parser.actioned?).to be true expect(parser.new_body).to match(/^Hello world! https:\/\/click\.#{message.domain.name}/) expect(parser.tracked_links).to eq 1 end end - end diff --git a/spec/lib/postal/query_string_spec.rb b/spec/lib/postal/query_string_spec.rb index 94c3abf..1e34488 100644 --- a/spec/lib/postal/query_string_spec.rb +++ b/spec/lib/postal/query_string_spec.rb @@ -1,45 +1,42 @@ -require 'rails_helper' +require "rails_helper" describe Postal::QueryString do - it "should work with a single item" do qs = Postal::QueryString.new("to: test@example.com") - expect(qs.to_hash['to']).to eq 'test@example.com' + expect(qs.to_hash["to"]).to eq "test@example.com" end - it "should work with a multiple items" do qs = Postal::QueryString.new("to: test@example.com from: another@example.com") - expect(qs.to_hash['to']).to eq 'test@example.com' - expect(qs.to_hash['from']).to eq 'another@example.com' + expect(qs.to_hash["to"]).to eq "test@example.com" + expect(qs.to_hash["from"]).to eq "another@example.com" end it "should not require a space after the field name" do qs = Postal::QueryString.new("to:test@example.com from:another@example.com") - expect(qs.to_hash['to']).to eq 'test@example.com' - expect(qs.to_hash['from']).to eq 'another@example.com' + expect(qs.to_hash["to"]).to eq "test@example.com" + expect(qs.to_hash["from"]).to eq "another@example.com" end it "should return nil when it receives blank" do qs = Postal::QueryString.new("to:[blank]") - expect(qs.to_hash['to']).to eq nil + expect(qs.to_hash["to"]).to eq nil end it "should handle dates with spaces" do qs = Postal::QueryString.new("date: 2017-02-12 15:20") - expect(qs.to_hash['date']).to eq("2017-02-12 15:20") + expect(qs.to_hash["date"]).to eq("2017-02-12 15:20") end it "should return an array for multiple items" do qs = Postal::QueryString.new("to: test@example.com to: another@example.com") - expect(qs.to_hash['to']).to be_a(Array) - expect(qs.to_hash['to'][0]).to eq 'test@example.com' - expect(qs.to_hash['to'][1]).to eq 'another@example.com' + expect(qs.to_hash["to"]).to be_a(Array) + expect(qs.to_hash["to"][0]).to eq "test@example.com" + expect(qs.to_hash["to"][1]).to eq "another@example.com" end it "should work with a z in the string" do qs = Postal::QueryString.new("to: testaz@example.com") - expect(qs.to_hash['to']).to eq "testaz@example.com" + expect(qs.to_hash["to"]).to eq "testaz@example.com" end - end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 1980718..b4de236 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,16 +1,16 @@ -ENV['POSTAL_ENV'] = 'test' +ENV["POSTAL_ENV"] = "test" -require File.expand_path('../../config/environment', __FILE__) -require 'rspec/rails' -require 'spec_helper' -require 'factory_bot' -require 'database_cleaner' +require File.expand_path("../config/environment", __dir__) +require "rspec/rails" +require "spec_helper" +require "factory_bot" +require "database_cleaner" DatabaseCleaner.allow_remote_database_url = true ActiveRecord::Base.logger = Logger.new("/dev/null") FACTORIES_EXCLUDED_FROM_LINT = [] -Dir[File.expand_path('../factories/*.rb', __FILE__)].each { |f| require f } +Dir[File.expand_path("factories/*.rb", __dir__)].each { |f| require f } ActiveRecord::Migration.maintain_test_schema! RSpec.configure do |config| @@ -33,7 +33,7 @@ RSpec.configure do |config| # Because the mail databases don't use any transactions, all data left in the # database will be left there unless removed. DatabaseCleaner.start - GLOBAL_SERVER = FactoryBot.create(:server, :provision_database => true) + GLOBAL_SERVER = FactoryBot.create(:server, provision_database: true) end config.after(:suite) do @@ -44,5 +44,4 @@ RSpec.configure do |config| DatabaseCleaner.clean end end - end