liquid_feedback_frontend

view app/main/oauth2/authorization.lua @ 1538:25ea15b4bd5e

Reworked cookie session control, exceptions for API
author bsw
date Tue Oct 20 17:48:49 2020 +0200 (2020-10-20)
parents 32cc544d5a5b
children 2dc0ea4cdb4a
line source
1 local function show_error(text)
2 ui.title("Authorization")
3 ui.section(function()
4 ui.sectionHead(function()
5 ui.heading{ content = _"Error during authorization" }
6 end)
7 ui.sectionRow(function()
8 ui.container{ content = text }
9 end )
10 end )
11 end
13 local client_id = param.get("client_id")
14 local redirect_uri = param.get("redirect_uri")
15 local redirect_uri_explicit = redirect_uri and true or false
16 local response_type = param.get("response_type")
17 local state = param.get("state")
19 local no_scope_requested = true
21 local scopes = {
22 [0] = param.get("scope")
23 }
25 for i = 1, math.huge do
26 scopes[i] = param.get("scope" .. i)
27 if not scopes[i] then
28 break
29 end
30 end
32 if #scopes == 0 and not scopes[0] then
33 scopes[0] = "identification"
34 end
36 local requested_scopes = {}
38 for i = 0, #scopes do
39 if scopes[i] then
40 for scope in string.gmatch(scopes[i], "[^ ]+") do
41 requested_scopes[scope] = true
42 end
43 end
44 end
46 local system_application
47 local member_application
48 local client_name
49 local scopes_to_accept = table.new(requested_scopes)
50 local accepted_scopes = {}
52 local domain
54 if client_id then
55 domain = string.match(client_id, "^dynamic:([a-z0-9.-]+)$")
56 end
58 local dynamic_application_check
59 if domain then
60 if #domain > 255 then
61 return show_error(_"Domain too long")
62 end
63 if string.find(domain, "^%.") or string.find(domain, "%.$") or string.find(domain, "%.%.") then
64 return show_error(_"Invalid domain format")
65 end
66 if redirect_uri then
67 local redirect_uri_domain, magic = string.match(redirect_uri, "^[Hh][Tt][Tt][Pp][Ss]://([A-Za-z0-9_.-]+)/(.*)$")
68 if not redirect_uri_domain or string.lower(redirect_uri_domain) ~= domain or magic ~= config.oauth2.endpoint_magic then
69 return show_error(_"Redirect URI forbidden")
70 end
71 else
72 redirect_uri = "https://" .. domain .. "/" .. config.oauth2.endpoint_magic
73 end
74 dynamic_application_check = DynamicApplicationScope:check_scopes(domain, redirect_uri, response_type, requested_scopes)
75 if dynamic_application_check == "not_registered" then
76 return show_error(_"Redirect URI or response type not registered")
77 end
78 client_name = domain
79 member_application = MemberApplication:by_member_id_and_domain(app.session.member_id, domain)
80 if member_application then
81 for scope in string.gmatch(member_application.scope, "[^ ]+") do
82 accepted_scopes[scope] = true
83 scopes_to_accept[scope] = nil
84 end
85 end
86 else
87 system_application = SystemApplication:by_client_id(client_id)
88 if system_application then
89 if redirect_uri_explicit then
90 if
91 redirect_uri ~= system_application.default_redirect_uri
92 and not SystemApplicationRedirectUri:by_pk(system_application.id, redirect_uri)
93 then
94 return show_error(_"Redirect URI invalid")
95 end
96 else
97 redirect_uri = system_application.default_redirect_uri
98 end
99 if system_application.flow ~= response_type then
100 return show_error(_"Response type not allowed for given client")
101 end
102 client_name = system_application.name
103 member_application = MemberApplication:by_member_id_and_system_application_id(app.session.member_id, system_application.id)
104 end
105 end
107 if not client_name then
108 return show_error(_"Client ID invalid")
109 end
111 local function error_redirect(error_code, description)
112 local params = {
113 state = state,
114 error = error_code,
115 error_description = description
116 }
117 if response_type == "token" then
118 local anchor_params_list = {}
119 for k, v in pairs(params) do
120 anchor_params_list[#anchor_params_list+1] = k .. "=" .. encode.url_part(v)
121 end
122 local anchor = table.concat(anchor_params_list, "&")
123 request.redirect{
124 external = redirect_uri .. "#" .. anchor
125 }
126 else
127 request.redirect{
128 external = redirect_uri,
129 params = params
130 }
131 end
132 end
134 if response_type ~= "code" and response_type ~= "token" then
135 return error_redirect("unsupported_response_type", "Invalid response type")
136 end
138 for i = 0, #scopes do
139 if scopes[i] == "" then
140 return error_redirect("invalid_scope", "Empty scope requested")
141 end
142 end
144 for scope in pairs(requested_scopes) do
145 local scope_valid = false
146 for i, entry in ipairs(config.oauth2.available_scopes) do
147 if scope == entry.scope or scope == entry.scope .. "_detached" then
148 scope_valid = true
149 break
150 end
151 end
152 if not scope_valid then
153 return error_redirect("invalid_scope", "Requested scope not available")
154 end
155 end
157 if system_application then
158 if system_application.permitted_scope then
159 local permitted_scopes = {}
160 for scope in string.gmatch(system_application.permitted_scope, "[^ ]+") do
161 permitted_scopes[scope] = true
162 end
163 for scope in pairs(requested_scopes) do
164 if not permitted_scopes[scope] then
165 return error_redirect("invalid_scope", "Scope not permitted")
166 end
167 end
168 end
169 if system_application.forbidden_scope then
170 for scope in string.gmatch(system_application.forbidden_scope, "[^ ]+") do
171 if requested_scopes[scope] then
172 return error_redirect("invalid_scope", "Scope forbidden")
173 end
174 end
175 end
176 if system_application.automatic_scope then
177 for scope in string.gmatch(system_application.automatic_scope, "[^ ]+") do
178 scopes_to_accept[scope] = nil
179 accepted_scopes[scope] = true
180 end
181 end
182 if member_application then
183 for scope in string.gmatch(member_application.scope, "[^ ]+") do
184 scopes_to_accept[scope] = nil
185 accepted_scopes[scope] = true
186 end
187 end
188 else
189 if dynamic_application_check == "missing_scope" then
190 return error_redirect("invalid_scope", "Scope not permitted")
191 end
192 end
194 if next(scopes_to_accept) then
195 ui.title("Application authorization")
196 ui.section(function()
197 ui.sectionHead(function()
198 ui.heading{ content = client_name }
199 ui.heading{ content = "wants to access your account" }
200 end)
201 if not system_application and not member_application then
202 ui.sectionRow(function()
203 ui.container{ content = _"Warning: Untrusted third party application." }
204 end)
205 end
206 ui.sectionRow(function()
207 ui.heading{ level = 3, content = _"Requested privileges:" }
208 ui.tag{ tag = "ul", attr = { class = "ul" }, content = function()
209 for i, entry in ipairs(config.oauth2.available_scopes) do
210 local name = entry.name[locale.get("lang")] or entry.scope
211 if accepted_scopes[entry.scope] or requested_scopes[entry.scope] or accepted_scopes[entry.scope .. "_detached"] or requested_scopes[entry.scope .. "_detached"] then
212 ui.tag{ tag = "li", content = function()
213 ui.tag{ content = name }
214 if accepted_scopes[entry.scope .. "_detached"] or requested_scopes[entry.scope .. "_detached"] then
215 slot.put(" ")
216 ui.tag{ content = _"(detached)" }
217 end
218 if scopes_to_accept[entry.scope] or scopes_to_accept[entry.scope .. "_detached"] then
219 slot.put(" ")
220 ui.tag{ content = _"(new)" }
221 end
222 -- TODO display changes
223 end }
224 end
225 end
226 end }
227 end )
228 local params = {
229 system_application_id = system_application and system_application.id or nil,
230 domain = domain,
231 redirect_uri = redirect_uri,
232 redirect_uri_explicit = redirect_uri_explicit,
233 state = state,
234 response_type = response_type
235 }
236 for i = 0, #scopes do
237 params["scope" .. i] = scopes[i]
238 end
239 ui.form{
240 module = "oauth2", action = "accept_scope", params = params,
241 routing = { default = { mode = "redirect", module = "oauth2", view = "authorization", params = request.get_param_strings() } },
242 content = function()
243 ui.sectionRow(function()
244 ui.submit{ text = _"Grant authorization", attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored " } }
245 slot.put("   ")
246 ui.link{ content = _"Decline authorization", attr = { class = "mdl-button mdl-js-button" }, external = redirect_uri, params = { error = "access_denied", error_description = "User declined to authorize client" } }
247 end )
248 end
249 }
250 end )
251 else
253 execute.chunk{ module = "oauth2", chunk = "_authorization", params = {
254 member_id = app.session.member_id,
255 system_application_id = system_application and system_application.id or nil,
256 domain = domain,
257 session_id = app.session.id,
258 redirect_uri = redirect_uri,
259 redirect_uri_explicit = redirect_uri_explicit,
260 scopes = scopes,
261 state = state,
262 response_type = response_type
263 } }
266 end

Impressum / About Us