liquid_feedback_frontend

diff app/main/oauth2/token.lua @ 1309:32cc544d5a5b

Cumulative patch for upcoming frontend version 4
author bsw/jbe
date Sun Jul 15 14:07:29 2018 +0200 (2018-07-15)
parents
children 3fcae27c2709
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/app/main/oauth2/token.lua	Sun Jul 15 14:07:29 2018 +0200
     1.3 @@ -0,0 +1,274 @@
     1.4 +if not request.is_post() then
     1.5 +  return execute.view { module = "index", view = "405" }
     1.6 +end
     1.7 +
     1.8 +slot.set_layout(nil, "application/json;charset=UTF-8")
     1.9 +
    1.10 +request.add_header("Cache-Control", "no-store")
    1.11 +request.add_header("Pragma", "no-cache")
    1.12 +
    1.13 +local function error_result(error_code, error_description)
    1.14 +  -- TODO special HTTP status codes for some errors?
    1.15 +  request.set_status("400 Bad Request")
    1.16 +  slot.put_into("data", json.export{ 
    1.17 +    error = error_code,
    1.18 +    error_description = error_description
    1.19 +  })
    1.20 +end
    1.21 +
    1.22 +local token
    1.23 +local grant_type = param.get("grant_type")
    1.24 +if grant_type == "authorization_code" then
    1.25 +  local code = param.get("code")
    1.26 +  token = Token:by_token_type_and_token("authorization", code)
    1.27 +elseif grant_type == "refresh_token" then
    1.28 +  local refresh_token = param.get("refresh_token")
    1.29 +  token = Token:by_token_type_and_token("refresh", refresh_token)
    1.30 +elseif grant_type == "access_token" then
    1.31 +  local access_token, access_token_err = util.get_access_token()
    1.32 +  if access_token_err then
    1.33 +    if access_token_err == "header_and_param" then
    1.34 +      return error_result("invalid_request", "Access token passed both via header and param")
    1.35 +    end
    1.36 +    error("Error in util.get_access_token")
    1.37 +  end
    1.38 +  token = Token:by_token_type_and_token("access", access_token)
    1.39 +else
    1.40 +  return error_result("unsupported_grant_type", "Grant type not supported")
    1.41 +end
    1.42 +
    1.43 +if not token then
    1.44 +  return error_result("invalid_grant", "Token invalid or expired")
    1.45 +end
    1.46 +
    1.47 +if grant_type == "authorization_code" then
    1.48 +  if not token.used then
    1.49 +    local expiry = db:query({"SELECT now() + (? || 'sec')::interval AS expiry", config.oauth2.authorization_code_lifetime }, "object").expiry
    1.50 +    token.used = true
    1.51 +    token.expiry = expiry
    1.52 +    token:save()
    1.53 +  else
    1.54 +    token:destroy()
    1.55 +    return error_result("invalid_grant", "Token invalid or expired")
    1.56 +  end
    1.57 +end
    1.58 +
    1.59 +if grant_type ~= "access_token" then
    1.60 +  local cert_ca = request.get_header("X-LiquidFeedback-CA")
    1.61 +  local cert_distinguished_name = request.get_header("X-SSL-DN")
    1.62 +  local cert_common_name
    1.63 +  if cert_distinguished_name then
    1.64 +    cert_common_name = string.match(cert_distinguished_name, "%f[^/\0]CN=([A-Za-z0-9_.-]+)%f[/\0]")
    1.65 +    if not cert_common_name then
    1.66 +      return error_result("invalid_client", "CN in X.509 certificate invalid")
    1.67 +    end
    1.68 +  else
    1.69 +    return error_result("invalid_client", "X.509 client authorization missing")
    1.70 +  end
    1.71 +  if token.system_application then
    1.72 +    if cert_ca ~= "private" then
    1.73 +      return error_result("invalid_client", "X.509 certificate not signed by private certificate authority or wrong endpoint used")
    1.74 +    end
    1.75 +    if cert_common_name ~= token.system_application.cert_common_name then
    1.76 +      return error_result("invalid_grant", "CN in X.509 certificate incorrect")
    1.77 +    end
    1.78 +  else
    1.79 +    if cert_ca ~= "public" then
    1.80 +      return error_result("invalid_client", "X.509 certificate not signed by publicly trusted certificate authority or wrong endpoint used")
    1.81 +    end
    1.82 +    if cert_common_name ~= token.domain then
    1.83 +      return error_result("invalid_grant", "CN in X.509 certificate incorrect")
    1.84 +    end
    1.85 +  end
    1.86 +  local client_id = param.get("client_id")
    1.87 +  if client_id then
    1.88 +    if token.system_application then
    1.89 +      if client_id ~= token.system_application.client_id then
    1.90 +        return error_result("invalid_grant", "Token was issued to another client")
    1.91 +      end
    1.92 +    else
    1.93 +      if client_id ~= "dynamic:" .. token.domain then
    1.94 +        return error_result ("invalid_grant", "Token was issued to another client")
    1.95 +      end
    1.96 +    end
    1.97 +  elseif grant_type == "authorization_code" and not cert_common_name then
    1.98 +    return error_result("invalid_request", "No client_id supplied for authorization_code request")
    1.99 +  end
   1.100 +end
   1.101 +
   1.102 +if grant_type == "authorization_code" then
   1.103 +  local redirect_uri = param.get("redirect_uri")
   1.104 +  if (token.redirect_uri_explicit or redirect_uri) and token.redirect_uri ~= redirect_uri then
   1.105 +    return error_result("invalid_request", "Redirect URI missing or invalid")
   1.106 +  end
   1.107 +end
   1.108 +
   1.109 +local scopes = {
   1.110 +  [0] = param.get("scope")
   1.111 +}
   1.112 +for i = 1, math.huge do
   1.113 +  scopes[i] = param.get("scope" .. i)
   1.114 +  if not scopes[i] then
   1.115 +    break
   1.116 +  end
   1.117 +end
   1.118 +
   1.119 +if not scopes[0] and #scopes == 0 then
   1.120 +  for dummy, token_scope in ipairs(token.token_scopes) do
   1.121 +    scopes[token_scope.index] = token_scope.scope
   1.122 +  end
   1.123 +end
   1.124 +
   1.125 +local allowed_scopes = {}
   1.126 +local requested_detached_scopes = {}
   1.127 +for scope in string.gmatch(token.scope, "[^ ]+") do
   1.128 +  allowed_scopes[scope] = true
   1.129 +end
   1.130 +for i = 0, #scopes do
   1.131 +  if scopes[i] then
   1.132 +    for scope in string.gmatch(scopes[i], "[^ ]+") do
   1.133 +      if string.match(scope, "_detached$") then
   1.134 +        requested_detached_scopes[scope] = true
   1.135 +      end
   1.136 +      if not allowed_scopes[scope] then
   1.137 +        return error_result("invalid_scope", "Scope exceeds limits")
   1.138 +      end
   1.139 +    end
   1.140 +  end
   1.141 +end
   1.142 +
   1.143 +local expiry 
   1.144 +
   1.145 +if grant_type == "access_token" then
   1.146 +  expiry = db:query({ "SELECT FLOOR(EXTRACT(EPOCH FROM ? - now())) AS access_time_left", token.expiry }, "object")
   1.147 +else
   1.148 +  expiry = db:query({ 
   1.149 +      "SELECT now() + (? || 'sec')::interval AS refresh, now() + (? || 'sec')::interval AS access",
   1.150 +      config.oauth2.refresh_token_lifetime,
   1.151 +      config.oauth2.access_token_lifetime
   1.152 +  }, "object")
   1.153 +end
   1.154 +
   1.155 +if grant_type == "refresh_token" then
   1.156 +  local requested_detached_scopes_list = {}
   1.157 +  for scope in pairs(requested_detached_scopes) do
   1.158 +    requested_detached_scopes_list[#requested_detached_scopes_list+1] = scope
   1.159 +  end
   1.160 +  local tokens_to_reduce = Token:old_refresh_token_by_token(token, requested_detached_scopes_list)
   1.161 +  for dummy, t in ipairs(tokens_to_reduce) do
   1.162 +    local t_scopes = {}
   1.163 +    for t_scope in string.gmatch(t.scope, "[^ ]+") do
   1.164 +      t_scopes[t_scope] = true
   1.165 +    end
   1.166 +    for scope in pairs(requested_detached_scopes) do
   1.167 +      local scope_without_detached = string.gmatch(scope, "(.+)_detached")
   1.168 +      if t_scope[scope] then
   1.169 +        t_scope[scope] = nil
   1.170 +        t_scope[scope_without_detached] = true
   1.171 +      end
   1.172 +    end
   1.173 +    local t_scope_list = {}
   1.174 +    for scope in pairs(t_scopes) do
   1.175 +      t_scope_list[#t_scope_list+1] = scope
   1.176 +    end
   1.177 +    t.scope = table.concat(t_scope_list, " ")
   1.178 +    t:save()
   1.179 +  end
   1.180 +end
   1.181 +
   1.182 +local r = json.object()
   1.183 +
   1.184 +local refresh_token
   1.185 +if 
   1.186 +  grant_type ~= "access_token"
   1.187 +  and (grant_type == "authorization_code" or #(Token:fresh_refresh_token_by_token(token)) == 0)
   1.188 +then
   1.189 +  refresh_token = Token:new()
   1.190 +  refresh_token.token_type = "refresh"
   1.191 +  if grant_type == "authorization_code" then
   1.192 +    refresh_token.authorization_token_id = token.id
   1.193 +  else
   1.194 +    refresh_token.authorization_token_id = token.authorization_token_id
   1.195 +  end
   1.196 +  refresh_token.member_id = token.member_id
   1.197 +  refresh_token.system_application_id = token.system_application_id
   1.198 +  refresh_token.domain = token.domain
   1.199 +  refresh_token.session_id = token.session_id
   1.200 +  refresh_token.expiry = expiry.refresh
   1.201 +  refresh_token.scope = token.scope
   1.202 +  refresh_token:save()
   1.203 +  r.refresh_token = refresh_token.token
   1.204 +end
   1.205 +
   1.206 +
   1.207 +r.token_type = "bearer"
   1.208 +if grant_type == "access_token" then
   1.209 +  r.expires_in = expiry.access_time_left
   1.210 +else
   1.211 +  r.expires_in = config.oauth2.access_token_lifetime
   1.212 +end
   1.213 +
   1.214 +for i = 0, #scopes do
   1.215 +  if scopes[i] then
   1.216 +    local scope = scopes[i]
   1.217 +    local access_token = Token:new()
   1.218 +    access_token.token_type = "access"
   1.219 +    if grant_type == "authorization_code" then
   1.220 +      access_token.authorization_token_id = token.id
   1.221 +    else
   1.222 +      access_token.authorization_token_id = token.authorization_token_id
   1.223 +    end
   1.224 +    access_token.member_id = token.member_id
   1.225 +    access_token.system_application_id = token.system_application_id
   1.226 +    access_token.domain = token.domain
   1.227 +    access_token.session_id = token.session_id
   1.228 +    if grant_type == "access_token" then
   1.229 +      access_token.expiry = token.expiry
   1.230 +    else
   1.231 +      access_token.expiry = expiry.access
   1.232 +    end
   1.233 +    access_token.scope = scope
   1.234 +    access_token:save()
   1.235 +    if refresh_token then
   1.236 +      local refresh_token_scope = TokenScope:new()
   1.237 +      refresh_token_scope.token_id = refresh_token.id
   1.238 +      refresh_token_scope.index = i
   1.239 +      refresh_token_scope.scope = scope
   1.240 +      refresh_token_scope:save()
   1.241 +    end
   1.242 +    local index = i == 0 and "" or i
   1.243 +    r["access_token" .. index] = access_token.token
   1.244 +  end
   1.245 +end
   1.246 +
   1.247 +r.member_id = token.member_id
   1.248 +if token.member.role then
   1.249 +  r.member_is_role = true
   1.250 +end
   1.251 +if token.session then
   1.252 +  r.real_member_id = token.real_member_id  
   1.253 +end
   1.254 +
   1.255 +if param.get("include_member", atom.boolean) then
   1.256 +  if allowed_scopes.identification or allowed_scopes.authentication then
   1.257 +    local member = token.member
   1.258 +    r.member = json.object{
   1.259 +      id = member.id,
   1.260 +      name = member.name,
   1.261 +    }
   1.262 +    if token.session and token.session.real_member then
   1.263 +      r.real_member = json.object{
   1.264 +        id = token.session.real_member.id,
   1.265 +        name = token.session.real_member.name,
   1.266 +      }
   1.267 +    end
   1.268 +    if allowed_scopes.identification then
   1.269 +      r.member.identification = member.identification
   1.270 +      if token.session and token.session.real_member then
   1.271 +        r.real_member.identification = token.session.real_member.identification
   1.272 +      end
   1.273 +    end
   1.274 +  end
   1.275 +end
   1.276 +
   1.277 +slot.put_into("data", json.export(r))

Impressum / About Us