diff --git a/lib/md_tools.sh b/lib/md_tools.sh index 0707664..61a6d7d 100755 --- a/lib/md_tools.sh +++ b/lib/md_tools.sh @@ -21,7 +21,7 @@ # # Usage: getEntityFromFile -f MD_PATH ID # -# A return code > 1 is a serious error worthy of early termination +# A return code > 1 is a fatal error. ####################################################################### getEntityFromFile () { @@ -33,6 +33,7 @@ getEntityFromFile () { local md_path local entityID local entityDescriptor + local exit_code local opt local OPTARG @@ -91,9 +92,14 @@ getEntityFromFile () { fi # get the entity descriptor from the metadata file using parameterized xslt - entityDescriptor=$( /bin/cat $md_path \ - | /usr/bin/xsltproc --stringparam entityID $entityID $LIB_DIR/extract_entity.xsl - + entityDescriptor=$( \ + /usr/bin/xsltproc --stringparam entityID $entityID $LIB_DIR/extract_entity.xsl $md_path ) + exit_code=$? + if [ "$exit_code" -ne 0 ]; then + echo "ERROR: $FUNCNAME: xsltproc failed (exit code $exit_code): $entityID" >&2 + return 3 + fi if [ -z "$entityDescriptor" ]; then echo "ERROR: $FUNCNAME: no entity descriptor for entityID: $entityID" >&2 @@ -104,13 +110,14 @@ getEntityFromFile () { } ####################################################################### -# Get entity metadata from a Metadata Query Server. +# Get entity metadata from a metadata query server, that is, +# a server that conforms to the Metadata Query Protocol. # # Usage: getEntityFromServer -T TMP_DIR -u MDQ_BASE_URL ID # # A temporary file containing the HTTP response is created in TMP_DIR. # -# A return code > 1 is a serious error worthy of early termination +# A return code > 1 is a fatal error. ####################################################################### getEntityFromServer () { @@ -164,11 +171,11 @@ getEntityFromServer () { fi if [ -z "$tmp_dir" ]; then - echo "ERROR: $FUNCNAME: TMP_DIR (option -d) does not exist" >&2 + echo "ERROR: $FUNCNAME: TMP_DIR (option -T) does not exist" >&2 return 2 fi if [ ! -d "$tmp_dir" ]; then - echo "ERROR: $FUNCNAME: temp directory does not exist: $tmp_dir" >&2 + echo "ERROR: $FUNCNAME: temporary directory does not exist: $tmp_dir" >&2 return 2 fi tmp_response_file="$tmp_dir/mdq_response.txt" @@ -256,6 +263,459 @@ construct_mdq_url () { fi } +####################################################################### +# Extract a pair of identifiers from the given entity metadata. +# +# Usage: extractIdentifiers [-f MD_FILE] +# +# The optional MD_FILE argument is the path to a local metadata file. +# If the MD_FILE argument is omitted on the command line, the input is +# taken from stdin. In any case, the root element of the metadata file +# is assumed to be an md:EntityDescriptor element. +# +# This function outputs the following tab-delimited text fields: +# +# entityID registrarID +# +# where entityID is the value of the ./@entityID XML attribute. The +# entityID is guaranteed to be non-null. +# +# The registrarID is the value of the following XML attribute: +# +# ./md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority +# +# Since the mdrpi:RegistrationInfo element is an optional element, +# the registrarID may be null. +####################################################################### +extractIdentifiers () { + + if [ "$COMMAND_PATHS" != true ]; then + echo "ERROR: $FUNCNAME: global command paths not found" >&2 + return 2 + fi + + # determine the source lib directory + if [ -z "$LIB_DIR" ]; then + echo "ERROR: $FUNCNAME requires env var LIB_DIR" >&2 + return 2 + fi + if [ ! -d "$LIB_DIR" ]; then + echo "ERROR: $FUNCNAME: directory does not exist: $LIB_DIR" >&2 + return 2 + fi + + # check the temporary directory + if [ -z "$TMPDIR" ]; then + echo "ERROR: $FUNCNAME requires env var TMPDIR" >&2 + return 2 + fi + if [ ! -d "$TMPDIR" ]; then + echo "ERROR: $FUNCNAME: directory does not exist: $TMPDIR" >&2 + return 2 + fi + + local xml_file + local xsl_file + local tmp_file + + local opt + local OPTARG + local OPTIND + while getopts ":f:" opt; do + case $opt in + f) + xml_file="$OPTARG" + ;; + \?) + echo "ERROR: $FUNCNAME: Unrecognized option: -$OPTARG" >&2 + return 2 + ;; + :) + echo "ERROR: $FUNCNAME: Option -$OPTARG requires an argument" >&2 + return 2 + ;; + esac + done + + # make sure there are no command-line arguments + shift $(( OPTIND - 1 )) + if [ $# -ne 0 ]; then + echo "ERROR: $FUNCNAME: incorrect number of arguments: $# (0 required)" >&2 + return 2 + fi + + # read the input into a temporary file + tmp_file="${TMPDIR}/tmp_metadata_in_$$.xml" + if [ -z "$xml_file" ]; then + # read input from stdin + /bin/cat - > "$tmp_file" + else + if [ ! -f "$xml_file" ]; then + echo "ERROR: $FUNCNAME: file does not exist: $xml_file" >&2 + return 2 + fi + /bin/cp "$xml_file" "$tmp_file" + fi + + # check the stylesheet + xsl_file="$LIB_DIR/entity_identifiers_txt.xsl" + if [ ! -f "$xsl_file" ]; then + echo "ERROR: $FUNCNAME: stylesheet does not exist: $xsl_file" >&2 + return 2 + fi + + /usr/bin/xsltproc "$xsl_file" "$tmp_file" +} + +####################################################################### +# Extract a list of names from the given IdP entity descriptor. +# +# Usage: extractIdPNames [-f MD_FILE] +# +# The optional MD_FILE argument is the path to a local metadata file. +# If the MD_FILE argument is omitted on the command line, the input is +# taken from stdin. In any case, the root element of the metadata file +# is assumed to be an md:EntityDescriptor element containing an +# md:IDPSSODescriptor child element, that is, an IdP role. +# +# This function outputs the following tab-delimited text fields: +# +# entityID DisplayName OrganizationName OrganizationDisplayName registrarID +# +# where entityID is the value of the ./@entityID XML attribute. The +# entityID is the only field guaranteed to be non-null. +# +# The DisplayName field is the value of the following XML element: +# +# ./md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:DisplayName[@xml:lang='en'] +# +# If the entity descriptor contains no IdP role, or there is no English- +# language version of the mdui:DisplayName, the DisplayName field is null. +# +# The OrganizationName and OrganizationDisplayName fields are the values +# of the following XML elements (resp.): +# +# ./md:Organization/md:OrganizationName[@xml:lang='en'] +# ./md:Organization/md:OrganizationDisplayName[@xml:lang='en'] +# +# If the entity descriptor contains no such elements, the corresponding +# output fields are null. +# +# Finally, the registrarID is the value of the following XML attribute: +# +# ./md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority +# +# Since the mdrpi:RegistrationInfo element is an optional element, +# the registrarID may be null. +####################################################################### +extractIdPNames () { + + if [ "$COMMAND_PATHS" != true ]; then + echo "ERROR: $FUNCNAME: global command paths not found" >&2 + return 2 + fi + + # determine the source lib directory + if [ -z "$LIB_DIR" ]; then + echo "ERROR: $FUNCNAME requires env var LIB_DIR" >&2 + return 2 + fi + if [ ! -d "$LIB_DIR" ]; then + echo "ERROR: $FUNCNAME: directory does not exist: $LIB_DIR" >&2 + return 2 + fi + + # check the temporary directory + if [ -z "$TMPDIR" ]; then + echo "ERROR: $FUNCNAME requires env var TMPDIR" >&2 + return 2 + fi + if [ ! -d "$TMPDIR" ]; then + echo "ERROR: $FUNCNAME: directory does not exist: $TMPDIR" >&2 + return 2 + fi + + local xml_file + local xsl_file + local tmp_file + + local opt + local OPTARG + local OPTIND + while getopts ":f:" opt; do + case $opt in + f) + xml_file="$OPTARG" + ;; + \?) + echo "ERROR: $FUNCNAME: Unrecognized option: -$OPTARG" >&2 + return 2 + ;; + :) + echo "ERROR: $FUNCNAME: Option -$OPTARG requires an argument" >&2 + return 2 + ;; + esac + done + + # make sure there are no command-line arguments + shift $(( OPTIND - 1 )) + if [ $# -ne 0 ]; then + echo "ERROR: $FUNCNAME: incorrect number of arguments: $# (0 required)" >&2 + return 2 + fi + + # read the input into a temporary file + tmp_file="${TMPDIR}/tmp_metadata_in_$$.xml" + if [ -z "$xml_file" ]; then + # read input from stdin + /bin/cat - > "$tmp_file" + else + if [ ! -f "$xml_file" ]; then + echo "ERROR: $FUNCNAME: file does not exist: $xml_file" >&2 + return 2 + fi + /bin/cp "$xml_file" "$tmp_file" + fi + + # check the stylesheet + xsl_file="$LIB_DIR/entity_idp_names_txt.xsl" + if [ ! -f "$xsl_file" ]; then + echo "ERROR: $FUNCNAME: stylesheet does not exist: $xsl_file" >&2 + return 2 + fi + + /usr/bin/xsltproc "$xsl_file" "$tmp_file" +} + +####################################################################### +# List the endpoints in the given metadata file. +# +# Usage: listEndpoints [-f MD_FILE] +# +# The optional MD_FILE argument is the path to a local metadata file. +# The root element of the metadata file is an md:EntityDescriptor +# element. If the MD_FILE argument is omitted on the command line, +# the input is taken from stdin. In any case, each line of output +# consists of the following space-separated fields: +# +# roleDescriptor endpointType binding location +# +# where roleDescriptor is one of the following: +# +# IDPSSODescriptor +# SPSSODescriptor +# AttributeAuthorityDescriptor +# +# and endpointType indicates the type of endpoint: +# +# SingleSignOnService +# SingleLogoutService +# ArtifactResolutionService +# AssertionConsumerService +# DiscoveryResponse +# RequestInitiator +# AttributeService +# +# For example, the roleDescriptor and the endpointType might be +# 'IDPSSODescriptor' and 'SingleSignOnService', respectively, in +# which case the endpoint is a so-called IdP SSO endpoint. +# +# A return code > 1 is a fatal error. +####################################################################### +listEndpoints () { + + if [ "$COMMAND_PATHS" != true ]; then + echo "ERROR: $FUNCNAME: global command paths not found" >&2 + return 2 + fi + + # determine the source lib directory + if [ -z "$LIB_DIR" ]; then + echo "ERROR: $FUNCNAME requires env var LIB_DIR" >&2 + return 2 + fi + if [ ! -d "$LIB_DIR" ]; then + echo "ERROR: $FUNCNAME: directory does not exist: $LIB_DIR" >&2 + return 2 + fi + + # check the temporary directory + if [ -z "$TMPDIR" ]; then + echo "ERROR: $FUNCNAME requires env var TMPDIR" >&2 + return 2 + fi + if [ ! -d "$TMPDIR" ]; then + echo "ERROR: $FUNCNAME: directory does not exist: $TMPDIR" >&2 + return 2 + fi + + local xml_file + local xsl_file + local tmp_file + + local opt + local OPTARG + local OPTIND + while getopts ":f:" opt; do + case $opt in + f) + xml_file="$OPTARG" + ;; + \?) + echo "ERROR: $FUNCNAME: Unrecognized option: -$OPTARG" >&2 + return 2 + ;; + :) + echo "ERROR: $FUNCNAME: Option -$OPTARG requires an argument" >&2 + return 2 + ;; + esac + done + + # make sure there are no command-line arguments + shift $(( OPTIND - 1 )) + if [ $# -ne 0 ]; then + echo "ERROR: $FUNCNAME: incorrect number of arguments: $# (0 required)" >&2 + return 2 + fi + + # read the input into a temporary file + tmp_file="${TMPDIR}/tmp_metadata_in_$$.xml" + if [ -z "$xml_file" ]; then + # read input from stdin + /bin/cat - > "$tmp_file" + else + if [ ! -f "$xml_file" ]; then + echo "ERROR: $FUNCNAME: file does not exist: $xml_file" >&2 + return 2 + fi + /bin/cp "$xml_file" "$tmp_file" + fi + + # check the stylesheet + xsl_file="$LIB_DIR/entity_endpoints_txt.xsl" + if [ ! -f "$xsl_file" ]; then + echo "ERROR: $FUNCNAME: stylesheet does not exist: $xsl_file" >&2 + return 2 + fi + + /usr/bin/xsltproc "$xsl_file" "$tmp_file" +} + +####################################################################### +# A convenience function that filters the output of the listEndpoints +# function. +# +# Usage: listEndpoints [-f MD_FILE] | filterEndpoints [-r ROLE] [-t TYPE] [-b BINDING] +# +# The ROLE argument of the -r option is a roleDescriptor. +# The TYPE argument of the -t option is an endpointType. +# The BINDING argument of the -b option is a binding URI. +# +# For example: +# +# $ echo "$entityDescriptor" | listEndpoints | filterEndpoints -r IDPSSODescriptor +# +# The above command lists only those endpoints contained in the +# IDPSSODescriptor element (if any). +# +# Here's another example: +# +# $ echo "$entityDescriptor" | listEndpoints | filterEndpoints -t SingleSignOnService -b $binding +# +# This command lists SSO endpoints with a particular binding. +####################################################################### +filterEndpoints () { + + if [ "$COMMAND_PATHS" != true ]; then + echo "ERROR: $FUNCNAME: global command paths not found" >&2 + return 2 + fi + + # filter nothing by default + local role='[^ ]+' + local type='[^ ]+' + local binding='[^ ]+' + + local opt + local OPTARG + local OPTIND + while getopts ":r:t:b:" opt; do + case $opt in + r) + role="$OPTARG" + ;; + t) + type="$OPTARG" + ;; + b) + binding="$OPTARG" + ;; + \?) + echo "ERROR: $FUNCNAME: Unrecognized option: -$OPTARG" >&2 + return 2 + ;; + :) + echo "ERROR: $FUNCNAME: Option -$OPTARG requires an argument" >&2 + return 2 + ;; + esac + done + + # make sure there are no command-line arguments + shift $(( OPTIND - 1 )) + if [ $# -ne 0 ]; then + echo "ERROR: $FUNCNAME: incorrect number of arguments: $# (0 required)" >&2 + return 2 + fi + + # filter all endpoints except those with the given role, type, and binding + /bin/cat - | $_GREP -E "^$role $type $binding " +} + +####################################################################### +# A convenience function that takes the output of the listEndpoints +# function and returns a list of the corresponding endpoint locations +# only. +# +# Usage: listEndpoints [-f MD_FILE] | listEndpointLocations +# +# For example: +# +# location=$( echo "$entityDescriptor" | listEndpoints \ +# | filterEndpoints -t SingleSignOnService -b $binding \ +# | listEndpointLocations \ +# | /usr/bin/head -n 1 +# ) +# +# The above command captures a single SSO endpoint with a particular +# binding. +# +# WARNING. The SingleSignOnService endpoint is non-indexed and +# therefore one would expect at most one endpoint for any given +# binding. Schema validators are unable to detect such an anomaly, +# however, so the listEndpointLocations function may return +# multiple values when just one is expected. +####################################################################### +listEndpointLocations () { + + if [ "$COMMAND_PATHS" != true ]; then + echo "ERROR: $FUNCNAME: global command paths not found" >&2 + return 2 + fi + + # make sure there are no command-line arguments + shift $(( OPTIND - 1 )) + if [ $# -ne 0 ]; then + echo "ERROR: $FUNCNAME: incorrect number of arguments: $# (0 required)" >&2 + return 2 + fi + + # cut the location column + /bin/cat - | $_CUT -f4 -d" " +} + ####################################################################### # This function is DEPRECATED. Use percent_encode instead. #