liquid_feedback_frontend

annotate app/main/oauth2/token.lua @ 1726:65b8703e2d8a

Allow to overwrite quick guide links in sidebar
author bsw
date Tue Sep 28 13:40:24 2021 +0200 (2021-09-28)
parents 8fde003bdeb0
children
rev   line source
bsw/jbe@1309 1 if not request.is_post() then
bsw/jbe@1309 2 return execute.view { module = "index", view = "405" }
bsw/jbe@1309 3 end
bsw/jbe@1309 4
bsw/jbe@1309 5 slot.set_layout(nil, "application/json;charset=UTF-8")
bsw/jbe@1309 6
bsw/jbe@1309 7 request.add_header("Cache-Control", "no-store")
bsw/jbe@1309 8 request.add_header("Pragma", "no-cache")
bsw/jbe@1309 9
bsw/jbe@1309 10 local function error_result(error_code, error_description)
bsw/jbe@1309 11 -- TODO special HTTP status codes for some errors?
bsw/jbe@1309 12 request.set_status("400 Bad Request")
bsw/jbe@1309 13 slot.put_into("data", json.export{
bsw/jbe@1309 14 error = error_code,
bsw/jbe@1309 15 error_description = error_description
bsw/jbe@1309 16 })
bsw/jbe@1309 17 end
bsw/jbe@1309 18
bsw/jbe@1309 19 local token
bsw/jbe@1309 20 local grant_type = param.get("grant_type")
bsw/jbe@1309 21 if grant_type == "authorization_code" then
bsw/jbe@1309 22 local code = param.get("code")
bsw/jbe@1309 23 token = Token:by_token_type_and_token("authorization", code)
bsw/jbe@1309 24 elseif grant_type == "refresh_token" then
bsw/jbe@1309 25 local refresh_token = param.get("refresh_token")
bsw/jbe@1309 26 token = Token:by_token_type_and_token("refresh", refresh_token)
bsw/jbe@1309 27 elseif grant_type == "access_token" then
bsw/jbe@1309 28 local access_token, access_token_err = util.get_access_token()
bsw/jbe@1309 29 if access_token_err then
bsw/jbe@1309 30 if access_token_err == "header_and_param" then
bsw/jbe@1309 31 return error_result("invalid_request", "Access token passed both via header and param")
bsw/jbe@1309 32 end
bsw/jbe@1309 33 error("Error in util.get_access_token")
bsw/jbe@1309 34 end
bsw/jbe@1309 35 token = Token:by_token_type_and_token("access", access_token)
bsw/jbe@1309 36 else
bsw/jbe@1309 37 return error_result("unsupported_grant_type", "Grant type not supported")
bsw/jbe@1309 38 end
bsw/jbe@1309 39
bsw/jbe@1309 40 if not token then
bsw/jbe@1309 41 return error_result("invalid_grant", "Token invalid or expired")
bsw/jbe@1309 42 end
bsw/jbe@1309 43
bsw/jbe@1309 44 if grant_type == "authorization_code" then
bsw/jbe@1309 45 if not token.used then
bsw/jbe@1309 46 local expiry = db:query({"SELECT now() + (? || 'sec')::interval AS expiry", config.oauth2.authorization_code_lifetime }, "object").expiry
bsw/jbe@1309 47 token.used = true
bsw/jbe@1309 48 token.expiry = expiry
bsw/jbe@1309 49 token:save()
bsw/jbe@1309 50 else
bsw/jbe@1309 51 token:destroy()
bsw/jbe@1309 52 return error_result("invalid_grant", "Token invalid or expired")
bsw/jbe@1309 53 end
bsw/jbe@1309 54 end
bsw/jbe@1309 55
bsw/jbe@1309 56 if grant_type ~= "access_token" then
bsw/jbe@1309 57 local cert_ca = request.get_header("X-LiquidFeedback-CA")
bsw/jbe@1309 58 local cert_distinguished_name = request.get_header("X-SSL-DN")
bsw/jbe@1309 59 local cert_common_name
bsw@1514 60
bsw@1514 61 if not token.system_application or token.system_application.cert_common_name then
bsw@1514 62 if cert_distinguished_name then
bsw@1514 63 cert_common_name = string.match(cert_distinguished_name, "%f[^/\0]CN=([A-Za-z0-9_.-]+)%f[/\0]")
bsw@1514 64 if not cert_common_name then
bsw@1676 65 cert_common_name = string.match(cert_distinguished_name, "^CN=([A-Za-z0-9_.-]+)")
bsw@1676 66 end
bsw@1676 67 if not cert_common_name then
bsw@1514 68 return error_result("invalid_client", "CN in X.509 certificate invalid")
bsw@1514 69 end
bsw@1514 70 else
bsw@1514 71 return error_result("invalid_client", "X.509 client authorization missing")
bsw/jbe@1309 72 end
bsw/jbe@1309 73 end
bsw/jbe@1309 74 if token.system_application then
bsw@1514 75 if token.system_application.cert_common_name then
bsw@1514 76 if cert_ca ~= "private" then
bsw@1514 77 return error_result("invalid_client", "X.509 certificate not signed by private certificate authority or wrong endpoint used")
bsw@1514 78 end
bsw@1514 79 if cert_common_name ~= token.system_application.cert_common_name then
bsw@1514 80 return error_result("invalid_grant", "CN in X.509 certificate incorrect")
bsw@1514 81 end
bsw/jbe@1309 82 end
bsw/jbe@1309 83 else
bsw/jbe@1309 84 if cert_ca ~= "public" then
bsw/jbe@1309 85 return error_result("invalid_client", "X.509 certificate not signed by publicly trusted certificate authority or wrong endpoint used")
bsw/jbe@1309 86 end
bsw/jbe@1309 87 if cert_common_name ~= token.domain then
bsw/jbe@1309 88 return error_result("invalid_grant", "CN in X.509 certificate incorrect")
bsw/jbe@1309 89 end
bsw/jbe@1309 90 end
bsw/jbe@1309 91 local client_id = param.get("client_id")
bsw/jbe@1309 92 if client_id then
bsw/jbe@1309 93 if token.system_application then
bsw/jbe@1309 94 if client_id ~= token.system_application.client_id then
bsw/jbe@1309 95 return error_result("invalid_grant", "Token was issued to another client")
bsw/jbe@1309 96 end
bsw/jbe@1309 97 else
bsw/jbe@1309 98 if client_id ~= "dynamic:" .. token.domain then
bsw/jbe@1309 99 return error_result ("invalid_grant", "Token was issued to another client")
bsw/jbe@1309 100 end
bsw/jbe@1309 101 end
bsw/jbe@1309 102 elseif grant_type == "authorization_code" and not cert_common_name then
bsw/jbe@1309 103 return error_result("invalid_request", "No client_id supplied for authorization_code request")
bsw/jbe@1309 104 end
bsw/jbe@1309 105 end
bsw/jbe@1309 106
bsw/jbe@1309 107 if grant_type == "authorization_code" then
bsw/jbe@1309 108 local redirect_uri = param.get("redirect_uri")
bsw/jbe@1309 109 if (token.redirect_uri_explicit or redirect_uri) and token.redirect_uri ~= redirect_uri then
bsw/jbe@1309 110 return error_result("invalid_request", "Redirect URI missing or invalid")
bsw/jbe@1309 111 end
bsw/jbe@1309 112 end
bsw/jbe@1309 113
bsw/jbe@1309 114 local scopes = {
bsw/jbe@1309 115 [0] = param.get("scope")
bsw/jbe@1309 116 }
bsw/jbe@1309 117 for i = 1, math.huge do
bsw/jbe@1309 118 scopes[i] = param.get("scope" .. i)
bsw/jbe@1309 119 if not scopes[i] then
bsw/jbe@1309 120 break
bsw/jbe@1309 121 end
bsw/jbe@1309 122 end
bsw/jbe@1309 123
bsw/jbe@1309 124 if not scopes[0] and #scopes == 0 then
bsw/jbe@1309 125 for dummy, token_scope in ipairs(token.token_scopes) do
bsw/jbe@1309 126 scopes[token_scope.index] = token_scope.scope
bsw/jbe@1309 127 end
bsw/jbe@1309 128 end
bsw/jbe@1309 129
bsw/jbe@1309 130 local allowed_scopes = {}
bsw/jbe@1309 131 local requested_detached_scopes = {}
bsw/jbe@1309 132 for scope in string.gmatch(token.scope, "[^ ]+") do
bsw/jbe@1309 133 allowed_scopes[scope] = true
bsw/jbe@1309 134 end
bsw/jbe@1309 135 for i = 0, #scopes do
bsw/jbe@1309 136 if scopes[i] then
bsw/jbe@1309 137 for scope in string.gmatch(scopes[i], "[^ ]+") do
bsw/jbe@1309 138 if string.match(scope, "_detached$") then
bsw/jbe@1309 139 requested_detached_scopes[scope] = true
bsw/jbe@1309 140 end
bsw/jbe@1309 141 if not allowed_scopes[scope] then
bsw/jbe@1309 142 return error_result("invalid_scope", "Scope exceeds limits")
bsw/jbe@1309 143 end
bsw/jbe@1309 144 end
bsw/jbe@1309 145 end
bsw/jbe@1309 146 end
bsw/jbe@1309 147
bsw/jbe@1309 148 local expiry
bsw/jbe@1309 149
bsw/jbe@1309 150 if grant_type == "access_token" then
bsw/jbe@1309 151 expiry = db:query({ "SELECT FLOOR(EXTRACT(EPOCH FROM ? - now())) AS access_time_left", token.expiry }, "object")
bsw/jbe@1309 152 else
bsw/jbe@1309 153 expiry = db:query({
bsw/jbe@1309 154 "SELECT now() + (? || 'sec')::interval AS refresh, now() + (? || 'sec')::interval AS access",
bsw/jbe@1309 155 config.oauth2.refresh_token_lifetime,
bsw/jbe@1309 156 config.oauth2.access_token_lifetime
bsw/jbe@1309 157 }, "object")
bsw/jbe@1309 158 end
bsw/jbe@1309 159
bsw/jbe@1309 160 if grant_type == "refresh_token" then
bsw/jbe@1309 161 local requested_detached_scopes_list = {}
bsw/jbe@1309 162 for scope in pairs(requested_detached_scopes) do
bsw/jbe@1309 163 requested_detached_scopes_list[#requested_detached_scopes_list+1] = scope
bsw/jbe@1309 164 end
bsw/jbe@1309 165 local tokens_to_reduce = Token:old_refresh_token_by_token(token, requested_detached_scopes_list)
bsw/jbe@1309 166 for dummy, t in ipairs(tokens_to_reduce) do
bsw/jbe@1309 167 local t_scopes = {}
bsw/jbe@1309 168 for t_scope in string.gmatch(t.scope, "[^ ]+") do
bsw/jbe@1309 169 t_scopes[t_scope] = true
bsw/jbe@1309 170 end
bsw/jbe@1309 171 for scope in pairs(requested_detached_scopes) do
bsw/jbe@1309 172 local scope_without_detached = string.gmatch(scope, "(.+)_detached")
bsw/jbe@1309 173 if t_scope[scope] then
bsw/jbe@1309 174 t_scope[scope] = nil
bsw/jbe@1309 175 t_scope[scope_without_detached] = true
bsw/jbe@1309 176 end
bsw/jbe@1309 177 end
bsw/jbe@1309 178 local t_scope_list = {}
bsw/jbe@1309 179 for scope in pairs(t_scopes) do
bsw/jbe@1309 180 t_scope_list[#t_scope_list+1] = scope
bsw/jbe@1309 181 end
bsw/jbe@1309 182 t.scope = table.concat(t_scope_list, " ")
bsw/jbe@1309 183 t:save()
bsw/jbe@1309 184 end
bsw/jbe@1309 185 end
bsw/jbe@1309 186
bsw/jbe@1309 187 local r = json.object()
bsw/jbe@1309 188
bsw/jbe@1309 189 local refresh_token
bsw/jbe@1309 190 if
bsw/jbe@1309 191 grant_type ~= "access_token"
bsw/jbe@1309 192 and (grant_type == "authorization_code" or #(Token:fresh_refresh_token_by_token(token)) == 0)
bsw/jbe@1309 193 then
bsw/jbe@1309 194 refresh_token = Token:new()
bsw/jbe@1309 195 refresh_token.token_type = "refresh"
bsw/jbe@1309 196 if grant_type == "authorization_code" then
bsw/jbe@1309 197 refresh_token.authorization_token_id = token.id
bsw/jbe@1309 198 else
bsw/jbe@1309 199 refresh_token.authorization_token_id = token.authorization_token_id
bsw/jbe@1309 200 end
bsw/jbe@1309 201 refresh_token.member_id = token.member_id
bsw/jbe@1309 202 refresh_token.system_application_id = token.system_application_id
bsw/jbe@1309 203 refresh_token.domain = token.domain
bsw/jbe@1309 204 refresh_token.session_id = token.session_id
bsw/jbe@1309 205 refresh_token.expiry = expiry.refresh
bsw/jbe@1309 206 refresh_token.scope = token.scope
bsw/jbe@1309 207 refresh_token:save()
bsw/jbe@1309 208 r.refresh_token = refresh_token.token
bsw/jbe@1309 209 end
bsw/jbe@1309 210
bsw/jbe@1309 211
bsw/jbe@1309 212 r.token_type = "bearer"
bsw/jbe@1309 213 if grant_type == "access_token" then
bsw/jbe@1309 214 r.expires_in = expiry.access_time_left
bsw/jbe@1309 215 else
bsw/jbe@1309 216 r.expires_in = config.oauth2.access_token_lifetime
bsw/jbe@1309 217 end
bsw/jbe@1309 218
bsw/jbe@1309 219 for i = 0, #scopes do
bsw/jbe@1309 220 if scopes[i] then
bsw/jbe@1309 221 local scope = scopes[i]
bsw/jbe@1309 222 local access_token = Token:new()
bsw/jbe@1309 223 access_token.token_type = "access"
bsw/jbe@1309 224 if grant_type == "authorization_code" then
bsw/jbe@1309 225 access_token.authorization_token_id = token.id
bsw/jbe@1309 226 else
bsw/jbe@1309 227 access_token.authorization_token_id = token.authorization_token_id
bsw/jbe@1309 228 end
bsw/jbe@1309 229 access_token.member_id = token.member_id
bsw/jbe@1309 230 access_token.system_application_id = token.system_application_id
bsw/jbe@1309 231 access_token.domain = token.domain
bsw/jbe@1309 232 access_token.session_id = token.session_id
bsw/jbe@1309 233 if grant_type == "access_token" then
bsw/jbe@1309 234 access_token.expiry = token.expiry
bsw/jbe@1309 235 else
bsw/jbe@1309 236 access_token.expiry = expiry.access
bsw/jbe@1309 237 end
bsw/jbe@1309 238 access_token.scope = scope
bsw/jbe@1309 239 access_token:save()
bsw/jbe@1309 240 if refresh_token then
bsw/jbe@1309 241 local refresh_token_scope = TokenScope:new()
bsw/jbe@1309 242 refresh_token_scope.token_id = refresh_token.id
bsw/jbe@1309 243 refresh_token_scope.index = i
bsw/jbe@1309 244 refresh_token_scope.scope = scope
bsw/jbe@1309 245 refresh_token_scope:save()
bsw/jbe@1309 246 end
bsw/jbe@1309 247 local index = i == 0 and "" or i
bsw/jbe@1309 248 r["access_token" .. index] = access_token.token
bsw/jbe@1309 249 end
bsw/jbe@1309 250 end
bsw/jbe@1309 251
bsw/jbe@1309 252 r.member_id = token.member_id
bsw/jbe@1309 253 if token.member.role then
bsw/jbe@1309 254 r.member_is_role = true
bsw/jbe@1309 255 end
bsw/jbe@1309 256 if token.session then
bsw/jbe@1309 257 r.real_member_id = token.real_member_id
bsw/jbe@1309 258 end
bsw/jbe@1309 259
bsw@1582 260 if allowed_scopes.identification or allowed_scopes.authentication then
bsw@1582 261 if param.get("include_member", atom.boolean) then
bsw/jbe@1309 262 local member = token.member
bsw/jbe@1309 263 r.member = json.object{
bsw/jbe@1309 264 id = member.id,
bsw/jbe@1309 265 name = member.name,
bsw/jbe@1309 266 }
bsw/jbe@1309 267 if token.session and token.session.real_member then
bsw/jbe@1309 268 r.real_member = json.object{
bsw/jbe@1309 269 id = token.session.real_member.id,
bsw/jbe@1309 270 name = token.session.real_member.name,
bsw/jbe@1309 271 }
bsw/jbe@1309 272 end
bsw/jbe@1309 273 if allowed_scopes.identification then
bsw/jbe@1309 274 r.member.identification = member.identification
bsw/jbe@1309 275 if token.session and token.session.real_member then
bsw/jbe@1309 276 r.real_member.identification = token.session.real_member.identification
bsw/jbe@1309 277 end
bsw/jbe@1309 278 end
bsw/jbe@1309 279 end
bsw/jbe@1309 280 end
bsw/jbe@1309 281
bsw/jbe@1309 282 slot.put_into("data", json.export(r))

Impressum / About Us