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