| 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@1514 | 60 | 
| bsw@1514 | 61   if not token.system_application or token.system_application.cert_common_name then | 
| bsw@1514 | 62     if cert_distinguished_name then | 
| bsw@1514 | 63       cert_common_name = string.match(cert_distinguished_name, "%f[^/\0]CN=([A-Za-z0-9_.-]+)%f[/\0]") | 
| bsw@1514 | 64       if not cert_common_name then | 
| bsw@1676 | 65         cert_common_name = string.match(cert_distinguished_name, "^CN=([A-Za-z0-9_.-]+)") | 
| bsw@1676 | 66       end | 
| bsw@1676 | 67       if not cert_common_name then | 
| bsw@1514 | 68         return error_result("invalid_client", "CN in X.509 certificate invalid") | 
| bsw@1514 | 69       end | 
| bsw@1514 | 70     else | 
| bsw@1514 | 71       return error_result("invalid_client", "X.509 client authorization missing") | 
| bsw/jbe@1309 | 72     end | 
| bsw/jbe@1309 | 73   end | 
| bsw/jbe@1309 | 74   if token.system_application then | 
| bsw@1514 | 75     if token.system_application.cert_common_name then | 
| bsw@1514 | 76       if cert_ca ~= "private" then | 
| bsw@1514 | 77         return error_result("invalid_client", "X.509 certificate not signed by private certificate authority or wrong endpoint used") | 
| bsw@1514 | 78       end | 
| bsw@1514 | 79       if cert_common_name ~= token.system_application.cert_common_name then | 
| bsw@1514 | 80         return error_result("invalid_grant", "CN in X.509 certificate incorrect") | 
| bsw@1514 | 81       end | 
| bsw/jbe@1309 | 82     end | 
| bsw/jbe@1309 | 83   else | 
| bsw/jbe@1309 | 84     if cert_ca ~= "public" then | 
| bsw/jbe@1309 | 85       return error_result("invalid_client", "X.509 certificate not signed by publicly trusted certificate authority or wrong endpoint used") | 
| bsw/jbe@1309 | 86     end | 
| bsw/jbe@1309 | 87     if cert_common_name ~= token.domain then | 
| bsw/jbe@1309 | 88       return error_result("invalid_grant", "CN in X.509 certificate incorrect") | 
| bsw/jbe@1309 | 89     end | 
| bsw/jbe@1309 | 90   end | 
| bsw/jbe@1309 | 91   local client_id = param.get("client_id") | 
| bsw/jbe@1309 | 92   if client_id then | 
| bsw/jbe@1309 | 93     if token.system_application then | 
| bsw/jbe@1309 | 94       if client_id ~= token.system_application.client_id then | 
| bsw/jbe@1309 | 95         return error_result("invalid_grant", "Token was issued to another client") | 
| bsw/jbe@1309 | 96       end | 
| bsw/jbe@1309 | 97     else | 
| bsw/jbe@1309 | 98       if client_id ~= "dynamic:" .. token.domain then | 
| bsw/jbe@1309 | 99         return error_result ("invalid_grant", "Token was issued to another client") | 
| bsw/jbe@1309 | 100       end | 
| bsw/jbe@1309 | 101     end | 
| bsw/jbe@1309 | 102   elseif grant_type == "authorization_code" and not cert_common_name then | 
| bsw/jbe@1309 | 103     return error_result("invalid_request", "No client_id supplied for authorization_code request") | 
| bsw/jbe@1309 | 104   end | 
| bsw/jbe@1309 | 105 end | 
| bsw/jbe@1309 | 106 | 
| bsw/jbe@1309 | 107 if grant_type == "authorization_code" then | 
| bsw/jbe@1309 | 108   local redirect_uri = param.get("redirect_uri") | 
| bsw/jbe@1309 | 109   if (token.redirect_uri_explicit or redirect_uri) and token.redirect_uri ~= redirect_uri then | 
| bsw/jbe@1309 | 110     return error_result("invalid_request", "Redirect URI missing or invalid") | 
| bsw/jbe@1309 | 111   end | 
| bsw/jbe@1309 | 112 end | 
| bsw/jbe@1309 | 113 | 
| bsw/jbe@1309 | 114 local scopes = { | 
| bsw/jbe@1309 | 115   [0] = param.get("scope") | 
| bsw/jbe@1309 | 116 } | 
| bsw/jbe@1309 | 117 for i = 1, math.huge do | 
| bsw/jbe@1309 | 118   scopes[i] = param.get("scope" .. i) | 
| bsw/jbe@1309 | 119   if not scopes[i] then | 
| bsw/jbe@1309 | 120     break | 
| bsw/jbe@1309 | 121   end | 
| bsw/jbe@1309 | 122 end | 
| bsw/jbe@1309 | 123 | 
| bsw/jbe@1309 | 124 if not scopes[0] and #scopes == 0 then | 
| bsw/jbe@1309 | 125   for dummy, token_scope in ipairs(token.token_scopes) do | 
| bsw/jbe@1309 | 126     scopes[token_scope.index] = token_scope.scope | 
| bsw/jbe@1309 | 127   end | 
| bsw/jbe@1309 | 128 end | 
| bsw/jbe@1309 | 129 | 
| bsw/jbe@1309 | 130 local allowed_scopes = {} | 
| bsw/jbe@1309 | 131 local requested_detached_scopes = {} | 
| bsw/jbe@1309 | 132 for scope in string.gmatch(token.scope, "[^ ]+") do | 
| bsw/jbe@1309 | 133   allowed_scopes[scope] = true | 
| bsw/jbe@1309 | 134 end | 
| bsw/jbe@1309 | 135 for i = 0, #scopes do | 
| bsw/jbe@1309 | 136   if scopes[i] then | 
| bsw/jbe@1309 | 137     for scope in string.gmatch(scopes[i], "[^ ]+") do | 
| bsw/jbe@1309 | 138       if string.match(scope, "_detached$") then | 
| bsw/jbe@1309 | 139         requested_detached_scopes[scope] = true | 
| bsw/jbe@1309 | 140       end | 
| bsw/jbe@1309 | 141       if not allowed_scopes[scope] then | 
| bsw/jbe@1309 | 142         return error_result("invalid_scope", "Scope exceeds limits") | 
| bsw/jbe@1309 | 143       end | 
| bsw/jbe@1309 | 144     end | 
| bsw/jbe@1309 | 145   end | 
| bsw/jbe@1309 | 146 end | 
| bsw/jbe@1309 | 147 | 
| bsw/jbe@1309 | 148 local expiry | 
| bsw/jbe@1309 | 149 | 
| bsw/jbe@1309 | 150 if grant_type == "access_token" then | 
| bsw/jbe@1309 | 151   expiry = db:query({ "SELECT FLOOR(EXTRACT(EPOCH FROM ? - now())) AS access_time_left", token.expiry }, "object") | 
| bsw/jbe@1309 | 152 else | 
| bsw/jbe@1309 | 153   expiry = db:query({ | 
| bsw/jbe@1309 | 154       "SELECT now() + (? || 'sec')::interval AS refresh, now() + (? || 'sec')::interval AS access", | 
| bsw/jbe@1309 | 155       config.oauth2.refresh_token_lifetime, | 
| bsw/jbe@1309 | 156       config.oauth2.access_token_lifetime | 
| bsw/jbe@1309 | 157   }, "object") | 
| bsw/jbe@1309 | 158 end | 
| bsw/jbe@1309 | 159 | 
| bsw/jbe@1309 | 160 if grant_type == "refresh_token" then | 
| bsw/jbe@1309 | 161   local requested_detached_scopes_list = {} | 
| bsw/jbe@1309 | 162   for scope in pairs(requested_detached_scopes) do | 
| bsw/jbe@1309 | 163     requested_detached_scopes_list[#requested_detached_scopes_list+1] = scope | 
| bsw/jbe@1309 | 164   end | 
| bsw/jbe@1309 | 165   local tokens_to_reduce = Token:old_refresh_token_by_token(token, requested_detached_scopes_list) | 
| bsw/jbe@1309 | 166   for dummy, t in ipairs(tokens_to_reduce) do | 
| bsw/jbe@1309 | 167     local t_scopes = {} | 
| bsw/jbe@1309 | 168     for t_scope in string.gmatch(t.scope, "[^ ]+") do | 
| bsw/jbe@1309 | 169       t_scopes[t_scope] = true | 
| bsw/jbe@1309 | 170     end | 
| bsw/jbe@1309 | 171     for scope in pairs(requested_detached_scopes) do | 
| bsw/jbe@1309 | 172       local scope_without_detached = string.gmatch(scope, "(.+)_detached") | 
| bsw/jbe@1309 | 173       if t_scope[scope] then | 
| bsw/jbe@1309 | 174         t_scope[scope] = nil | 
| bsw/jbe@1309 | 175         t_scope[scope_without_detached] = true | 
| bsw/jbe@1309 | 176       end | 
| bsw/jbe@1309 | 177     end | 
| bsw/jbe@1309 | 178     local t_scope_list = {} | 
| bsw/jbe@1309 | 179     for scope in pairs(t_scopes) do | 
| bsw/jbe@1309 | 180       t_scope_list[#t_scope_list+1] = scope | 
| bsw/jbe@1309 | 181     end | 
| bsw/jbe@1309 | 182     t.scope = table.concat(t_scope_list, " ") | 
| bsw/jbe@1309 | 183     t:save() | 
| bsw/jbe@1309 | 184   end | 
| bsw/jbe@1309 | 185 end | 
| bsw/jbe@1309 | 186 | 
| bsw/jbe@1309 | 187 local r = json.object() | 
| bsw/jbe@1309 | 188 | 
| bsw/jbe@1309 | 189 local refresh_token | 
| bsw/jbe@1309 | 190 if | 
| bsw/jbe@1309 | 191   grant_type ~= "access_token" | 
| bsw/jbe@1309 | 192   and (grant_type == "authorization_code" or #(Token:fresh_refresh_token_by_token(token)) == 0) | 
| bsw/jbe@1309 | 193 then | 
| bsw/jbe@1309 | 194   refresh_token = Token:new() | 
| bsw/jbe@1309 | 195   refresh_token.token_type = "refresh" | 
| bsw/jbe@1309 | 196   if grant_type == "authorization_code" then | 
| bsw/jbe@1309 | 197     refresh_token.authorization_token_id = token.id | 
| bsw/jbe@1309 | 198   else | 
| bsw/jbe@1309 | 199     refresh_token.authorization_token_id = token.authorization_token_id | 
| bsw/jbe@1309 | 200   end | 
| bsw/jbe@1309 | 201   refresh_token.member_id = token.member_id | 
| bsw/jbe@1309 | 202   refresh_token.system_application_id = token.system_application_id | 
| bsw/jbe@1309 | 203   refresh_token.domain = token.domain | 
| bsw/jbe@1309 | 204   refresh_token.session_id = token.session_id | 
| bsw/jbe@1309 | 205   refresh_token.expiry = expiry.refresh | 
| bsw/jbe@1309 | 206   refresh_token.scope = token.scope | 
| bsw/jbe@1309 | 207   refresh_token:save() | 
| bsw/jbe@1309 | 208   r.refresh_token = refresh_token.token | 
| bsw/jbe@1309 | 209 end | 
| bsw/jbe@1309 | 210 | 
| bsw/jbe@1309 | 211 | 
| bsw/jbe@1309 | 212 r.token_type = "bearer" | 
| bsw/jbe@1309 | 213 if grant_type == "access_token" then | 
| bsw/jbe@1309 | 214   r.expires_in = expiry.access_time_left | 
| bsw/jbe@1309 | 215 else | 
| bsw/jbe@1309 | 216   r.expires_in = config.oauth2.access_token_lifetime | 
| bsw/jbe@1309 | 217 end | 
| bsw/jbe@1309 | 218 | 
| bsw/jbe@1309 | 219 for i = 0, #scopes do | 
| bsw/jbe@1309 | 220   if scopes[i] then | 
| bsw/jbe@1309 | 221     local scope = scopes[i] | 
| bsw/jbe@1309 | 222     local access_token = Token:new() | 
| bsw/jbe@1309 | 223     access_token.token_type = "access" | 
| bsw/jbe@1309 | 224     if grant_type == "authorization_code" then | 
| bsw/jbe@1309 | 225       access_token.authorization_token_id = token.id | 
| bsw/jbe@1309 | 226     else | 
| bsw/jbe@1309 | 227       access_token.authorization_token_id = token.authorization_token_id | 
| bsw/jbe@1309 | 228     end | 
| bsw/jbe@1309 | 229     access_token.member_id = token.member_id | 
| bsw/jbe@1309 | 230     access_token.system_application_id = token.system_application_id | 
| bsw/jbe@1309 | 231     access_token.domain = token.domain | 
| bsw/jbe@1309 | 232     access_token.session_id = token.session_id | 
| bsw/jbe@1309 | 233     if grant_type == "access_token" then | 
| bsw/jbe@1309 | 234       access_token.expiry = token.expiry | 
| bsw/jbe@1309 | 235     else | 
| bsw/jbe@1309 | 236       access_token.expiry = expiry.access | 
| bsw/jbe@1309 | 237     end | 
| bsw/jbe@1309 | 238     access_token.scope = scope | 
| bsw/jbe@1309 | 239     access_token:save() | 
| bsw/jbe@1309 | 240     if refresh_token then | 
| bsw/jbe@1309 | 241       local refresh_token_scope = TokenScope:new() | 
| bsw/jbe@1309 | 242       refresh_token_scope.token_id = refresh_token.id | 
| bsw/jbe@1309 | 243       refresh_token_scope.index = i | 
| bsw/jbe@1309 | 244       refresh_token_scope.scope = scope | 
| bsw/jbe@1309 | 245       refresh_token_scope:save() | 
| bsw/jbe@1309 | 246     end | 
| bsw/jbe@1309 | 247     local index = i == 0 and "" or i | 
| bsw/jbe@1309 | 248     r["access_token" .. index] = access_token.token | 
| bsw/jbe@1309 | 249   end | 
| bsw/jbe@1309 | 250 end | 
| bsw/jbe@1309 | 251 | 
| bsw/jbe@1309 | 252 r.member_id = token.member_id | 
| bsw/jbe@1309 | 253 if token.member.role then | 
| bsw/jbe@1309 | 254   r.member_is_role = true | 
| bsw/jbe@1309 | 255 end | 
| bsw/jbe@1309 | 256 if token.session then | 
| bsw/jbe@1309 | 257   r.real_member_id = token.real_member_id | 
| bsw/jbe@1309 | 258 end | 
| bsw/jbe@1309 | 259 | 
| bsw@1582 | 260 if allowed_scopes.identification or allowed_scopes.authentication then | 
| bsw@1582 | 261   if param.get("include_member", atom.boolean) then | 
| bsw/jbe@1309 | 262     local member = token.member | 
| bsw/jbe@1309 | 263     r.member = json.object{ | 
| bsw/jbe@1309 | 264       id = member.id, | 
| bsw/jbe@1309 | 265       name = member.name, | 
| bsw/jbe@1309 | 266     } | 
| bsw/jbe@1309 | 267     if token.session and token.session.real_member then | 
| bsw/jbe@1309 | 268       r.real_member = json.object{ | 
| bsw/jbe@1309 | 269         id = token.session.real_member.id, | 
| bsw/jbe@1309 | 270         name = token.session.real_member.name, | 
| bsw/jbe@1309 | 271       } | 
| bsw/jbe@1309 | 272     end | 
| bsw/jbe@1309 | 273     if allowed_scopes.identification then | 
| bsw/jbe@1309 | 274       r.member.identification = member.identification | 
| bsw/jbe@1309 | 275       if token.session and token.session.real_member then | 
| bsw/jbe@1309 | 276         r.real_member.identification = token.session.real_member.identification | 
| bsw/jbe@1309 | 277       end | 
| bsw/jbe@1309 | 278     end | 
| bsw/jbe@1309 | 279   end | 
| bsw/jbe@1309 | 280 end | 
| bsw/jbe@1309 | 281 | 
| bsw/jbe@1309 | 282 slot.put_into("data", json.export(r)) |