From 24554e44478fadb3c86dea76773d456c5284596d Mon Sep 17 00:00:00 2001 From: Patrick J Cherry Date: Thu, 22 Mar 2012 17:31:27 +0000 Subject: Rejigged caching of alert_group, and the way source lists are searched, to (hopefully) cut down on DNS resolving. --- lib/mauve/alert.rb | 124 ++++++++++++++++++++++++++++++++++------------ lib/mauve/mauve_resolv.rb | 8 +-- lib/mauve/source_list.rb | 37 ++++++++------ 3 files changed, 117 insertions(+), 52 deletions(-) diff --git a/lib/mauve/alert.rb b/lib/mauve/alert.rb index d867ccd..edaa2ad 100644 --- a/lib/mauve/alert.rb +++ b/lib/mauve/alert.rb @@ -146,15 +146,18 @@ module Mauve # # Find the AlertGroup by name if we've got a cached value # - @alert_group = AlertGroup.all.find{|a| self.cached_alert_group == a.name} if @alert_group.nil? and self.cached_alert_group + alert_group = AlertGroup.all.find{|a| self.cached_alert_group == a.name} if self.cached_alert_group - # - # If we've not found the alert group by name, or the object hasn't been - # saved since the alert group was last resolved, look for it again. - # - @alert_group = AlertGroup.matches(self).first if @alert_group.nil? + if alert_group.nil? + # + # If we've not found the alert group by name look for it again, the + # proper way. + # + alert_group = AlertGroup.matches(self).first + self.cached_alert_group = alert_group.name unless alert_group.nil? + end - @alert_group + alert_group end # Pick out the source lists that match this alert by subject. @@ -169,32 +172,53 @@ module Mauve # @param [String] listname # @return [Boolean] def in_source_list?(listname) - list = Mauve::Configuration.current.source_lists[listname] - return false unless list.is_a?(SourceList) - self.subject_hosts_and_ips.any?{|host| list.includes?(host) } - end + source_list = Mauve::Configuration.current.source_lists[listname] + return false unless source_list.is_a?(SourceList) - def subject_hosts_and_ips - if @subject_hosts_and_ips.nil? or @subject_hosts_and_ips_last_resolved_at.nil? or (Time.now - 1800) > @subject_hosts_and_ips_last_resolved_at - host = self.subject - # - # Pick out hostnames from URIs. - # - if host =~ /^[a-z][a-z0-9+-]+:\/\// - begin - uri = URI.parse(host) - host = uri.host unless uri.host.nil? - rescue URI::InvalidURIError => ex - # ugh - logger.warn "Did not recognise URI #{host}" - end + host = self.subject + + # + # Pick out hostnames from URIs. + # + if host =~ /^[a-z][a-z0-9+-]+:\/\// + begin + uri = URI.parse(host) + host = uri.host unless uri.host.nil? + rescue URI::InvalidURIError => ex + # ugh + logger.warn "Did not recognise URI #{host}" end + end - @subject_hosts_and_ips = ([host] + MauveResolv.get_ips_for(host)).flatten - @subject_hosts_and_ips_last_resolved_at = Time.now + return true if source_list.list.any? do |l| + case l + when String + host == l + when Regexp + host =~ l + when IPAddr + begin + l.include?(IPAddr.new(host)) + rescue ArgumentError + # rescue random IPAddr argument errors + false + end + else + false + end end - @subject_hosts_and_ips + return false unless source_list.list.any?{|l| l.is_a?(IPAddr)} + + @subject_ips ||= MauveResolv.get_ips_for(host).collect{|i| IPAddr.new(i)} + + return false if @subject_ips.nil? or @subject_ips.empty? + + return source_list.list.select{|i| i.is_a?(IPAddr)}.any? do |list_ip| + @subject_ips.any?{|ip| list_ip.include?(ip)} + end + + return false end # Returns the alert level @@ -229,6 +253,38 @@ module Mauve # # @return [String] def detail; attribute_get(:detail) || "_No detail set._" ; end + + # + # Set the subject -- this clears the cached_alert_group. + # + def subject=(s) + attribute_set(:subject, s) + self.cached_alert_group = nil + end + + # + # Set the detail -- this clears the cached_alert_group. + # + def detail=(s) + attribute_set(:detail, s) + self.cached_alert_group = nil + end + + # + # Set the source -- this clears the cached_alert_group. + # + def source=(s) + attribute_set(:source, s) + self.cached_alert_group = nil + end + + # + # Set the summary -- this clears the cached_alert_group. + # + def summary=(s) + attribute_set(:summary, s) + self.cached_alert_group = nil + end protected @@ -326,6 +382,10 @@ module Mauve true end + # + # + # + # Remove all history for an alert, when an alert is destroyed. # # @@ -372,9 +432,8 @@ module Mauve # # Re-cache the alert group. # - @alert_group = nil self.cached_alert_group = nil - self.cached_alert_group = self.alert_group.name + self.alert_group unless save logger.error("Couldn't save #{self}") @@ -428,9 +487,8 @@ module Mauve # # Re-cache the alert group. # - @alert_group = nil self.cached_alert_group = nil - self.cached_alert_group = self.alert_group.name + self.alert_group logger.info("Postponing raise of #{self} until #{postpone_until} as it was last updated in a prior run of Mauve.") else @@ -446,7 +504,7 @@ module Mauve # # Cache the alert group, but only if not already set. # - self.cached_alert_group = self.alert_group.name if self.cached_alert_group.nil? + self.alert_group end unless save diff --git a/lib/mauve/mauve_resolv.rb b/lib/mauve/mauve_resolv.rb index a9c7526..8ce8969 100644 --- a/lib/mauve/mauve_resolv.rb +++ b/lib/mauve/mauve_resolv.rb @@ -15,12 +15,10 @@ module Mauve # @return [Array] Array of IP addresses, as Strings. # def get_ips_for(host) - pp host ips = [] - @count ||= 0 Resolv::DNS.open do |dns| %w(A AAAA).each do |type| - @count += 1 + self.count += 1 if $debug begin ips += dns.getresources(host, Resolv::DNS::Resource::IN.const_get(type)).collect{|a| a.address.to_s} rescue Resolv::ResolvError, Resolv::ResolvTimeout => e @@ -35,6 +33,10 @@ module Mauve @count ||= 0 end + def count=(c) + @count = c + end + # @return [Log4r::Logger] def logger @logger ||= Log4r::Logger.new(self.to_s) diff --git a/lib/mauve/source_list.rb b/lib/mauve/source_list.rb index 63a7aa6..a6baa2b 100644 --- a/lib/mauve/source_list.rb +++ b/lib/mauve/source_list.rb @@ -19,7 +19,7 @@ module Mauve # class SourceList - attr_reader :label, :list, :last_resolved_at + attr_reader :label, :last_resolved_at ## Default contructor. def initialize (label) @@ -101,6 +101,15 @@ module Mauve @logger ||= Log4r::Logger.new self.class.to_s end + def list + # + # Redo resolution every thirty minutes + # + resolve if @resolved_list.empty? or @last_resolved_at.nil? or (Time.now - 1800) > @last_resolved_at + + @resolved_list + end + # # Return whether or not a list contains a source. # @@ -117,11 +126,6 @@ module Mauve # @param [String] host The host to look for. # @return [Boolean] def includes?(host) - # - # Redo resolution every thirty minutes - # - resolve if @resolved_list.empty? or @last_resolved_at.nil? or (Time.now - 1800) > @last_resolved_at - # # Pick out hostnames from URIs. # @@ -135,7 +139,7 @@ module Mauve end end - return true if @resolved_list.any? do |l| + return true if self.list.any? do |l| case l when String host == l @@ -153,15 +157,15 @@ module Mauve end end -# return false unless @resolved_list.any?{|l| l.is_a?(IPAddr)} -# -# ips = MauveResolv.get_ips_for(host).collect{|i| IPAddr.new(i)} -# -# return false if ips.empty? -# -# return @resolved_list.select{|i| i.is_a?(IPAddr)}.any? do |list_ip| -# ips.any?{|ip| list_ip.include?(ip)} -# end + return false unless self.list.any?{|l| l.is_a?(IPAddr)} + + ips = MauveResolv.get_ips_for(host).collect{|i| IPAddr.new(i)} + + return false if ips.empty? + + return self.list.select{|i| i.is_a?(IPAddr)}.any? do |list_ip| + ips.any?{|ip| list_ip.include?(ip)} + end return false end @@ -181,6 +185,7 @@ module Mauve host end end + @resolved_list = new_list.flatten end end -- cgit v1.2.1 From 499c144b8cfd973d3766fd81a4e702aa0013de0e Mon Sep 17 00:00:00 2001 From: Patrick J Cherry Date: Thu, 22 Mar 2012 17:39:26 +0000 Subject: Update to alert to clear cached IPs for the alert subject when it changes. --- lib/mauve/alert.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/mauve/alert.rb b/lib/mauve/alert.rb index edaa2ad..9d6ab85 100644 --- a/lib/mauve/alert.rb +++ b/lib/mauve/alert.rb @@ -199,7 +199,7 @@ module Mauve when IPAddr begin l.include?(IPAddr.new(host)) - rescue ArgumentError + rescue ArgumentError => err # rescue random IPAddr argument errors false end @@ -258,32 +258,33 @@ module Mauve # Set the subject -- this clears the cached_alert_group. # def subject=(s) - attribute_set(:subject, s) self.cached_alert_group = nil + @subject_ips = nil + attribute_set(:subject, s) end # # Set the detail -- this clears the cached_alert_group. # def detail=(s) - attribute_set(:detail, s) self.cached_alert_group = nil + attribute_set(:detail, s) end # # Set the source -- this clears the cached_alert_group. # def source=(s) - attribute_set(:source, s) self.cached_alert_group = nil + attribute_set(:source, s) end # # Set the summary -- this clears the cached_alert_group. # def summary=(s) - attribute_set(:summary, s) self.cached_alert_group = nil + attribute_set(:summary, s) end protected -- cgit v1.2.1 From 2622cd5d2cb322b78229d345d82076a582925ae2 Mon Sep 17 00:00:00 2001 From: Patrick J Cherry Date: Thu, 22 Mar 2012 17:40:15 +0000 Subject: Updated a couple of test helpers. --- test/th_mauve.rb | 2 +- test/th_mauve_resolv.rb | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test/th_mauve.rb b/test/th_mauve.rb index 7972b9d..2b97e64 100644 --- a/test/th_mauve.rb +++ b/test/th_mauve.rb @@ -69,7 +69,7 @@ module Mauve @logger = Log4r::Logger.new 'Mauve' @outputter = Mauve::TestOutputter.new("test") @outputter.formatter = Log4r::PatternFormatter.new( :pattern => "%d %l %m" ) - @outputter.level = Log4r::WARN + @outputter.level = ($debug ? Log4r::DEBUG : Log4r::WARN) @logger.outputters << @outputter return @logger end diff --git a/test/th_mauve_resolv.rb b/test/th_mauve_resolv.rb index 98b597b..10bdacc 100644 --- a/test/th_mauve_resolv.rb +++ b/test/th_mauve_resolv.rb @@ -14,13 +14,19 @@ module Mauve alias_method :get_ips_for_without_testing, :get_ips_for def get_ips_for_with_testing(host) + lookup = { "test-1.example.com" => %w(1.2.3.4 2001:1:2:3::4), "test-2.example.com" => %w(1.2.3.5 2001:1:2:3::5), "www.example.com" => %w(1.2.3.4), "www2.example.com" => %w(1.2.3.5 2001:2::2) } - lookup[host] || get_ips_for_without_testing(host) + if lookup.has_key?(host) + self.count += lookup[host].length if $debug + lookup[host] + else + get_ips_for_without_testing(host) + end end alias_method :get_ips_for, :get_ips_for_with_testing -- cgit v1.2.1 From dc443284c4b5f59a4447797f88730d9fe1bc0b45 Mon Sep 17 00:00:00 2001 From: Patrick J Cherry Date: Thu, 22 Mar 2012 17:40:38 +0000 Subject: Tidied up login authentication + tests (woo!) --- lib/mauve/authentication.rb | 9 ++- lib/mauve/web_interface.rb | 17 ++-- test/tc_mauve_web_interface.rb | 175 +++++++++++++++++++++++++++++++++++++++++ test/test_mauve.rb | 1 + views/login.haml | 2 +- 5 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 test/tc_mauve_web_interface.rb diff --git a/lib/mauve/authentication.rb b/lib/mauve/authentication.rb index d0d4596..54743f1 100644 --- a/lib/mauve/authentication.rb +++ b/lib/mauve/authentication.rb @@ -139,7 +139,7 @@ module Mauve client.login(login, response) return true rescue XMLRPC::FaultException => fault - logger.warn "Authentication for #{login} failed: #{fault.faultCode}: #{fault.faultString}" + logger.warn "#{self.class} for #{login} failed: #{fault.faultCode}: #{fault.faultString}" return false rescue IOError => ex logger.warn "#{ex.class} during auth for #{login} (#{ex.to_s})" @@ -164,7 +164,12 @@ module Mauve # @return [Boolean] def authenticate(login,password) super - Digest::SHA1.hexdigest(password) == Mauve::Configuration.current.people[login].password + if ( Digest::SHA1.hexdigest(password) == Mauve::Configuration.current.people[login].password ) + return true + else + logger.warn "#{self.class} for #{login} failed" + return false + end end end diff --git a/lib/mauve/web_interface.rb b/lib/mauve/web_interface.rb index b998ad8..225cc33 100644 --- a/lib/mauve/web_interface.rb +++ b/lib/mauve/web_interface.rb @@ -4,6 +4,7 @@ require 'redcloth' require 'json' require 'mauve/authentication' +require 'mauve/http_server' tilt_lib = "tilt" begin @@ -120,7 +121,6 @@ EOF unless ok_urls.include?(request.path_info) flash['error'] = "You must be logged in to access that page." - status 403 redirect "/login?next_page=#{request.path_info}" unless no_redirect_urls.any?{|u| /^#{u}/ =~ request.path_info } end end @@ -146,7 +146,9 @@ EOF if @person redirect '/' else + @username = nil @next_page = params[:next_page] || '/' + status 403 if flash['error'] haml :login end end @@ -154,7 +156,7 @@ EOF post '/login' do usr = params['username'].to_s pwd = params['password'].to_s - next_page = params['next_page'].to_s + next_page = params['next_page'] || "/" # # Make sure we don't magically logout automatically :) @@ -165,14 +167,19 @@ EOF session['username'] = usr redirect next_page else - flash['error'] = "You must be logged in to access that page." - redirect "/login?next_page=#{next_page}" + flash['error'] = "Authentication failed." + status 401 +# redirect "/login?next_page=#{next_page}" + @title += " Login" + @username = usr + @next_page = next_page + haml :login end end get '/logout' do session.delete('username') - flash['error'] = "You have logged out!" + flash['info'] = "You have logged out!" redirect '/login' end diff --git a/test/tc_mauve_web_interface.rb b/test/tc_mauve_web_interface.rb new file mode 100644 index 0000000..a120c37 --- /dev/null +++ b/test/tc_mauve_web_interface.rb @@ -0,0 +1,175 @@ +$:.unshift "../lib" + +require 'th_mauve' +require 'th_mauve_resolv' + +require 'mauve/alert' +require 'mauve/proto' +require 'mauve/server' +require 'mauve/configuration' +require 'mauve/configuration_builder' +require 'mauve/configuration_builders' + +require 'rack/test' + +ENV['RACK_ENV'] = 'test' + +class WebInterfaceTest < Mauve::UnitTest + include Rack::Test::Methods + include Mauve + + SESSION_KEY="mauvealert" + + class SessionData + def initialize(cookies) + @cookies = cookies + @data = cookies[WebInterfaceTest::SESSION_KEY] + if @data + @data = @data.unpack("m*").first + @data = Marshal.load(@data) + else + @data = {} + end + end + + def [](key) + @data[key] + end + + def []=(key, value) + @data[key] = value + session_data = Marshal.dump(@data) + session_data = [session_data].pack("m*") + @cookies.merge("#{WebInterfaceTest::SESSION_KEY}=#{Rack::Utils.escape(session_data)}", URI.parse("//example.org//")) + raise "session variable not set" unless @cookies[WebInterfaceTest::SESSION_KEY] == session_data + end + end + + def session + SessionData.new(rack_test_session.instance_variable_get(:@rack_mock_session).cookie_jar) + end + + def setup + super + setup_database + + # + # BytemarkAuth test users are: + # + # test1: ummVRu7qF + # test2: POKvBqLT7 + # + config =< WebInterfaceTest::SESSION_KEY, :secret => "testing-1234") + end + + def test_log_in + # Check we get the login page when going to "/" before logging in. + get '/' + follow_redirect! while last_response.redirect? + assert last_response.ok? + assert last_response.body.include?("Mauve: Login") + assert session['__FLASH__'].empty? + + # Check we can access this page before logging in. + get '/alerts' + assert(session['__FLASH__'].has_key?(:error),"The flash error wasn't set following forbidden access") + follow_redirect! while last_response.redirect? + assert_equal(403, last_response.status, "The HTTP status wasn't 403") + assert last_response.body.include?("Mauve: Login") + assert session['__FLASH__'].empty? + + # + # Try to falsify our login. + # + session['username'] = "test1" + get '/alerts' + assert(session['__FLASH__'].has_key?(:error),"The flash error wasn't set following forbidden access") + follow_redirect! while last_response.redirect? + assert_equal(403, last_response.status, "The HTTP status wasn't 403") + assert last_response.body.include?("Mauve: Login") + assert session['__FLASH__'].empty? + + # + # OK login with a bad password + # + post '/login', :username => 'test1', :password => 'badpassword' + assert_equal(401, last_response.status, "A bad login did not produce a 401 response") + assert(last_response.body.include?("Mauve: Login")) + assert(session['__FLASH__'].has_key?(:error),"The flash error wasn't set") + + post '/login', :username => 'test1', :password => 'ummVRu7qF' + follow_redirect! while last_response.redirect? + assert last_response.body.include?('Mauve: ') + + get '/logout' + follow_redirect! while last_response.redirect? + assert last_response.ok? + end + + def test_alerts_show_subject + post '/login', :username => 'test1', :password => 'ummVRu7qF' + follow_redirect! while last_response.redirect? + assert last_response.body.include?('Mauve: ') + + a = Alert.new(:source => "www.example.com", :alert_id => "test_raise!") + a.raise! + + get '/alerts/raised/subject' + end + +end + + diff --git a/test/test_mauve.rb b/test/test_mauve.rb index bdce3aa..ce83c6c 100644 --- a/test/test_mauve.rb +++ b/test/test_mauve.rb @@ -24,6 +24,7 @@ tc_mauve_people_list.rb tc_mauve_person.rb tc_mauve_source_list.rb tc_mauve_time.rb +tc_mauve_web_interface.rb ).each do |s| require s end diff --git a/views/login.haml b/views/login.haml index 3deee76..d1e9484 100644 --- a/views/login.haml +++ b/views/login.haml @@ -2,7 +2,7 @@ %fieldset %legend Please log in %label{:for => "username"} Username - %input{:name => 'username', :type => 'text', :autocorrect => "off", :autocapitalize => "off"}/ + %input{:name => 'username', :type => 'text', :autocorrect => "off", :autocapitalize => "off", :value => @username}/ %br %label{:for => "password", :title => "This is either your Single Sign On password or a PIN."} Password / PIN %input{:name => 'password', :type => 'password'}/ -- cgit v1.2.1