diff options
-rw-r--r-- | lib/mauve/alert.rb | 38 | ||||
-rw-r--r-- | lib/mauve/authentication.rb | 90 | ||||
-rw-r--r-- | lib/mauve/configuration_builders/server.rb | 34 | ||||
-rw-r--r-- | lib/mauve/notifier.rb | 19 | ||||
-rw-r--r-- | lib/mauve/processor.rb | 82 | ||||
-rw-r--r-- | lib/mauve/server.rb | 140 | ||||
-rw-r--r-- | test/alert_and_notification_logic.rb | 391 | ||||
-rw-r--r-- | test/tc_mauve_alert_changed.rb | 27 | ||||
-rw-r--r-- | test/tc_mauve_authentication.rb | 168 | ||||
-rw-r--r-- | test/tc_mauve_notification.rb | 30 | ||||
-rw-r--r-- | test/tc_mauve_web_interface.rb | 24 | ||||
-rw-r--r-- | test/test_mauve.rb | 21 | ||||
-rw-r--r-- | test/th_mauve.rb | 4 |
13 files changed, 502 insertions, 566 deletions
diff --git a/lib/mauve/alert.rb b/lib/mauve/alert.rb index 28079a2..f249913 100644 --- a/lib/mauve/alert.rb +++ b/lib/mauve/alert.rb @@ -297,6 +297,7 @@ module Mauve attributes.each do |key, val| next if html_permitted_in.include?(key) + next unless attribute_dirty?(key) next unless val.is_a?(String) attribute_set(key, Alert.remove_html(val)) @@ -304,6 +305,7 @@ module Mauve attributes.each do |key, val| next unless html_permitted_in.include?(key) + next unless attribute_dirty?(key) next unless val.is_a?(String) attribute_set(key, Alert.clean_html(val)) @@ -607,29 +609,31 @@ module Mauve end class << self - - # Removes HTML from a string + + # Removes or cleans HTML from a string # - # @param [String] txt String to clean + # + # @param [String] str String to clean + # @param [Hash] conf Sanitize::Config thingy # @return [String] - def remove_html(txt) - Sanitize.clean( - txt.to_s, - Sanitize::Config::DEFAULT - ) + def remove_html(str, conf = Sanitize::Config::DEFAULT) + raise ArgumentError, "Expected a string, got a #{str.class}" unless str.is_a?(String) + + if str =~ /<[^0-9 <&.-]/ + Sanitize.clean( str, conf ) + else + str + end end # Cleans HTML in a string, removing dangerous elements/contents. # - # @param [String] txt String to clean + # @param [String] str String to clean # @return [String] - def clean_html(txt) - Sanitize.clean( - txt.to_s, - Sanitize::Config::RELAXED.merge({:remove_contents => true}) - ) + def clean_html(str) + remove_html(str, Sanitize::Config::RELAXED.merge({:remove_contents => true})) end - + # All alerts currently raised # # @return [Array] @@ -725,7 +729,7 @@ module Mauve # Make sure there is no HTML in the update source. Need to do this # here because we use the html-free version in the database save hook. # - update.source = Alert.remove_html(update.source) + update.source = Alert.remove_html(update.source.to_s) # Update each alert supplied # @@ -749,7 +753,7 @@ module Mauve # because of the database save hook will clear it out, causing this # search to fail. # - alert.id = Alert.remove_html(alert.id) + alert.id = Alert.remove_html(alert.id.to_s) alert_db = first(:alert_id => alert.id, :source => update.source) || new(:alert_id => alert.id, :source => update.source) diff --git a/lib/mauve/authentication.rb b/lib/mauve/authentication.rb index 54743f1..c467a1d 100644 --- a/lib/mauve/authentication.rb +++ b/lib/mauve/authentication.rb @@ -1,7 +1,11 @@ # encoding: UTF-8 require 'sha1' require 'xmlrpc/client' -require 'timeout' + +# +# This allows poking of the SSL attributes of the http client. +# +module XMLRPC ; class Client ; attr_reader :http ; end ; end module Mauve @@ -23,8 +27,6 @@ module Mauve raise ArgumentError.new("Password must be a string, not a #{password.class}") if String != password.class raise ArgumentError.new("Login or/and password is/are empty.") if login.empty? || password.empty? - return false unless Mauve::Configuration.current.people.has_key?(login) - false end @@ -68,7 +70,7 @@ module Mauve unless true == result logger.info "Authentication for #{login} failed" # Rate limit - sleep 5 + sleep Server.instance.failed_login_delay end result @@ -83,45 +85,6 @@ module Mauve Mauve::Authentication::ORDER << self - # Set up the Bytemark authenticator - # - # @todo allow configuration of where the server is. - # - # @param [String] srv Authentication server name - # @param [String] port Port overwhich authentication should take place - # - # @return [Mauve::AuthBytemark] - # - def initialize (srv='auth.bytemark.co.uk', port=443) - raise ArgumentError.new("Server must be a String, not a #{srv.class}") if String != srv.class - raise ArgumentError.new("Port must be a Fixnum, not a #{port.class}") if Fixnum != port.class - @srv = srv - @port = port - @timeout = 7 - - self - end - - # Tests to see if a server is alive, alive-o. - # - # @deprecated Not really needed. - # - # @return [Boolean] - def ping - begin - Timeout.timeout(@timeout) do - s = TCPSocket.open(@srv, @port) - s.close() - return true - end - rescue Timeout::Error => ex - return false - rescue => ex - return false - end - return false - end - # Authenticate against the Bytemark server # # @param [String] login @@ -131,15 +94,38 @@ module Mauve def authenticate(login, password) super - client = XMLRPC::Client.new(@srv,"/",@port,nil,nil,nil,nil,true,@timeout).proxy("bytemark.auth") + # + # Don't bother checking if no auth_url has been set. + # + return false unless Server.instance.bytemark_auth_url.is_a?(URI) + + # + # Don't bother checking if the person doesn't exist. + # + return false unless Mauve::Configuration.current.people.has_key?(login) + + uri = Server.instance.bytemark_auth_url + timeout = Server.instance.remote_http_timeout + # host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil, user=nil, password=nil, use_ssl=nil, timeout=nil) + client = XMLRPC::Client.new(uri.host, uri.path, uri.port, nil, nil, uri.user, uri.password, uri.scheme == "https", timeout) + + # + # Make sure we verify our peer before attempting login. + # + if client.http.use_ssl? + client.http.ca_path = "/etc/ssl/certs/" + client.http.verify_mode = Server.instance.remote_https_verify_mode + end begin - challenge = client.getChallengeForUser(login) + proxy = client.proxy("bytemark.auth") + challenge = proxy.getChallengeForUser(login) response = Digest::SHA1.new.update(challenge).update(password).hexdigest - client.login(login, response) + proxy.login(login, response) return true rescue XMLRPC::FaultException => fault - logger.warn "#{self.class} for #{login} failed: #{fault.faultCode}: #{fault.faultString}" + logger.warn "#{self.class} for #{login} failed" + logger.debug "#{fault.faultCode}: #{fault.faultString}" return false rescue IOError => ex logger.warn "#{ex.class} during auth for #{login} (#{ex.to_s})" @@ -164,6 +150,16 @@ module Mauve # @return [Boolean] def authenticate(login,password) super + # + # Don't bother checking if the person doesn't exist. + # + return false unless Mauve::Configuration.current.people.has_key?(login) + + # + # Don't bother checking if no password has been set. + # + return false if Mauve::Configuration.current.people[login].password.nil? + if ( Digest::SHA1.hexdigest(password) == Mauve::Configuration.current.people[login].password ) return true else diff --git a/lib/mauve/configuration_builders/server.rb b/lib/mauve/configuration_builders/server.rb index e3654b9..d22ed87 100644 --- a/lib/mauve/configuration_builders/server.rb +++ b/lib/mauve/configuration_builders/server.rb @@ -163,7 +163,39 @@ module Mauve # The period of sleep during which no heartbeats are raised. # is_attribute "initial_sleep" - + + # + # The next two attributes determine if packet/notitication bufferes are + # used. These both default to "true" + # + is_attribute "use_packet_buffer" + is_attribute "use_notification_buffer" + + # + # This is where the calendar is located. The request paths are hard-coded. + # + is_attribute "bytemark_calendar_url" + + # + # This is where the Bytemark authentication server is located. + # + is_attribute "bytemark_auth_url" + + # + # This is the level of SSL verification used when making external HTTPS connections. + # + is_attribute "remote_https_verify_mode" + + # + # This is the default timeout when making remote HTTP requests + # + is_attribute "remote_http_timeout" + + # + # This is the default sleep time after an authentication attempt has failed. + # + is_attribute "failed_login_delay" + def builder_setup @result = Mauve::Server.instance end diff --git a/lib/mauve/notifier.rb b/lib/mauve/notifier.rb index 8a26b2c..c473acf 100644 --- a/lib/mauve/notifier.rb +++ b/lib/mauve/notifier.rb @@ -31,6 +31,18 @@ module Mauve end end + + # + # This sends the notification for an alert + # + def notify(alert, at) + if alert.alert_group.nil? + logger.warn "Could not notify for #{alert} since there are no matching alert groups" + else + alert.alert_group.notify(alert, at) + end + end + private @@ -86,12 +98,7 @@ module Mauve # Empty the buffer, one notification at a time. # sz.times do - alert, at = Server.notification_pop - if alert.alert_group.nil? - logger.warn "Could not notify for #{alert} since there are no matching alert groups" - else - alert.alert_group.notify(alert, at) - end + notify(*Server.notification_pop) end end diff --git a/lib/mauve/processor.rb b/lib/mauve/processor.rb index 9896530..0ac7b59 100644 --- a/lib/mauve/processor.rb +++ b/lib/mauve/processor.rb @@ -78,6 +78,44 @@ module Mauve # do_processor end + + # This processes an incoming packet. It is in a seperate method so it can + # be (de)coupled as needed from the UDP server. + # + def process_packet(data, client, received_at) + # + # Uh-oh. Nil data? That's craaaazy + # + return nil if data.nil? + + ip_source = "#{client[3]}" + update = Proto::AlertUpdate.new + + update.parse_from_string(data) + + if @transmission_id_cache[update.transmission_id.to_s] + logger.debug("Ignoring duplicate transmission id #{update.transmission_id}") + return nil + end + + logger.debug "Update #{update.transmission_id} sent at #{update.transmission_time} received at #{received_at.to_i} from "+ + "'#{update.source}'@#{ip_source} alerts #{update.alert.length}" + + Alert.receive_update(update, received_at, ip_source) + + rescue Protobuf::InvalidWireType, + NotImplementedError, + DataObjects::IntegrityError => ex + + logger.error "#{ex} (#{ex.class}) while parsing #{data.length} bytes "+ + "starting '#{data[0..15].inspect}' from #{ip_source}" + + logger.debug ex.backtrace.join("\n") + + ensure + @transmission_id_cache[update.transmission_id.to_s] = Time.now + end + private @@ -157,52 +195,12 @@ module Mauve sz = Server.packet_buffer_size sz.times do - data, client, received_at = Server.packet_pop - - # - # Uh-oh. Nil data? That's craaaazy - # - next if data.nil? - - - # logger.debug("Got #{data.inspect} from #{client.inspect}") - - ip_source = "#{client[3]}" - update = Proto::AlertUpdate.new - - begin - update.parse_from_string(data) - - if @transmission_id_cache[update.transmission_id.to_s] - logger.debug("Ignoring duplicate transmission id #{update.transmission_id}") - # - # Continue with next packet. - # - next - end - - logger.debug "Update #{update.transmission_id} sent at #{update.transmission_time} received at #{received_at.to_i} from "+ - "'#{update.source}'@#{ip_source} alerts #{update.alert.length}" - - Alert.receive_update(update, received_at, ip_source) - - rescue Protobuf::InvalidWireType, - NotImplementedError, - DataObjects::IntegrityError => ex - - logger.error "#{ex} (#{ex.class}) while parsing #{data.length} bytes "+ - "starting '#{data[0..15].inspect}' from #{ip_source}" - - logger.debug ex.backtrace.join("\n") - - ensure - @transmission_id_cache[update.transmission_id.to_s] = Time.now - end + process_packet(*Server.packet_pop) end end def timer_should_stop? - (Server.packet_buffer_size > 0) + (Server.packet_buffer_size > 0) or self.should_stop? end end diff --git a/lib/mauve/server.rb b/lib/mauve/server.rb index 3e82858..2b0e101 100644 --- a/lib/mauve/server.rb +++ b/lib/mauve/server.rb @@ -27,6 +27,7 @@ module Mauve attr_reader :hostname, :database, :initial_sleep attr_reader :packet_buffer, :notification_buffer, :started_at + attr_reader :bytemark_auth_url, :bytemark_calendar_url, :remote_http_timeout, :remote_https_verify_mode, :failed_login_delay include Singleton @@ -40,7 +41,7 @@ module Mauve @started_at = Time.now @initial_sleep = 300 - + # # Keep these queues here to prevent a crash in a subthread losing all the # subsquent things in the queue. @@ -49,6 +50,23 @@ module Mauve @notification_buffer = [] # + # Set the auth/calendar URLs + # + @bytemark_auth_url = nil + @bytemark_calendar_url = nil + + # + # Set a couple of params for remote HTTP requests. + # + @remote_http_timeout = 5 + @remote_https_verify_mode = OpenSSL::SSL::VERIFY_PEER + + # + # Rate limit login attempts to limit the success of brute-forcing. + # + @failed_login_delay = 1 + + # # Set up a blank config. # Configuration.current = Configuration.new if Mauve::Configuration.current.nil? @@ -69,7 +87,110 @@ module Mauve raise ArgumentError, "database must be a string" unless d.is_a?(String) @database = d end - + + # Sets up the packet buffer (or not). The argument can be "false" or "no" + # or a FalseClass object for no. Anything else makes no change. + # + # @param [String] arg + # @return [Array or nil] + def use_packet_buffer=(arg) + if arg.is_a?(FalseClass) or arg =~ /^(n(o)?|f(alse)?)$/i + @packet_buffer = nil + end + + @packet_buffer + end + + # Sets up the notification buffer (or not). The argument can be "false" or + # "no" or a FalseClass object for no. Anything else makes no change. + # + # @param [String] arg + # @return [Array or nil] + def use_notification_buffer=(arg) + if arg.is_a?(FalseClass) or arg =~ /^(n(o)?|f(alse)?)$/i + @notification_buffer = nil + end + + @notification_buffer + end + + # Set the calendar URL. + # + # @param [String] arg + # @return [URI] + def bytemark_calendar_url=(arg) + raise ArgumentError, "bytemark_calendar_url must be a string" unless arg.is_a?(String) + + @bytemark_calendar_url = URI.parse(arg) + + # + # Make sure we get an HTTP URL. + # + raise ArgumentError, "bytemark_calendar_url must be an HTTP(S) URL." unless %w(http https).include?(@bytemark_calendar_url.scheme) + + # + # Set a default request path, if none was given + # + @bytemark_calendar_url.path="/" if @bytemark_calendar_url.path.empty? + + @bytemark_calendar_url + end + + # Set the Bytemark Authentication URL + # + # @param [String] arg + # @return [URI] + def bytemark_auth_url=(arg) + raise ArgumentError, "bytemark_auth_url must be a string" unless arg.is_a?(String) + + @bytemark_auth_url = URI.parse(arg) + # + # Make sure we get an HTTP URL. + # + raise ArgumentError, "bytemark_auth_url must be an HTTP(S) URL." unless %w(http https).include?(@bytemark_auth_url.scheme) + + # + # Set a default request path, if none was given + # + @bytemark_auth_url.path="/" if @bytemark_auth_url.path.empty? + + @bytemark_auth_url + end + + # Sets the timeout when making remote HTTP requests + # + # @param [Integer] arg + # @return [Integer] + def remote_http_timeout=(arg) + raise ArgumentError, "initial_sleep must be an integer" unless s.is_a?(Integer) + @remote_http_timeout = arg + end + + # Sets the SSL verification mode when makeing remote HTTPS requests + # + # @param [String] arg must be one of "none" or "peer" + # @return [Constant] + def remote_https_verify_mode=(arg) + @remote_https_verify_mode = case arg + when "peer" + OpenSSL::SSL::VERIFY_PEER + when "none" + OpenSSL::SSL::VERIFY_NONE + else + raise ArgumentError, "remote_https_verify_mode must be either 'peer' or 'none'" + end + end + + # Set the delay added following a failed login attempt. + # + # @param [Numeric] arg Number of seconds to delay following a failed login attempt + # @return [Numeric] + # + def failed_login_delay=(arg) + raise ArgumentError, "initial_sleep must be numeric" unless arg.is_a?(Numeric) + @failed_login_delay = arg + end + # Set the sleep period during which notifications about old alerts are # suppressed. # @@ -97,11 +218,8 @@ module Mauve # @return [NilClass] def setup # + # Set up the database # - # - @packet_buffer = [] - @notification_buffer = [] - DataMapper.setup(:default, @database) # DataMapper.logger = Log4r::Logger.new("Mauve::DataMapper") @@ -261,6 +379,8 @@ module Mauve # @param [String] a Packet from the UDP server def packet_enq(a) instance.packet_buffer.push(a) + rescue NoMethodError + Processor.instance.process_packet(*a) end # Shift a packet off the front of the +packet buffer+ @@ -275,6 +395,8 @@ module Mauve # @return [Integer} def packet_buffer_size instance.packet_buffer.size + rescue NoMethodError + 0 end alias packet_push packet_enq @@ -285,6 +407,8 @@ module Mauve # @param [Array] a Notification array, consisting of a Person and the args to Mauve::Person#send_alert def notification_enq(a) instance.notification_buffer.push(a) + rescue NoMethodError + Notifier.instance.notify(*a) end # Shift a notification off the front of the +notification_buffer+ @@ -299,8 +423,10 @@ module Mauve # @return [Integer] def notification_buffer_size instance.notification_buffer.size + rescue NoMethodError + 0 end - + alias notification_push notification_enq alias notification_pop notification_deq diff --git a/test/alert_and_notification_logic.rb b/test/alert_and_notification_logic.rb deleted file mode 100644 index 19b2478..0000000 --- a/test/alert_and_notification_logic.rb +++ /dev/null @@ -1,391 +0,0 @@ -# Mauve server tests - alerts and notification logic. Define the basic workings -# so that we know what should happen when we send sequences of alerts at -# different times. -# -# These aren't really unit tests, just narrative specifications as to what -# should happen under what stimuli. I suspect I will break these down into -# smaller units if things break under otherwise difficult conditions. -# - -$: << __FILE__.split("/")[0..-2].join("/") -require 'test/unit' -require 'mauve_test_helper' -require 'mauve_time' - -class AlertAndNotificationLogic < Test::Unit::TestCase - include MauveTestHelper - - def configuration_template - <<-TEMPLATE - # This is the head of all the configuration files. Filenames are relative - # to the cwd, which is assumed to be a fleeting test directory. - - server { - ip "127.0.0.1" - port #{@port_alerts ||= 44444} - log_file ENV['TEST_LOG'] ? STDOUT : "#{dir}/log" - log_level 0 - database "sqlite3:///#{dir}/mauve_test.db" - transmission_id_expire_time 600 - - # doesn't restart nicely at the moment - #web_interface { - # port #{@port_web ||= 44444} - #} - } - - # - # All notifications are sent to files which we can open up and check during - # our tests. Network delivery is not tested in this script. - # - - notification_method("xmpp") { - deliver_to_queue AlertAndNotificationLogic::Notifications - deliver_to_file "#{dir}/xmpp.txt" - disable_normal_delivery! - - jid "mauveserv@chat.bytemark.co.uk" - password "foo" - } - - notification_method("email") { - deliver_to_queue AlertAndNotificationLogic::Notifications - deliver_to_file "#{dir}/email.txt" - disable_normal_delivery! - - # add in SMTP server, username, password etc. - # default to sending through localhost - from "matthew@bytemark.co.uk" - server "bytemail.bytemark.co.uk" - subject_prefix "[Bytemark alerts] " - - } - - notification_method("sms") { - provider "AQL" - deliver_to_queue AlertAndNotificationLogic::Notifications - deliver_to_file "#{dir}/sms.txt" - disable_normal_delivery! - - username "x" - password "x" - from "01904890890" - max_messages_per_alert 3 - } - - # a person common to all our tests - - person("joe_bloggs") { - urgent { sms("12345") } - normal { email("12345@joe_bloggs.email") } - low { xmpp("12345@joe_bloggs.xmpp") } - } - - person("jimmy_junior") { - urgent { sms("66666") } - normal { email("jimmy@junior.email") } - low { email("jimmy@junior.email") } - } - - alert_group { - includes { source == "rare-and-important" } - acknowledgement_time 60.minutes - level URGENT - - notify("joe_bloggs") { every 10.minutes } - } - - alert_group { - includes { source == "noisy-and-annoying" || alert_id == "whine" } - acknowledgement_time 24.hours - level LOW - - notify("jimmy_junior") { every 2.hours } - notify("joe_bloggs") { - every 30.minutes - during { - unacknowledged 6.hours - } - } - } - - alert_group { - includes { source == "can-wait-until-monday" } - level NORMAL - - notify("jimmy_junior") { - every 30.minutes - during { days_in_week(1..5) && hours_in_day(9..5) } - } - notify("joe_bloggs") { - every 2.hours - during { days_in_week(1..5) && hours_in_day(9..5) } - } - } - - # catch-all - alert_group { - acknowledgement_time 1.minute - level NORMAL - - notify("joe_bloggs") { every 1.hour } - } - TEMPLATE - end - - def setup - start_server(configuration_template) - end - - def teardown - stop_server - # no tests should leave notifications on the stack - assert_no_notification - end - - # Raise one alert, check representation in database, and that alert is - # received as expected. - # - def test_basic_fields_are_recognised - mauvesend("-o my_source -i alert1 -s \"alert1 summary\" -d \"alert1 detail\" -u \"alert1 subject\"") - - assert_not_nil(alert = Alert.first) - assert_equal("my_source", alert.source) - assert_equal("alert1", alert.alert_id) - assert_equal("alert1 summary", alert.summary) - assert_equal("alert1 detail", alert.detail) - assert_equal("alert1 subject", alert.subject) - assert(alert.raised?) - assert(!alert.cleared?) - assert(!alert.acknowledged?) - - with_next_notification do |destination, this_alert, other_alerts| - assert_equal("12345@joe_bloggs.email", destination) - assert_equal(Alert.first, this_alert) - assert_equal([Alert.first], other_alerts) - end - - end - - # Check that a simple automatic raise, acknowledge & auto-clear request - # work properly. - # - def test_auto_raise_and_clear - # Raise the alert, wait for it to be processed - mauvesend("-o my_source -i alert1 -s \"alert1 summary\" -d \"alert1 detail\" -u \"alert1 subject\" -r +5m -c +10m") - - # Check internal state - # - assert(!Alert.first.raised?, "Auto-raising alert raised early") - assert(!Alert.first.cleared?, "Auto-clearing alert cleared early") - assert(!Alert.first.acknowledged?, "Alert acknowledged when I didn't expect it") - - # We asked for it to be raised in 5 minutes, so no alert yet... - # - assert_no_notification - - # Push forward to when the alert should be raised, check it has been - # - Time.advance(5.minutes) - assert(Alert.first.raised?, "#{Alert.first.inspect} should be raised by now") - assert(!Alert.first.cleared?, "#{Alert.first.inspect} should not be cleared") - - # Check that we have a notification - # - with_next_notification do |destination, this_alert, other_alerts| - assert_equal("12345@joe_bloggs.email", destination) - assert_equal(Alert.first, this_alert) - assert_equal('raised', this_alert.update_type) - end - - # Simulate manual acknowledgement - # - Alert.first.acknowledge!(Configuration.current.people["joe_bloggs"]) - Timers.restart_and_then_wait_until_idle - assert(Alert.first.acknowledged?, "Acknowledgement didn't work") - - # Check that the acknowledgement has caused a notification - # - with_next_notification do |destination, this_alert, other_alerts| - assert_equal("12345@joe_bloggs.email", destination) - assert_equal(Alert.first, this_alert) - assert_equal('acknowledged', this_alert.update_type, this_alert.inspect) - end - assert(Alert.first.acknowledged?) - assert(Alert.first.raised?) - assert(!Alert.first.cleared?) - - # Now with the config set to un-acknowledge alerts after only 1 minute, - # try winding time on and check that this happens. - # - Time.advance(2.minutes) - with_next_notification do |destination, this_alert, other_alerts| - assert_equal("12345@joe_bloggs.email", destination) - assert_equal(Alert.first, this_alert) - assert_equal('raised', this_alert.update_type, this_alert.inspect) - end - - # Check that auto-clearing works four minutes later - # - Time.advance(5.minutes) - assert(Alert.first.cleared?) - assert(!Alert.first.raised?) - - # Finally check for a notification that auto-clearing has happened - # - with_next_notification do |destination, this_alert, other_alerts| - assert_equal("12345@joe_bloggs.email", destination) - assert_equal(Alert.first, this_alert) - assert_equal('cleared', this_alert.update_type, this_alert.inspect) - end - - # And see that no further reminders are sent a while later - Time.advance(1.day) - assert_no_notification - end - - def test_one_alert_changes_from_outside - # Raise our test alert, wait for it to be processed - mauvesend("-o my_source -i alert1 -s \"alert1 summary\" -d \"alert1 detail\" -u \"alert1 subject\"") - - # Check internal representation, external notification - # - assert(Alert.first.raised?) - assert(!Alert.first.cleared?) - with_next_notification do |destination, this_alert, other_alerts| - assert_equal('raised', this_alert.update_type, this_alert.inspect) - end - - # Check we get reminders every hour, and no more - # - 12.times do - Time.advance(1.hour) - with_next_notification do |destination, this_alert, other_alerts| - assert_equal('raised', this_alert.update_type, this_alert.inspect) - end - assert_no_notification - end - - # Clear the alert, wait for it to be processed - mauvesend("-o my_source -i alert1 -c now") - assert(!Alert.first.raised?) - assert(Alert.first.cleared?) - with_next_notification do |destination, this_alert, other_alerts| - assert_equal('cleared', this_alert.update_type, this_alert.inspect) - end - - # Check we can raise the same alert again - Time.advance(1.minute) - mauvesend("-o my_source -i alert1 -s \"alert1 summary\" -d \"alert1 detail\" -u \"alert1 subject\" -r now") - assert(Alert.first.raised?, Alert.first.inspect) - assert(!Alert.first.cleared?, Alert.first.inspect) - with_next_notification do |destination, this_alert, other_alerts| - assert_equal('raised', this_alert.update_type, this_alert.inspect) - end - end - - def test_alert_groups - # check that this alert is reminded more often than normal - mauvesend("-o rare-and-important -i alert1 -s \"rare and important alert\"") - assert(Alert.first.raised?) - assert(!Alert.first.cleared?) - - 10.times do - with_next_notification do |destination, this_alert, other_alerts| - assert_equal('raised', this_alert.update_type, this_alert.inspect) - assert_equal('12345', destination) - Time.advance(10.minutes) - end - end - discard_next_notification - end - - def test_future_raising - mauvesend("-i heartbeat -c now -r +10m -s \"raise in the future\"") - assert(!Alert.first.raised?) - assert(Alert.first.cleared?) - assert_no_notification - - # Check the future alert goes off - # - Time.advance(10.minutes) - assert(Alert.first.raised?) - assert(!Alert.first.cleared?) - with_next_notification do |destination, this_alert, other_alerts| - assert_equal('raised', this_alert.update_type, this_alert.inspect) - end - - # Check that a repeat of the "heartbeat" update clears it, and we get - # a notification. - # - mauvesend("-i heartbeat -c now -r +10m -s \"raise in the future\"") - assert(!Alert.first.raised?) - assert(Alert.first.cleared?) - with_next_notification do |destination, this_alert, other_alerts| - assert_equal('cleared', this_alert.update_type, this_alert.inspect) - end - - # Check that a re-send of the same clear alert doesn't send another - # notification - # - Time.advance(1.minute) - mauvesend("-i heartbeat -c now -r +10m -s \"raise in the future\"") - assert(!Alert.first.raised?) - assert(Alert.first.cleared?) - assert_no_notification - - # Check that a skewed resend doesn't confuse it - # - mauvesend("-i heartbeat -c +1m -r +11m -s \"raise in the future\"") - assert(!Alert.first.raised?) - assert(Alert.first.cleared?) - Time.advance(1.minute) - assert(!Alert.first.raised?) - assert(Alert.first.cleared?) - assert_no_notification - end - - # Make sure that using the "replace all flag" works as expected. - # - def test_replace_flag - mauvesend("-p") - #mauvesend("-p") - assert_no_notification - - mauvesend("-i test1 -s\"\test1\"") - assert(Alert.first.raised?) - with_next_notification do |destination, this_alert, other_alerts| - assert_equal('raised', this_alert.update_type, this_alert.inspect) - end - assert_no_notification - - mauvesend("-p") - #mauvesend("-p") - with_next_notification do |destination, this_alert, other_alerts| - assert_equal('cleared', this_alert.update_type, this_alert.inspect) - end - assert_no_notification - end - - def test_earliest_date - alert = Alert.create!( - :alert_id => "test_id", - :source => "test1", - :subject => "test subject", - :summary => "test summary", - :raised_at => nil, - :will_raise_at => Time.now + 60, - :will_clear_at => Time.now + 120, - :update_type => "cleared", - :updated_at => Time.now - ) - assert(alert) - - assert(AlertEarliestDate.first.alert == alert) - end - -end - - - - diff --git a/test/tc_mauve_alert_changed.rb b/test/tc_mauve_alert_changed.rb index 167ea75..6cd51fb 100644 --- a/test/tc_mauve_alert_changed.rb +++ b/test/tc_mauve_alert_changed.rb @@ -24,6 +24,10 @@ class TcMauveAlertChanged < Mauve::UnitTest def test_reminder config=<<EOF +server { + use_notification_buffer false +} + notification_method("email") { debug! deliver_to_queue [] @@ -63,8 +67,6 @@ EOF # In order to send the notification and stick in the reminder, we need to # process the buffer. # - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } - assert_equal(notifications, notification_buffer.length) assert_equal(reminders, AlertChanged.count) @@ -82,12 +84,7 @@ EOF alert.clear! notifications += 1 - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(notifications, notification_buffer.length) - # - # Process the buffer again - # - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(reminders, AlertChanged.count) Timecop.freeze(Time.now + 10.minutes) @@ -95,7 +92,6 @@ EOF # # Send NO MORE notifications. # - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(notifications, notification_buffer.length) assert_equal(reminders, AlertChanged.count) @@ -103,6 +99,10 @@ EOF def test_only_send_one_alert_on_unacknowledge config=<<EOF +server { + use_notification_buffer false +} + notification_method("email") { debug! deliver_to_queue [] @@ -131,12 +131,10 @@ EOF alert = Alert.new(:source => "test", :alert_id => "test_alert", :summary => "test alert") alert.raise! - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(1, notification_buffer.length, "Wrong no of notifications sent after raise.") assert_equal(1, AlertChanged.count, "Wrong no of AlertChangeds created after raise.") alert.acknowledge!(Configuration.current.people["test_person"], Time.now + 10.minutes) - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(2, notification_buffer.length, "Wrong no of notifications sent after raise.") assert_equal(2, AlertChanged.count, "Wrong no of AlertChangeds created after acknowledge.") @@ -145,7 +143,6 @@ EOF # Timecop.freeze(Time.now + 10.minutes) AlertChanged.all.each{|ac| ac.poll} - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(2, notification_buffer.length, "Extra notifications sent when alertchangeds are polled.") # @@ -154,19 +151,21 @@ EOF alert.poll assert(!alert.acknowledged?,"Alert not unacknowledged") assert(alert.raised?,"Alert not raised following unacknowledgment") - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(3, notification_buffer.length, "No re-raise notification sent.") # # If we poll the AlertChangeds again, no further notification should be sent. # AlertChanged.all.each{|ac| ac.poll} - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(3, notification_buffer.length, "Extra notifications sent when alertchangeds are polled.") end def test_only_set_one_alert_changed_on_a_reminder_after_multiple_raises_and_clears config=<<EOF +server { + use_notification_buffer false +} + notification_method("email") { debug! deliver_to_queue [] @@ -211,7 +210,6 @@ EOF # # No notification should have been sent, since it is the middle of the night # - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(0, notification_buffer.length, "No notifications should have been sent.") assert(alert.cleared?) @@ -222,7 +220,6 @@ EOF # # Still no alerts should be sent. # - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(0, notification_buffer.length, "No notifications should have been sent.") assert(alert.raised?) diff --git a/test/tc_mauve_authentication.rb b/test/tc_mauve_authentication.rb new file mode 100644 index 0000000..d0f2d4f --- /dev/null +++ b/test/tc_mauve_authentication.rb @@ -0,0 +1,168 @@ +$:.unshift "../lib" + + +require 'th_mauve' +require 'th_mauve_resolv' + +require 'mauve/server' +require 'mauve/authentication' +require 'mauve/configuration' +require 'mauve/configuration_builder' +require 'mauve/configuration_builders' + +class TcMauveAuthentication < Mauve::UnitTest + include Mauve + + + def setup + super + setup_database + end + + def teardown + teardown_database + super + end + + def test_default_auth_always_fails + config=<<EOF +server { + failed_login_delay 0 +} +EOF + + Configuration.current = ConfigurationBuilder.parse(config) + Server.instance.setup + assert_equal(false, Authentication.authenticate("test","password")) + # + # No warning + # + assert_nil(logger_shift) + + end + + def test_local_auth + config=<<EOF +server { + failed_login_delay 0 +} + +person ("test") { + password "#{Digest::SHA1.new.hexdigest("password")}" + all { true } +} +EOF + + Configuration.current = ConfigurationBuilder.parse(config) + Server.instance.setup + assert(!Authentication.authenticate("test","badpassword")) + # + # Should warn that a bad password has been used. + # + assert_match(/AuthLocal for test failed/, logger_shift) + assert(Authentication.authenticate("test","password")) + # + # No warnings + # + assert_nil(logger_shift) + end + + + def test_local_auth + config=<<EOF +server { + failed_login_delay 0 +} + +person ("nopass") { } + +person ("test") { + password "#{Digest::SHA1.new.hexdigest("password")}" +} +EOF + + Configuration.current = ConfigurationBuilder.parse(config) + Server.instance.setup + assert(!Authentication.authenticate("nopass","badpassword")) + logger_shift + assert(!Authentication.authenticate("test","badpassword")) + logger_shift + assert(Authentication.authenticate("test","password")) + end + + def test_bytemark_auth + # + # BytemarkAuth test users are: + # test1: ummVRu7qF + # test2: POKvBqLT7 + # + config=<<EOF +server { + failed_login_delay 0 + bytemark_auth_url "https://auth.bytemark.co.uk/" +} + +person ("test1") { } + +person ("test2") { + password "#{Digest::SHA1.new.hexdigest("password")}" +} + +person ("test3") { + password "#{Digest::SHA1.new.hexdigest("password")}" +} +EOF + + Configuration.current = ConfigurationBuilder.parse(config) + Server.instance.setup + + # + # Test to make sure auth can fail + # + assert(!Authentication.authenticate("test1","password")) + # + # Should issue a warning for just bytemark auth failing, and no more. + # + assert_match(/AuthBytemark for test1 failed/, logger_shift) + assert_nil(logger_shift) + + assert(Authentication.authenticate("test1","ummVRu7qF")) + # + # Shouldn't issue any warnings. + # + assert_nil(logger_shift) + + # + # Test to make sure that in the event of failure we fall back to local + # auth, which should also fail in this case. + # + assert(!Authentication.authenticate("test2","badpassword")) + assert_match(/AuthBytemark for test2 failed/, logger_shift) + assert_match(/AuthLocal for test2 failed/, logger_shift) + + # + # Test to make sure that in the event of failure we fall back to local + # auth, which should pass in this case. + # + assert(Authentication.authenticate("test2","password")) + # + # Should issue a warning for just bytemark auth failing, and no more. + # + assert_match(/AuthBytemark for test2 failed/, logger_shift) + assert_nil(logger_shift) + + # + # Finally test to make sure local-only still works + # + assert(Authentication.authenticate("test3","password")) + # + # Should issue a warning for just bytemark auth failing, and no more. + # + assert_match(/AuthBytemark for test3 failed/, logger_shift) + assert_nil(logger_shift) + + end + + + +end diff --git a/test/tc_mauve_notification.rb b/test/tc_mauve_notification.rb index bac389f..7ff0d79 100644 --- a/test/tc_mauve_notification.rb +++ b/test/tc_mauve_notification.rb @@ -185,6 +185,10 @@ class TcMauveNotification < Mauve::UnitTest t = Time.now config=<<EOF +server { + use_notification_buffer false +} + notification_method("email") { debug! deliver_to_queue [] @@ -251,7 +255,6 @@ EOF # # Also make sure that only 2 notifications has been sent.. # - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(2, notification_buffer.size, "Wrong number of notifications sent") # @@ -277,14 +280,25 @@ EOF # Makes sure a reminder is set at the start of the notify clause. # def test_reminder_is_set_at_start_of_during - config=<<EOF +server { + use_notification_buffer false +} + +notification_method("email") { + debug! + deliver_to_queue [] + disable_normal_delivery! +} + person ("test1") { - all { true } + email "test1@example.com" + all { email } } person ("test2") { - all { true } + email "test2@example.com" + all { email } } alert_group("default") { @@ -315,7 +329,6 @@ EOF ) alert.raise! - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } assert_equal(1, Alert.count, "Wrong number of alerts saved") assert_equal(1, AlertChanged.count, "Wrong number of reminders inserted") @@ -324,7 +337,6 @@ EOF assert_equal("urgent", a.level, "Level is wrong for #{a.person}") assert_equal("raised", a.update_type, "Update type is wrong for #{a.person}") assert_equal(Time.now + 5.minutes, a.remind_at,"reminder time is wrong for #{a.person}") - end @@ -335,6 +347,10 @@ EOF def test_no_race_conditions_in_during config=<<EOF +server { + use_notification_buffer false +} + notification_method("email") { debug! deliver_to_queue [] @@ -383,8 +399,6 @@ EOF Timecop.travel(Time.now + 7.hours + 59.minutes + 59.seconds) alert.raise! - assert_nothing_raised{ Notifier.instance.__send__(:main_loop) } - assert_equal(1, notification_buffer.size, "Wrong number of notifications sent") end diff --git a/test/tc_mauve_web_interface.rb b/test/tc_mauve_web_interface.rb index a120c37..54c9697 100644 --- a/test/tc_mauve_web_interface.rb +++ b/test/tc_mauve_web_interface.rb @@ -53,12 +53,6 @@ class WebInterfaceTest < Mauve::UnitTest super setup_database - # - # BytemarkAuth test users are: - # - # test1: ummVRu7qF - # test2: POKvBqLT7 - # config =<<EOF server { hostname "localhost" @@ -70,13 +64,8 @@ server { } } -person ("test0") { - password "#{Digest::SHA1.new.hexdigest("password")}" - all { true } -} - person ("test1") { - password "#{Digest::SHA1.new.hexdigest("ummVRu7qF")}" + password "#{Digest::SHA1.new.hexdigest("goodpassword")}" all { true } } @@ -150,9 +139,16 @@ EOF 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' + # + # This last login attempt produces two warning messages (one for each auth + # type), so pop them both off the logger. + # + logger_pop ; logger_pop + + post '/login', :username => 'test1', :password => 'goodpassword' follow_redirect! while last_response.redirect? assert last_response.body.include?('Mauve: ') + assert last_response.ok? get '/logout' follow_redirect! while last_response.redirect? @@ -160,7 +156,7 @@ EOF end def test_alerts_show_subject - post '/login', :username => 'test1', :password => 'ummVRu7qF' + post '/login', :username => 'test1', :password => 'goodpassword' follow_redirect! while last_response.redirect? assert last_response.body.include?('Mauve: ') diff --git a/test/test_mauve.rb b/test/test_mauve.rb index ce83c6c..a0531d7 100644 --- a/test/test_mauve.rb +++ b/test/test_mauve.rb @@ -8,24 +8,9 @@ require 'pp' require 'test/unit' require 'th_mauve' -%w( -tc_mauve_alert_changed.rb -tc_mauve_alert_group.rb -tc_mauve_alert.rb -tc_mauve_configuration_builder.rb -tc_mauve_configuration_builders_alert_group.rb -tc_mauve_configuration_builders_logger.rb -tc_mauve_configuration_builders_notification_method.rb -tc_mauve_configuration_builders_person.rb -tc_mauve_configuration_builders_server.rb -tc_mauve_history.rb -tc_mauve_notification.rb -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| +%w(. test).each do |dir| +Dir.glob(File.join(dir,"tc_*.rb")).each do |s| require s end +end diff --git a/test/th_mauve.rb b/test/th_mauve.rb index 2b97e64..ce29e11 100644 --- a/test/th_mauve.rb +++ b/test/th_mauve.rb @@ -77,6 +77,10 @@ module Mauve def logger_pop @outputter.pop end + + def logger_shift + @outputter.shift + end def teardown_logger logger = Log4r::Logger['Mauve'] |