stormpath-sdk-php icon indicating copy to clipboard operation
stormpath-sdk-php copied to clipboard

Caching Does Not Work

Open eyespies opened this issue 9 years ago • 0 comments

Issue Type (Leave One)

Bug

Version affected

1.16, possibly others as well.

Summary

When using the stormpath-sdk-lumen package and after enabling the system to use either Redis or Memcached, the first authentication attempt when using a Bearer token works successfully, however subsequent attempts fail with the following:

InvalidArgumentException in UrlGenerator.php line 215:
Route [stormpath.login] not defined.

This is a misleading error. I found that this error is (correctly) thrown because I have not configured stormpath.login route, HOWEVER, my credentials had just worked. I found that if I clear the cache, the Bearer token works again. I tracked the error down to these lines in the stormpath-sdk-lumen package, the file src\Http\Middleware\Authenticate.php:

public function isAuthenticated(Request $request)
    {
        $token = $request->bearerToken();

        if(null === $token) {
            $token = $request->cookie(config('stormpath.web.accessTokenCookie.name'));
        }

        if($token instanceof \Symfony\Component\HttpFoundation\Cookie) {
            $token = $token->getValue();
        }

        try {
            (new \Stormpath\Oauth\VerifyAccessToken(app('stormpath.application')))->verify($token);
            return true;
        } catch (\Exception $re) {
            return false;
        }
    }

That said, I do NOT believe this to be an error in the Lumen package, but rather the general PHP package. When I dump the exception that occurs, I get more details:

exception 'ErrorException' with message 'Argument 2 passed to Stormpath\Resource\Resource::__construct() must be an instance of stdClass, string given' in
/var/www/redis.dev/vendor/stormpath/sdk/src/Resource/Resource.php:36 Stack trace: 
#0 /var/www/redis.dev/vendor/stormpath/sdk/src/Resource/Resource.php(36): Laravel\Lumen\Application->Laravel\Lumen\Concerns\{closure}(4096, 'Argument 2 pass...', '/var/www/redis....', 36, Array) 
#1 [internal function]: Stormpath\Resource\Resource->__construct(Object(Stormpath\DataStore\DefaultDataStore), 'Object', Array) 
#2 /var/www/redis.dev/vendor/stormpath/sdk/src/DataStore/DefaultResourceFactory.php(40): ReflectionClass->newInstanceArgs(Array) 
#3 /var/www/redis.dev/vendor/stormpath/sdk/src/DataStore/DefaultDataStore.php(126): Stormpath\DataStore\DefaultResourceFactory->instantiate('Application', Array) 
#4 /var/www/redis.dev/vendor/stormpath/sdk/src/Client.php(128): Stormpath\DataStore\DefaultDataStore->getResource('https://api.sto...', 'Application', Array) 
#5 /var/www/redis.dev/vendor/stormpath/sdk/src/Resource/Application.php(55): Stormpath\Client::get('https://api.sto...', 'Application', 'applications', Array) 
#6 /var/www/redis.dev/vendor/stormpath/lumen/src/Support/StormpathServiceProvider.php(136): Stormpath\Resource\Application::get('https://api.sto...') 
#7 /var/www/redis.dev/vendor/illuminate/container/Container.php(735): Stormpath\Lumen\Support\StormpathServiceProvider->Stormpath\Lumen\Support\{closure}(Object(Laravel\Lumen\Application), Array) 
#8 /var/www/redis.dev/vendor/illuminate/container/Container.php(633): Illuminate\Container\Container->build(Object(Closure), Array) 
#9 /var/www/redis.dev/vendor/laravel/lumen-framework/src/Application.php(205): Illuminate\Container\Container->make('stormpath.appli...', Array) #10 /var/www/redis.dev/vendor/laravel/lumen-framework/src/helpers.php(39): Laravel\Lumen\Application->make('stormpath.appli...', Array) #11 /var/www/redis.dev/vendor/stormpath/lumen/src/Http/Middleware/Authenticate.php(68): app('stormpath.appli...')
#12 /var/www/redis.dev/vendor/stormpath/lumen/src/Http/Middleware/Authenticate.php(43): Stormpath\Lumen\Http\Middleware\Authenticate->isAuthenticated(Object(Illuminate\Http\Request)) 
#13 [internal function]: Stormpath\Lumen\Http\Middleware\Authenticate->handle(Object(Illuminate\Http\Request), Object(Closure)) 
#14 /var/www/redis.dev/vendor/illuminate/pipeline/Pipeline.php(136): call_user_func_array(Array, Array) 
#15 [internal function]: Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request)) 
#16 /var/www/redis.dev/vendor/laravel/lumen-framework/src/Routing/Pipeline.php(32): call_user_func(Object(Closure), Object(Illuminate\Http\Request)) 
#17 [internal function]: Laravel\Lumen\Routing\Pipeline->Laravel\Lumen\Routing\{closure}(Object(Illuminate\Http\Request)) 
#18 /var/www/redis.dev/vendor/illuminate/pipeline/Pipeline.php(103): call_user_func(Object(Closure), Object(Illuminate\Http\Request)) 
#19 /var/www/redis.dev/vendor/laravel/lumen-framework/src/Concerns/RoutesRequests.php(626): Illuminate\Pipeline\Pipeline->then(Object(Closure)) 
#20 /var/www/redis.dev/vendor/laravel/lumen-framework/src/Concerns/RoutesRequests.php(475): Laravel\Lumen\Application->sendThroughPipeline(Array, Object(Closure)) #21 /var/www/redis.dev/vendor/laravel/lumen-framework/src/Concerns/RoutesRequests.php(449): Laravel\Lumen\Application->handleFoundRoute(Array) 
#22 /var/www/redis.dev/vendor/laravel/lumen-framework/src/Concerns/RoutesRequests.php(381): Laravel\Lumen\Application->handleDispatcherResponse(Array)
#23 /var/www/redis.dev/vendor/laravel/lumen-framework/src/Concerns/RoutesRequests.php(629): Laravel\Lumen\Application->Laravel\Lumen\Concerns\{closure}() 
#24 /var/www/redis.dev/vendor/laravel/lumen-framework/src/Concerns/RoutesRequests.php(382): Laravel\Lumen\Application->sendThroughPipeline(Array, Object(Closure)) 
#25 /var/www/redis.dev/vendor/laravel/lumen-framework/src/Concerns/RoutesRequests.php(327): Laravel\Lumen\Application->dispatch(NULL) 
#26 /var/www/redis.dev/public/index.php(28): Laravel\Lumen\Application->run() 
#27 {main}

Within this package (stormpath-sdk-php), I then checked src\DataStore\DefaultDataStore.php, I found the following code:

    public function getResource($href, $className, array $options = array())
    {
        if ($this->needsToBeFullyQualified($href))
        {
            $href = $this->qualify($href);
        }

        $queryString = $this->getQueryString($options);

        if (!$data = $this->isResourceCached($href, $options)) {
            $data = $this->executeRequest(Request::METHOD_GET, $href, '', $queryString);
        }
        if($this->resourceIsCacheable($data)) {
            $this->addDataToCache($data, $queryString);
        }

        $resolver = DefaultClassNameResolver::getInstance();
        $className = $resolver->resolve($className, $data, $options);

        return $this->resourceFactory->instantiate($className, array($data, $queryString));
    }

If I disable the check for isResourceCached(), (forcing the call to $data = $this->executeRequest(Request::METHOD_GET, $href, '', $queryString);) the code executes perfectly every time. In addition, when I dump the contents,during a failure scenario, of the $data variable that has been retrieved from Cache, the second array element is the string 'Object'.

So from what I can tell, when the data is serialized and stored in the cache, the object is not properly serialized and instead the string 'Object' is stored in the cache.

Here is output from a telnet session to the Redis cache storage system:

+OK
keys *
*0
keys *
*17
$71
https://api.stormpath.com/v1/accountStoreMappings/<obfuscated>
$75
https://api.stormpath.com/v1/applications/<obfuscated>/idSiteModel
$39
laravel:stormpath.accountCreationPolicy
$72
https://api.stormpath.com/v1/directories/<obfuscated>/provider
$71
https://api.stormpath.com/v1/directories/<obfuscated>/provider
$34
laravel:stormpath.passwordPolicies
$62
https://api.stormpath.com/v1/directories/<obfuscated>
$63
https://api.stormpath.com/v1/applications/<obfuscated>
$44
laravel:stormpath.defaultAccountStoreMapping
$31
laravel:stormpath.accountStores
$67
https://api.stormpath.com/v1/passwordPolicies/<obfuscated>
$31
laravel:stormpath.resourcesWarm
$84
https://api.stormpath.com/v1/directories/<obfuscated>:accountCreationPolicy
$29
laravel:stormpath.idsitemodel
$38
laravel:stormpath.accountStoreMappings
$32
laravel:stormpath.passwordPolicy
$64
https://api.stormpath.com/v1/accessTokens/<obfuscated>
get https://api.stormpath.com/v1/applications/<obfuscated>
$6
Object

Steps to Reproduce

  1. Enable Redis or Memcached caching
  2. Authenticate to the site
  3. Using the Bearer token from the authentication response, access a protected resource, because the cache is empty, the system will respond as expected.
  4. Repeat the previous step, this time the system will either throw an error (if the stormpath.login route is not configured) or redirect you to the login page (if the stormpath.login route is configured) (NOTE: This applies only to the Lumen integration; any other result will be dependent on the PHP package your using)

Expected Results

All requests after the first will complete successfully AND, because caching is enabled, they should complete much more quickly (without caching, our response times typically average about 450ms, of which about 400ms is waiting for Stormpath API calls - according to NewRelic APM).

Actual Results

The first request receives a successful response; subsequent requests all fail.

More Info

eyespies avatar Jul 25 '16 17:07 eyespies