jbe/bsw@20: --[[-- jbe/bsw@20: claimed_identifier, -- identifier owned by the user jbe/bsw@20: errmsg, -- error message in case of failure jbe/bsw@20: errcode = -- error code in case of failure (TODO: not implemented yet) jbe/bsw@20: auth.openid.verify( jbe/bsw@20: force_https = force_https, -- only allow https jbe/bsw@20: curl_options = curl_options -- options passed to "curl" binary, when performing discovery jbe/bsw@20: ) jbe/bsw@20: jbe/bsw@20: --]]-- jbe/bsw@20: jbe/bsw@20: function auth.openid.verify(args) jbe/bsw@20: local args = args or {} jbe/bsw@20: if cgi.params["openid.ns"] ~= "http://specs.openid.net/auth/2.0" then jbe/bsw@20: return nil, "No indirect OpenID 2.0 message received." jbe/bsw@20: end jbe/bsw@20: local mode = cgi.params["openid.mode"] jbe/bsw@20: if mode == "id_res" then jbe/bsw@20: local return_to_url = cgi.params["openid.return_to"] jbe/bsw@20: if not return_to_url then jbe/bsw@20: return nil, "No return_to URL received in answer." jbe/bsw@20: end jbe/bsw@20: if return_to_url ~= encode.url{ jbe/bsw@20: base = request.get_absolute_baseurl(), jbe/bsw@20: module = request.get_module(), jbe/bsw@20: view = request.get_view() jbe/bsw@20: } then jbe/bsw@20: return nil, "return_to URL not matching." jbe/bsw@20: end jbe/bsw@20: local discovery_args = table.new(args) jbe/bsw@20: local claimed_identifier = cgi.params["openid.claimed_id"] jbe/bsw@20: if not claimed_identifier then jbe/bsw@20: return nil, "No claimed identifier received." jbe/bsw@20: end jbe/bsw@20: local cropped_identifier = string.match(claimed_identifier, "[^#]*") jbe/bsw@20: local normalized_identifier = auth.openid._normalize_url( jbe/bsw@20: cropped_identifier jbe/bsw@20: ) jbe/bsw@20: if not normalized_identifier then jbe/bsw@20: return nil, "Claimed identifier could not be normalized." jbe/bsw@20: end jbe/bsw@20: if normalized_identifier ~= cropped_identifier then jbe/bsw@20: return nil, "Claimed identifier was not normalized." jbe/bsw@20: end jbe/bsw@20: discovery_args.user_supplied_identifier = cropped_identifier jbe/bsw@20: local dd, errmsg, errcode = auth.openid.discover(discovery_args) jbe/bsw@20: if not dd then jbe/bsw@20: return nil, errmsg, errcode jbe/bsw@20: end jbe/bsw@20: if not dd.claimed_identifier then jbe/bsw@20: return nil, "Identifier is an OpenID Provider." jbe/bsw@20: end jbe/bsw@20: if dd.claimed_identifier ~= cropped_identifier then jbe/bsw@20: return nil, "Claimed identifier does not match." jbe/bsw@20: end jbe/bsw@20: local nonce = cgi.params["openid.response_nonce"] jbe/bsw@20: if not nonce then jbe/bsw@20: return nil, "Did not receive a response nonce." jbe/bsw@20: end jbe/bsw@20: local year, month, day, hour, minute, second = string.match( jbe/bsw@20: nonce, jbe/bsw@20: "^([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" jbe/bsw@20: ) jbe/bsw@20: if not year then jbe/bsw@20: return nil, "Response nonce did not contain a parsable date/time." jbe/bsw@20: end jbe/bsw@20: local ts = atom.timestamp{ jbe/bsw@20: year = tonumber(year), jbe/bsw@20: month = tonumber(month), jbe/bsw@20: day = tonumber(day), jbe/bsw@20: hour = tonumber(hour), jbe/bsw@20: minute = tonumber(minute), jbe/bsw@20: second = tonumber(second) jbe/bsw@20: } jbe/bsw@20: -- NOTE: 50 hours margin allows us to ignore time zone issues here: jbe/bsw@20: if math.abs(ts - atom.timestamp:get_current()) > 3600 * 50 then jbe/bsw@20: return nil, "Response nonce contains wrong time or local time is wrong." jbe/bsw@20: end jbe/bsw@20: local params = {} jbe/bsw@20: for key, value in pairs(cgi.params) do jbe/bsw@20: local trimmed_key = string.match(key, "^openid%.(.+)") jbe/bsw@20: if trimmed_key then jbe/bsw@20: params[key] = value jbe/bsw@20: end jbe/bsw@20: end jbe/bsw@20: params["openid.mode"] = "check_authentication" jbe/bsw@20: local options = table.new(args.curl_options) jbe/bsw@20: for key, value in pairs(params) do jbe/bsw@20: options[#options+1] = "--data-urlencode" jbe/bsw@20: options[#options+1] = key .. "=" .. value jbe/bsw@20: end jbe/bsw@20: local status, headers, body = auth.openid._curl(dd.op_endpoint, options) jbe/bsw@20: if status ~= 200 then jbe/bsw@20: return nil, "Authorization could not be verified." jbe/bsw@20: end jbe/bsw@20: local result = {} jbe/bsw@20: for key, value in string.gmatch(body, "([^\n:]+):([^\n]*)") do jbe/bsw@20: result[key] = value jbe/bsw@20: end jbe/bsw@20: if result.ns ~= "http://specs.openid.net/auth/2.0" then jbe/bsw@20: return nil, "No OpenID 2.0 message replied." jbe/bsw@20: end jbe/bsw@20: if result.is_valid == "true" then jbe/bsw@20: return claimed_identifier jbe/bsw@20: else jbe/bsw@20: return nil, "Signature invalid." jbe/bsw@20: end jbe/bsw@20: elseif mode == "cancel" then jbe/bsw@20: return nil, "Authorization failed according to OpenID provider." jbe/bsw@20: else jbe/bsw@20: return nil, "Unexpected OpenID mode." jbe/bsw@20: end jbe/bsw@20: end