Mercurial > prosody-modules
comparison mod_auth_http_cookie/mod_auth_http_cookie.lua @ 3037:bae7b0a002ef
mod_auth_http_cookie: Possibly temporary fork of mod_http_auth_async that adds cookie auth support
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Thu, 24 May 2018 13:25:13 +0100 |
| parents | |
| children | 9a89ec5030b5 |
comparison
equal
deleted
inserted
replaced
| 3036:f7ebf8fcd602 | 3037:bae7b0a002ef |
|---|---|
| 1 -- Prosody IM | |
| 2 -- Copyright (C) 2008-2013 Matthew Wild | |
| 3 -- Copyright (C) 2008-2013 Waqas Hussain | |
| 4 -- Copyright (C) 2014 Kim Alvefur | |
| 5 -- | |
| 6 -- This project is MIT/X11 licensed. Please see the | |
| 7 -- COPYING file in the source package for more information. | |
| 8 -- | |
| 9 | |
| 10 local new_sasl = require "util.sasl".new; | |
| 11 local base64 = require "util.encodings".base64.encode; | |
| 12 local have_async, async = pcall(require, "util.async"); | |
| 13 | |
| 14 local nodeprep = require "util.encodings".stringprep.nodeprep; | |
| 15 | |
| 16 local log = module._log; | |
| 17 local host = module.host; | |
| 18 | |
| 19 local password_auth_url = module:get_option_string("http_auth_url", ""):gsub("$host", host); | |
| 20 | |
| 21 local cookie_auth_url = module:get_option_string("http_cookie_auth_url"); | |
| 22 if cookie_auth_url then | |
| 23 cookie_auth_url = cookie_auth_url:gsub("$host", host); | |
| 24 end | |
| 25 | |
| 26 local external_needs_authzid = cookie_auth_url and cookie_auth_url:match("$user"); | |
| 27 | |
| 28 if password_auth_url == "" and not cookie_auth_url then error("http_auth_url or http_cookie_auth_url required") end | |
| 29 | |
| 30 | |
| 31 local provider = {}; | |
| 32 | |
| 33 -- globals required by socket.http | |
| 34 if rawget(_G, "PROXY") == nil then | |
| 35 rawset(_G, "PROXY", false) | |
| 36 end | |
| 37 if rawget(_G, "base_parsed") == nil then | |
| 38 rawset(_G, "base_parsed", false) | |
| 39 end | |
| 40 if not have_async then -- FINE! Set your globals then | |
| 41 prosody.unlock_globals() | |
| 42 require "ltn12" | |
| 43 require "socket" | |
| 44 require "socket.http" | |
| 45 require "ssl.https" | |
| 46 prosody.lock_globals() | |
| 47 end | |
| 48 | |
| 49 local function async_http_request(url, headers) | |
| 50 module:log("debug", "async_http_auth()"); | |
| 51 local http = require "net.http"; | |
| 52 local wait, done = async.waiter(); | |
| 53 local content, code, request, response; | |
| 54 local ex = { | |
| 55 headers = headers; | |
| 56 } | |
| 57 local function cb(content_, code_, request_, response_) | |
| 58 content, code, request, response = content_, code_, request_, response_; | |
| 59 done(); | |
| 60 end | |
| 61 http.request(url, ex, cb); | |
| 62 wait(); | |
| 63 log("debug", "response code %s", tostring(code)); | |
| 64 if code >= 200 and code <= 299 then | |
| 65 return true, content; | |
| 66 end | |
| 67 return nil; | |
| 68 end | |
| 69 | |
| 70 local function sync_http_request(url, headers) | |
| 71 module:log("debug", "sync_http_auth()"); | |
| 72 require "ltn12"; | |
| 73 local http = require "socket.http"; | |
| 74 local https = require "ssl.https"; | |
| 75 local request; | |
| 76 if string.sub(url, 1, string.len('https')) == 'https' then | |
| 77 request = https.request; | |
| 78 else | |
| 79 request = http.request; | |
| 80 end | |
| 81 local body_chunks = {}; | |
| 82 local _, code, headers, status = request{ | |
| 83 url = url, | |
| 84 headers = headers; | |
| 85 sink = ltn12.sink.table(body_chunks); | |
| 86 }; | |
| 87 log("debug", "response code %s %s", type(code), tostring(code)); | |
| 88 if type(code) == "number" and code >= 200 and code <= 299 then | |
| 89 log("debug", "success") | |
| 90 return true, table.concat(body_chunks); | |
| 91 end | |
| 92 return nil; | |
| 93 end | |
| 94 | |
| 95 local http_request = have_async and async_http_request or sync_http_request; | |
| 96 | |
| 97 function http_test_password(username, password) | |
| 98 local url = password_auth_url:gsub("$user", username):gsub("$password", password); | |
| 99 log("debug", "Testing password for user %s at host %s with URL %s", username, host, url); | |
| 100 local ok = (http_request(url, { Authorization = "Basic "..base64(username..":"..password); })); | |
| 101 if not ok then | |
| 102 return nil, "not authorized"; | |
| 103 end | |
| 104 return true; | |
| 105 end | |
| 106 | |
| 107 function http_test_cookie(cookie, username) | |
| 108 local url = external_needs_authzid and cookie_auth_url:gsub("$user", username) or cookie_auth_url; | |
| 109 log("debug", "Testing cookie auth for user %s at host %s with URL %s", username or "<unknown>", host, url); | |
| 110 local ok, resp = http_request(url, { Cookie = cookie; }); | |
| 111 if not ok then | |
| 112 return nil, "not authorized"; | |
| 113 end | |
| 114 | |
| 115 return external_needs_authzid or resp; | |
| 116 end | |
| 117 | |
| 118 function provider.test_password(username, password) | |
| 119 return http_test_password(username, password); | |
| 120 end | |
| 121 | |
| 122 function provider.users() | |
| 123 return function() | |
| 124 return nil; | |
| 125 end | |
| 126 end | |
| 127 | |
| 128 function provider.set_password(username, password) | |
| 129 return nil, "Changing passwords not supported"; | |
| 130 end | |
| 131 | |
| 132 function provider.user_exists(username) | |
| 133 return true; | |
| 134 end | |
| 135 | |
| 136 function provider.create_user(username, password) | |
| 137 return nil, "User creation not supported"; | |
| 138 end | |
| 139 | |
| 140 function provider.delete_user(username) | |
| 141 return nil , "User deletion not supported"; | |
| 142 end | |
| 143 | |
| 144 local function get_session_cookies(session) | |
| 145 local response = session.conn._http_open_response; | |
| 146 local request = response and response.request; | |
| 147 if request then | |
| 148 return request.headers.cookie; | |
| 149 end | |
| 150 end | |
| 151 | |
| 152 function provider.get_sasl_handler(session) | |
| 153 local cookie = cookie_auth_url and get_session_cookies(session); | |
| 154 log("debug", "Request cookie: %s", cookie); | |
| 155 return new_sasl(host, { | |
| 156 plain_test = function(sasl, username, password, realm) | |
| 157 return provider.test_password(username, password), true; | |
| 158 end; | |
| 159 external = cookie and function (authzid) | |
| 160 if external_needs_authzid then | |
| 161 -- Authorize the username provided by the client, using request cookie | |
| 162 if authzid ~= "" then | |
| 163 module:log("warn", "Client requested authzid, but cookie auth URL does not contain $user variable"); | |
| 164 return nil; | |
| 165 end | |
| 166 local success = http_test_cookie(cookie); | |
| 167 if not success then | |
| 168 return nil; | |
| 169 end | |
| 170 return nodeprep(authzid), true; | |
| 171 else | |
| 172 -- Authorize client using request cookie, username comes from auth server | |
| 173 if authzid == "" then | |
| 174 module:log("warn", "Client did not provide authzid, but cookie auth URL contains $user variable"); | |
| 175 return nil; | |
| 176 end | |
| 177 local unprepped_username = http_test_cookie(cookie, nodeprep(authzid)); | |
| 178 local username = nodeprep(unprepped_username); | |
| 179 if not username then | |
| 180 if unprepped_username then | |
| 181 log("warn", "Username supplied by cookie_auth_url is not valid for XMPP"); | |
| 182 end | |
| 183 return nil; | |
| 184 end | |
| 185 return username, true; | |
| 186 end; | |
| 187 end; | |
| 188 }); | |
| 189 end | |
| 190 | |
| 191 module:provides("auth", provider); |
