bsw/jbe@1309: if not request.is_post() then bsw/jbe@1309: return execute.view { module = "index", view = "405" } bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: slot.set_layout(nil, "application/json;charset=UTF-8") bsw/jbe@1309: bsw/jbe@1309: request.add_header("Cache-Control", "no-store") bsw/jbe@1309: request.add_header("Pragma", "no-cache") bsw/jbe@1309: bsw/jbe@1309: local function error_result(error_code, error_description) bsw/jbe@1309: -- TODO special HTTP status codes for some errors? bsw/jbe@1309: request.set_status("400 Bad Request") bsw/jbe@1309: slot.put_into("data", json.export{ bsw/jbe@1309: error = error_code, bsw/jbe@1309: error_description = error_description bsw/jbe@1309: }) bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local token bsw/jbe@1309: local grant_type = param.get("grant_type") bsw/jbe@1309: if grant_type == "authorization_code" then bsw/jbe@1309: local code = param.get("code") bsw/jbe@1309: token = Token:by_token_type_and_token("authorization", code) bsw/jbe@1309: elseif grant_type == "refresh_token" then bsw/jbe@1309: local refresh_token = param.get("refresh_token") bsw/jbe@1309: token = Token:by_token_type_and_token("refresh", refresh_token) bsw/jbe@1309: elseif grant_type == "access_token" then bsw/jbe@1309: local access_token, access_token_err = util.get_access_token() bsw/jbe@1309: if access_token_err then bsw/jbe@1309: if access_token_err == "header_and_param" then bsw/jbe@1309: return error_result("invalid_request", "Access token passed both via header and param") bsw/jbe@1309: end bsw/jbe@1309: error("Error in util.get_access_token") bsw/jbe@1309: end bsw/jbe@1309: token = Token:by_token_type_and_token("access", access_token) bsw/jbe@1309: else bsw/jbe@1309: return error_result("unsupported_grant_type", "Grant type not supported") bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if not token then bsw/jbe@1309: return error_result("invalid_grant", "Token invalid or expired") bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if grant_type == "authorization_code" then bsw/jbe@1309: if not token.used then bsw/jbe@1309: local expiry = db:query({"SELECT now() + (? || 'sec')::interval AS expiry", config.oauth2.authorization_code_lifetime }, "object").expiry bsw/jbe@1309: token.used = true bsw/jbe@1309: token.expiry = expiry bsw/jbe@1309: token:save() bsw/jbe@1309: else bsw/jbe@1309: token:destroy() bsw/jbe@1309: return error_result("invalid_grant", "Token invalid or expired") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if grant_type ~= "access_token" then bsw/jbe@1309: local cert_ca = request.get_header("X-LiquidFeedback-CA") bsw/jbe@1309: local cert_distinguished_name = request.get_header("X-SSL-DN") bsw/jbe@1309: local cert_common_name bsw/jbe@1309: if cert_distinguished_name then bsw/jbe@1309: cert_common_name = string.match(cert_distinguished_name, "%f[^/\0]CN=([A-Za-z0-9_.-]+)%f[/\0]") bsw/jbe@1309: if not cert_common_name then bsw/jbe@1309: return error_result("invalid_client", "CN in X.509 certificate invalid") bsw/jbe@1309: end bsw/jbe@1309: else bsw/jbe@1309: return error_result("invalid_client", "X.509 client authorization missing") bsw/jbe@1309: end bsw/jbe@1309: if token.system_application then bsw/jbe@1309: if cert_ca ~= "private" then bsw/jbe@1309: return error_result("invalid_client", "X.509 certificate not signed by private certificate authority or wrong endpoint used") bsw/jbe@1309: end bsw/jbe@1309: if cert_common_name ~= token.system_application.cert_common_name then bsw/jbe@1309: return error_result("invalid_grant", "CN in X.509 certificate incorrect") bsw/jbe@1309: end bsw/jbe@1309: else bsw/jbe@1309: if cert_ca ~= "public" then bsw/jbe@1309: return error_result("invalid_client", "X.509 certificate not signed by publicly trusted certificate authority or wrong endpoint used") bsw/jbe@1309: end bsw/jbe@1309: if cert_common_name ~= token.domain then bsw/jbe@1309: return error_result("invalid_grant", "CN in X.509 certificate incorrect") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: local client_id = param.get("client_id") bsw/jbe@1309: if client_id then bsw/jbe@1309: if token.system_application then bsw/jbe@1309: if client_id ~= token.system_application.client_id then bsw/jbe@1309: return error_result("invalid_grant", "Token was issued to another client") bsw/jbe@1309: end bsw/jbe@1309: else bsw/jbe@1309: if client_id ~= "dynamic:" .. token.domain then bsw/jbe@1309: return error_result ("invalid_grant", "Token was issued to another client") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: elseif grant_type == "authorization_code" and not cert_common_name then bsw/jbe@1309: return error_result("invalid_request", "No client_id supplied for authorization_code request") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if grant_type == "authorization_code" then bsw/jbe@1309: local redirect_uri = param.get("redirect_uri") bsw/jbe@1309: if (token.redirect_uri_explicit or redirect_uri) and token.redirect_uri ~= redirect_uri then bsw/jbe@1309: return error_result("invalid_request", "Redirect URI missing or invalid") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local scopes = { bsw/jbe@1309: [0] = param.get("scope") bsw/jbe@1309: } bsw/jbe@1309: for i = 1, math.huge do bsw/jbe@1309: scopes[i] = param.get("scope" .. i) bsw/jbe@1309: if not scopes[i] then bsw/jbe@1309: break bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if not scopes[0] and #scopes == 0 then bsw/jbe@1309: for dummy, token_scope in ipairs(token.token_scopes) do bsw/jbe@1309: scopes[token_scope.index] = token_scope.scope bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local allowed_scopes = {} bsw/jbe@1309: local requested_detached_scopes = {} bsw/jbe@1309: for scope in string.gmatch(token.scope, "[^ ]+") do bsw/jbe@1309: allowed_scopes[scope] = true bsw/jbe@1309: end bsw/jbe@1309: for i = 0, #scopes do bsw/jbe@1309: if scopes[i] then bsw/jbe@1309: for scope in string.gmatch(scopes[i], "[^ ]+") do bsw/jbe@1309: if string.match(scope, "_detached$") then bsw/jbe@1309: requested_detached_scopes[scope] = true bsw/jbe@1309: end bsw/jbe@1309: if not allowed_scopes[scope] then bsw/jbe@1309: return error_result("invalid_scope", "Scope exceeds limits") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local expiry bsw/jbe@1309: bsw/jbe@1309: if grant_type == "access_token" then bsw/jbe@1309: expiry = db:query({ "SELECT FLOOR(EXTRACT(EPOCH FROM ? - now())) AS access_time_left", token.expiry }, "object") bsw/jbe@1309: else bsw/jbe@1309: expiry = db:query({ bsw/jbe@1309: "SELECT now() + (? || 'sec')::interval AS refresh, now() + (? || 'sec')::interval AS access", bsw/jbe@1309: config.oauth2.refresh_token_lifetime, bsw/jbe@1309: config.oauth2.access_token_lifetime bsw/jbe@1309: }, "object") bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if grant_type == "refresh_token" then bsw/jbe@1309: local requested_detached_scopes_list = {} bsw/jbe@1309: for scope in pairs(requested_detached_scopes) do bsw/jbe@1309: requested_detached_scopes_list[#requested_detached_scopes_list+1] = scope bsw/jbe@1309: end bsw/jbe@1309: local tokens_to_reduce = Token:old_refresh_token_by_token(token, requested_detached_scopes_list) bsw/jbe@1309: for dummy, t in ipairs(tokens_to_reduce) do bsw/jbe@1309: local t_scopes = {} bsw/jbe@1309: for t_scope in string.gmatch(t.scope, "[^ ]+") do bsw/jbe@1309: t_scopes[t_scope] = true bsw/jbe@1309: end bsw/jbe@1309: for scope in pairs(requested_detached_scopes) do bsw/jbe@1309: local scope_without_detached = string.gmatch(scope, "(.+)_detached") bsw/jbe@1309: if t_scope[scope] then bsw/jbe@1309: t_scope[scope] = nil bsw/jbe@1309: t_scope[scope_without_detached] = true bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: local t_scope_list = {} bsw/jbe@1309: for scope in pairs(t_scopes) do bsw/jbe@1309: t_scope_list[#t_scope_list+1] = scope bsw/jbe@1309: end bsw/jbe@1309: t.scope = table.concat(t_scope_list, " ") bsw/jbe@1309: t:save() bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local r = json.object() bsw/jbe@1309: bsw/jbe@1309: local refresh_token bsw/jbe@1309: if bsw/jbe@1309: grant_type ~= "access_token" bsw/jbe@1309: and (grant_type == "authorization_code" or #(Token:fresh_refresh_token_by_token(token)) == 0) bsw/jbe@1309: then bsw/jbe@1309: refresh_token = Token:new() bsw/jbe@1309: refresh_token.token_type = "refresh" bsw/jbe@1309: if grant_type == "authorization_code" then bsw/jbe@1309: refresh_token.authorization_token_id = token.id bsw/jbe@1309: else bsw/jbe@1309: refresh_token.authorization_token_id = token.authorization_token_id bsw/jbe@1309: end bsw/jbe@1309: refresh_token.member_id = token.member_id bsw/jbe@1309: refresh_token.system_application_id = token.system_application_id bsw/jbe@1309: refresh_token.domain = token.domain bsw/jbe@1309: refresh_token.session_id = token.session_id bsw/jbe@1309: refresh_token.expiry = expiry.refresh bsw/jbe@1309: refresh_token.scope = token.scope bsw/jbe@1309: refresh_token:save() bsw/jbe@1309: r.refresh_token = refresh_token.token bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: bsw/jbe@1309: r.token_type = "bearer" bsw/jbe@1309: if grant_type == "access_token" then bsw/jbe@1309: r.expires_in = expiry.access_time_left bsw/jbe@1309: else bsw/jbe@1309: r.expires_in = config.oauth2.access_token_lifetime bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: for i = 0, #scopes do bsw/jbe@1309: if scopes[i] then bsw/jbe@1309: local scope = scopes[i] bsw/jbe@1309: local access_token = Token:new() bsw/jbe@1309: access_token.token_type = "access" bsw/jbe@1309: if grant_type == "authorization_code" then bsw/jbe@1309: access_token.authorization_token_id = token.id bsw/jbe@1309: else bsw/jbe@1309: access_token.authorization_token_id = token.authorization_token_id bsw/jbe@1309: end bsw/jbe@1309: access_token.member_id = token.member_id bsw/jbe@1309: access_token.system_application_id = token.system_application_id bsw/jbe@1309: access_token.domain = token.domain bsw/jbe@1309: access_token.session_id = token.session_id bsw/jbe@1309: if grant_type == "access_token" then bsw/jbe@1309: access_token.expiry = token.expiry bsw/jbe@1309: else bsw/jbe@1309: access_token.expiry = expiry.access bsw/jbe@1309: end bsw/jbe@1309: access_token.scope = scope bsw/jbe@1309: access_token:save() bsw/jbe@1309: if refresh_token then bsw/jbe@1309: local refresh_token_scope = TokenScope:new() bsw/jbe@1309: refresh_token_scope.token_id = refresh_token.id bsw/jbe@1309: refresh_token_scope.index = i bsw/jbe@1309: refresh_token_scope.scope = scope bsw/jbe@1309: refresh_token_scope:save() bsw/jbe@1309: end bsw/jbe@1309: local index = i == 0 and "" or i bsw/jbe@1309: r["access_token" .. index] = access_token.token bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: r.member_id = token.member_id bsw/jbe@1309: if token.member.role then bsw/jbe@1309: r.member_is_role = true bsw/jbe@1309: end bsw/jbe@1309: if token.session then bsw/jbe@1309: r.real_member_id = token.real_member_id bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if param.get("include_member", atom.boolean) then bsw/jbe@1309: if allowed_scopes.identification or allowed_scopes.authentication then bsw/jbe@1309: local member = token.member bsw/jbe@1309: r.member = json.object{ bsw/jbe@1309: id = member.id, bsw/jbe@1309: name = member.name, bsw/jbe@1309: } bsw/jbe@1309: if token.session and token.session.real_member then bsw/jbe@1309: r.real_member = json.object{ bsw/jbe@1309: id = token.session.real_member.id, bsw/jbe@1309: name = token.session.real_member.name, bsw/jbe@1309: } bsw/jbe@1309: end bsw/jbe@1309: if allowed_scopes.identification then bsw/jbe@1309: r.member.identification = member.identification bsw/jbe@1309: if token.session and token.session.real_member then bsw/jbe@1309: r.real_member.identification = token.session.real_member.identification bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: slot.put_into("data", json.export(r))