webmcp
annotate framework/env/auth/openid/discover.lua @ 23:3a6fe8663b26
Code cleanup and documentation added; Year in copyright notice changed to 2009-2010
Details:
- Changed quoting style in auth.openid.xrds_document{...}
- Fixed documentation for auth.openid.initiate{...}
- Added documentation for mondelefant
- Code-cleanup in mondelefant:
-- removed unneccessary lines "rows = PQntuples(res); cols = PQnfields(res);"
-- avoided extra copy of first argument (self) in mondelefant_conn_query
-- no rawget in meta-method "__index" of database result lists and objects
-- removed unreachable "return 0;" in meta-method "__newindex" of database result lists and objects
- Year in copyright notice changed to 2009-2010
- Version string changed to "1.1.1"
Details:
- Changed quoting style in auth.openid.xrds_document{...}
- Fixed documentation for auth.openid.initiate{...}
- Added documentation for mondelefant
- Code-cleanup in mondelefant:
-- removed unneccessary lines "rows = PQntuples(res); cols = PQnfields(res);"
-- avoided extra copy of first argument (self) in mondelefant_conn_query
-- no rawget in meta-method "__index" of database result lists and objects
-- removed unreachable "return 0;" in meta-method "__newindex" of database result lists and objects
- Year in copyright notice changed to 2009-2010
- Version string changed to "1.1.1"
author | jbe |
---|---|
date | Fri Jun 04 19:00:34 2010 +0200 (2010-06-04) |
parents | 47ddf0f86009 |
children |
rev | line source |
---|---|
jbe/bsw@20 | 1 --[[-- |
jbe/bsw@20 | 2 discovery_data, -- table containing "claimed_identifier", "op_endpoint" and "op_local_identifier" |
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.discover{ |
jbe/bsw@20 | 6 user_supplied_identifier = user_supplied_identifier, -- string given by user |
jbe/bsw@20 | 7 https_as_default = https_as_default, -- default to https |
jbe/bsw@20 | 8 curl_options = curl_options -- options passed to "curl" binary, when performing discovery |
jbe/bsw@20 | 9 } |
jbe/bsw@20 | 10 |
jbe/bsw@20 | 11 --]]-- |
jbe/bsw@20 | 12 |
jbe/bsw@20 | 13 -- helper function |
jbe/bsw@20 | 14 local function decode_entities(str) |
jbe/bsw@20 | 15 local str = str |
jbe/bsw@20 | 16 str = string.gsub(value, "<", '<') |
jbe/bsw@20 | 17 str = string.gsub(value, ">", '>') |
jbe/bsw@20 | 18 str = string.gsub(value, """, '"') |
jbe/bsw@20 | 19 str = string.gsub(value, "&", '&') |
jbe/bsw@20 | 20 return str |
jbe/bsw@20 | 21 end |
jbe/bsw@20 | 22 |
jbe/bsw@20 | 23 -- helper function |
jbe/bsw@20 | 24 local function get_tag_value( |
jbe/bsw@20 | 25 str, -- HTML document or document snippet |
jbe/bsw@20 | 26 match_tag, -- tag name |
jbe/bsw@20 | 27 match_key, -- attribute key to match |
jbe/bsw@20 | 28 match_value, -- attribute value to match |
jbe/bsw@20 | 29 result_key -- attribute key of value to return |
jbe/bsw@20 | 30 ) |
jbe/bsw@20 | 31 -- NOTE: The following parameters are case insensitive |
jbe/bsw@20 | 32 local match_tag = string.lower(match_tag) |
jbe/bsw@20 | 33 local match_key = string.lower(match_key) |
jbe/bsw@20 | 34 local match_value = string.lower(match_value) |
jbe/bsw@20 | 35 local result_key = string.lower(result_key) |
jbe/bsw@20 | 36 for tag, attributes in |
jbe/bsw@20 | 37 string.gmatch(str, "<([0-9A-Za-z_-]+) ([^>]*)>") |
jbe/bsw@20 | 38 do |
jbe/bsw@20 | 39 local tag = string.lower(tag) |
jbe/bsw@20 | 40 if tag == match_tag then |
jbe/bsw@20 | 41 local matching = false |
jbe/bsw@20 | 42 for key, value in |
jbe/bsw@20 | 43 string.gmatch(attributes, '([0-9A-Za-z_-]+)="([^"<>]*)"') |
jbe/bsw@20 | 44 do |
jbe/bsw@20 | 45 local key = string.lower(key) |
jbe/bsw@20 | 46 local value = decode_entities(value) |
jbe/bsw@20 | 47 if key == match_key then |
jbe/bsw@20 | 48 -- NOTE: match_key must only match one key of space seperated list |
jbe/bsw@20 | 49 for value in string.gmatch(value, "[^ ]+") do |
jbe/bsw@20 | 50 if string.lower(value) == match_value then |
jbe/bsw@20 | 51 matching = true |
jbe/bsw@20 | 52 break |
jbe/bsw@20 | 53 end |
jbe/bsw@20 | 54 end |
jbe/bsw@20 | 55 end |
jbe/bsw@20 | 56 if key == result_key then |
jbe/bsw@20 | 57 result_value = value |
jbe/bsw@20 | 58 end |
jbe/bsw@20 | 59 end |
jbe/bsw@20 | 60 if matching then |
jbe/bsw@20 | 61 return result_value |
jbe/bsw@20 | 62 end |
jbe/bsw@20 | 63 end |
jbe/bsw@20 | 64 end |
jbe/bsw@20 | 65 return nil |
jbe/bsw@20 | 66 end |
jbe/bsw@20 | 67 |
jbe/bsw@20 | 68 -- helper function |
jbe/bsw@20 | 69 local function tag_contents(str, match_tag) |
jbe/bsw@20 | 70 local pos = 0 |
jbe/bsw@20 | 71 local tagpos, closing, tag |
jbe/bsw@20 | 72 local function next_tag() |
jbe/bsw@20 | 73 local prefix |
jbe/bsw@20 | 74 tagpos, prefix, tag, pos = string.match( |
jbe/bsw@20 | 75 str, |
jbe/bsw@20 | 76 "()<(/?)([0-9A-Za-z:_-]+)[^>]*>()", |
jbe/bsw@20 | 77 pos |
jbe/bsw@20 | 78 ) |
jbe/bsw@20 | 79 closing = (prefix == "/") |
jbe/bsw@20 | 80 end |
jbe/bsw@20 | 81 return function() |
jbe/bsw@20 | 82 repeat |
jbe/bsw@20 | 83 next_tag() |
jbe/bsw@20 | 84 if not tagpos then return nil end |
jbe/bsw@20 | 85 local stripped_tag |
jbe/bsw@20 | 86 if string.find(tag, ":") then |
jbe/bsw@20 | 87 stripped_tag = string.match(tag, ":([^:]*)$") |
jbe/bsw@20 | 88 else |
jbe/bsw@20 | 89 stripped_tag = tag |
jbe/bsw@20 | 90 end |
jbe/bsw@20 | 91 until stripped_tag == match_tag and not closing |
jbe/bsw@20 | 92 local content_start = pos |
jbe/bsw@20 | 93 local used_tag = tag |
jbe/bsw@20 | 94 local counter = 0 |
jbe/bsw@20 | 95 while true do |
jbe/bsw@20 | 96 repeat |
jbe/bsw@20 | 97 next_tag() |
jbe/bsw@20 | 98 if not tagpos then return nil end |
jbe/bsw@20 | 99 until tag == used_tag |
jbe/bsw@20 | 100 if closing then |
jbe/bsw@20 | 101 if counter > 0 then |
jbe/bsw@20 | 102 counter = counter - 1 |
jbe/bsw@20 | 103 else |
jbe/bsw@20 | 104 return string.sub(str, content_start, tagpos-1) |
jbe/bsw@20 | 105 end |
jbe/bsw@20 | 106 else |
jbe/bsw@20 | 107 counter = counter + 1 |
jbe/bsw@20 | 108 end |
jbe/bsw@20 | 109 end |
jbe/bsw@20 | 110 local content = string.sub(rest, 1, startpos-1) |
jbe/bsw@20 | 111 str = string.sub(rest, endpos+1) |
jbe/bsw@20 | 112 return content |
jbe/bsw@20 | 113 end |
jbe/bsw@20 | 114 end |
jbe/bsw@20 | 115 |
jbe/bsw@20 | 116 local function strip(str) |
jbe/bsw@20 | 117 local str = str |
jbe/bsw@20 | 118 string.gsub(str, "^[ \t\r\n]+", "") |
jbe/bsw@20 | 119 string.gsub(str, "[ \t\r\n]+$", "") |
jbe/bsw@20 | 120 return str |
jbe/bsw@20 | 121 end |
jbe/bsw@20 | 122 |
jbe/bsw@20 | 123 function auth.openid.discover(args) |
jbe/bsw@20 | 124 local url = string.match(args.user_supplied_identifier, "[^#]*") |
jbe/bsw@20 | 125 -- NOTE: XRIs are not supported |
jbe/bsw@20 | 126 if |
jbe/bsw@20 | 127 string.find(url, "^[Xx][Rr][Ii]://") or |
jbe/bsw@20 | 128 string.find(url, "^[=@+$!(]") |
jbe/bsw@20 | 129 then |
jbe/bsw@20 | 130 return nil, "XRI identifiers are not supported." |
jbe/bsw@20 | 131 end |
jbe/bsw@20 | 132 -- Prepend http:// or https://, if neccessary: |
jbe/bsw@20 | 133 if not string.find(url, "://") then |
jbe/bsw@20 | 134 if args.default_to_https then |
jbe/bsw@20 | 135 url = "https://" .. url |
jbe/bsw@20 | 136 else |
jbe/bsw@20 | 137 url = "http://" .. url |
jbe/bsw@20 | 138 end |
jbe/bsw@20 | 139 end |
jbe/bsw@20 | 140 -- Either an xrds_document or an html_document will be fetched |
jbe/bsw@20 | 141 local xrds_document, html_document |
jbe/bsw@20 | 142 -- Repeat max 10 times to avoid endless redirection loops |
jbe/bsw@20 | 143 local redirects = 0 |
jbe/bsw@20 | 144 while true do |
jbe/bsw@20 | 145 local status, headers, body = auth.openid._curl(url, args.curl_options) |
jbe/bsw@20 | 146 if not status then |
jbe/bsw@20 | 147 return nil, "Error while locating XRDS or HTML file for discovery." |
jbe/bsw@20 | 148 end |
jbe/bsw@20 | 149 -- Check, if we are redirected: |
jbe/bsw@20 | 150 local location = string.match( |
jbe/bsw@20 | 151 headers, |
jbe/bsw@20 | 152 "\r?\n[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[ \t]*([^\r\n]+)" |
jbe/bsw@20 | 153 ) |
jbe/bsw@20 | 154 if location then |
jbe/bsw@20 | 155 -- If we are redirected too often, then return an error. |
jbe/bsw@20 | 156 if redirects >= 10 then |
jbe/bsw@20 | 157 return nil, "Too many redirects." |
jbe/bsw@20 | 158 end |
jbe/bsw@20 | 159 -- Otherwise follow the redirection by changing the variable "url" |
jbe/bsw@20 | 160 -- and by incrementing the redirect counter. |
jbe/bsw@20 | 161 url = location |
jbe/bsw@20 | 162 redirects = redirects + 1 |
jbe/bsw@20 | 163 else |
jbe/bsw@20 | 164 -- Check, if there is an X-XRDS-Location header |
jbe/bsw@20 | 165 -- pointing to an XRDS document: |
jbe/bsw@20 | 166 local xrds_location = string.match( |
jbe/bsw@20 | 167 headers, |
jbe/bsw@20 | 168 "\r?\n[Xx]%-[Xx][Rr][Dd][Ss]%-[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[ \t]*([^\r\n]+)" |
jbe/bsw@20 | 169 ) |
jbe/bsw@20 | 170 -- If there is no X-XRDS-Location header, there might be an |
jbe/bsw@20 | 171 -- http-equiv meta tag serving the same purpose: |
jbe/bsw@20 | 172 if not xrds_location and status == 200 then |
jbe/bsw@20 | 173 xrds_location = get_tag_value(body, "meta", "http-equiv", "X-XRDS-Location", "content") |
jbe/bsw@20 | 174 end |
jbe/bsw@20 | 175 if xrds_location then |
jbe/bsw@20 | 176 -- If we know the XRDS-Location, we can fetch the XRDS document |
jbe/bsw@20 | 177 -- from that location: |
jbe/bsw@20 | 178 local status, headers, body = auth.openid._curl(xrds_location, args.curl_options) |
jbe/bsw@20 | 179 if not status then |
jbe/bsw@20 | 180 return nil, "XRDS document could not be loaded." |
jbe/bsw@20 | 181 end |
jbe/bsw@20 | 182 if status ~= 200 then |
jbe/bsw@20 | 183 return nil, "XRDS document not found where expected." |
jbe/bsw@20 | 184 end |
jbe/bsw@20 | 185 xrds_document = body |
jbe/bsw@20 | 186 break |
jbe/bsw@20 | 187 elseif |
jbe/bsw@20 | 188 -- If the Content-Type header is set accordingly, then we already |
jbe/bsw@20 | 189 -- should have received an XRDS document: |
jbe/bsw@20 | 190 string.find( |
jbe/bsw@20 | 191 headers, |
jbe/bsw@20 | 192 "\r?\n[Cc][Oo][Nn][Tt][Ee][Nn][Tt]%-[Tt][Yy][Pp][Ee]:[ \t]*application/xrds%+xml\r?\n" |
jbe/bsw@20 | 193 ) |
jbe/bsw@20 | 194 then |
jbe/bsw@20 | 195 if status ~= 200 then |
jbe/bsw@20 | 196 return nil, "XRDS document announced but not found." |
jbe/bsw@20 | 197 end |
jbe/bsw@20 | 198 xrds_document = body |
jbe/bsw@20 | 199 break |
jbe/bsw@20 | 200 else |
jbe/bsw@20 | 201 -- Otherwise we should have received an HTML document: |
jbe/bsw@20 | 202 if status ~= 200 then |
jbe/bsw@20 | 203 return nil, "No XRDS or HTML document found for discovery." |
jbe/bsw@20 | 204 end |
jbe/bsw@20 | 205 html_document = body |
jbe/bsw@20 | 206 break; |
jbe/bsw@20 | 207 end |
jbe/bsw@20 | 208 end |
jbe/bsw@20 | 209 end |
jbe/bsw@20 | 210 local claimed_identifier -- OpenID identifier the user claims to own |
jbe/bsw@20 | 211 local op_endpoint -- OpenID provider endpoint URL |
jbe/bsw@20 | 212 local op_local_identifier -- optional user identifier, local to the OpenID provider |
jbe/bsw@20 | 213 if xrds_document then |
jbe/bsw@20 | 214 -- If we got an XRDS document, we look for a matching <Service> entry: |
jbe/bsw@20 | 215 for content in tag_contents(xrds_document, "Service") do |
jbe/bsw@20 | 216 local service_uri, service_localid |
jbe/bsw@20 | 217 for content in tag_contents(content, "URI") do |
jbe/bsw@20 | 218 if not string.find(content, "[<>]") then |
jbe/bsw@20 | 219 service_uri = strip(content) |
jbe/bsw@20 | 220 break |
jbe/bsw@20 | 221 end |
jbe/bsw@20 | 222 end |
jbe/bsw@20 | 223 for content in tag_contents(content, "LocalID") do |
jbe/bsw@20 | 224 if not string.find(content, "[<>]") then |
jbe/bsw@20 | 225 service_localid = strip(content) |
jbe/bsw@20 | 226 break |
jbe/bsw@20 | 227 end |
jbe/bsw@20 | 228 end |
jbe/bsw@20 | 229 for content in tag_contents(content, "Type") do |
jbe/bsw@20 | 230 if not string.find(content, "[<>]") then |
jbe/bsw@20 | 231 local content = strip(content) |
jbe/bsw@20 | 232 if content == "http://specs.openid.net/auth/2.0/server" then |
jbe/bsw@20 | 233 -- The user entered a provider identifier, thus claimed_identifier |
jbe/bsw@20 | 234 -- and op_local_identifier will be set to nil. |
jbe/bsw@20 | 235 op_endpoint = service_uri |
jbe/bsw@20 | 236 break |
jbe/bsw@20 | 237 elseif content == "http://specs.openid.net/auth/2.0/signon" then |
jbe/bsw@20 | 238 -- The user entered his/her own identifier. |
jbe/bsw@20 | 239 claimed_identifier = url |
jbe/bsw@20 | 240 op_endpoint = service_uri |
jbe/bsw@20 | 241 op_local_identifier = service_localid |
jbe/bsw@20 | 242 break |
jbe/bsw@20 | 243 end |
jbe/bsw@20 | 244 end |
jbe/bsw@20 | 245 end |
jbe/bsw@20 | 246 end |
jbe/bsw@20 | 247 elseif html_document then |
jbe/bsw@20 | 248 -- If we got an HTML document, we look for matching <link .../> tags: |
jbe/bsw@20 | 249 claimed_identifier = url |
jbe/bsw@20 | 250 op_endpoint = get_tag_value( |
jbe/bsw@20 | 251 html_document, |
jbe/bsw@20 | 252 "link", "rel", "openid2.provider", "href" |
jbe/bsw@20 | 253 ) |
jbe/bsw@20 | 254 op_local_identifier = get_tag_value( |
jbe/bsw@20 | 255 html_document, |
jbe/bsw@20 | 256 "link", "rel", "openid2.local_id", "href" |
jbe/bsw@20 | 257 ) |
jbe/bsw@20 | 258 else |
jbe/bsw@20 | 259 error("Assertion failed") -- should not happen |
jbe/bsw@20 | 260 end |
jbe/bsw@20 | 261 if not op_endpoint then |
jbe/bsw@20 | 262 return nil, "No OpenID endpoint found." |
jbe/bsw@20 | 263 end |
jbe/bsw@20 | 264 if claimed_identifier then |
jbe/bsw@20 | 265 claimed_identifier = auth.openid._normalize_url(claimed_identifier) |
jbe/bsw@20 | 266 if not claimed_identifier then |
jbe/bsw@20 | 267 return nil, "Claimed identifier could not be normalized." |
jbe/bsw@20 | 268 end |
jbe/bsw@20 | 269 end |
jbe/bsw@20 | 270 return { |
jbe/bsw@20 | 271 claimed_identifier = claimed_identifier, |
jbe/bsw@20 | 272 op_endpoint = op_endpoint, |
jbe/bsw@20 | 273 op_local_identifier = op_local_identifier |
jbe/bsw@20 | 274 } |
jbe/bsw@20 | 275 end |