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