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