Header & Auth Methods

Introduction

The TestSipLuaAgent Lua API library file test_sip_agent.lua provides a method for accessing SIP message headers and constructing header values related to authentication.

Header & Auth Methods

Header Fetch

The header_fetch method fetches a specific header value from a SIP message that is returned by an “expect” API method such as invite_expect_response.

The header_fetch takes the following arguments:

Argument Type Description
headers Object Pass this parameter as request.headers or response.headers from the returned request or response message.
header_name String The name (case-insensitive) of the header to process and return.

The returned value is nil (with a failed match test being generated) if the header is not found.

If the indicated header is found then a single table structure is returned:

Argument Type Description
header Object A returned table containing information about the matched header.
.name String The name as per the first matching header line found.
.value String The complete string value of the first matching header line.
.values Array of String An array of each of the comma-separated parts of the matching header value.
.values_hash Table Each of the comma-separated parts of the first matching header value is examined to see if it has the form of a header parameter i.e. "(key)=(parameter)". Each matching parameter is added as an entry to this table where the key is the upper-case parameter key and the value is the parameter value.

Example: See the below example for auth_header_value.

Auth Header Response

The auth_header_response method implements the algorithm for building the Authorization header Response part using specified parameters. It can be used to check the Response part in a received Authorization header.

The method takes the following arguments:

Argument Type Description
uri String The URI from the request that has been challenged.
method String The SIP method from the request that has been challenged.
realm String The authentication realm.
username String The username for the authenticating user.
password String The user's password.
nonce String The nonce from the authentication challenge.
cnonce String The nonce used by the authenticator.

Example: Challenge a BYE Request, and check the authentication.

-- Expect BYE at the end of a test call.
tsuo.bye_expect_request (context)

-- Ask for auth on the BYE.
local realm = 'your realm here'
local nonce = '0a4f113b'  -- (Should be a unique random value, usually hex or base64.)
www_auth_header = string.format ('Digest realm="%s",nonce="%s",algorithm=MD5,qop=auth', realm, nonce)
tsuo.bye_send_response (context, 401, "Unauthorized", { { name = 'WWW-Authenticate', value = www_auth_header } })

-- These are the credentials we expect.  Of course they could be looked up from a database etc.
local expected_username = 'user'
local expected_password = 'pass'

-- Expect a BYE retry with auth.
local request = tsuo.bye_expect_request (context)
tv = match.elapsed ("BYE Re-Auth (immediate)", tv, 0.0)

-- Check that the auth is valid.
local authorization_header = tsuo.header_fetch (request.headers, "Authorization")
local actual_user = authorization_header.values_hash['DIGEST USERNAME']
if (actual_user == expected_username) then
    match.pass ("Expected BYE Authorization Header Username", "BYE Authorization Header Username matches.")

    local expected_response = tsuo.auth_header_response (request.uri, 'BYE', realm, expected_username, expected_password, nonce, authorization_header.values_hash['CNONCE'])
    local actual_response = authorization_header.values_hash['RESPONSE']
    if (actual_response == expected_response) then
        match.pass ("Expected BYE Authorization Header Response", "BYE Authorization Header Response matches.")
        tsuo.bye_send_response (context, 200, "OK")
    else
        match.fail ("Expected BYE Authorization Header Response", "BYE Authorization Header Response does not match.")
        tsuo.bye_send_response (context, 403, "Forbidden")
    end

else
    match.fail ("Expected BYE Authorization Header Username", "BYE Authorization Header Username does not match.")
    tsuo.bye_send_response (context, 403, "Forbidden")
end

Auth Header Value

The auth_header_value method implements the algorithm for building an Authorization header based on a received WWWW-Authenticate challenge header.

The method takes the following arguments:

Argument Type Description
context Object An INVITE or non-INVITE context created by invite_context or non_invite_context returned by invite_expect_request.
method String The SIP Request method.
www_auth_header Object The WWW-Authenticate header value returned from header_fetch.
username String The username for the authenticating user.
password String The user's password.

Example: INVITE Request is challenged, and authenticated.

local n2svcd = require "n2.n2svcd"
local utils = require "n2.utils"
local match = require "n2.n2svcd.tester.match"
local manage = require "n2.n2svcd.tester.manage"
local edr_file_agent = require "n2.n2svcd.edr_file_agent"
local tsuo = require "n2.n2svcd.tester.test_sip_agent"

local args = ...

-- Switch the authentication mode to DIGEST.  BYE DOES require authentication.
n2svcd.management_configuration_scalar ('LHO', 'sip_auth_schema', 'digest')
n2svcd.management_configuration_scalar ('LHO', 'sip_open_bye', '0')
n2svcd.management_configuration_scalar ('LHO', 'sip_open_ack', '1')

-- Checkpoint stats for "Logic" and "LHO" apps.
local stats_Logic = match.stats ('Logic')
local stats_LHO = match.stats ('LHO')

-- Static for our call.
local endpoints = tsuo.default_endpoints ({ local_rtp_port = 3668 })
local calling_party = '665566'
local called_party = '4000'
local username = '665566'
local password = 'ABC'

-- Get a SIP outcall context from our helper library.
local context = tsuo.invite_context (endpoints, calling_party, called_party, {
    contact_sip_instance = '"<urn:uuid:27b843f9-d300-49fb-b108-fff03a6369e3>"',
    contact_expires = '3600'
})

-- Construct the SDP
local sdp_offer = tsuo.sdp_offer (context, tsuo.SDP_LINPHONE)

-- Start timer for elapsed checking.
local tv = match.elapsed ()

-- Construct and Send INVITE Request.
tsuo.invite_send_request (context, sdp_offer, {
    { name = 'Record-Route', value = '<sip:p2.domain.com;lr>'},
    { name = 'Record-Route', value = '<sip:p1.example.com;lr>'},
})

-- Expect INVITE response (401 Unauthorized) and ACK.
local response = tsuo.invite_expect_response (context, 401, "Unauthorized")
tsuo.invite_send_decline_ack (context)

-- Generate the authorization header for our request.
local www_auth_header = tsuo.header_fetch (response.headers, "WWW-Authenticate")
local authorization_header = tsuo.auth_header_value (context, 'INVITE', www_auth_header, username, password)

-- Re-Send INVITE Request with Authorization.
tsuo.invite_send_request (context, sdp_offer, {
    { name = "Authorization", value = authorization_header },
    { name = 'Record-Route', value = '<sip:p2.domain.com;lr>'},
    { name = 'Record-Route', value = '<sip:p1.example.com;lr>'},
})

-- Expect Trying & Ringing.
tsuo.invite_expect_response (context, 100, "Trying", { ['User-Agent'] = "N-Squared LHO" })
tsuo.invite_expect_response (context, 180, "Ringing", {
    ['Record-Route'] = { '<sip:p2.domain.com;lr>', '<sip:p1.example.com;lr>' }
})
tv = match.elapsed ("Ring Notification (immediate)", tv, 0.0)