aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/mauve/alert.rb38
-rw-r--r--lib/mauve/authentication.rb90
-rw-r--r--lib/mauve/configuration_builders/server.rb34
-rw-r--r--lib/mauve/notifier.rb19
-rw-r--r--lib/mauve/processor.rb82
-rw-r--r--lib/mauve/server.rb140
-rw-r--r--test/alert_and_notification_logic.rb391
-rw-r--r--test/tc_mauve_alert_changed.rb27
-rw-r--r--test/tc_mauve_authentication.rb168
-rw-r--r--test/tc_mauve_notification.rb30
-rw-r--r--test/tc_mauve_web_interface.rb24
-rw-r--r--test/test_mauve.rb21
-rw-r--r--test/th_mauve.rb4
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']