liquid_feedback_frontend
view app/main/oauth2/authorization.lua @ 1852:e593570a23c5
More efficient algorithm
| author | bsw | 
|---|---|
| date | Tue Mar 22 10:35:44 2022 +0100 (2022-03-22) | 
| parents | 2dc0ea4cdb4a | 
| children | 
 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
