Page MenuHomeGRNET

No OneTemporary

File Metadata

Created
Sun, May 17, 10:30 AM
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 8761224..fd70334 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,34 +1,39 @@
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
attr_writer :breadcrumb
+ helper_method :admin?
+
+ def admin?
+ params.key?(:admin)
+ end
private
def group
@group ||= domain.group
end
def domain
@domain ||= domain_scope.find(params[:domain_id] || params[:id])
end
def record
@record ||= record_scope.find(params[:record_id] || params[:id])
end
def group_scope
@group_scope ||= Group.all
end
def domain_scope
@domain_scope ||= Domain.where(group: group_scope)
end
def record_scope
@record_scope ||= domain.records
end
end
diff --git a/app/controllers/records_controller.rb b/app/controllers/records_controller.rb
index 2c8a1c0..60f7046 100644
--- a/app/controllers/records_controller.rb
+++ b/app/controllers/records_controller.rb
@@ -1,49 +1,53 @@
class RecordsController < ApplicationController
before_action :domain
before_action :record, only: [:edit, :update, :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
redirect_to domain, notice: 'Record was successfully created.'
else
render :new
end
end
# PATCH/PUT /records/1
def update
if @record.update(edit_record_params)
redirect_to domain, notice: 'Record was successfully updated.'
else
render :edit
end
end
# DELETE /records/1
def destroy
@record.destroy
redirect_to domain, notice: 'Record was successfully destroyed.'
end
private
def edit_record_params
- params.require(:record).permit(:name, :content, :prio, :disabled)
+ params.require(:record).permit(:name, :content, :prio, :disabled).tap { |r|
+ r[:drop_privileges] = true if not admin?
+ }
end
def new_record_params
- params.require(:record).permit(:name, :content, :type, :prio)
+ params.require(:record).permit(:name, :content, :type, :prio).tap { |r|
+ r[:drop_privileges] = true if not admin?
+ }
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de6be79..876d94e 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,2 +1,8 @@
module ApplicationHelper
+ def can_edit?(object)
+ return true if admin?
+ return true unless object.respond_to?(:editable?)
+
+ object.editable?
+ end
end
diff --git a/app/models/record.rb b/app/models/record.rb
index 9fe1d27..912eb05 100644
--- a/app/models/record.rb
+++ b/app/models/record.rb
@@ -1,75 +1,100 @@
require 'ipaddr'
+require_dependency 'drop_privileges_validator'
class Record < ActiveRecord::Base
belongs_to :domain
def self.record_types
[
'SOA', 'NS', 'CNAME',
'A', 'AAAA',
'MX',
'TXT', 'SPF', 'SRV', 'SSHFP',
'PTR',
]
end
+ def self.allowed_record_types
+ record_types - WebDNS.settings[:prohibit_records_types]
+ end
+
validates :name, presence: true
validates :type, inclusion: { in: record_types }
+ # Don't allow the following actions on drop privileges mode
+ validates_drop_privileges :type,
+ message: 'You cannot touch that record!',
+ unless: -> { Record.allowed_record_types.include?(type) }
+ validates_drop_privileges :name,
+ message: 'You cannot touch top level NS records!',
+ if: -> { type == 'NS' && domain_record? }
+
before_validation :guess_reverse_name
before_validation :set_name
after_save :update_zone_serial
after_destroy :update_zone_serial
def short
return '' if name == domain.name
return '' if name.blank?
File.basename(name, ".#{domain.name}")
end
+ def domain_record?
+ name.blank? || name == domain.name
+ end
+
+ # Editable by a non-admin user
+ def editable?
+ return false unless Record.allowed_record_types.include?(type)
+ return false if type == 'NS' && domain_record?
+
+ true
+ end
+
def supports_prio?
false
end
# Create record specific urls for all record types
#
# Overrides default rails STI
def self.model_name
return super if self == Record
Record.model_name
end
def to_dns
[name, 'IN', type, supports_prio? ? prio : nil, content].compact.join(' ')
end
private
# Hooks
def guess_reverse_name
return if not type == 'PTR'
return if not domain.reverse?
return if name.blank?
reverse = IPAddr.new(name).reverse
self.name = reverse if reverse.end_with?(domain.name)
rescue IPAddr::InvalidAddressError # rubycop:disable HandleExceptions
end
# Powerdns expects full domain names
def set_name
self.name = domain.name if name.blank?
self.name = "#{name}.#{domain.name}" if not name.end_with?(domain.name)
end
def update_zone_serial
# SOA records handle serial themselves
return true if type == 'SOA'
domain.soa.bump_serial!
end
end
diff --git a/app/views/domains/show.html.erb b/app/views/domains/show.html.erb
index ec738f4..e866df0 100644
--- a/app/views/domains/show.html.erb
+++ b/app/views/domains/show.html.erb
@@ -1,31 +1,37 @@
<table class="table table-striped table-hover">
<thead>
<tr>
<th colspan="5">Records</th>
<th colspan="3">Controls</th>
</tr>
</thead>
<tbody>
<% @domain.records.each do |record| %>
<tr class="<%= record.disabled? ? 'warning' : '' %>">
<td><%= record.name %></td>
<td>IN</td>
<td><%= record.type %></td>
<td><%= record.supports_prio? ? record.prio : '' %></td>
<td><%= record.content %></td>
- <td>
- <% if record.disabled? %>
- <%= link_to 'Enable', enable_domain_record_path(@domain, record), method: :put %>
- <% else %>
- <%= link_to 'Disable', disable_domain_record_path(@domain, record), method: :put %>
- <% end %>
- </td>
- <td><%= link_to 'Edit', edit_domain_record_path(@domain, record) %></td>
- <td><%= link_to 'Remove', [@domain, record], method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <% if can_edit?(record) %>
+ <td>
+ <% if record.disabled? %>
+ <%= link_to 'Enable', enable_domain_record_path(@domain, record), method: :put %>
+ <% else %>
+ <%= link_to 'Disable', disable_domain_record_path(@domain, record), method: :put %>
+ <% end %>
+ </td>
+ <td><%= link_to 'Edit', edit_domain_record_path(@domain, record) if can_edit?(record) %></td>
+ <td><%= link_to 'Remove', [@domain, record], method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <% else %>
+ <td/>
+ <td/>
+ <td/>
+ <% end %>
</tr>
<% end %>
</tbody>
</table>
<p><%= link_to 'New Record', new_domain_record_path(@domain) %></p>
diff --git a/app/views/records/_form.html.erb b/app/views/records/_form.html.erb
index 418f2ba..93c1fa7 100644
--- a/app/views/records/_form.html.erb
+++ b/app/views/records/_form.html.erb
@@ -1,14 +1,14 @@
<%= bootstrap_form_for([@domain, @record], layout: :horizontal, label_col: 'col-sm-2', control_col: 'col-sm-8') do |f| %>
<%= f.text_field :name, value: @record.short, label: 'Record', append: name_field_append(@record) %>
<% if @record.persisted? %>
<%= f.static_control :type %>
<% else %>
- <%= f.select :type, Record.record_types %>
+ <%= f.select :type, Record.allowed_record_types %>
<% end %>
<%= f.text_field :prio, placeholder: 10, wrapper_class: @record.supports_prio? ? '' : 'hidden' %>
<%= f.text_field :content %>
<%= f.submit 'Save', class: 'btn btn-primary col-sm-offset-2' %>
<% end %>
diff --git a/config/initializers/00_settings.rb b/config/initializers/00_settings.rb
index 88695d4..c6bf360 100644
--- a/config/initializers/00_settings.rb
+++ b/config/initializers/00_settings.rb
@@ -1,13 +1,16 @@
WebDNS = Base
WebDNS.settings[:soa_defaults] = {
primary_ns: 'ns.example.com',
contact: 'domainmaster@example.com',
serial: 1,
refresh: 10_800,
retry: 3600,
expire: 604_800,
nx: 3600
}
WebDNS.settings[:serial_strategy] = Strategies::Date
+
+# Don't allow to create SOA records
+WebDNS.settings[:prohibit_records_types] = ['SOA']
diff --git a/test/models/ns_test.rb b/test/models/ns_test.rb
index a099692..6d81be5 100644
--- a/test/models/ns_test.rb
+++ b/test/models/ns_test.rb
@@ -1,14 +1,29 @@
require 'test_helper'
class NSTest < ActiveSupport::TestCase
setup do
@record = build(:ns)
end
test 'save' do
@record.save
assert_empty @record.errors
end
+ test 'drop privileges on zone NS records' do
+ @record.drop_privileges = true
+ @record.save
+
+ assert_not_empty @record.errors[:name]
+ end
+
+ test 'doesnt drop privileges on non zone NS records' do
+ @record.name = 'other'
+ @record.drop_privileges = true
+
+ @record.save
+
+ assert_empty @record.errors[:name]
+ end
end
diff --git a/test/models/soa_test.rb b/test/models/soa_test.rb
index a96a30f..4096ca2 100644
--- a/test/models/soa_test.rb
+++ b/test/models/soa_test.rb
@@ -1,60 +1,69 @@
require 'test_helper'
class SOATest < ActiveSupport::TestCase
def setup
domain = create(:domain)
@record = domain.soa
end
test 'bump_serial!' do
@record.save!
assert_serial_update @record do
@record.bump_serial!
end
end
test 'updating attributes bumps serial' do
@record.save!
assert_serial_update @record do
@record.contact = 'admin@example.com'
@record.save!
end
end
+ test 'drop privileges' do
+ @record.contact = 'admin@example.com'
+ @record.drop_privileges = true
+ assert_not @record.editable?
+
+ @record.save
+ assert_not_empty @record.errors[:type]
+ end
+
class DateSerialTests < ActiveSupport::TestCase
setup do
domain = create(:date_domain)
@record = domain.soa
end
test 'last bump of the day' do
assert_equal Strategies::Date, @record.domain.serial_strategy
freeze_time do
last_for_day = Time.now.strftime('%Y%m%d99').to_i
@record.serial = last_for_day
@record.save!
assert_serial_update @record do
@record.bump_serial!
end
end
end
test 'existing serial points to a future date' do
assert_equal Strategies::Date, @record.domain.serial_strategy
freeze_time do
future_day = (Time.now + 1.week).strftime('%Y%m%d00').to_i
@record.serial = future_day
@record.save!
assert_serial_update @record do
@record.bump_serial!
end
end
end
end
end

Event Timeline