diff --git a/Gemfile.lock b/Gemfile.lock index 6c3bffd..7ec42c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,7 +77,7 @@ GEM fssm (>= 0.2.7) sass (~> 3.1) database_cleaner (0.6.7) - devise (1.4.7) + devise (1.4.8) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.0.3) warden (~> 1.0.3) @@ -98,8 +98,8 @@ GEM thor (~> 0.14.6) guard-rspec (0.4.5) guard (>= 0.4.0) - guard-spork (0.3.0) - guard (>= 0.8.2) + guard-spork (0.3.1) + guard (>= 0.8.4) spork (>= 0.8.4) highline (1.6.2) hike (1.2.1) @@ -107,6 +107,7 @@ GEM jquery-rails (1.0.14) railties (~> 3.0) thor (~> 0.14) + json (1.6.1) json_pure (1.6.1) libv8 (3.3.10.2) mail (2.3.0) @@ -159,7 +160,8 @@ GEM thor (~> 0.14.6) rake (0.9.2) rb-fsevent (0.4.3.1) - rdoc (3.9.4) + rdoc (3.10) + json (~> 1.4) rspec (2.6.0) rspec-core (~> 2.6.0) rspec-expectations (~> 2.6.0) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e1e3dbc..9f85e43 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -23,4 +23,10 @@ class ApplicationController < ActionController::Base def ensure_nested_under_domain raise CanCan::AccessDenied, "not found" unless nested? and nested_parent_record.is_a?(Domain) end + + def client_remote_ip + request.env["HTTP_X_FORWARDED_FOR"] + end + helper_method :client_remote_ip + end diff --git a/app/controllers/as_controller.rb b/app/controllers/as_controller.rb index 61c80c5..18a59a8 100644 --- a/app/controllers/as_controller.rb +++ b/app/controllers/as_controller.rb @@ -1,6 +1,8 @@ class AsController < ApplicationController + respond_to :html, :xml, :json + active_scaffold :a do |conf| - conf.list.columns = [:name, :type, :content, :ttl, :prio, :change_date] + conf.columns = [:name, :type, :content, :ttl, :prio, :change_date, :authentication_token] conf.create.columns = [:name, :content, :ttl,] conf.update.columns = [:name, :content, :ttl] conf.columns[:content].label = 'IP' @@ -9,7 +11,18 @@ class AsController < ApplicationController conf.columns[:ttl].options = {:i18n_number => {:delimiter => ''}} conf.actions.exclude :show end - before_filter :ensure_nested_under_domain + before_filter :ensure_nested_under_domain, :except => 'modify' + skip_before_filter :authenticate_user!, :only => 'modify' + protect_from_forgery :except => 'modify' + skip_authorize_resource :only => :modify + + # TODO: externalize + def modify + @a = A.where(:authentication_token => params[:authentication_token]).first! + @a.content = params[:ip] || client_remote_ip + @a.save! + respond_with @a + end protected @@ -24,8 +37,9 @@ class AsController < ApplicationController # override, we make our own sti logic def new_model - model = beginning_of_chain - model.new + model = beginning_of_chain.new + model.name = nested_parent_record.name + model end # override to close create form after success diff --git a/app/controllers/cnames_controller.rb b/app/controllers/cnames_controller.rb index 6882616..5c6ddae 100644 --- a/app/controllers/cnames_controller.rb +++ b/app/controllers/cnames_controller.rb @@ -1,6 +1,6 @@ class CnamesController < ApplicationController active_scaffold :cname do |conf| - conf.list.columns = [:name, :type, :content, :ttl, :prio, :change_date] + conf.columns = [:name, :type, :content, :ttl, :prio, :change_date, :authentication_token] conf.create.columns = [:name, :content, :ttl] conf.update.columns = [:name, :content, :ttl] conf.columns[:content].label = 'FQDN' @@ -24,8 +24,9 @@ class CnamesController < ApplicationController # override, we make our own sti logic def new_model - model = beginning_of_chain - model.new + model = beginning_of_chain.new + model.name = nested_parent_record.name + model end # override to close create form after success diff --git a/app/controllers/mxes_controller.rb b/app/controllers/mxes_controller.rb index 941ee51..d5f1114 100644 --- a/app/controllers/mxes_controller.rb +++ b/app/controllers/mxes_controller.rb @@ -1,6 +1,6 @@ class MxesController < ApplicationController active_scaffold :mx do |conf| - conf.list.columns = [:name, :type, :content, :ttl, :prio, :change_date] + conf.columns = [:name, :type, :content, :ttl, :prio, :change_date, :authentication_token] conf.create.columns = [:content, :ttl, :prio] conf.update.columns = [:content, :ttl, :prio] conf.columns[:content].label = 'MX' @@ -32,8 +32,9 @@ class MxesController < ApplicationController # override, we make our own sti logic def new_model - model = beginning_of_chain - model.new + model = beginning_of_chain.new + model.name = nested_parent_record.name + model end # override to close create form after success diff --git a/app/controllers/ns_controller.rb b/app/controllers/ns_controller.rb index 5a60069..220df01 100644 --- a/app/controllers/ns_controller.rb +++ b/app/controllers/ns_controller.rb @@ -1,6 +1,6 @@ class NsController < ApplicationController active_scaffold :ns do |conf| - conf.list.columns = [:name, :type, :content, :ttl, :prio, :change_date] + conf.columns = [:name, :type, :content, :ttl, :prio, :change_date, :authentication_token] conf.create.columns = [:name, :content, :ttl] conf.update.columns = [:name, :content, :ttl] conf.subform.columns = [:content, :ttl] diff --git a/app/controllers/records_controller.rb b/app/controllers/records_controller.rb index f393b31..58c8cd0 100644 --- a/app/controllers/records_controller.rb +++ b/app/controllers/records_controller.rb @@ -18,7 +18,7 @@ class RecordsController < ApplicationController active_scaffold :record do |conf| conf.sti_children = [:SOA, :NS, :MX, :A, :CNAME, :TXT] - conf.columns = [:name, :type, :content, :ttl, :prio, :change_date] + conf.columns = [:name, :type, :content, :ttl, :prio, :change_date, :authentication_token] conf.columns[:change_date].list_ui = :timestamp conf.columns[:ttl].options = {:i18n_number => {:delimiter => ''}} # conf.create.link.label = "Add Record" diff --git a/app/controllers/soas_controller.rb b/app/controllers/soas_controller.rb index aef601b..1248a78 100644 --- a/app/controllers/soas_controller.rb +++ b/app/controllers/soas_controller.rb @@ -1,6 +1,6 @@ class SoasController < ApplicationController active_scaffold :soa do |conf| - conf.list.columns = [:name, :type, :content, :ttl, :prio, :change_date] + conf.columns = [:name, :type, :content, :ttl, :prio, :change_date, :authentication_token] conf.create.columns = [:contact, :ttl] conf.update.columns = [:contact, :ttl] conf.columns[:change_date].list_ui = :timestamp diff --git a/app/controllers/txts_controller.rb b/app/controllers/txts_controller.rb index ed8f778..4a169ba 100644 --- a/app/controllers/txts_controller.rb +++ b/app/controllers/txts_controller.rb @@ -1,6 +1,6 @@ class TxtsController < ApplicationController active_scaffold :txt do |conf| - conf.list.columns = [:name, :type, :content, :ttl, :prio, :change_date] + conf.columns = [:name, :type, :content, :ttl, :prio, :change_date, :authentication_token] conf.create.columns = [:name, :content, :ttl] conf.update.columns = [:name, :content, :ttl] # conf.columns[:content].label = 'Content' @@ -24,8 +24,9 @@ class TxtsController < ApplicationController # override, we make our own sti logic def new_model - model = beginning_of_chain - model.new + model = beginning_of_chain.new + model.name = nested_parent_record.name + model end # override to close create form after success diff --git a/app/models/record.rb b/app/models/record.rb index 54e2e17..f21e053 100644 --- a/app/models/record.rb +++ b/app/models/record.rb @@ -14,7 +14,9 @@ class Record < ActiveRecord::Base :less_than => 2**31, :only_integer => true }, :allow_blank => true + validates :authentication_token, :presence => true, :uniqueness => true + before_validation :generate_token, :on => :create before_validation :prepare_name! before_save :update_change_date after_save :update_soa_serial @@ -31,6 +33,15 @@ class Record < ActiveRecord::Base def to_label; "#{type} #{content}" end + # Generate a token by looping and ensuring does not already exist. + # stolen from Devise + def generate_token + self.authentication_token = loop do + token = Devise.friendly_token + break token unless self.class.exists?(:authentication_token => token) + end + end + protected def prepare_name! diff --git a/app/validators/ip_validator.rb b/app/validators/ip_validator.rb index 0adf150..a3c983f 100644 --- a/app/validators/ip_validator.rb +++ b/app/validators/ip_validator.rb @@ -3,6 +3,27 @@ # @author Kim Nørgaard class IpValidator < ActiveModel::EachValidator + IPV4_REGEX = /^ + (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3} + (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) + $/x + + IPV6_REGEX = /^ + ( + (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) + |(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) + |(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) + |(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) + |(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) + |(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) + |(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) + |(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) + |(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) + |([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) + |(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) + |(([0-9A-Fa-f]{1,4}:){1,7}:) + )$/x + # @param [Hash] options Options for validation # @option options [Symbol] :ip_type (:any) The IP address type (:any, :v4 or :v6) # @see ActiveModel::EachValidator#new @@ -26,31 +47,14 @@ private # @param [String] address the ipv4 address # @return [Boolean] the validation result def ipv4?(address) - address =~ /^ - (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3} - (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) - $/x + address =~ IPV4_REGEX end # Validates IPv6 address # @param [String] address the ipv6 address # @return [Boolean] the validation result def ipv6?(address) - address =~ /^ - ( - (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) - |(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) - |(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) - |(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) - |(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) - |(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) - |(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) - |(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) - |(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) - |([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) - |(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) - |(([0-9A-Fa-f]{1,4}:){1,7}:) - )$/x + address =~ IPV6_REGEX end # Validates IP (v4 or v6) address diff --git a/app/views/fragments/_top.html.erb b/app/views/fragments/_top.html.erb index fe80fb1..3640564 100644 --- a/app/views/fragments/_top.html.erb +++ b/app/views/fragments/_top.html.erb @@ -16,7 +16,7 @@