liquid_feedback_frontend
diff app/main/oauth2/authorization.lua @ 1309:32cc544d5a5b
Cumulative patch for upcoming frontend version 4
author | bsw/jbe |
---|---|
date | Sun Jul 15 14:07:29 2018 +0200 (2018-07-15) |
parents | |
children | 2dc0ea4cdb4a |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/app/main/oauth2/authorization.lua Sun Jul 15 14:07:29 2018 +0200 1.3 @@ -0,0 +1,267 @@ 1.4 +local function show_error(text) 1.5 + ui.title("Authorization") 1.6 + ui.section(function() 1.7 + ui.sectionHead(function() 1.8 + ui.heading{ content = _"Error during authorization" } 1.9 + end) 1.10 + ui.sectionRow(function() 1.11 + ui.container{ content = text } 1.12 + end ) 1.13 + end ) 1.14 +end 1.15 + 1.16 +local client_id = param.get("client_id") 1.17 +local redirect_uri = param.get("redirect_uri") 1.18 +local redirect_uri_explicit = redirect_uri and true or false 1.19 +local response_type = param.get("response_type") 1.20 +local state = param.get("state") 1.21 + 1.22 +local no_scope_requested = true 1.23 + 1.24 +local scopes = { 1.25 + [0] = param.get("scope") 1.26 +} 1.27 + 1.28 +for i = 1, math.huge do 1.29 + scopes[i] = param.get("scope" .. i) 1.30 + if not scopes[i] then 1.31 + break 1.32 + end 1.33 +end 1.34 + 1.35 +if #scopes == 0 and not scopes[0] then 1.36 + scopes[0] = "identification" 1.37 +end 1.38 + 1.39 +local requested_scopes = {} 1.40 + 1.41 +for i = 0, #scopes do 1.42 + if scopes[i] then 1.43 + for scope in string.gmatch(scopes[i], "[^ ]+") do 1.44 + requested_scopes[scope] = true 1.45 + end 1.46 + end 1.47 +end 1.48 + 1.49 +local system_application 1.50 +local member_application 1.51 +local client_name 1.52 +local scopes_to_accept = table.new(requested_scopes) 1.53 +local accepted_scopes = {} 1.54 + 1.55 +local domain 1.56 + 1.57 +if client_id then 1.58 + domain = string.match(client_id, "^dynamic:([a-z0-9.-]+)$") 1.59 +end 1.60 + 1.61 +local dynamic_application_check 1.62 +if domain then 1.63 + if #domain > 255 then 1.64 + return show_error(_"Domain too long") 1.65 + end 1.66 + if string.find(domain, "^%.") or string.find(domain, "%.$") or string.find(domain, "%.%.") then 1.67 + return show_error(_"Invalid domain format") 1.68 + end 1.69 + if redirect_uri then 1.70 + local redirect_uri_domain, magic = string.match(redirect_uri, "^[Hh][Tt][Tt][Pp][Ss]://([A-Za-z0-9_.-]+)/(.*)$") 1.71 + if not redirect_uri_domain or string.lower(redirect_uri_domain) ~= domain or magic ~= config.oauth2.endpoint_magic then 1.72 + return show_error(_"Redirect URI forbidden") 1.73 + end 1.74 + else 1.75 + redirect_uri = "https://" .. domain .. "/" .. config.oauth2.endpoint_magic 1.76 + end 1.77 + dynamic_application_check = DynamicApplicationScope:check_scopes(domain, redirect_uri, response_type, requested_scopes) 1.78 + if dynamic_application_check == "not_registered" then 1.79 + return show_error(_"Redirect URI or response type not registered") 1.80 + end 1.81 + client_name = domain 1.82 + member_application = MemberApplication:by_member_id_and_domain(app.session.member_id, domain) 1.83 + if member_application then 1.84 + for scope in string.gmatch(member_application.scope, "[^ ]+") do 1.85 + accepted_scopes[scope] = true 1.86 + scopes_to_accept[scope] = nil 1.87 + end 1.88 + end 1.89 +else 1.90 + system_application = SystemApplication:by_client_id(client_id) 1.91 + if system_application then 1.92 + if redirect_uri_explicit then 1.93 + if 1.94 + redirect_uri ~= system_application.default_redirect_uri 1.95 + and not SystemApplicationRedirectUri:by_pk(system_application.id, redirect_uri) 1.96 + then 1.97 + return show_error(_"Redirect URI invalid") 1.98 + end 1.99 + else 1.100 + redirect_uri = system_application.default_redirect_uri 1.101 + end 1.102 + if system_application.flow ~= response_type then 1.103 + return show_error(_"Response type not allowed for given client") 1.104 + end 1.105 + client_name = system_application.name 1.106 + member_application = MemberApplication:by_member_id_and_system_application_id(app.session.member_id, system_application.id) 1.107 + end 1.108 +end 1.109 + 1.110 +if not client_name then 1.111 + return show_error(_"Client ID invalid") 1.112 +end 1.113 + 1.114 +local function error_redirect(error_code, description) 1.115 + local params = { 1.116 + state = state, 1.117 + error = error_code, 1.118 + error_description = description 1.119 + } 1.120 + if response_type == "token" then 1.121 + local anchor_params_list = {} 1.122 + for k, v in pairs(params) do 1.123 + anchor_params_list[#anchor_params_list+1] = k .. "=" .. encode.url_part(v) 1.124 + end 1.125 + local anchor = table.concat(anchor_params_list, "&") 1.126 + request.redirect{ 1.127 + external = redirect_uri .. "#" .. anchor 1.128 + } 1.129 + else 1.130 + request.redirect{ 1.131 + external = redirect_uri, 1.132 + params = params 1.133 + } 1.134 + end 1.135 +end 1.136 + 1.137 +if response_type ~= "code" and response_type ~= "token" then 1.138 + return error_redirect("unsupported_response_type", "Invalid response type") 1.139 +end 1.140 + 1.141 +for i = 0, #scopes do 1.142 + if scopes[i] == "" then 1.143 + return error_redirect("invalid_scope", "Empty scope requested") 1.144 + end 1.145 +end 1.146 + 1.147 +for scope in pairs(requested_scopes) do 1.148 + local scope_valid = false 1.149 + for i, entry in ipairs(config.oauth2.available_scopes) do 1.150 + if scope == entry.scope or scope == entry.scope .. "_detached" then 1.151 + scope_valid = true 1.152 + break 1.153 + end 1.154 + end 1.155 + if not scope_valid then 1.156 + return error_redirect("invalid_scope", "Requested scope not available") 1.157 + end 1.158 +end 1.159 + 1.160 +if system_application then 1.161 + if system_application.permitted_scope then 1.162 + local permitted_scopes = {} 1.163 + for scope in string.gmatch(system_application.permitted_scope, "[^ ]+") do 1.164 + permitted_scopes[scope] = true 1.165 + end 1.166 + for scope in pairs(requested_scopes) do 1.167 + if not permitted_scopes[scope] then 1.168 + return error_redirect("invalid_scope", "Scope not permitted") 1.169 + end 1.170 + end 1.171 + end 1.172 + if system_application.forbidden_scope then 1.173 + for scope in string.gmatch(system_application.forbidden_scope, "[^ ]+") do 1.174 + if requested_scopes[scope] then 1.175 + return error_redirect("invalid_scope", "Scope forbidden") 1.176 + end 1.177 + end 1.178 + end 1.179 + if system_application.automatic_scope then 1.180 + for scope in string.gmatch(system_application.automatic_scope, "[^ ]+") do 1.181 + scopes_to_accept[scope] = nil 1.182 + accepted_scopes[scope] = true 1.183 + end 1.184 + end 1.185 + if member_application then 1.186 + for scope in string.gmatch(member_application.scope, "[^ ]+") do 1.187 + scopes_to_accept[scope] = nil 1.188 + accepted_scopes[scope] = true 1.189 + end 1.190 + end 1.191 +else 1.192 + if dynamic_application_check == "missing_scope" then 1.193 + return error_redirect("invalid_scope", "Scope not permitted") 1.194 + end 1.195 +end 1.196 + 1.197 +if next(scopes_to_accept) then 1.198 + ui.title("Application authorization") 1.199 + ui.section(function() 1.200 + ui.sectionHead(function() 1.201 + ui.heading{ content = client_name } 1.202 + ui.heading{ content = "wants to access your account" } 1.203 + end) 1.204 + if not system_application and not member_application then 1.205 + ui.sectionRow(function() 1.206 + ui.container{ content = _"Warning: Untrusted third party application." } 1.207 + end) 1.208 + end 1.209 + ui.sectionRow(function() 1.210 + ui.heading{ level = 3, content = _"Requested privileges:" } 1.211 + ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() 1.212 + for i, entry in ipairs(config.oauth2.available_scopes) do 1.213 + local name = entry.name[locale.get("lang")] or entry.scope 1.214 + if accepted_scopes[entry.scope] or requested_scopes[entry.scope] or accepted_scopes[entry.scope .. "_detached"] or requested_scopes[entry.scope .. "_detached"] then 1.215 + ui.tag{ tag = "li", content = function() 1.216 + ui.tag{ content = name } 1.217 + if accepted_scopes[entry.scope .. "_detached"] or requested_scopes[entry.scope .. "_detached"] then 1.218 + slot.put(" ") 1.219 + ui.tag{ content = _"(detached)" } 1.220 + end 1.221 + if scopes_to_accept[entry.scope] or scopes_to_accept[entry.scope .. "_detached"] then 1.222 + slot.put(" ") 1.223 + ui.tag{ content = _"(new)" } 1.224 + end 1.225 + -- TODO display changes 1.226 + end } 1.227 + end 1.228 + end 1.229 + end } 1.230 + end ) 1.231 + local params = { 1.232 + system_application_id = system_application and system_application.id or nil, 1.233 + domain = domain, 1.234 + redirect_uri = redirect_uri, 1.235 + redirect_uri_explicit = redirect_uri_explicit, 1.236 + state = state, 1.237 + response_type = response_type 1.238 + } 1.239 + for i = 0, #scopes do 1.240 + params["scope" .. i] = scopes[i] 1.241 + end 1.242 + ui.form{ 1.243 + module = "oauth2", action = "accept_scope", params = params, 1.244 + routing = { default = { mode = "redirect", module = "oauth2", view = "authorization", params = request.get_param_strings() } }, 1.245 + content = function() 1.246 + ui.sectionRow(function() 1.247 + ui.submit{ text = _"Grant authorization", attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored " } } 1.248 + slot.put(" ") 1.249 + 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" } } 1.250 + end ) 1.251 + end 1.252 + } 1.253 + end ) 1.254 +else 1.255 + 1.256 + execute.chunk{ module = "oauth2", chunk = "_authorization", params = { 1.257 + member_id = app.session.member_id, 1.258 + system_application_id = system_application and system_application.id or nil, 1.259 + domain = domain, 1.260 + session_id = app.session.id, 1.261 + redirect_uri = redirect_uri, 1.262 + redirect_uri_explicit = redirect_uri_explicit, 1.263 + scopes = scopes, 1.264 + state = state, 1.265 + response_type = response_type 1.266 + } } 1.267 + 1.268 + 1.269 +end 1.270 +