Page Menu
Home
GRNET
Search
Configure Global Search
Log In
Files
F905046
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Fri, Aug 29, 7:53 PM
Size
15 KB
Mime Type
text/x-diff
Expires
Sun, Aug 31, 7:53 PM (21 h, 13 m)
Engine
blob
Format
Raw Data
Handle
251960
Attached To
rWEBDNS WebDNS (edet4)
View Options
diff --git a/app/controllers/records_controller.rb b/app/controllers/records_controller.rb
index 162ef80..3a9fd47 100644
--- a/app/controllers/records_controller.rb
+++ b/app/controllers/records_controller.rb
@@ -1,139 +1,143 @@
class RecordsController < ApplicationController
before_action :authenticate_user!
before_action :domain, except: [:search]
before_action :editable_transform_params, only: [:editable]
before_action :record, only: [:edit, :update, :editable, :destroy]
# GET /records/new
def new
@record = domain.records.build
end
# GET /records/1/edit
def edit
end
# POST /records
def create
@record = domain.records.new(new_record_params)
if @record.save
notify_record(@record, :create)
redirect_to domain, notice: 'Record was successfully created.'
else
flash[:alert] = 'There were some errors creating the record!'
render :new
end
end
# PATCH/PUT /records/1
def update
if @record.update(edit_record_params)
notify_record(@record, :update)
redirect_to domain, notice: 'Record was successfully updated.'
else
render :edit
end
end
def valid
@record = domain.records.new(new_record_params)
if @record.valid?
response = {
record: @record.as_bulky_json,
errors: false
}
render json: response
else
render json: { errors: @record.errors.full_messages.join(', ') }
end
end
def bulk
ops, err = @domain.bulk(params)
if err.empty?
notify_record_bulk(@domain, ops)
render json: { ok: true }
else
render json: { errors: err }
end
end
def editable
@record.assign_attributes(edit_record_params)
if @record.valid?
if @save
@record.save!
notify_record(@record, :update)
end
response = {
attribute: @attr,
value: @record.read_attribute(@attr),
serial: @domain.soa(true).serial,
record: @record.as_bulky_json,
saved: @save
}
render json: response
else
render text: @record.errors[@attr].join(', '), status: 400
end
end
# DELETE /records/1
def destroy
if @record.type == 'SOA'
redirect_to domain, alert: 'SOA records cannot be deleted!'
return
end
@record.destroy
notify_record(@record, :destroy)
redirect_to domain, notice: 'Record was successfully destroyed.'
end
# GET /search
def search
@records = Record
.where(domain: show_domain_scope)
.includes(:domain)
.search(params[:q]) # scope by domain
@records = Record.smart_order(@records)
end
private
# Modify params to use standard Rails patterns
def editable_transform_params
@attr = params[:name]
@save = params[:save] != 'false'
params[:record] = { params[:name] => params[:value] }
end
def edit_record_params
if @record.type == 'SOA'
permitted = [:contact, :serial, :refresh, :retry, :expire, :nx]
else
permitted = [:name, :content, :ttl, :prio, :disabled]
end
params.require(:record).permit(*permitted).tap { |r|
r[:drop_privileges] = true if not admin?
}
end
def new_record_params
params.require(:record).permit(:name, :content, :ttl, :type, :prio).tap { |r|
r[:drop_privileges] = true if not admin?
}
end
def notify_record(*args)
notification.notify_record(current_user, *args) if WebDNS.settings[:notifications]
end
+
+ def notify_record_bulk(*args)
+ notification.notify_record_bulk(current_user, *args) if WebDNS.settings[:notifications]
+ end
end
diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
index c7cde23..1f5a649 100644
--- a/app/mailers/notification_mailer.rb
+++ b/app/mailers/notification_mailer.rb
@@ -1,29 +1,39 @@
class NotificationMailer < ActionMailer::Base
default from: WebDNS.settings[:mail_from]
PREFIXES = {
create: 'Created',
update: 'Modified',
destroy: 'Deleted',
}
def notify_record(record:, context:, user:, admin:, others:, changes:)
@record = record
@context = context
@user = user
@admin = admin
@changes = changes
mail(to: others, subject: "[webdns] [record] #{PREFIXES[context.to_sym]} #{record.to_short_dns}")
end
+ def notify_record_bulk(domain:, user:, admin:, others:, operations:)
+ @domain = domain
+ @user = user
+ @admin = admin
+ @operations = operations
+
+ mail(to: others, subject: "[webdns] [record] Bulk operations for '#{domain.name}'")
+ end
+
def notify_domain(domain:, context:, user:, admin:, others:, changes:)
@domain = domain
@context = context
@user = user
@admin = admin
@changes = changes
mail(to: others, subject: "[webdns] [domain] #{PREFIXES[context.to_sym]} #{domain.name}")
end
+
end
diff --git a/app/views/notification_mailer/notify_record_bulk.text.erb b/app/views/notification_mailer/notify_record_bulk.text.erb
new file mode 100644
index 0000000..f16b233
--- /dev/null
+++ b/app/views/notification_mailer/notify_record_bulk.text.erb
@@ -0,0 +1,17 @@
+Domain: <%= @domain.name %>
+By: <%= @user.email %> <%= '(admin)' if @admin %>
+
+<% @operations.each do |context, record, changes| %>
+
+Action: <%= context %>
+Record: <%= record.name %>
+Type: <%= record.type %>
+State: <%= record.to_dns %>
+<% if context == :update -%>
+Changes:
+<% changes.each do |key, change| -%>
+<%= key %> from <%= change.first %>
+<%= key %> to <%= change.last %>
+<% end -%>
+<% end -%>
+<% end -%>
diff --git a/lib/notification.rb b/lib/notification.rb
index ef60c99..2caa0da 100644
--- a/lib/notification.rb
+++ b/lib/notification.rb
@@ -1,100 +1,139 @@
require 'singleton'
class Notification
include Singleton
+ # Send out a notification about bulk record operations.
+ def notify_record_bulk(user, domain, ops)
+ ActiveSupport::Notifications.instrument(
+ 'webdns.record.bulk',
+ user: user,
+ domain: domain,
+ ops: ops)
+ end
+
# Send out a notification about notable record changes.
def notify_record(user, record, context)
ActiveSupport::Notifications.instrument(
'webdns.record',
user: user,
context: context,
object: record)
end
# Send out a notification about notable domain changes.
def notify_domain(user, domain, context)
ActiveSupport::Notifications.instrument(
'webdns.domain',
user: user,
context: context,
object: domain)
end
# Subscribe to domain/record notifications.
def hook
hook_record
+ hook_record_bulk
hook_domain
end
private
def hook_record
ActiveSupport::Notifications
.subscribe 'webdns.record' do |_name, _started, _finished, _unique_id, data|
handle_record(data)
end
end
+ def hook_record_bulk
+ ActiveSupport::Notifications
+ .subscribe 'webdns.record.bulk' do |_name, _started, _finished, _unique_id, data|
+ handle_record_bulk(data)
+ end
+ end
+
def hook_domain
ActiveSupport::Notifications
.subscribe 'webdns.domain' do |_name, _started, _finished, _unique_id, data|
handle_domain(data)
end
end
def handle_record(data)
record, context, user = data.values_at(:object, :context, :user)
domain = record.domain
changes = filter_changes(record)
return if changes.empty? && context == :update
others = domain.group.users.where.not(id: user.id).pluck(:email)
return if others.empty?
admin_action = !user.groups.exists?(domain.group_id)
NotificationMailer.notify_record(
record: record,
context: context,
user: user,
admin: admin_action,
others: others,
changes: changes
).deliver
end
+ def handle_record_bulk(data)
+ ops, domain, user = data.values_at(:ops, :domain, :user)
+
+ operations = []
+ operations += ops[:deletes].map { |rec| [:destroy, rec, nil] }
+ operations += ops[:changes].map { |rec| [:update, rec, filter_changes(rec)] }
+ operations += ops[:additions].map { |rec| [:create, rec, nil] }
+
+ others = domain.group.users.where.not(id: user.id).pluck(:email)
+ return if others.empty?
+
+ admin_action = !user.groups.exists?(domain.group_id)
+
+ NotificationMailer.notify_record_bulk(
+ user: user,
+ admin: admin_action,
+ others: others,
+ domain: domain,
+ operations: operations,
+ ).deliver
+ end
+
def handle_domain(data)
domain, context, user = data.values_at(:object, :context, :user)
changes = filter_changes(domain)
return if changes.empty? && context == :update
others = domain.group.users.where.not(id: user.id).pluck(:email)
return if others.empty?
admin_action = !user.groups.exists?(domain.group_id)
NotificationMailer.notify_domain(
domain: domain,
context: context,
user: user,
admin: admin_action,
others: others,
changes: changes
).deliver
end
private
def filter_changes(record)
changes = record.previous_changes
# Nobody is interested in those
changes.delete('updated_at')
changes.delete('created_at')
changes
end
end
diff --git a/test/mailers/notification_mailer_test.rb b/test/mailers/notification_mailer_test.rb
index 33a9d40..4d3bb4c 100644
--- a/test/mailers/notification_mailer_test.rb
+++ b/test/mailers/notification_mailer_test.rb
@@ -1,146 +1,175 @@
require 'test_helper'
class NotificationMailerTest < ActionMailer::TestCase
class DomainNotificationMailerTest < ActionMailer::TestCase
def setup
@notification = Notification.instance
@group = create(:group_with_users)
@domain = create(:domain, group: @group)
@record = build(:a, name: 'a', domain: @domain)
end
test 'domain add' do
@record.save!
@notification.notify_domain(@group.users.first, @domain, :create)
assert_not ActionMailer::Base.deliveries.empty?
mail = ActionMailer::Base.deliveries.last
assert_equal [@group.users.last.email], mail.to
assert_includes mail.subject, 'Created'
assert_includes mail.body.to_s, "Domain: #{@domain.name}"
assert_includes mail.body.to_s, "By: #{@group.users.first.email}"
end
test 'domain edit' do
@record.save!
@domain.type = 'SLAVE'
@domain.master = '1.2.3.4'
@domain.save!
@notification.notify_domain(@group.users.first, @domain, :update)
assert_not ActionMailer::Base.deliveries.empty?
mail = ActionMailer::Base.deliveries.last
assert_equal [@group.users.last.email], mail.to
assert_includes mail.subject, 'Modified'
assert_includes mail.body.to_s, "Domain: #{@domain.name}"
assert_includes mail.body.to_s, "By: #{@group.users.first.email}"
assert_includes mail.body.to_s, 'type from NATIVE'
assert_includes mail.body.to_s, 'type to SLAVE'
assert_includes mail.body.to_s, 'master from (empty)'
assert_includes mail.body.to_s, 'master to 1.2.3.4'
end
test 'domain destroy' do
@record.save!
@domain.destroy!
@notification.notify_domain(@group.users.first, @domain, :destroy)
assert_not ActionMailer::Base.deliveries.empty?
mail = ActionMailer::Base.deliveries.last
assert_equal [@group.users.last.email], mail.to
assert_includes mail.subject, 'Deleted'
assert_includes mail.body.to_s, "Domain: #{@domain.name}"
assert_includes mail.body.to_s, "By: #{@group.users.first.email}"
end
end
class DomainNotificationMailerTest < ActionMailer::TestCase
test 'record add' do
@record.save!
@notification.notify_record(@group.users.first, @record, :create)
assert_not ActionMailer::Base.deliveries.empty?
mail = ActionMailer::Base.deliveries.last
assert_equal [@group.users.last.email], mail.to
assert_includes mail.subject, 'Created'
assert_includes mail.body.to_s, "Record: #{@record.name}"
assert_includes mail.body.to_s, "Domain: #{@domain.name}"
assert_includes mail.body.to_s, "State: #{@record.to_dns}"
assert_includes mail.body.to_s, "By: #{@group.users.first.email}"
end
test 'record edit' do
@record.save!
prev_content = @record.content
@record.content = '1.1.1.1'
@record.save!
@notification.notify_record(@group.users.first, @record, :update)
assert_not ActionMailer::Base.deliveries.empty?
mail = ActionMailer::Base.deliveries.last
assert_equal [@group.users.last.email], mail.to
assert_includes mail.subject, 'Modified'
assert_includes mail.body.to_s, "Record: #{@record.name}"
assert_includes mail.body.to_s, "Domain: #{@domain.name}"
assert_includes mail.body.to_s, "State: #{@record.to_dns}"
assert_includes mail.body.to_s, "By: #{@group.users.first.email}"
assert_includes mail.body.to_s, "content from #{prev_content}"
assert_includes mail.body.to_s, 'content to 1.1.1.1'
end
test 'soa edit' do
@record = @domain.soa
prev_content = @record.content
@record.nx = 10
@record.save!
@notification.notify_record(@group.users.first, @record, :update)
assert_not ActionMailer::Base.deliveries.empty?
mail = ActionMailer::Base.deliveries.last
assert_equal [@group.users.last.email], mail.to
assert_includes mail.subject, 'Modified'
assert_includes mail.body.to_s, "Record: #{@record.name}"
assert_includes mail.body.to_s, "Domain: #{@domain.name}"
assert_includes mail.body.to_s, "State: #{@record.to_dns}"
assert_includes mail.body.to_s, "By: #{@group.users.first.email}"
assert_includes mail.body.to_s, "content from #{prev_content}"
assert_includes mail.body.to_s, "content to #{@record.content}"
assert_includes mail.body.to_s, ' 10'
end
test 'record destroy' do
@record.save!
@record.destroy!
@notification.notify_record(@group.users.first, @record, :destroy)
assert_not ActionMailer::Base.deliveries.empty?
mail = ActionMailer::Base.deliveries.last
assert_equal [@group.users.last.email], mail.to
assert_includes mail.subject, 'Deleted'
assert_includes mail.body.to_s, "Record: #{@record.name}"
assert_includes mail.body.to_s, "Domain: #{@domain.name}"
assert_includes mail.body.to_s, "By: #{@group.users.first.email}"
end
+
+ test 'bulk operations' do
+ a = create(:a, domain: @domain)
+ aaaa = create(:aaaa, domain: @domain)
+ new = build(:mx, domain: @domain)
+
+ changes = {}.tap { |c|
+ c[:deletes] = [a.id]
+ c[:changes] = { aaaa.id => { content: '::42' }}
+ c[:additions] = { 1 => new.as_bulky_json }
+ }
+
+ ops, err = @domain.bulk(changes)
+ assert_empty err
+
+ @notification.notify_record_bulk(@group.users.first, @domain, ops)
+
+ assert_not ActionMailer::Base.deliveries.empty?
+ mail = ActionMailer::Base.deliveries.last
+
+ assert_equal [@group.users.last.email], mail.to
+ assert_includes mail.subject, 'Bulk'
+ assert_includes mail.body.to_s, "Domain: #{@domain.name}"
+ assert_includes mail.body.to_s, "By: #{@group.users.first.email}"
+ assert_includes mail.body.to_s, "Action: destroy"
+ assert_includes mail.body.to_s, "Action: update"
+ assert_includes mail.body.to_s, "Action: create"
+ end
+
end
end
Event Timeline
Log In to Comment