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