| rev | 
   line source | 
| 
bsw/jbe@1309
 | 
     1 if not request.is_post() then
 | 
| 
bsw/jbe@1309
 | 
     2   return execute.view { module = "index", view = "405" }
 | 
| 
bsw/jbe@1309
 | 
     3 end
 | 
| 
bsw/jbe@1309
 | 
     4 
 | 
| 
bsw/jbe@1309
 | 
     5 slot.set_layout(nil, "application/json;charset=UTF-8")
 | 
| 
bsw/jbe@1309
 | 
     6 
 | 
| 
bsw/jbe@1309
 | 
     7 request.add_header("Cache-Control", "no-store")
 | 
| 
bsw/jbe@1309
 | 
     8 request.add_header("Pragma", "no-cache")
 | 
| 
bsw/jbe@1309
 | 
     9 
 | 
| 
bsw/jbe@1309
 | 
    10 local function error_result(error_code, error_description)
 | 
| 
bsw/jbe@1309
 | 
    11   -- TODO special HTTP status codes for some errors?
 | 
| 
bsw/jbe@1309
 | 
    12   request.set_status("400 Bad Request")
 | 
| 
bsw/jbe@1309
 | 
    13   slot.put_into("data", json.export{ 
 | 
| 
bsw/jbe@1309
 | 
    14     error = error_code,
 | 
| 
bsw/jbe@1309
 | 
    15     error_description = error_description
 | 
| 
bsw/jbe@1309
 | 
    16   })
 | 
| 
bsw/jbe@1309
 | 
    17 end
 | 
| 
bsw/jbe@1309
 | 
    18 
 | 
| 
bsw/jbe@1309
 | 
    19 local token
 | 
| 
bsw/jbe@1309
 | 
    20 local grant_type = param.get("grant_type")
 | 
| 
bsw/jbe@1309
 | 
    21 if grant_type == "authorization_code" then
 | 
| 
bsw/jbe@1309
 | 
    22   local code = param.get("code")
 | 
| 
bsw/jbe@1309
 | 
    23   token = Token:by_token_type_and_token("authorization", code)
 | 
| 
bsw/jbe@1309
 | 
    24 elseif grant_type == "refresh_token" then
 | 
| 
bsw/jbe@1309
 | 
    25   local refresh_token = param.get("refresh_token")
 | 
| 
bsw/jbe@1309
 | 
    26   token = Token:by_token_type_and_token("refresh", refresh_token)
 | 
| 
bsw/jbe@1309
 | 
    27 elseif grant_type == "access_token" then
 | 
| 
bsw/jbe@1309
 | 
    28   local access_token, access_token_err = util.get_access_token()
 | 
| 
bsw/jbe@1309
 | 
    29   if access_token_err then
 | 
| 
bsw/jbe@1309
 | 
    30     if access_token_err == "header_and_param" then
 | 
| 
bsw/jbe@1309
 | 
    31       return error_result("invalid_request", "Access token passed both via header and param")
 | 
| 
bsw/jbe@1309
 | 
    32     end
 | 
| 
bsw/jbe@1309
 | 
    33     error("Error in util.get_access_token")
 | 
| 
bsw/jbe@1309
 | 
    34   end
 | 
| 
bsw/jbe@1309
 | 
    35   token = Token:by_token_type_and_token("access", access_token)
 | 
| 
bsw/jbe@1309
 | 
    36 else
 | 
| 
bsw/jbe@1309
 | 
    37   return error_result("unsupported_grant_type", "Grant type not supported")
 | 
| 
bsw/jbe@1309
 | 
    38 end
 | 
| 
bsw/jbe@1309
 | 
    39 
 | 
| 
bsw/jbe@1309
 | 
    40 if not token then
 | 
| 
bsw/jbe@1309
 | 
    41   return error_result("invalid_grant", "Token invalid or expired")
 | 
| 
bsw/jbe@1309
 | 
    42 end
 | 
| 
bsw/jbe@1309
 | 
    43 
 | 
| 
bsw/jbe@1309
 | 
    44 if grant_type == "authorization_code" then
 | 
| 
bsw/jbe@1309
 | 
    45   if not token.used then
 | 
| 
bsw/jbe@1309
 | 
    46     local expiry = db:query({"SELECT now() + (? || 'sec')::interval AS expiry", config.oauth2.authorization_code_lifetime }, "object").expiry
 | 
| 
bsw/jbe@1309
 | 
    47     token.used = true
 | 
| 
bsw/jbe@1309
 | 
    48     token.expiry = expiry
 | 
| 
bsw/jbe@1309
 | 
    49     token:save()
 | 
| 
bsw/jbe@1309
 | 
    50   else
 | 
| 
bsw/jbe@1309
 | 
    51     token:destroy()
 | 
| 
bsw/jbe@1309
 | 
    52     return error_result("invalid_grant", "Token invalid or expired")
 | 
| 
bsw/jbe@1309
 | 
    53   end
 | 
| 
bsw/jbe@1309
 | 
    54 end
 | 
| 
bsw/jbe@1309
 | 
    55 
 | 
| 
bsw/jbe@1309
 | 
    56 if grant_type ~= "access_token" then
 | 
| 
bsw/jbe@1309
 | 
    57   local cert_ca = request.get_header("X-LiquidFeedback-CA")
 | 
| 
bsw/jbe@1309
 | 
    58   local cert_distinguished_name = request.get_header("X-SSL-DN")
 | 
| 
bsw/jbe@1309
 | 
    59   local cert_common_name
 | 
| 
bsw/jbe@1309
 | 
    60   if cert_distinguished_name then
 | 
| 
bsw/jbe@1309
 | 
    61     cert_common_name = string.match(cert_distinguished_name, "%f[^/\0]CN=([A-Za-z0-9_.-]+)%f[/\0]")
 | 
| 
bsw/jbe@1309
 | 
    62     if not cert_common_name then
 | 
| 
bsw/jbe@1309
 | 
    63       return error_result("invalid_client", "CN in X.509 certificate invalid")
 | 
| 
bsw/jbe@1309
 | 
    64     end
 | 
| 
bsw/jbe@1309
 | 
    65   else
 | 
| 
bsw/jbe@1309
 | 
    66     return error_result("invalid_client", "X.509 client authorization missing")
 | 
| 
bsw/jbe@1309
 | 
    67   end
 | 
| 
bsw/jbe@1309
 | 
    68   if token.system_application then
 | 
| 
bsw/jbe@1309
 | 
    69     if cert_ca ~= "private" then
 | 
| 
bsw/jbe@1309
 | 
    70       return error_result("invalid_client", "X.509 certificate not signed by private certificate authority or wrong endpoint used")
 | 
| 
bsw/jbe@1309
 | 
    71     end
 | 
| 
bsw/jbe@1309
 | 
    72     if cert_common_name ~= token.system_application.cert_common_name then
 | 
| 
bsw/jbe@1309
 | 
    73       return error_result("invalid_grant", "CN in X.509 certificate incorrect")
 | 
| 
bsw/jbe@1309
 | 
    74     end
 | 
| 
bsw/jbe@1309
 | 
    75   else
 | 
| 
bsw/jbe@1309
 | 
    76     if cert_ca ~= "public" then
 | 
| 
bsw/jbe@1309
 | 
    77       return error_result("invalid_client", "X.509 certificate not signed by publicly trusted certificate authority or wrong endpoint used")
 | 
| 
bsw/jbe@1309
 | 
    78     end
 | 
| 
bsw/jbe@1309
 | 
    79     if cert_common_name ~= token.domain then
 | 
| 
bsw/jbe@1309
 | 
    80       return error_result("invalid_grant", "CN in X.509 certificate incorrect")
 | 
| 
bsw/jbe@1309
 | 
    81     end
 | 
| 
bsw/jbe@1309
 | 
    82   end
 | 
| 
bsw/jbe@1309
 | 
    83   local client_id = param.get("client_id")
 | 
| 
bsw/jbe@1309
 | 
    84   if client_id then
 | 
| 
bsw/jbe@1309
 | 
    85     if token.system_application then
 | 
| 
bsw/jbe@1309
 | 
    86       if client_id ~= token.system_application.client_id then
 | 
| 
bsw/jbe@1309
 | 
    87         return error_result("invalid_grant", "Token was issued to another client")
 | 
| 
bsw/jbe@1309
 | 
    88       end
 | 
| 
bsw/jbe@1309
 | 
    89     else
 | 
| 
bsw/jbe@1309
 | 
    90       if client_id ~= "dynamic:" .. token.domain then
 | 
| 
bsw/jbe@1309
 | 
    91         return error_result ("invalid_grant", "Token was issued to another client")
 | 
| 
bsw/jbe@1309
 | 
    92       end
 | 
| 
bsw/jbe@1309
 | 
    93     end
 | 
| 
bsw/jbe@1309
 | 
    94   elseif grant_type == "authorization_code" and not cert_common_name then
 | 
| 
bsw/jbe@1309
 | 
    95     return error_result("invalid_request", "No client_id supplied for authorization_code request")
 | 
| 
bsw/jbe@1309
 | 
    96   end
 | 
| 
bsw/jbe@1309
 | 
    97 end
 | 
| 
bsw/jbe@1309
 | 
    98 
 | 
| 
bsw/jbe@1309
 | 
    99 if grant_type == "authorization_code" then
 | 
| 
bsw/jbe@1309
 | 
   100   local redirect_uri = param.get("redirect_uri")
 | 
| 
bsw/jbe@1309
 | 
   101   if (token.redirect_uri_explicit or redirect_uri) and token.redirect_uri ~= redirect_uri then
 | 
| 
bsw/jbe@1309
 | 
   102     return error_result("invalid_request", "Redirect URI missing or invalid")
 | 
| 
bsw/jbe@1309
 | 
   103   end
 | 
| 
bsw/jbe@1309
 | 
   104 end
 | 
| 
bsw/jbe@1309
 | 
   105 
 | 
| 
bsw/jbe@1309
 | 
   106 local scopes = {
 | 
| 
bsw/jbe@1309
 | 
   107   [0] = param.get("scope")
 | 
| 
bsw/jbe@1309
 | 
   108 }
 | 
| 
bsw/jbe@1309
 | 
   109 for i = 1, math.huge do
 | 
| 
bsw/jbe@1309
 | 
   110   scopes[i] = param.get("scope" .. i)
 | 
| 
bsw/jbe@1309
 | 
   111   if not scopes[i] then
 | 
| 
bsw/jbe@1309
 | 
   112     break
 | 
| 
bsw/jbe@1309
 | 
   113   end
 | 
| 
bsw/jbe@1309
 | 
   114 end
 | 
| 
bsw/jbe@1309
 | 
   115 
 | 
| 
bsw/jbe@1309
 | 
   116 if not scopes[0] and #scopes == 0 then
 | 
| 
bsw/jbe@1309
 | 
   117   for dummy, token_scope in ipairs(token.token_scopes) do
 | 
| 
bsw/jbe@1309
 | 
   118     scopes[token_scope.index] = token_scope.scope
 | 
| 
bsw/jbe@1309
 | 
   119   end
 | 
| 
bsw/jbe@1309
 | 
   120 end
 | 
| 
bsw/jbe@1309
 | 
   121 
 | 
| 
bsw/jbe@1309
 | 
   122 local allowed_scopes = {}
 | 
| 
bsw/jbe@1309
 | 
   123 local requested_detached_scopes = {}
 | 
| 
bsw/jbe@1309
 | 
   124 for scope in string.gmatch(token.scope, "[^ ]+") do
 | 
| 
bsw/jbe@1309
 | 
   125   allowed_scopes[scope] = true
 | 
| 
bsw/jbe@1309
 | 
   126 end
 | 
| 
bsw/jbe@1309
 | 
   127 for i = 0, #scopes do
 | 
| 
bsw/jbe@1309
 | 
   128   if scopes[i] then
 | 
| 
bsw/jbe@1309
 | 
   129     for scope in string.gmatch(scopes[i], "[^ ]+") do
 | 
| 
bsw/jbe@1309
 | 
   130       if string.match(scope, "_detached$") then
 | 
| 
bsw/jbe@1309
 | 
   131         requested_detached_scopes[scope] = true
 | 
| 
bsw/jbe@1309
 | 
   132       end
 | 
| 
bsw/jbe@1309
 | 
   133       if not allowed_scopes[scope] then
 | 
| 
bsw/jbe@1309
 | 
   134         return error_result("invalid_scope", "Scope exceeds limits")
 | 
| 
bsw/jbe@1309
 | 
   135       end
 | 
| 
bsw/jbe@1309
 | 
   136     end
 | 
| 
bsw/jbe@1309
 | 
   137   end
 | 
| 
bsw/jbe@1309
 | 
   138 end
 | 
| 
bsw/jbe@1309
 | 
   139 
 | 
| 
bsw/jbe@1309
 | 
   140 local expiry 
 | 
| 
bsw/jbe@1309
 | 
   141 
 | 
| 
bsw/jbe@1309
 | 
   142 if grant_type == "access_token" then
 | 
| 
bsw/jbe@1309
 | 
   143   expiry = db:query({ "SELECT FLOOR(EXTRACT(EPOCH FROM ? - now())) AS access_time_left", token.expiry }, "object")
 | 
| 
bsw/jbe@1309
 | 
   144 else
 | 
| 
bsw/jbe@1309
 | 
   145   expiry = db:query({ 
 | 
| 
bsw/jbe@1309
 | 
   146       "SELECT now() + (? || 'sec')::interval AS refresh, now() + (? || 'sec')::interval AS access",
 | 
| 
bsw/jbe@1309
 | 
   147       config.oauth2.refresh_token_lifetime,
 | 
| 
bsw/jbe@1309
 | 
   148       config.oauth2.access_token_lifetime
 | 
| 
bsw/jbe@1309
 | 
   149   }, "object")
 | 
| 
bsw/jbe@1309
 | 
   150 end
 | 
| 
bsw/jbe@1309
 | 
   151 
 | 
| 
bsw/jbe@1309
 | 
   152 if grant_type == "refresh_token" then
 | 
| 
bsw/jbe@1309
 | 
   153   local requested_detached_scopes_list = {}
 | 
| 
bsw/jbe@1309
 | 
   154   for scope in pairs(requested_detached_scopes) do
 | 
| 
bsw/jbe@1309
 | 
   155     requested_detached_scopes_list[#requested_detached_scopes_list+1] = scope
 | 
| 
bsw/jbe@1309
 | 
   156   end
 | 
| 
bsw/jbe@1309
 | 
   157   local tokens_to_reduce = Token:old_refresh_token_by_token(token, requested_detached_scopes_list)
 | 
| 
bsw/jbe@1309
 | 
   158   for dummy, t in ipairs(tokens_to_reduce) do
 | 
| 
bsw/jbe@1309
 | 
   159     local t_scopes = {}
 | 
| 
bsw/jbe@1309
 | 
   160     for t_scope in string.gmatch(t.scope, "[^ ]+") do
 | 
| 
bsw/jbe@1309
 | 
   161       t_scopes[t_scope] = true
 | 
| 
bsw/jbe@1309
 | 
   162     end
 | 
| 
bsw/jbe@1309
 | 
   163     for scope in pairs(requested_detached_scopes) do
 | 
| 
bsw/jbe@1309
 | 
   164       local scope_without_detached = string.gmatch(scope, "(.+)_detached")
 | 
| 
bsw/jbe@1309
 | 
   165       if t_scope[scope] then
 | 
| 
bsw/jbe@1309
 | 
   166         t_scope[scope] = nil
 | 
| 
bsw/jbe@1309
 | 
   167         t_scope[scope_without_detached] = true
 | 
| 
bsw/jbe@1309
 | 
   168       end
 | 
| 
bsw/jbe@1309
 | 
   169     end
 | 
| 
bsw/jbe@1309
 | 
   170     local t_scope_list = {}
 | 
| 
bsw/jbe@1309
 | 
   171     for scope in pairs(t_scopes) do
 | 
| 
bsw/jbe@1309
 | 
   172       t_scope_list[#t_scope_list+1] = scope
 | 
| 
bsw/jbe@1309
 | 
   173     end
 | 
| 
bsw/jbe@1309
 | 
   174     t.scope = table.concat(t_scope_list, " ")
 | 
| 
bsw/jbe@1309
 | 
   175     t:save()
 | 
| 
bsw/jbe@1309
 | 
   176   end
 | 
| 
bsw/jbe@1309
 | 
   177 end
 | 
| 
bsw/jbe@1309
 | 
   178 
 | 
| 
bsw/jbe@1309
 | 
   179 local r = json.object()
 | 
| 
bsw/jbe@1309
 | 
   180 
 | 
| 
bsw/jbe@1309
 | 
   181 local refresh_token
 | 
| 
bsw/jbe@1309
 | 
   182 if 
 | 
| 
bsw/jbe@1309
 | 
   183   grant_type ~= "access_token"
 | 
| 
bsw/jbe@1309
 | 
   184   and (grant_type == "authorization_code" or #(Token:fresh_refresh_token_by_token(token)) == 0)
 | 
| 
bsw/jbe@1309
 | 
   185 then
 | 
| 
bsw/jbe@1309
 | 
   186   refresh_token = Token:new()
 | 
| 
bsw/jbe@1309
 | 
   187   refresh_token.token_type = "refresh"
 | 
| 
bsw/jbe@1309
 | 
   188   if grant_type == "authorization_code" then
 | 
| 
bsw/jbe@1309
 | 
   189     refresh_token.authorization_token_id = token.id
 | 
| 
bsw/jbe@1309
 | 
   190   else
 | 
| 
bsw/jbe@1309
 | 
   191     refresh_token.authorization_token_id = token.authorization_token_id
 | 
| 
bsw/jbe@1309
 | 
   192   end
 | 
| 
bsw/jbe@1309
 | 
   193   refresh_token.member_id = token.member_id
 | 
| 
bsw/jbe@1309
 | 
   194   refresh_token.system_application_id = token.system_application_id
 | 
| 
bsw/jbe@1309
 | 
   195   refresh_token.domain = token.domain
 | 
| 
bsw/jbe@1309
 | 
   196   refresh_token.session_id = token.session_id
 | 
| 
bsw/jbe@1309
 | 
   197   refresh_token.expiry = expiry.refresh
 | 
| 
bsw/jbe@1309
 | 
   198   refresh_token.scope = token.scope
 | 
| 
bsw/jbe@1309
 | 
   199   refresh_token:save()
 | 
| 
bsw/jbe@1309
 | 
   200   r.refresh_token = refresh_token.token
 | 
| 
bsw/jbe@1309
 | 
   201 end
 | 
| 
bsw/jbe@1309
 | 
   202 
 | 
| 
bsw/jbe@1309
 | 
   203 
 | 
| 
bsw/jbe@1309
 | 
   204 r.token_type = "bearer"
 | 
| 
bsw/jbe@1309
 | 
   205 if grant_type == "access_token" then
 | 
| 
bsw/jbe@1309
 | 
   206   r.expires_in = expiry.access_time_left
 | 
| 
bsw/jbe@1309
 | 
   207 else
 | 
| 
bsw/jbe@1309
 | 
   208   r.expires_in = config.oauth2.access_token_lifetime
 | 
| 
bsw/jbe@1309
 | 
   209 end
 | 
| 
bsw/jbe@1309
 | 
   210 
 | 
| 
bsw/jbe@1309
 | 
   211 for i = 0, #scopes do
 | 
| 
bsw/jbe@1309
 | 
   212   if scopes[i] then
 | 
| 
bsw/jbe@1309
 | 
   213     local scope = scopes[i]
 | 
| 
bsw/jbe@1309
 | 
   214     local access_token = Token:new()
 | 
| 
bsw/jbe@1309
 | 
   215     access_token.token_type = "access"
 | 
| 
bsw/jbe@1309
 | 
   216     if grant_type == "authorization_code" then
 | 
| 
bsw/jbe@1309
 | 
   217       access_token.authorization_token_id = token.id
 | 
| 
bsw/jbe@1309
 | 
   218     else
 | 
| 
bsw/jbe@1309
 | 
   219       access_token.authorization_token_id = token.authorization_token_id
 | 
| 
bsw/jbe@1309
 | 
   220     end
 | 
| 
bsw/jbe@1309
 | 
   221     access_token.member_id = token.member_id
 | 
| 
bsw/jbe@1309
 | 
   222     access_token.system_application_id = token.system_application_id
 | 
| 
bsw/jbe@1309
 | 
   223     access_token.domain = token.domain
 | 
| 
bsw/jbe@1309
 | 
   224     access_token.session_id = token.session_id
 | 
| 
bsw/jbe@1309
 | 
   225     if grant_type == "access_token" then
 | 
| 
bsw/jbe@1309
 | 
   226       access_token.expiry = token.expiry
 | 
| 
bsw/jbe@1309
 | 
   227     else
 | 
| 
bsw/jbe@1309
 | 
   228       access_token.expiry = expiry.access
 | 
| 
bsw/jbe@1309
 | 
   229     end
 | 
| 
bsw/jbe@1309
 | 
   230     access_token.scope = scope
 | 
| 
bsw/jbe@1309
 | 
   231     access_token:save()
 | 
| 
bsw/jbe@1309
 | 
   232     if refresh_token then
 | 
| 
bsw/jbe@1309
 | 
   233       local refresh_token_scope = TokenScope:new()
 | 
| 
bsw/jbe@1309
 | 
   234       refresh_token_scope.token_id = refresh_token.id
 | 
| 
bsw/jbe@1309
 | 
   235       refresh_token_scope.index = i
 | 
| 
bsw/jbe@1309
 | 
   236       refresh_token_scope.scope = scope
 | 
| 
bsw/jbe@1309
 | 
   237       refresh_token_scope:save()
 | 
| 
bsw/jbe@1309
 | 
   238     end
 | 
| 
bsw/jbe@1309
 | 
   239     local index = i == 0 and "" or i
 | 
| 
bsw/jbe@1309
 | 
   240     r["access_token" .. index] = access_token.token
 | 
| 
bsw/jbe@1309
 | 
   241   end
 | 
| 
bsw/jbe@1309
 | 
   242 end
 | 
| 
bsw/jbe@1309
 | 
   243 
 | 
| 
bsw/jbe@1309
 | 
   244 r.member_id = token.member_id
 | 
| 
bsw/jbe@1309
 | 
   245 if token.member.role then
 | 
| 
bsw/jbe@1309
 | 
   246   r.member_is_role = true
 | 
| 
bsw/jbe@1309
 | 
   247 end
 | 
| 
bsw/jbe@1309
 | 
   248 if token.session then
 | 
| 
bsw/jbe@1309
 | 
   249   r.real_member_id = token.real_member_id  
 | 
| 
bsw/jbe@1309
 | 
   250 end
 | 
| 
bsw/jbe@1309
 | 
   251 
 | 
| 
bsw/jbe@1309
 | 
   252 if param.get("include_member", atom.boolean) then
 | 
| 
bsw/jbe@1309
 | 
   253   if allowed_scopes.identification or allowed_scopes.authentication then
 | 
| 
bsw/jbe@1309
 | 
   254     local member = token.member
 | 
| 
bsw/jbe@1309
 | 
   255     r.member = json.object{
 | 
| 
bsw/jbe@1309
 | 
   256       id = member.id,
 | 
| 
bsw/jbe@1309
 | 
   257       name = member.name,
 | 
| 
bsw/jbe@1309
 | 
   258     }
 | 
| 
bsw/jbe@1309
 | 
   259     if token.session and token.session.real_member then
 | 
| 
bsw/jbe@1309
 | 
   260       r.real_member = json.object{
 | 
| 
bsw/jbe@1309
 | 
   261         id = token.session.real_member.id,
 | 
| 
bsw/jbe@1309
 | 
   262         name = token.session.real_member.name,
 | 
| 
bsw/jbe@1309
 | 
   263       }
 | 
| 
bsw/jbe@1309
 | 
   264     end
 | 
| 
bsw/jbe@1309
 | 
   265     if allowed_scopes.identification then
 | 
| 
bsw/jbe@1309
 | 
   266       r.member.identification = member.identification
 | 
| 
bsw/jbe@1309
 | 
   267       if token.session and token.session.real_member then
 | 
| 
bsw/jbe@1309
 | 
   268         r.real_member.identification = token.session.real_member.identification
 | 
| 
bsw/jbe@1309
 | 
   269       end
 | 
| 
bsw/jbe@1309
 | 
   270     end
 | 
| 
bsw/jbe@1309
 | 
   271   end
 | 
| 
bsw/jbe@1309
 | 
   272 end
 | 
| 
bsw/jbe@1309
 | 
   273 
 | 
| 
bsw/jbe@1309
 | 
   274 slot.put_into("data", json.export(r))
 |