comparison mod_http_oauth2/mod_http_oauth2.lua @ 5254:b0ccdd12a70d

mod_http_oauth2: Prepare to handle multiple e.g. non-role scopes This is to prepare to handle scopes like "openid" that don't map to roles.
author Kim Alvefur <zash@zash.se>
date Thu, 16 Mar 2023 17:03:48 +0100
parents 85f0c6c1c24f
children 001c8fdc91a4
comparison
equal deleted inserted replaced
5253:d3b2d42daaee 5254:b0ccdd12a70d
72 -- Tie it to the host if global 72 -- Tie it to the host if global
73 verification_key = hashes.hmac_sha256(registration_key, module.host); 73 verification_key = hashes.hmac_sha256(registration_key, module.host);
74 jwt_sign, jwt_verify = jwt.init(registration_algo, registration_key, registration_key, registration_options); 74 jwt_sign, jwt_verify = jwt.init(registration_algo, registration_key, registration_key, registration_options);
75 end 75 end
76 76
77 local function parse_scopes(scope_string)
78 return array(scope_string:gmatch("%S+"));
79 end
80
77 local function filter_scopes(username, host, requested_scope_string) 81 local function filter_scopes(username, host, requested_scope_string)
78 if host ~= module.host then 82 if host ~= module.host then
79 return usermanager.get_jid_role(username.."@"..host, module.host).name; 83 return usermanager.get_jid_role(username.."@"..host, module.host).name;
80 end 84 end
81 85
82 if requested_scope_string then -- Specific role requested 86 local selected_role, granted_scopes = nil, array();
83 -- TODO: The requested scope string is technically a space-delimited list 87
84 -- of scopes, but for simplicity we're mapping this slot to role names. 88 if requested_scope_string then -- Specific role(s) requested
85 if usermanager.user_can_assume_role(username, module.host, requested_scope_string) then 89 local requested_scopes = parse_scopes(requested_scope_string);
86 return requested_scope_string; 90 for _, scope in ipairs(requested_scopes) do
87 end 91 if selected_role == nil and usermanager.user_can_assume_role(username, module.host, scope) then
88 end 92 selected_role = scope;
89 93 end
90 return usermanager.get_user_role(username, module.host).name; 94 end
95 end
96
97 if not selected_role then
98 -- By default use the users' default role
99 selected_role = usermanager.get_user_role(username, module.host).name;
100 end
101 granted_scopes:push(selected_role);
102
103 return granted_scopes:concat(" "), selected_role;
91 end 104 end
92 105
93 local function code_expires_in(code) --> number, seconds until code expires 106 local function code_expires_in(code) --> number, seconds until code expires
94 return os.difftime(code.expires, os.time()); 107 return os.difftime(code.expires, os.time());
95 end 108 end
138 -- client needs to be revoked 151 -- client needs to be revoked
139 local function client_subset(client) 152 local function client_subset(client)
140 return { name = client.client_name; uri = client.client_uri }; 153 return { name = client.client_name; uri = client.client_uri };
141 end 154 end
142 155
143 local function new_access_token(token_jid, scope, ttl, client) 156 local function new_access_token(token_jid, role, scope, ttl, client)
144 local token_data; 157 local token_data = {};
145 if client then 158 if client then
146 token_data = { oauth2_client = client_subset(client) }; 159 token_data.oauth2_client = client_subset(client);
147 end 160 end
148 local token = tokens.create_jid_token(token_jid, token_jid, scope, ttl, token_data, "oauth2"); 161 if next(token_data) == nil then
162 token_data = nil;
163 end
164 local token = tokens.create_jid_token(token_jid, token_jid, role, ttl, token_data, "oauth2");
149 return { 165 return {
150 token_type = "bearer"; 166 token_type = "bearer";
151 access_token = token; 167 access_token = token;
152 expires_in = ttl; 168 expires_in = ttl;
153 scope = scope; 169 scope = scope;
186 if not usermanager.test_password(request_username, request_host, request_password) then 202 if not usermanager.test_password(request_username, request_host, request_password) then
187 return oauth_error("invalid_grant", "incorrect credentials"); 203 return oauth_error("invalid_grant", "incorrect credentials");
188 end 204 end
189 205
190 local granted_jid = jid.join(request_username, request_host, request_resource); 206 local granted_jid = jid.join(request_username, request_host, request_resource);
191 local granted_scopes = filter_scopes(request_username, request_host, params.scope); 207 local granted_scopes, granted_role = filter_scopes(request_username, request_host, params.scope);
192 return json.encode(new_access_token(granted_jid, granted_scopes, nil)); 208 return json.encode(new_access_token(granted_jid, granted_role, granted_scopes, nil));
193 end 209 end
194 210
195 function response_type_handlers.code(client, params, granted_jid) 211 function response_type_handlers.code(client, params, granted_jid)
196 local request_username, request_host = jid.split(granted_jid); 212 local request_username, request_host = jid.split(granted_jid);
197 local granted_scopes = filter_scopes(request_username, request_host, params.scope); 213 local granted_scopes, granted_role = filter_scopes(request_username, request_host, params.scope);
198 214
199 local code = id.medium(); 215 local code = id.medium();
200 local ok = codes:set(params.client_id .. "#" .. code, { 216 local ok = codes:set(params.client_id .. "#" .. code, {
201 expires = os.time() + 600; 217 expires = os.time() + 600;
202 granted_jid = granted_jid; 218 granted_jid = granted_jid;
203 granted_scopes = granted_scopes; 219 granted_scopes = granted_scopes;
220 granted_role = granted_role;
204 }); 221 });
205 if not ok then 222 if not ok then
206 return {status_code = 429}; 223 return {status_code = 429};
207 end 224 end
208 225
243 end 260 end
244 261
245 -- Implicit flow 262 -- Implicit flow
246 function response_type_handlers.token(client, params, granted_jid) 263 function response_type_handlers.token(client, params, granted_jid)
247 local request_username, request_host = jid.split(granted_jid); 264 local request_username, request_host = jid.split(granted_jid);
248 local granted_scopes = filter_scopes(request_username, request_host, params.scope); 265 local granted_scopes, granted_role = filter_scopes(request_username, request_host, params.scope);
249 local token_info = new_access_token(granted_jid, granted_scopes, nil, client); 266 local token_info = new_access_token(granted_jid, granted_role, granted_scopes, nil, client);
250 267
251 local redirect = url.parse(get_redirect_uri(client, params.redirect_uri)); 268 local redirect = url.parse(get_redirect_uri(client, params.redirect_uri));
252 token_info.state = params.state; 269 token_info.state = params.state;
253 redirect.fragment = http.formencode(token_info); 270 redirect.fragment = http.formencode(token_info);
254 271
293 if not code or type(code) ~= "table" or code_expired(code) then 310 if not code or type(code) ~= "table" or code_expired(code) then
294 module:log("debug", "authorization_code invalid or expired: %q", code); 311 module:log("debug", "authorization_code invalid or expired: %q", code);
295 return oauth_error("invalid_client", "incorrect credentials"); 312 return oauth_error("invalid_client", "incorrect credentials");
296 end 313 end
297 314
298 return json.encode(new_access_token(code.granted_jid, code.granted_scopes, nil, client)); 315 return json.encode(new_access_token(code.granted_jid, code.granted_role, code.granted_scopes, nil, client));
299 end 316 end
300 317
301 -- Used to issue/verify short-lived tokens for the authorization process below 318 -- Used to issue/verify short-lived tokens for the authorization process below
302 local new_user_token, verify_user_token = jwt.init("HS256", random.bytes(32), nil, { default_ttl = 600 }); 319 local new_user_token, verify_user_token = jwt.init("HS256", random.bytes(32), nil, { default_ttl = 600 });
303 320
412 if not request_host or request_host ~= module.host then 429 if not request_host or request_host ~= module.host then
413 return oauth_error("invalid_request", "invalid JID"); 430 return oauth_error("invalid_request", "invalid JID");
414 end 431 end
415 if request_password == component_secret then 432 if request_password == component_secret then
416 local granted_jid = jid.join(request_username, request_host, request_resource); 433 local granted_jid = jid.join(request_username, request_host, request_resource);
417 return json.encode(new_access_token(granted_jid, nil, nil)); 434 return json.encode(new_access_token(granted_jid, nil, nil, nil));
418 end 435 end
419 return oauth_error("invalid_grant", "incorrect credentials"); 436 return oauth_error("invalid_grant", "incorrect credentials");
420 end 437 end
421 438
422 -- TODO How would this make sense with components? 439 -- TODO How would this make sense with components?