From d26845d7be6dad30c52152f933d8dafd64904f9b Mon Sep 17 00:00:00 2001
From: Tom Scavo <trscavo@internet2.edu>
Date: Sat, 10 Jun 2017 15:55:14 -0400
Subject: [PATCH] Improve error handling

---
 bin/compute_vital_statistics.sh | 182 ++++++++++++++++++++++++++------
 1 file changed, 149 insertions(+), 33 deletions(-)

diff --git a/bin/compute_vital_statistics.sh b/bin/compute_vital_statistics.sh
index 1f44f6a..44afa56 100755
--- a/bin/compute_vital_statistics.sh
+++ b/bin/compute_vital_statistics.sh
@@ -22,9 +22,12 @@
 
 display_help () {
 /bin/cat <<- HELP_MSG
-	This script parses one or more SAML metadata aggregates and 
-	produces a JSON file of vital statistics (such as validUntil, 
-	creationInstant, etc.)
+	This script parses one or more SAML metadata files and 
+	produces a JSON file of vital statistics. Each metadata file
+	must contain the following attributes:
+	
+	  /md:EntitiesDescriptor/@validUntil
+	  /md:EntitiesDescriptor/md:Extensions/mdrpi:PublicationInfo/@creationInstant
 	
 	The script depends on cached metadata. It will not fetch
 	a metadata file from the server.
@@ -86,21 +89,28 @@ display_help () {
 	
 	OUTPUT
 	
-	The script outputs a JSON file containing a single array.
-	Each array element is a JavaScript object with the following 
-	fields:
-	
-	  metadataLocation:  location
-	  currentTime:       dateTime
-	  validUntil:        dateTime
-	  creationInstant:   dateTime
-	  validityInterval:  duration
-	  untilInvalid:      duration
-	  sinceCreation:     duration
+	The script outputs a JSON file to OUT_DIR:
+	
+	  $out_filename
+	  
+	The JSON file contains a single array. Each array element is 
+	a JavaScript object with the following fields:
+	
+	  successFlag       boolean    success or failure?
+	  message           string     message string
+	  metadataLocation  string     HTTP location
+	  currentTime       string     ISO 8601 dateTime
+	  validUntil        dateTime   ISO 8601 dateTime
+	  creationInstant   dateTime   ISO 8601 dateTime
+	  validityInterval  duration   ISO 8601 duration
+	  untilInvalid      duration   ISO 8601 duration
+	  sinceCreation     duration   ISO 8601 duration
 	  
 	For example:
 	
 	  {
+	    "successFlag": true,
+	    "message": "Integrity of compressed metadata confirmed",
 	    "metadataLocation": "http://md.incommon.org/InCommon/InCommon-metadata.xml",
 	    "currentTime": "2017-06-06T23:57:54Z",
 	    "validUntil": "2017-06-16T18:41:12Z",
@@ -190,6 +200,9 @@ if [ ! -f "$xsl_path" ]; then
 	exit 2
 fi
 
+# output filename
+out_filename="md_vital_statistics.json"
+
 #######################################################################
 # Process command-line options and arguments
 #######################################################################
@@ -266,7 +279,6 @@ fi
 
 # specify temporary files
 xml_path="${tmp_dir}/saml-metadata.xml"
-out_filename=md_vital_statistics.json
 out_file="${tmp_dir}/$out_filename"
 
 #######################################################################
@@ -281,6 +293,7 @@ escape_special_json_chars () {
 }
 
 append_json_object () {
+	local message=$( escape_special_json_chars "$message" )
 	local metadataLocation=$( escape_special_json_chars "$md_location" )
 	local currentTime=$( escape_special_json_chars "$now" )
 	local validUntil=$( escape_special_json_chars "$validUntil" )
@@ -289,8 +302,13 @@ append_json_object () {
 	local untilInvalid=$( escape_special_json_chars "$untilInvalid" )
 	local sinceCreation=$( escape_special_json_chars "$sinceCreation" )
 
+	local boolean_value="true"
+	! $success && boolean_value="false"
+	
 	/bin/cat <<- JSON_OBJECT
 	  {
+	    "successFlag": $boolean_value,
+	    "message": "$message",
 	    "metadataLocation": "$metadataLocation",
 	    "currentTime": "$currentTime",
 	    "validUntil": "$validUntil",
@@ -302,7 +320,9 @@ append_json_object () {
 JSON_OBJECT
 }
 
-parse_metadata () {
+get_metadata () {
+
+	local status_code
 
 	md_location="$1"
 
@@ -311,73 +331,169 @@ parse_metadata () {
 	status_code=$?
 	if [ $status_code -eq 1 ]; then
 		# metadata must be cached
+		success=false
+		message="Metadata not found"
 		print_log_message -E "$script_name: metadata file not cached: $md_location"
-		clean_up_and_exit -d "$tmp_dir" 1
+		return 1
 	fi
 	if [ $status_code -gt 1 ]; then
+		success=false
+		message="Request for metadata failed"
 		print_log_message -E "$script_name: conditional_get failed ($status_code) on location: $md_location"
-		clean_up_and_exit -d "$tmp_dir" $status_code
+		return 3
 	fi
+
+	return 0
+}
+
+parse_metadata () {
+
+	local status_code
+	local tstamps
+	local validityIntervalSecs
+	local secsUntilInvalid
+	local secsSinceCreation
+
 	print_log_message -I "$script_name parsing cached metadata file: $md_location"
 
 	# extract @ID, @creationInstant, @validUntil (in that order)
 	tstamps=$( /usr/bin/xsltproc $xsl_path $xml_path )
+	status_code=$?
+	if [ $status_code -ne 0 ]; then
+		success=false
+		message="Unable to parse metadata"
+		print_log_message -E "$script_name: xsltproc failed ($status_code) on script: $xsl_path"
+		return 0
+	fi
 
-	# If @validUntil missing, then FAIL
+	# get @validUntil
 	validUntil=$( echo "$tstamps" | $_CUT -f3 )
+	status_code=$?
+	if [ $status_code -ne 0 ]; then
+		success=false
+		message="Unable to parse @validUntil"
+		print_log_message -E "$script_name: cut failed ($status_code)"
+		return 0
+	fi
+
+	# if @validUntil is missing, then FAIL
 	if [ -z "$validUntil" ]; then
+		success=false
+		message="XML attribute @validUntil not found"
 		print_log_message -E "$script_name: @validUntil not found"
-		clean_up_and_exit -d "$tmp_dir" 4
+		return 0
 	fi
 	print_log_message -D "$script_name found @validUntil: $validUntil"
 
-	# If @creationInstant missing, then FAIL
+	# get @creationInstant
 	creationInstant=$( echo "$tstamps" | $_CUT -f2 )
+	status_code=$?
+	if [ $status_code -ne 0 ]; then
+		success=false
+		message="Unable to parse @creationInstant"
+		print_log_message -E "$script_name: cut failed ($status_code)"
+		return 0
+	fi
+
+	# if @creationInstant is missing, then FAIL
 	if [ -z "$creationInstant" ]; then
+		success=false
+		message="XML attribute @creationInstant not found"
 		print_log_message -E "$script_name: @creationInstant not found"
-		clean_up_and_exit -d "$tmp_dir" 5
+		return 0
 	fi
 	print_log_message -D "$script_name found @creationInstant: $creationInstant"
 
-	# If validityInterval > 14 days, then FAIL
+	# compute validityInterval
 	validityIntervalSecs=$( secsUntil -b $creationInstant $validUntil )
+	status_code=$?
+	if [ $status_code -ne 0 ]; then
+		success=false
+		message="Unable to compute validity interval"
+		print_log_message -E "$script_name: secsUntil failed ($status_code)"
+		return 0
+	fi
+
+	# if validityInterval > 14 days, then FAIL  # TODO: Generalize
 	if (( validityIntervalSecs > 14*24*60*60 )); then
+		success=false
+		message="Validity interval too large"
 		print_log_message -E "$script_name: validity interval exceeds maximum: $validityIntervalSecs"
-		clean_up_and_exit -d "$tmp_dir" 6
+		return 0
 	fi
 	validityInterval=$( secs2duration $validityIntervalSecs )
 	print_log_message -D "$script_name computed validity interval: $validityInterval"
 
 	# compute NOW
 	now=$( dateTime_now_canonical )
+	status_code=$?
+	if [ $status_code -ne 0 ]; then
+		success=false
+		message="Unable to compute current time"
+		print_log_message -E "$script_name: dateTime_now_canonical failed ($status_code)"
+		return 0
+	fi
 
-	# If secsUntilInvalid <= 0, then FAIL
+	# compute secsUntilInvalid
 	secsUntilInvalid=$( echo $validUntil | secsUntil -b $now )
+	status_code=$?
+	if [ $status_code -ne 0 ]; then
+		success=false
+		message="Unable to compute time to expiration"
+		print_log_message -E "$script_name: secsUntil failed ($status_code)"
+		return 0
+	fi
+
+	# if secsUntilInvalid <= 0, then FAIL
 	if (( secsUntilInvalid <= 0 )); then
-		print_log_message -E "$script_name: seconds until invalid not positive: $secsUntilInvalid"
-		clean_up_and_exit -d "$tmp_dir" 7
+		success=false
+		message="Metadata is expired"
+		print_log_message -C "$script_name: seconds until invalid not positive: $secsUntilInvalid"
+		return 0
 	fi
 	untilInvalid=$( secs2duration "$secsUntilInvalid" )
 	print_log_message -D "$script_name computed time until invalid: $untilInvalid"
 
-	# If secsSinceCreation <= 0, then FAIL
+	# compute secsSinceCreation
 	secsSinceCreation=$( echo $creationInstant | secsSince -e $now )
+	status_code=$?
+	if [ $status_code -ne 0 ]; then
+		success=false
+		message="Unable to compute time since creation"
+		print_log_message -E "$script_name: secsSince failed ($status_code)"
+		return 0
+	fi
+
+	# if secsSinceCreation <= 0, then FAIL
 	if (( secsSinceCreation <= 0 )); then
-		print_log_message -E "$script_name: seconds since creation not positive: $secsSinceCreation"
-		clean_up_and_exit -d "$tmp_dir" 8
+		success=false
+		message="Metadata is not valid"
+		print_log_message -C "$script_name: seconds since creation not positive: $secsSinceCreation"
+		return 0
 	fi
 	sinceCreation=$( secs2duration "$secsSinceCreation" )
 	print_log_message -D "$script_name computed time since creation: $sinceCreation"
+	
+	# success
+	message="Metadata successfully parsed"
 }
 
-print_vital_statistics () {
+print_output_file () {
+
+	local status_code
 
 	# begin output list
 	printf "[\n"
 
 	while true; do
 	
-		parse_metadata "$1"
+		success=true
+		
+		get_metadata "$1"
+		status_code=$?
+		if [ $status_code -eq 0 ]; then
+			parse_metadata
+		fi
 		append_json_object
 		
 		shift; (( "$#" )) || break
@@ -397,7 +513,7 @@ print_vital_statistics () {
 print_log_message -I "$script_name BEGIN"
 
 # create the JSON output
-print_vital_statistics "$@" > "$out_file"
+print_output_file "$@" > "$out_file"
 print_log_message -I "$script_name writing output file: $out_filename"
 
 # move the output file to the web directory