Mercurial > prosody-modules
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? |
