webmcp
diff framework/env/auth/openid/verify.lua @ 20:47ddf0f86009
OpenID 2.0 Relying Party support
author | jbe/bsw |
---|---|
date | Fri Apr 02 02:11:32 2010 +0200 (2010-04-02) |
parents | |
children | 32ec28229bb5 |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/framework/env/auth/openid/verify.lua Fri Apr 02 02:11:32 2010 +0200 1.3 @@ -0,0 +1,113 @@ 1.4 +--[[-- 1.5 +claimed_identifier, -- identifier owned by the user 1.6 +errmsg, -- error message in case of failure 1.7 +errcode = -- error code in case of failure (TODO: not implemented yet) 1.8 +auth.openid.verify( 1.9 + force_https = force_https, -- only allow https 1.10 + curl_options = curl_options -- options passed to "curl" binary, when performing discovery 1.11 +) 1.12 + 1.13 +--]]-- 1.14 + 1.15 +function auth.openid.verify(args) 1.16 + local args = args or {} 1.17 + if cgi.params["openid.ns"] ~= "http://specs.openid.net/auth/2.0" then 1.18 + return nil, "No indirect OpenID 2.0 message received." 1.19 + end 1.20 + local mode = cgi.params["openid.mode"] 1.21 + if mode == "id_res" then 1.22 + local return_to_url = cgi.params["openid.return_to"] 1.23 + if not return_to_url then 1.24 + return nil, "No return_to URL received in answer." 1.25 + end 1.26 + if return_to_url ~= encode.url{ 1.27 + base = request.get_absolute_baseurl(), 1.28 + module = request.get_module(), 1.29 + view = request.get_view() 1.30 + } then 1.31 + return nil, "return_to URL not matching." 1.32 + end 1.33 + local discovery_args = table.new(args) 1.34 + local claimed_identifier = cgi.params["openid.claimed_id"] 1.35 + if not claimed_identifier then 1.36 + return nil, "No claimed identifier received." 1.37 + end 1.38 + local cropped_identifier = string.match(claimed_identifier, "[^#]*") 1.39 + local normalized_identifier = auth.openid._normalize_url( 1.40 + cropped_identifier 1.41 + ) 1.42 + if not normalized_identifier then 1.43 + return nil, "Claimed identifier could not be normalized." 1.44 + end 1.45 + if normalized_identifier ~= cropped_identifier then 1.46 + return nil, "Claimed identifier was not normalized." 1.47 + end 1.48 + discovery_args.user_supplied_identifier = cropped_identifier 1.49 + local dd, errmsg, errcode = auth.openid.discover(discovery_args) 1.50 + if not dd then 1.51 + return nil, errmsg, errcode 1.52 + end 1.53 + if not dd.claimed_identifier then 1.54 + return nil, "Identifier is an OpenID Provider." 1.55 + end 1.56 + if dd.claimed_identifier ~= cropped_identifier then 1.57 + return nil, "Claimed identifier does not match." 1.58 + end 1.59 + local nonce = cgi.params["openid.response_nonce"] 1.60 + if not nonce then 1.61 + return nil, "Did not receive a response nonce." 1.62 + end 1.63 + local year, month, day, hour, minute, second = string.match( 1.64 + nonce, 1.65 + "^([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9])T([0-9][0-9]):([0-9][0-9]):([0-9][0-9])Z" 1.66 + ) 1.67 + if not year then 1.68 + return nil, "Response nonce did not contain a parsable date/time." 1.69 + end 1.70 + local ts = atom.timestamp{ 1.71 + year = tonumber(year), 1.72 + month = tonumber(month), 1.73 + day = tonumber(day), 1.74 + hour = tonumber(hour), 1.75 + minute = tonumber(minute), 1.76 + second = tonumber(second) 1.77 + } 1.78 + -- NOTE: 50 hours margin allows us to ignore time zone issues here: 1.79 + if math.abs(ts - atom.timestamp:get_current()) > 3600 * 50 then 1.80 + return nil, "Response nonce contains wrong time or local time is wrong." 1.81 + end 1.82 + local params = {} 1.83 + for key, value in pairs(cgi.params) do 1.84 + local trimmed_key = string.match(key, "^openid%.(.+)") 1.85 + if trimmed_key then 1.86 + params[key] = value 1.87 + end 1.88 + end 1.89 + params["openid.mode"] = "check_authentication" 1.90 + local options = table.new(args.curl_options) 1.91 + for key, value in pairs(params) do 1.92 + options[#options+1] = "--data-urlencode" 1.93 + options[#options+1] = key .. "=" .. value 1.94 + end 1.95 + local status, headers, body = auth.openid._curl(dd.op_endpoint, options) 1.96 + if status ~= 200 then 1.97 + return nil, "Authorization could not be verified." 1.98 + end 1.99 + local result = {} 1.100 + for key, value in string.gmatch(body, "([^\n:]+):([^\n]*)") do 1.101 + result[key] = value 1.102 + end 1.103 + if result.ns ~= "http://specs.openid.net/auth/2.0" then 1.104 + return nil, "No OpenID 2.0 message replied." 1.105 + end 1.106 + if result.is_valid == "true" then 1.107 + return claimed_identifier 1.108 + else 1.109 + return nil, "Signature invalid." 1.110 + end 1.111 + elseif mode == "cancel" then 1.112 + return nil, "Authorization failed according to OpenID provider." 1.113 + else 1.114 + return nil, "Unexpected OpenID mode." 1.115 + end 1.116 +end