webmcp
view framework/env/auth/openid/verify.lua @ 88:c743d80859cf
Added tag v1.2.4 for changeset a5106c0a9be4
| author | jbe | 
|---|---|
| date | Fri Jul 06 12:54:26 2012 +0200 (2012-07-06) | 
| parents | 47ddf0f86009 | 
| children | 32ec28229bb5 | 
 line source
     1 --[[--
     2 claimed_identifier,                        -- identifier owned by the user
     3 errmsg,                                    -- error message in case of failure
     4 errcode =                                  -- error code in case of failure (TODO: not implemented yet)
     5 auth.openid.verify(
     6   force_https              = force_https,  -- only allow https
     7   curl_options             = curl_options  -- options passed to "curl" binary, when performing discovery
     8 )
    10 --]]--
    12 function auth.openid.verify(args)
    13   local args = args or {}
    14   if cgi.params["openid.ns"] ~= "http://specs.openid.net/auth/2.0" then
    15     return nil, "No indirect OpenID 2.0 message received."
    16   end
    17   local mode = cgi.params["openid.mode"]
    18   if mode == "id_res" then
    19     local return_to_url = cgi.params["openid.return_to"]
    20     if not return_to_url then
    21       return nil, "No return_to URL received in answer."
    22     end
    23     if return_to_url ~= encode.url{
    24       base   = request.get_absolute_baseurl(),
    25       module = request.get_module(),
    26       view   = request.get_view()
    27     } then
    28       return nil, "return_to URL not matching."
    29     end
    30     local discovery_args = table.new(args)
    31     local claimed_identifier = cgi.params["openid.claimed_id"]
    32     if not claimed_identifier then
    33       return nil, "No claimed identifier received."
    34     end
    35     local cropped_identifier = string.match(claimed_identifier, "[^#]*")
    36     local normalized_identifier = auth.openid._normalize_url(
    37       cropped_identifier
    38     )
    39     if not normalized_identifier then
    40       return nil, "Claimed identifier could not be normalized."
    41     end
    42     if normalized_identifier ~= cropped_identifier then
    43       return nil, "Claimed identifier was not normalized."
    44     end
    45     discovery_args.user_supplied_identifier = cropped_identifier
    46     local dd, errmsg, errcode = auth.openid.discover(discovery_args)
    47     if not dd then
    48       return nil, errmsg, errcode
    49     end
    50     if not dd.claimed_identifier then
    51       return nil, "Identifier is an OpenID Provider."
    52     end
    53     if dd.claimed_identifier ~= cropped_identifier then
    54       return nil, "Claimed identifier does not match."
    55     end
    56     local nonce = cgi.params["openid.response_nonce"]
    57     if not nonce then
    58       return nil, "Did not receive a response nonce."
    59     end 
    60     local year, month, day, hour, minute, second = string.match(
    61       nonce,
    62       "^([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"
    63     )
    64     if not year then
    65       return nil, "Response nonce did not contain a parsable date/time."
    66     end
    67     local ts = atom.timestamp{
    68       year   = tonumber(year),
    69       month  = tonumber(month),
    70       day    = tonumber(day),
    71       hour   = tonumber(hour),
    72       minute = tonumber(minute),
    73       second = tonumber(second)
    74     }
    75     -- NOTE: 50 hours margin allows us to ignore time zone issues here:
    76     if math.abs(ts - atom.timestamp:get_current()) > 3600 * 50 then
    77       return nil, "Response nonce contains wrong time or local time is wrong."
    78     end
    79     local params = {}
    80     for key, value in pairs(cgi.params) do
    81       local trimmed_key = string.match(key, "^openid%.(.+)")
    82       if trimmed_key then
    83         params[key] = value
    84       end
    85     end
    86     params["openid.mode"] = "check_authentication"
    87     local options = table.new(args.curl_options)
    88     for key, value in pairs(params) do
    89       options[#options+1] = "--data-urlencode"
    90       options[#options+1] = key .. "=" .. value
    91     end
    92     local status, headers, body = auth.openid._curl(dd.op_endpoint, options)
    93     if status ~= 200 then
    94       return nil, "Authorization could not be verified."
    95     end
    96     local result = {}
    97     for key, value in string.gmatch(body, "([^\n:]+):([^\n]*)") do
    98       result[key] = value
    99     end
   100     if result.ns ~= "http://specs.openid.net/auth/2.0" then
   101       return nil, "No OpenID 2.0 message replied."
   102     end
   103     if result.is_valid == "true" then
   104       return claimed_identifier
   105     else
   106       return nil, "Signature invalid."
   107     end
   108   elseif mode == "cancel" then
   109     return nil, "Authorization failed according to OpenID provider."
   110   else
   111     return nil, "Unexpected OpenID mode."
   112   end
   113 end
