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))
|