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
child 3223: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);