bsw/jbe@1309: local function show_error(text) bsw/jbe@1309: ui.title("Authorization") bsw/jbe@1309: ui.section(function() bsw/jbe@1309: ui.sectionHead(function() bsw/jbe@1309: ui.heading{ content = _"Error during authorization" } bsw/jbe@1309: end) bsw/jbe@1309: ui.sectionRow(function() bsw/jbe@1309: ui.container{ content = text } bsw/jbe@1309: end ) bsw/jbe@1309: end ) bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local client_id = param.get("client_id") bsw/jbe@1309: local redirect_uri = param.get("redirect_uri") bsw/jbe@1309: local redirect_uri_explicit = redirect_uri and true or false bsw/jbe@1309: local response_type = param.get("response_type") bsw/jbe@1309: local state = param.get("state") bsw/jbe@1309: bsw/jbe@1309: local no_scope_requested = true bsw/jbe@1309: bsw/jbe@1309: local scopes = { bsw/jbe@1309: [0] = param.get("scope") bsw/jbe@1309: } bsw/jbe@1309: bsw/jbe@1309: for i = 1, math.huge do bsw/jbe@1309: scopes[i] = param.get("scope" .. i) bsw/jbe@1309: if not scopes[i] then bsw/jbe@1309: break bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if #scopes == 0 and not scopes[0] then bsw/jbe@1309: scopes[0] = "identification" bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local requested_scopes = {} bsw/jbe@1309: bsw/jbe@1309: for i = 0, #scopes do bsw/jbe@1309: if scopes[i] then bsw/jbe@1309: for scope in string.gmatch(scopes[i], "[^ ]+") do bsw/jbe@1309: requested_scopes[scope] = true bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local system_application bsw/jbe@1309: local member_application bsw/jbe@1309: local client_name bsw/jbe@1309: local scopes_to_accept = table.new(requested_scopes) bsw/jbe@1309: local accepted_scopes = {} bsw/jbe@1309: bsw/jbe@1309: local domain bsw/jbe@1309: bsw/jbe@1309: if client_id then bsw/jbe@1309: domain = string.match(client_id, "^dynamic:([a-z0-9.-]+)$") bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local dynamic_application_check bsw/jbe@1309: if domain then bsw/jbe@1309: if #domain > 255 then bsw/jbe@1309: return show_error(_"Domain too long") bsw/jbe@1309: end bsw/jbe@1309: if string.find(domain, "^%.") or string.find(domain, "%.$") or string.find(domain, "%.%.") then bsw/jbe@1309: return show_error(_"Invalid domain format") bsw/jbe@1309: end bsw/jbe@1309: if redirect_uri then bsw/jbe@1309: local redirect_uri_domain, magic = string.match(redirect_uri, "^[Hh][Tt][Tt][Pp][Ss]://([A-Za-z0-9_.-]+)/(.*)$") bsw/jbe@1309: if not redirect_uri_domain or string.lower(redirect_uri_domain) ~= domain or magic ~= config.oauth2.endpoint_magic then bsw/jbe@1309: return show_error(_"Redirect URI forbidden") bsw/jbe@1309: end bsw/jbe@1309: else bsw/jbe@1309: redirect_uri = "https://" .. domain .. "/" .. config.oauth2.endpoint_magic bsw/jbe@1309: end bsw/jbe@1309: dynamic_application_check = DynamicApplicationScope:check_scopes(domain, redirect_uri, response_type, requested_scopes) bsw/jbe@1309: if dynamic_application_check == "not_registered" then bsw/jbe@1309: return show_error(_"Redirect URI or response type not registered") bsw/jbe@1309: end bsw/jbe@1309: client_name = domain bsw/jbe@1309: member_application = MemberApplication:by_member_id_and_domain(app.session.member_id, domain) bsw/jbe@1309: if member_application then bsw/jbe@1309: for scope in string.gmatch(member_application.scope, "[^ ]+") do bsw/jbe@1309: accepted_scopes[scope] = true bsw/jbe@1309: scopes_to_accept[scope] = nil bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: else bsw/jbe@1309: system_application = SystemApplication:by_client_id(client_id) bsw/jbe@1309: if system_application then bsw/jbe@1309: if redirect_uri_explicit then bsw/jbe@1309: if bsw/jbe@1309: redirect_uri ~= system_application.default_redirect_uri bsw/jbe@1309: and not SystemApplicationRedirectUri:by_pk(system_application.id, redirect_uri) bsw/jbe@1309: then bsw/jbe@1309: return show_error(_"Redirect URI invalid") bsw/jbe@1309: end bsw/jbe@1309: else bsw/jbe@1309: redirect_uri = system_application.default_redirect_uri bsw/jbe@1309: end bsw/jbe@1309: if system_application.flow ~= response_type then bsw/jbe@1309: return show_error(_"Response type not allowed for given client") bsw/jbe@1309: end bsw/jbe@1309: client_name = system_application.name bsw/jbe@1309: member_application = MemberApplication:by_member_id_and_system_application_id(app.session.member_id, system_application.id) bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if not client_name then bsw/jbe@1309: return show_error(_"Client ID invalid") bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local function error_redirect(error_code, description) bsw/jbe@1309: local params = { bsw/jbe@1309: state = state, bsw/jbe@1309: error = error_code, bsw/jbe@1309: error_description = description bsw/jbe@1309: } bsw/jbe@1309: if response_type == "token" then bsw/jbe@1309: local anchor_params_list = {} bsw/jbe@1309: for k, v in pairs(params) do bsw/jbe@1309: anchor_params_list[#anchor_params_list+1] = k .. "=" .. encode.url_part(v) bsw/jbe@1309: end bsw/jbe@1309: local anchor = table.concat(anchor_params_list, "&") bsw/jbe@1309: request.redirect{ bsw/jbe@1309: external = redirect_uri .. "#" .. anchor bsw/jbe@1309: } bsw/jbe@1309: else bsw/jbe@1309: request.redirect{ bsw/jbe@1309: external = redirect_uri, bsw/jbe@1309: params = params bsw/jbe@1309: } bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if response_type ~= "code" and response_type ~= "token" then bsw/jbe@1309: return error_redirect("unsupported_response_type", "Invalid response type") bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: for i = 0, #scopes do bsw/jbe@1309: if scopes[i] == "" then bsw/jbe@1309: return error_redirect("invalid_scope", "Empty scope requested") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: for scope in pairs(requested_scopes) do bsw/jbe@1309: local scope_valid = false bsw/jbe@1309: for i, entry in ipairs(config.oauth2.available_scopes) do bsw/jbe@1309: if scope == entry.scope or scope == entry.scope .. "_detached" then bsw/jbe@1309: scope_valid = true bsw/jbe@1309: break bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: if not scope_valid then bsw/jbe@1309: return error_redirect("invalid_scope", "Requested scope not available") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if system_application then bsw/jbe@1309: if system_application.permitted_scope then bsw/jbe@1309: local permitted_scopes = {} bsw/jbe@1309: for scope in string.gmatch(system_application.permitted_scope, "[^ ]+") do bsw/jbe@1309: permitted_scopes[scope] = true bsw/jbe@1309: end bsw/jbe@1309: for scope in pairs(requested_scopes) do bsw/jbe@1309: if not permitted_scopes[scope] then bsw/jbe@1309: return error_redirect("invalid_scope", "Scope not permitted") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: if system_application.forbidden_scope then bsw/jbe@1309: for scope in string.gmatch(system_application.forbidden_scope, "[^ ]+") do bsw/jbe@1309: if requested_scopes[scope] then bsw/jbe@1309: return error_redirect("invalid_scope", "Scope forbidden") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: if system_application.automatic_scope then bsw/jbe@1309: for scope in string.gmatch(system_application.automatic_scope, "[^ ]+") do bsw/jbe@1309: scopes_to_accept[scope] = nil bsw/jbe@1309: accepted_scopes[scope] = true bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: if member_application then bsw/jbe@1309: for scope in string.gmatch(member_application.scope, "[^ ]+") do bsw/jbe@1309: scopes_to_accept[scope] = nil bsw/jbe@1309: accepted_scopes[scope] = true bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: else bsw/jbe@1309: if dynamic_application_check == "missing_scope" then bsw/jbe@1309: return error_redirect("invalid_scope", "Scope not permitted") bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if next(scopes_to_accept) then bsw/jbe@1309: ui.title("Application authorization") bsw/jbe@1309: ui.section(function() bsw/jbe@1309: ui.sectionHead(function() bsw/jbe@1309: ui.heading{ content = client_name } bsw/jbe@1309: ui.heading{ content = "wants to access your account" } bsw/jbe@1309: end) bsw/jbe@1309: if not system_application and not member_application then bsw/jbe@1309: ui.sectionRow(function() bsw/jbe@1309: ui.container{ content = _"Warning: Untrusted third party application." } bsw/jbe@1309: end) bsw/jbe@1309: end bsw/jbe@1309: ui.sectionRow(function() bsw/jbe@1309: ui.heading{ level = 3, content = _"Requested privileges:" } bsw/jbe@1309: ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() bsw/jbe@1309: for i, entry in ipairs(config.oauth2.available_scopes) do bsw/jbe@1309: local name = entry.name[locale.get("lang")] or entry.scope bsw/jbe@1309: if accepted_scopes[entry.scope] or requested_scopes[entry.scope] or accepted_scopes[entry.scope .. "_detached"] or requested_scopes[entry.scope .. "_detached"] then bsw/jbe@1309: ui.tag{ tag = "li", content = function() bsw/jbe@1309: ui.tag{ content = name } bsw/jbe@1309: if accepted_scopes[entry.scope .. "_detached"] or requested_scopes[entry.scope .. "_detached"] then bsw/jbe@1309: slot.put(" ") bsw/jbe@1309: ui.tag{ content = _"(detached)" } bsw/jbe@1309: end bsw/jbe@1309: if scopes_to_accept[entry.scope] or scopes_to_accept[entry.scope .. "_detached"] then bsw/jbe@1309: slot.put(" ") bsw/jbe@1309: ui.tag{ content = _"(new)" } bsw/jbe@1309: end bsw/jbe@1309: -- TODO display changes bsw/jbe@1309: end } bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: end } bsw/jbe@1309: end ) bsw/jbe@1309: local params = { bsw/jbe@1309: system_application_id = system_application and system_application.id or nil, bsw/jbe@1309: domain = domain, bsw/jbe@1309: redirect_uri = redirect_uri, bsw/jbe@1309: redirect_uri_explicit = redirect_uri_explicit, bsw/jbe@1309: state = state, bsw/jbe@1309: response_type = response_type bsw/jbe@1309: } bsw/jbe@1309: for i = 0, #scopes do bsw/jbe@1309: params["scope" .. i] = scopes[i] bsw/jbe@1309: end bsw/jbe@1309: ui.form{ bsw/jbe@1309: module = "oauth2", action = "accept_scope", params = params, bsw/jbe@1309: routing = { default = { mode = "redirect", module = "oauth2", view = "authorization", params = request.get_param_strings() } }, bsw/jbe@1309: content = function() bsw/jbe@1309: ui.sectionRow(function() bsw/jbe@1309: ui.submit{ text = _"Grant authorization", attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored " } } bsw/jbe@1309: slot.put("   ") bsw/jbe@1309: 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" } } bsw/jbe@1309: end ) bsw/jbe@1309: end bsw/jbe@1309: } bsw/jbe@1309: end ) bsw/jbe@1309: else bsw/jbe@1309: bsw/jbe@1309: execute.chunk{ module = "oauth2", chunk = "_authorization", params = { bsw/jbe@1309: member_id = app.session.member_id, bsw/jbe@1309: system_application_id = system_application and system_application.id or nil, bsw/jbe@1309: domain = domain, bsw/jbe@1309: session_id = app.session.id, bsw/jbe@1309: redirect_uri = redirect_uri, bsw/jbe@1309: redirect_uri_explicit = redirect_uri_explicit, bsw/jbe@1309: scopes = scopes, bsw/jbe@1309: state = state, bsw/jbe@1309: response_type = response_type bsw/jbe@1309: } } bsw/jbe@1309: bsw/jbe@1309: bsw/jbe@1309: end bsw/jbe@1309: