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