Page MenuHomeGRNET

No OneTemporary

File Metadata

Created
Mon, May 19, 2:01 AM
diff --git a/app/helpers/records_helper.rb b/app/helpers/records_helper.rb
index 9ea218e..e0f974c 100644
--- a/app/helpers/records_helper.rb
+++ b/app/helpers/records_helper.rb
@@ -1,14 +1,22 @@
module RecordsHelper
# Smart suffix for records
#
# On forward zones returns the zone name.
# On reverse zones returns the zone name but also tries to infer the subnet.
#
# Returns a smart suffix string.
def name_field_append(record)
return ".#{record.domain.name}" if not record.domain.reverse?
".#{record.domain.name} (#{record.domain.subnet})"
end
+
+ # List of record types usually used for that domain type
+ def record_types_for_domain(domain)
+ return Record.reverse_records if domain.reverse?
+ return Record.enum_records if domain.enum?
+
+ Record.forward_records
+ end
end
diff --git a/app/models/domain.rb b/app/models/domain.rb
index 2e2a7fc..fc1fa52 100644
--- a/app/models/domain.rb
+++ b/app/models/domain.rb
@@ -1,99 +1,104 @@
class Domain < ActiveRecord::Base
self.inheritance_column = :nx
# List all supported domain types.
def self.domain_types
[
'NATIVE',
'MASTER',
'SLAVE',
]
end
belongs_to :group
has_many :records
has_one :soa, class_name: SOA
validates :group_id, presence: true
validates :name, uniqueness: true, presence: true
validates :type, presence: true, inclusion: { in: domain_types }
validates :master, presence: true, ipv4: true, if: :slave?
after_create :generate_soa
after_create :generate_ns
attr_writer :serial_strategy
# Get the zone's serial strategy.
#
# Returns one of the supported serial strategies.
def serial_strategy
@serial_strategy ||= WebDNS.settings[:serial_strategy]
end
# Returns true if this a reverse zone.
def reverse?
name.end_with?('.in-addr.arpa') || name.end_with?('.ip6.arpa')
end
+ # Returns true if this a ENUM zone.
+ def enum?
+ name.end_with?('.e164.arpa')
+ end
+
# Returns true if this is a slave zone.
def slave?
type == 'SLAVE'
end
# Compute subnet for reverse records
def subnet
return if not reverse?
if name.end_with?('.in-addr.arpa')
subnet_v4
elsif name.end_with?('.ip6.arpa')
subnet_v6
end
end
private
def subnet_v4
# get ip octets (remove .in-addr.arpa)
octets = name.split('.')[0...-2].reverse
return if octets.any? { |_| false }
mask = 8 * octets.size
octets += [0, 0, 0, 0]
ip = IPAddr.new octets[0, 4].join('.')
[ip, mask].join('/')
end
def subnet_v6
nibbles = name.split('.')[0...-2].reverse
return if nibbles.any? { |_| false }
mask = 4 * nibbles.size
nibbles += [0] * 32
ip = IPAddr.new nibbles[0, 32].in_groups_of(4).map(&:join).join(':')
[ip, mask].join('/')
end
# Hooks
def generate_soa
soa_record = SOA.new(domain: self)
soa_record.save!
end
def generate_ns
return if slave?
return if WebDNS.settings[:default_ns].empty?
WebDNS.settings[:default_ns].each { |ns|
Record.find_or_create_by!(domain: self, type: 'NS', name: '', content: ns)
}
end
end
diff --git a/app/models/record.rb b/app/models/record.rb
index 26f9b1b..6109ccb 100644
--- a/app/models/record.rb
+++ b/app/models/record.rb
@@ -1,198 +1,203 @@
require 'ipaddr'
require_dependency 'drop_privileges_validator'
class Record < ActiveRecord::Base
belongs_to :domain
# Powerdns inserts empty records on slave zones,
# we want to hide them
#
# http://mailman.powerdns.com/pipermail/pdns-users/2013-December/010389.html
default_scope { where.not(type: nil) }
# List all supported DNS RR types.
def self.record_types
[
'A', 'AAAA', 'CNAME',
'MX',
'TXT', 'SPF', 'SRV', 'SSHFP',
'SOA', 'NS',
'PTR', 'NAPTR'
]
end
# List types usually used in forward zones.
def self.forward_records
record_types - ['SOA', 'PTR']
end
# List types usually used in reverse zones.
def self.reverse_records
['PTR', 'CNAME', 'TXT', 'NS', 'NAPTR']
end
+ # List types usually used in enum zones.
+ def self.enum_records
+ ['NAPTR', 'CNAME', 'TXT', 'NS']
+ end
+
# List types that can be touched by a simple user.
def self.allowed_record_types
record_types - WebDNS.settings[:prohibit_records_types]
end
validates :name, presence: true
validates :type, inclusion: { in: record_types }
# http://mark.lindsey.name/2009/03/never-use-dns-ttl-of-zero-0.html
validates_numericality_of :ttl,
allow_nil: true, # Default pdns TTL
only_integer: true,
greater_than: 0,
less_than_or_equal_to: 2_147_483_647
# Don't allow the following actions on drop privileges mode
validate :no_touching_for_slave_zones, if: -> { domain.slave? }
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
# Smart sort a list of records.
#
# Order by:
# * Top level records
# * Record name
# * SOA
# * NS
# * Friendly type
# * Priority
# * Content
#
# records - The list of records to order.
#
# Returns the list sorted.
def self.smart_order(records)
records.sort_by { |r|
[
r.domain_record? ? 0 : 1, # Zone records
r.name,
r.type == 'SOA' ? 0 : 1,
r.type == 'NS' ? 0 : 1,
record_types.index(r.type), # Friendly type
r.prio,
r.content
]
}
end
# Get the a short name for the record (without the zone suffix).
#
# Returns a string.
def short
return '' if name == domain.name
return '' if name.blank?
File.basename(name, ".#{domain.name}")
end
# Returns true if this is a zone record.
def domain_record?
name.blank? || name == domain.name
end
# Find out if the record is edittable.
#
# by - Editable by :user or :admin.
#
# Returns true if the record is editable.
def editable?(by = :user)
return false if domain.slave?
case by
when :user
return false unless Record.allowed_record_types.include?(type)
return false if type == 'NS' && domain_record?
end
true
end
# Find out this record type supports priorities.
#
# We set this to false by default, record types that support priorities.
# shoule override this.
#
# Returns true this record type support priorities.
def supports_prio?
false
end
# Make sure rails generates record specific urls for all record types.
#
# Overrides default rails STI behavior.
def self.model_name
return super if self == Record
Record.model_name
end
# Generate the usual admin friendly DNS record line.
#
# Returns a string.
def to_dns
[name, ttl, 'IN', type, supports_prio? ? prio : nil, content].compact.join(' ')
end
# Generate a shorter version of the DNS record line.
#
# Returns a string.
def to_short_dns
[name, 'IN', type].join(' ')
end
private
# Validations
def no_touching_for_slave_zones
# Allow automatic SOA creation for slave zones
# powerdns needs a valid serial to compare it with master
return if type == 'SOA' && validation_context == :create
errors.add(:type, 'This is a slave zone!')
end
# 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 remove_terminating_dot
self.content = content.gsub(/\.+\Z/, '')
end
def update_zone_serial
# SOA records handle serial themselves
return true if type == 'SOA'
return true if !domain
domain.soa.bump_serial!
end
end
diff --git a/app/views/domains/index.html.erb b/app/views/domains/index.html.erb
index 47058bc..2daced8 100644
--- a/app/views/domains/index.html.erb
+++ b/app/views/domains/index.html.erb
@@ -1,50 +1,52 @@
<% if current_user.memberships.empty? %>
<div class="jumbotron">
<h2>Wellcome to WebDNS!</h2>
<p>
In order to manage domains you have to be a member of a group.
</p>
<p>
You can either contact an admin to create a new group for you, or ask another user for an invite to an existing group.
</p>
</div>
<% end %>
<table class="table table-striped">
<thead>
</thead>
<tbody>
<% @domains.group_by(&:group).each do |group, domains| %>
<tr>
<th colspan="2"><%= link_to group.name, group_path(group) %></th>
<th colspan="2">Controls</th>
</tr>
<% domains.each do |domain| %>
<tr>
<td><%= link_to domain.name, domain %></td>
<td>
<% if domain.reverse? %>
<%= abbr_glyph('chevron-left', 'Reverse') %>
+ <% elsif domain.enum? %>
+ <%= abbr_glyph('phone-alt', 'Enum') %>
<% else %>
<%= abbr_glyph('chevron-right', 'Forward') %>
<% end %>
<% if domain.slave? %>
<%= abbr_glyph('link', 'Slave') %>
<% end %>
</td>
<td><%= link_to_edit edit_domain_path(domain) %></td>
<td><%= link_to_destroy domain, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<p>
<% if current_user.memberships.any? %>
<%= link_to 'Add Domain', new_domain_path, class: 'btn btn-primary' %>
<% else %>
<%= link_to 'Add Domain', new_domain_path, class: 'btn btn-primary disabled' %>
<% end %>
</p>
diff --git a/app/views/groups/show.html.erb b/app/views/groups/show.html.erb
index 1417b3a..418aa55 100644
--- a/app/views/groups/show.html.erb
+++ b/app/views/groups/show.html.erb
@@ -1,78 +1,80 @@
<% content_for :more_breadcrumbs do %>
<li>
<%= link_to_edit edit_admin_group_path(@group) %>
<%= link_to_destroy admin_group_path(@group), method: :delete, data: { confirm: 'Are you sure?' } %>
</li>
<% end if admin? %>
<ul class="nav nav-tabs">
<li role="presentation" class="active"><a data-toggle="tab" href="#domains">Domains</a></li>
<li role="presentation"><a data-toggle="tab" href="#members">Members</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="domains">
<table class="table table-striped table-hover">
<thead>
<tr>
<th colspan="2">Domain</th>
<th colspan="2">Controls</th>
</tr>
</thead>
<tbody>
<% @group.domains.each do |domain| %>
<tr>
<td><%= link_to domain.name, domain %></td>
<td>
<% if domain.reverse? %>
<%= abbr_glyph('chevron-left', 'Reverse') %>
+ <% elsif domain.enum? %>
+ <%= abbr_glyph('phone-alt', 'Enum') %>
<% else %>
<%= abbr_glyph('chevron-right', 'Forward') %>
<% end %>
<% if domain.slave? %>
<%= abbr_glyph('link', 'Slave') %>
<% end %>
</td>
<td><%= link_to_edit edit_domain_path(domain) %></td>
<td><%= link_to_destroy domain, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<p>
<%= link_to 'Add Domain', new_domain_path(group_id: @group.id), class: 'btn btn-primary' %>
</p>
</div>
<div role="tabpanel" class="tab-pane" id="members">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Member</th>
<th>Controls</th>
</tr>
</thead>
<tbody>
<% @group.memberships.includes(:user).each do |membership| %>
<tr>
<td><%= membership.user.email %><%= " (you)" if current_user == membership.user %></td>
<td>
<%= link_to_destroy destroy_member_group_path(@group, membership.user_id), method: :delete %>
</td>
</tr>
<% end %>
</tbody>
</table>
<p>
<%= bootstrap_form_tag(url: create_member_group_path(@group), layout: :inline) do |f| %>
<%= f.text_field :email, prepend: 'Add Member', hide_label: true, id: 'js-search-member', data: { group: @group.id } %>
<%= f.submit 'Add', class: 'btn btn-primary' %>
<% end %>
</p>
</div>
</div>
diff --git a/app/views/records/_form.html.erb b/app/views/records/_form.html.erb
index 5e7da70..65f2cf6 100644
--- a/app/views/records/_form.html.erb
+++ b/app/views/records/_form.html.erb
@@ -1,15 +1,15 @@
<%= 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, @domain.reverse? ? Record.reverse_records : Record.forward_records %>
+ <%= f.select :type, record_types_for_domain(@domain) %>
<% end %>
<%= f.text_field :prio, placeholder: 10, wrapper_class: @record.supports_prio? ? '' : 'hidden' %>
<%= f.text_field :ttl, label: 'TTL' %>
<%= f.text_field :content %>
<%= f.submit 'Save', class: 'btn btn-primary col-sm-offset-2' %>
<% end %>

Event Timeline