8i | 9i | 10g | 11g | 12c | 13c | 18c | 19c | 21c | 23c | Misc | PL/SQL | SQL | RAC | WebLogic | Linux
Script: digest_auth_api.sql
( Download Script )
CREATE OR REPLACE PACKAGE digest_auth_api AS -- -------------------------------------------------------------------------- -- Name : https://oracle-base.com/dba/miscellaneous/digest_auth_api.sql -- Author : Tim Hall -- Description : API to allow digest authentication when using UTL_HTTP. -- The aim is this only replaces UTL_HTTP.BEGIN_REQUEST. -- All other coding (wallet handling and processing the response) -- are still done by you, in the normal way. -- -- References : This is heavily inspired by the blog post by Gary Myers. -- http://blog.sydoracle.com/2014/03/plsql-utlhttp-and-digest-authentication.html -- I make liberal use of the ideas, and in some cases the code, he discussed in -- that blog post! -- For setting up certificates and wallets, see this article. -- https://oracle-base.com/articles/misc/utl_http-and-ssl -- -- License : Free for personal and commercial use. -- You can amend the code, but leave existing the headers, current -- amendments history and links intact. -- Copyright and disclaimer available here: -- https://oracle-base.com/misc/site-info.php#copyright -- Ammedments : -- When Who What -- =========== ======== ================================================= -- 11-DEC-2015 Tim Hall Initial Creation -- 30-JUN-2016 Tim Hall Add debug_on and debug_off procedures. -- -------------------------------------------------------------------------- /* Example call. SET SERVEROUTPUT ON DECLARE l_url VARCHAR2(32767) := 'https://example.com/ws/get-something'; l_http_request UTL_HTTP.req; l_http_response UTL_HTTP.resp; l_text VARCHAR2(32767); BEGIN -- Set wallet credentials. UTL_HTTP.set_wallet('file:/path/to/wallet', 'wallet-password'); -- Make a HTTP request and get the response. l_http_request := digest_auth_api.begin_request(p_url => l_url, p_username => 'my-username', p_password => 'my-password', p_method => 'GET'); l_http_response := UTL_HTTP.get_response(l_http_request); -- Loop through the response. BEGIN LOOP UTL_HTTP.read_text(l_http_response, l_text, 32767); DBMS_OUTPUT.put_line (l_text); END LOOP; EXCEPTION WHEN UTL_HTTP.end_of_body THEN UTL_HTTP.end_response(l_http_response); END; EXCEPTION WHEN OTHERS THEN UTL_HTTP.end_response(l_http_response); RAISE; END; */ -- -------------------------------------------------------------------------- PROCEDURE debug_on; PROCEDURE debug_off; FUNCTION begin_request(p_url IN VARCHAR2, p_username IN VARCHAR2, p_password IN VARCHAR2, p_method IN VARCHAR2 DEFAULT 'GET', p_http_version IN VARCHAR2 DEFAULT 'HTTP/1.1', p_req_cnt IN PLS_INTEGER DEFAULT 1) RETURN UTL_HTTP.req; END digest_auth_api; / SHOW ERRORS CREATE OR REPLACE PACKAGE BODY digest_auth_api AS -- -------------------------------------------------------------------------- -- Name : https://oracle-base.com/dba/miscellaneous/digest_auth_api.sql -- Author : Tim Hall -- Description : API to allow digest authentication when using UTL_HTTP. -- The aim is this only replaces UTL_HTTP.BEGIN_REQUEST. -- All other coding (wallet handling and processing the response) -- are still done by you, in the normal way. -- -- References : This is heavily inspired by the blog post by Gary Myers. -- http://blog.sydoracle.com/2014/03/plsql-utlhttp-and-digest-authentication.html -- I make liberal use of the ideas, and in some cases the code, he discussed in -- that blog post! -- For setting up certificates and wallets, see this article. -- https://oracle-base.com/articles/misc/utl_http-and-ssl -- -- License : Free for personal and commercial use. -- You can amend the code, but leave existing the headers, current -- amendments history and links intact. -- Copyright and disclaimer available here: -- https://oracle-base.com/misc/site-info.php#copyright -- Ammedments : -- When Who What -- =========== ======== ================================================= -- 11-DEC-2015 Tim Hall Initial Creation -- 30-JUN-2016 Tim Hall Add debug_on and debug_off procedures. -- -------------------------------------------------------------------------- -- Package variables. g_debug BOOLEAN := FALSE; -- Set by call to get_header_info. g_server VARCHAR2(32767); g_realm VARCHAR2(32767); g_qop VARCHAR2(32767); g_nonce VARCHAR2(32767); g_opaque VARCHAR2(32767); g_cnonce VARCHAR2(32767); -- Prototypes. PROCEDURE debug (p_text IN VARCHAR2); PROCEDURE init; PROCEDURE get_header_info (p_http_response IN OUT NOCOPY UTL_HTTP.resp); FUNCTION get_response (p_username IN VARCHAR2, p_password IN VARCHAR2, p_uri IN VARCHAR2, p_method IN VARCHAR2 DEFAULT 'GET', p_req_cnt IN NUMBER DEFAULT 1) RETURN VARCHAR2; -- Real stuff starts here. -- ----------------------------------------------------------------------------- PROCEDURE debug_on AS BEGIN g_debug := TRUE; END debug_on; -- ----------------------------------------------------------------------------- -- ----------------------------------------------------------------------------- PROCEDURE debug_off AS BEGIN g_debug := FALSE; END debug_off; -- ----------------------------------------------------------------------------- -- ----------------------------------------------------------------------------- PROCEDURE debug (p_text IN VARCHAR2) AS BEGIN IF g_debug THEN DBMS_OUTPUT.put_line(p_text); END IF; END debug; -- ----------------------------------------------------------------------------- -- ----------------------------------------------------------------------------- PROCEDURE init IS BEGIN g_server := NULL; g_realm := NULL; g_qop := NULL; g_nonce := NULL; g_opaque := NULL; g_cnonce := NULL; END init; -- ----------------------------------------------------------------------------- -- ----------------------------------------------------------------------------- PROCEDURE get_header_info (p_http_response IN OUT NOCOPY UTL_HTTP.resp) IS l_name VARCHAR2(256); l_value VARCHAR2(1024); BEGIN FOR i IN 1..UTL_HTTP.get_header_count(p_http_response) LOOP UTL_HTTP.get_header(p_http_response, i, l_name, l_value); debug('------ Header (' || i || ') ------'); debug('l_name=' || l_name); debug('l_value=' || l_value); IF l_name = 'Server' THEN g_server := l_value; debug('g_server=' || g_server); END IF; IF l_name = 'WWW-Authenticate' THEN g_realm := SUBSTR(REGEXP_SUBSTR(l_value, 'realm="[^"]+' ),8); g_qop := SUBSTR(REGEXP_SUBSTR(l_value, 'qop="[^"]+' ),6); g_nonce := SUBSTR(REGEXP_SUBSTR(l_value, 'nonce="[^"]+' ),8); g_opaque := SUBSTR(REGEXP_SUBSTR(l_value, 'opaque="[^"]+'),9); debug('g_realm=' || g_realm); debug('g_qop=' || g_qop); debug('g_nonce=' || g_nonce); debug('g_opaque=' || g_opaque); END IF; END LOOP; g_cnonce := LOWER(UTL_RAW.cast_to_raw(DBMS_OBFUSCATION_TOOLKIT.md5(input_string => DBMS_RANDOM.value))); debug('g_cnonce=' || g_cnonce); END get_header_info; -- ----------------------------------------------------------------------------- -- ----------------------------------------------------------------------------- FUNCTION get_response (p_username IN VARCHAR2, p_password IN VARCHAR2, p_uri IN VARCHAR2, p_method IN VARCHAR2 DEFAULT 'GET', p_req_cnt IN NUMBER DEFAULT 1) RETURN VARCHAR2 IS l_text VARCHAR2(2000); l_raw RAW(2000); l_out VARCHAR2(60); l_ha1 VARCHAR2(40); l_ha2 VARCHAR2(40); BEGIN l_text := p_username || ':' || g_realm || ':' || p_password; l_raw := UTL_RAW.cast_to_raw(l_text); l_out := DBMS_OBFUSCATION_TOOLKIT.md5(input => l_raw); l_ha1 := LOWER(l_out); l_text := p_method || ':' || p_uri; l_raw := UTL_RAW.cast_to_raw(l_text); l_out := DBMS_OBFUSCATION_TOOLKIT.md5(input => l_raw); l_ha2 := LOWER(l_out); l_text := l_ha1 || ':' || g_nonce || ':' || LPAD(p_req_cnt,8,0) || ':' || g_cnonce || ':' || g_qop || ':' || l_ha2; l_raw := UTL_RAW.cast_to_raw(l_text); l_out := DBMS_OBFUSCATION_TOOLKIT.md5(input => l_raw); RETURN LOWER(l_out); END get_response; -- ----------------------------------------------------------------------------- -- ----------------------------------------------------------------------------- FUNCTION begin_request(p_url IN VARCHAR2, p_username IN VARCHAR2, p_password IN VARCHAR2, p_method IN VARCHAR2 DEFAULT 'GET', p_http_version IN VARCHAR2 DEFAULT 'HTTP/1.1', p_req_cnt IN PLS_INTEGER DEFAULT 1) RETURN UTL_HTTP.req AS l_http_request UTL_HTTP.req; l_http_response UTL_HTTP.resp; l_text VARCHAR2(32767); l_uri VARCHAR2(32767); l_response VARCHAR2(32767); BEGIN init; -- Make a request that will fail to get the header information. -- This will be used to build up the pieces for the digest authentication -- using a call to get_header_info. l_http_request := UTL_HTTP.begin_request(p_url, p_method); l_http_response := UTL_HTTP.get_response(l_http_request); get_header_info (l_http_response); UTL_HTTP.end_response(l_http_response); -- Get everything after the domain as the URI. l_uri := SUBSTR(p_url, INSTR(p_url, '/', 1, 3)); l_response := get_response(p_username => p_username, p_password => p_password, p_uri => l_uri, p_method => p_method, p_req_cnt => p_req_cnt); -- Build the final digest string. l_text := 'Digest username="' || p_username ||'",'|| ' realm="' || g_realm ||'",'|| ' nonce="' || g_nonce ||'",'|| ' uri="' || l_uri ||'",'|| ' response="' || l_response ||'",'|| ' qop=' || g_qop ||',' || ' nc=' || LPAD(p_req_cnt,8,0) ||',' || ' cnonce="' || g_cnonce ||'"'; IF g_opaque IS NOT NULL THEN l_text := l_text || ',opaque="'||g_opaque||'"'; END IF; debug(l_text); -- Make the new request and set the digest authorization. l_http_request := UTL_HTTP.begin_request(p_url, p_method); UTL_HTTP.SET_HEADER(l_http_request, 'Authorization', l_text); RETURN l_http_request; EXCEPTION WHEN OTHERS THEN UTL_HTTP.end_response(l_http_response); RAISE; END begin_request; -- ----------------------------------------------------------------------------- END digest_auth_api; / SHOW ERRORS