Monday, November 08, 2010 at 12:04 AM.
system.verbs.builtins.mainResponder.security.httpAuthentication
on httpAuthentication (realm, pta=nil, groupname="default", memberList=nil, securityLevel=0, fldebug=false, domain="/", noncesExpireAfterMinutes=15) { <<Change Notes <<Docs: http://docserver.userland.com/mainResponder/security/httpAuthentication <<4/16/00; 1:21:20 AM by JES <<localized all error strings <<Tuesday, October 26, 1999 at 4:12:49 PM by AR <<Basic Access Authentication as well as Digest Access Authentication are supported per RFC 2617 <<We assume that we are called from a mainResponder #security script. If we aren't, please pass us the address of the param table. <<Sunday, November 07, 1999 at 9:26:41 PM by AR <<username is now case-insensitive <<Sunday, December 19, 1999 at 4:30:04 PM by AR <<Closed a security hole in the Basic Authentication process. <<05/01/00; 6:40:25 PM by JES <<Changed getString calls to use a replacement table address instead of a lists <<06/19/00; 9:55:54 AM by JES <<localization bug fix: when outside wsf context, html.getPageTableAddress was failing in mainResponder.getString <<Added support for Frontier language setting in user.prefs <<4/16/00; 4:50:45 PM by JES: temporary page table for localization support <<for localization, we have to have a page table with at least the language, so we make a temporary one local (tempTable); new (tableType, @tempTable); if pta != nil { // if in wsf context, use the language defined by the site, if any if defined (pta^.language) { tempTable.language = pta^.language}}; if not defined (tempTable.language) { // outside wsf context, or no language defined for site if defined (config.mainResponder.globals.language) { tempTable.language = config.mainResponder.globals.language} else { if defined (user.prefs.language) { tempTable.language = user.prefs.language} else { tempTable.language = "English"}}}; on H (s) { //MD5 hash function return (string.hashMD5 (s))}; on KD (secret, data) { //Section 3.2.1 of RFC 2617 return (H (secret + ":" + data))}; on A1 () { //Section 3.2.2.2 of RFC 2617 return (authTable.username + ":" + realm + ":" + adrMember^.password)}; on A2 () { //Section 3.2.2.3 of RFC 2617 if flAuthInt { return (pta^.method + ":" + authTable.uri + ":" + H (pta^.requestBody))} else { return (pta^.method + ":" + authTable.uri)}}; on requestDigest () { //Section 3.2.2.1 of RFC 2617 if flAuth or flAuthInt { return (KD (H (A1 ()), authTable.nonce + ":" + authTable.nc + ":" + authTable.cnonce + ":" + authTable.qop + ":" + H (A2 ())))} else { return (KD (H (A1 ()), authTable.nonce + ":" + H (A2 ())))}}; on initNonces () { if not defined (adrNonces^) { new (tableType, adrNonces)}}; on generateNonce () { local (nonce, now = clock.now ()); nonce = H (now + ":" + temp.Frontier.startupTime + ":" + pta^.client + ":" + domain + ":" + memAvail () + ":" + string.padWithZeros (random (0, infinity), 8)); local (adrNonce = @adrNonces^.[nonce]); new (tableType, adrNonce); adrNonce^.nc = 1; adrNonce^.expires = now + noncesExpireAfterMinutes * 60; return (nonce)}; on deleteNonce () { try {delete (@adrNonces^.[authTable.nonce])}; return (true)}; on httpUnauthorized (flStale=false) { //try again with proper credentials on addToAuthHeader (s) { local (adr = @pta^.responseHeaders.["WWW-Authenticate"]); if defined (adr^) { if typeOf (adr^) == listType { adr^ = adr^ + {s}} else { adr^ = {adr^} + {s}}} else { adr^ = s}; return}; if flAcceptDigest { initNonces (); deleteNonce (); local (s = ""); s = s + "Digest realm=\"" + realm + "\","; s = s + " domain=\"" + domain + "\","; s = s + " nonce=\"" + generateNonce () + "\","; if flStale { s = s + " stale=true,"}; s = s + " algorithm=MD5,"; s = s + " qop=\"auth,auth-int\""; addToAuthHeader (s)}; if flAcceptBasic { addToAuthHeader ("Basic realm=\"" + realm + "\"")}; pta^.responseBody = webserver.util.buildErrorPage ("401 UNAUTHORIZED", mainResponder.getString ("security.http401Error", pta: @tempTable)); // 4/16/00 JES: localized pta^.code = 401; if flDebug { adrDebugTable^.["WWW-Authenticate"] = pta^.responseHeaders.["WWW-Authenticate"]; adrDebugTable^.code = pta^.code}; scriptError ("!return")}; //prevent mainResponder.respond from overwriting our response on httpForbidden (s=nil) { //don't bother to try again, permanent failure pta^.code = 403; if s == nil { s = mainResponder.getString ("security.http403Error", pta: @tempTable)}; // 4/16/00 JES: localized pta^.responseBody = webserver.util.buildErrorPage ("403 FORBIDDEN", s); if flDebug { adrDebugTable^.code = pta^.code}; scriptError ("!return")}; //prevent mainResponder.respond from overwriting our response on httpBadRequest (s=nil) { //don't bother to try again, malformed request pta^.code = 400; if s == nil { s = mainResponder.getString ("security.http400Error", pta: @tempTable)}; // 4/16/00 JES: localized pta^.responseBody = webserver.util.buildErrorPage ("400 BAD REQUEST", s); if flDebug { adrDebugTable^.code = pta^.code}; scriptError ("!return")}; //prevent mainResponder.respond from overwriting our response on checkMember (username, password = nil) { //set adrMember or throw an error adrMember = mainResponder.members.getMemberTable (groupname, username); if not defined (adrMember^) { httpUnauthorized ()}; if (password != nil) and (password != adrMember^.password) { httpUnauthorized ()}; if memberList != nil { //make sure the client is in the specified list of members local (lowerName = string.lower (username)); if typeOf (memberList) == listType { local (memberName, flAuthorized = false); for memberName in memberList { if string.lower (memberName) == lowerName { flAuthorized = true; break}}; if not flAuthorized { httpUnauthorized ()}} else { if string.lower (memberList) != lowerName { httpUnauthorized ()}}}}; on fakeCookie (username, password) { //add a cookie to the request headers so mainResponder.members.checkMembership won't complain if not defined (pta^.requestHeaders.cookies) { new (tableType, @pta^.requestHeaders.cookies)}; local (adrMembers = mainResponder.members.getMembershipTable (groupname)); local (myCookieName = string.innerCaseName (adrMembers^.cookieName)); pta^.requestHeaders.cookies.[myCookieName] = string.urlEncode (username + "\t" + password)}; local (adrMember); local (flAuth = false, flAuthInt = false); //our internal state, don't mess with these local (adrNonces = @system.temp.httpDigestAuthentication); local (authTable, adrDebugTable); if pta == nil { pta = parentOf (client)}; //assume we're called from a #security script if flDebug { adrDebugTable = log.addToGuestDatabase ("debugHttpAuthentication"); adrDebugTable^.securityLevel = securityLevel; if defined (pta^.requestHeaders.Authorization) { adrDebugTable^.Authorization = pta^.requestHeaders.Authorization}; if defined (pta^.requestHeaders.["User-Agent"]) { adrDebugTable^.["User-Agent"] = pta^.requestHeaders.["User-Agent"]}; if defined (pta^.client) { adrDebugTable^.client = pta^.client}; if defined (pta^.uri) { adrDebugTable^.uri = pta^.uri}}; local (flAcceptDigest = false, flAcceptBasic = false, flOneTimeNonces = false); if (securityLevel > 0) and (not defined (string.hashMD5)) { //for compatibility with 6.0 httpForbidden (mainResponder.getString ("security.httpUnsupportedSecurityLevel", pta: @tempTable))}; // 4/16/00 JES: localized case securityLevel { //initialize the above based on specified security level 0 { flAcceptBasic = true}; 1 { flAcceptBasic = true; flAcceptDigest = true}; 2 { flAcceptDigest = true}; 3 { flAcceptDigest = true; flOneTimeNonces = true}} else { httpForbidden (mainResponder.getString ("security.httpUnspecifiedSecurityLevel", pta: @tempTable))}; // 4/16/00 JES: localized if not defined (pta^.requestHeaders.Authorization) { httpUnauthorized ()}; local (s, authScheme); s = string (pta^.requestHeaders.Authorization); authScheme = string.nthField (string.lower (s), " ", 1); s = string.trimWhiteSpace (string.delete (s, 1, string.length (authScheme))); case authScheme { "basic" { if not flAcceptBasic { httpUnauthorized ()}; local (credentials, value, username, password); credentials = string.nthField (s, " ", 1); credentials = string.popTrailing (s, ','); value = string (base64.decode (credentials)); username = string.nthField (value, ":", 1); password = string.delete (value, 1, string.length (username) + 1); if flDebug { adrDebugTable^.username = username; adrDebugTable^.password = password}; if (string.length (username) == 0) or (string.length (password) == 0) { //AR 12/19/1999 httpUnauthorized ()}; checkMember (username, password); fakeCookie (username, password)}; "digest" { if not flAcceptDigest { httpUnauthorized ()}; new (tableType, @authTable); loop { //parse Authorization header local (name, value); bundle { //loop over white-space and delimiters local (ix = 1); while " \t," contains string.nthChar (s, ix) { ix++}; s = string.mid (s, ix, string.length (s))}; bundle { //extract name name = string.nthField (s, "=", 1); if string.length (name) == 0 { break}; s = string.delete (s, 1, string.length (name) + 1)}; bundle { //extract value local (ix = 2); if string.nthChar (s, 1) == "\"" { while ix <= string.length (s) { case s[ix] { '"' { value = string.mid (s, 2, ix-2); value = string.replaceAll (value, "\\\"", "\""); //unquote break}; '\\' { ix++}}; ix++}} else { while ix <= string.length (s) { case s[ix] { ' '; '\t'; ',' { break}}; ix++}; value = string.mid (s, 1, ix-1)}; if sizeof (value) == 0 { break}; s = string.delete (s, 1, ix)}; authTable.[name] = value}; if flDebug { table.assign (@adrDebugTable^.authTable, authTable)}; local (replacementTable); new (tableType, @replacementTable); // 05/01/00 JES: use replacement table instead of a list bundle { //check for bad requests on checkRequiredField (field) { if not defined (authTable.[field]) { replacementTable.field = field; httpBadRequest (mainResponder.getString ("security.httpMissingFieldError", @replacementTable, pta: @tempTable))}}; // 4/16/00 JES: localized on checkForbiddenField (field) { if defined (authTable.[field]) { replacementTable.field = field; httpBadRequest (mainResponder.getString ("security.httpIllegalFieldError", @replacementTable, pta: @tempTable))}}; // 4/16/00 JES: localized checkRequiredField ("username"); checkRequiredField ("realm"); checkRequiredField ("nonce"); checkRequiredField ("uri"); bundle { //check URI local (requestURI = string.nthField (pta^.firstLine, " ", 2)); if requestURI beginsWith "http://" { //it's a future-style full URL local (pathParts = string.urlSplit (requestURI)); requestURI = "/" + pathParts[3]}; if requestURI != authTable.uri { httpBadRequest (mainResponder.getString ("security.httpBadURIDirective", pta: @tempTable))}}; // 4/14/00 JES: localized checkRequiredField ("response"); if defined (authTable.qop) { local (qop = string.lower (authTable.qop)); flAuth = (qop == "auth"); flAuthInt = (qop == "auth-int"); if not (flAuth or flAuthInt) { httpBadRequest (mainResponder.getString ("security.httpBadQopField", pta: @tempTable))}; // 4/14/00 JES: localized checkRequiredField ("cnonce"); checkRequiredField ("nc")} else { checkForbiddenField ("cnonce"); checkForbiddenField ("nc")}; if defined (authTable.algorithm) and (authTable.algorithm != "MD5") { httpBadRequest (mainResponder.getString ("security.httpUnsupportedDigestAlgorithm", pta: @tempTable))}}; // 4/14/00 JES: localized if (string.length (authTable.username) == 0) { //AR 12/19/1999 httpUnauthorized ()}; checkMember (authTable.username); initNonces (); local (adrNonce = @adrNonces^.[authTable.nonce]); if not defined (adrNonce^) { httpUnauthorized ()}; if flAuth or flAuthInt { //check nonce-count local (s = string.hex (adrNonce^.nc)); s = string.delete (s, 1, 2); //delete "0x" s = string.filledString ("0", 8 - string.length (s)) + s; s = string.lower (s); if s != authTable.nc { //nonce count out of sequence httpUnauthorized ()}; adrNonce^.nc++}; if authTable.response != requestDigest () { httpUnauthorized ()}; if clock.now () > adrNonce^.expires { //is nonce stale? httpUnauthorized (flStale:true)}; if flOneTimeNonces and not (flAuth or flAuthInt) { //send a new nonce, expire the current one adrNonce^.expires = clock.now () - 1; pta^.responseHeaders.["Authentication-Info"] = "nextnonce=\"" + generateNonce () + "\""; if flDebug { adrDebugTable^.["Authentication-Info"] = pta^.responseHeaders.["Authentication-Info"]}}; fakeCookie (authTable.username, adrMember^.password)}} else { httpUnauthorized ()}; return (true)}
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.