Luracast Restler Multiple Authentication Classes Not Allowing Access - php

I have two authentication classes defined.
API Keys (APIKeyAuth)
OAUTH2 (OAUTH2Server)
In my index.php I have the following defined
$r = new Restler();
$r->addAuthenticationClass('APIKeyAuth');
$r->addAuthenticationClass('OAUTH2Server');
I then protect one of the rest methods for APIKeyAuth
/**
* #access protected
* #class APIKeyAuth{#requires apikey}
*/
public function .......etc
If I debug it , it goes through the first step and $authObj (see code below from restler.php) will
be APIKeyAuth. It checks __isAllowed and returns true ... which is good.
It then however goes through OAUTH2Server (which in my opinion it shouldn't as the rest method has
been decorated to use APIKeyAuth.
So it goes through and __isAllowed in OAUTH2Server is false so then the user will get a Unauthorzied response.
foreach ($this->authClasses as $authClass) {
$authObj = Scope::get($authClass);
if (!method_exists($authObj,
Defaults::$authenticationMethod)
) {
throw new RestException (
500, 'Authentication Class ' .
'should implement iAuthenticate');
} elseif (
!$authObj->{Defaults::$authenticationMethod}()
) {
throw new RestException(401);
}
}
Do I need to alter the OAUTH2 Server to check if its using an API Key and add logic ? (seems wrong approach).

Restler upto RC5 handles authentication classes serially, meaning that all the authentication classes must return true to go through the protected api call
Since RC6 this has changed to parallel, meaning that any one of the authentication class can allow access to the protected api

Related

Testing Laravel controller internals with a mock

So I have this Laravel controller and I want to test it.
It is an OAuth client, so a callback is needed to finish the setup.
I want to test the OAuth callback.
The code is something like this:
public function callback(): \Illuminate\Http\RedirectResponse
{
$aCookie = request()->cookie('cookie');
$authCode = request()->get('code')
$connection = OAuthpackage::getConnection();
// Store the credentials in the database
}
To test if the credentials are stored correctly in de database I want to mock the OAuthpackage because it is fine to give it face credentials.
My first thought was to just test the controller by calling that directly. The thing with that is that I don't only need to mock the Oauthpackage but also the request class because I need to face the cookie getting in the code in the GET request.
Now I read on the internet that you probably would not want to mock the request class.
So I thought about just doing the request in the test and then seeing the output.
1 It is just the regular flow
2 I need only one mock
This is what I came up with:
public function testAppCallback()
{
$user = Auth::user();
$connectionFactory = $this->createMock(OAuthPackage::class);
$connectionFactory->method('getConnection')->willReturn(new DummyConenection());
$this->disableCookieEncryption();
$response = $this->withCookies([
'cookie' => 'http://localhost',
])->get('/oauth?code=my_auth_code');
}
The DummyClass just inherits from the real class, but this way I can test its type in the debugger. Turns out that the DummyClass is not being used.
It seems like Laravel boots up a whole new instance as soon as I make a web request and therefore forgets all about the DummyClass.
How should I go about to solve this problem?

How to use Oauth2 Provider instead of Oauth1 in Laravel Socialite?

I'm currently developing social listening API with twitter, the flow is logged in user (using basic API Token to send request to the API) are redirected to twitter app and log in to twitter account through redirect link provided by Laravel Socialite. After successfully login the account info will be saved in MySQL along with user who registered the account.
I'm using Laravel 7 with Socialite v5.4.0
i've beend trying to provide query parameters in the callback url using :
Socialite::driver('twitter')
->redirectUrl('https://some-site-url.com/twitter/login/callback?api_token={sample-token}')
->redirect()->getTargetUrl());
but Socialite return error Call to undefined method Laravel\\Socialite\\One\\TwitterProvider::redirectUrl()",
thats when i realized that the currently used twitter provider is using Oauth1 Laravel\Socialite\One\TwitterProvider. when i look at the vendor in Laravel\Socialite\\SocialiteManager.php its creating instance of this :
/**
* Create an instance of the specified driver.
*
* #return \Laravel\Socialite\One\AbstractProvider
*/
protected function createTwitterDriver()
{
$config = $this->config->get('services.twitter');
return new TwitterProvider(
$this->container->make('request'), new TwitterServer($this->formatConfig($config))
);
}
but in the same Laravel\Socialite\\SocialiteManager.php its also have method to create Oauth2 instance like below :
/**
* Create an instance of the specified driver.
*
* #return \Laravel\Socialite\Two\AbstractProvider
*/
protected function createTwitterOAuth2Driver()
{
$config = $this->config->get('services.twitter');
return $this->buildProvider(
TwitterOAuth2Provider::class, $config
);
}
Questions
now my question is how to force the Socialite::driver('twitter') method to use Oauth2 instead of Oauth1 which basically is available in the Socialite itself ? i have tried to override the method but found no link to which instance is calling SocialiteManager.php, so currently i have only tried to modify the vendor function to return Oauth2 AbstractProvider (which i know its really ugly approach but i feel really curious), its like this :
// /**
// * Create an instance of the specified driver.
// *
// * #return \Laravel\Socialite\One\AbstractProvider
// */
// protected function createTwitterDriver()
// {
// $config = $this->config->get('services.twitter');
// return new TwitterProvider(
// $this->container->make('request'), new TwitterServer($this->formatConfig($config))
// );
// }
/**
* Create an instance of the specified driver.
*
* #return \Laravel\Socialite\Two\AbstractProvider
*/
protected function createTwitterDriver()
{
$config = $this->config->get('services.twitter');
return $this->buildProvider(
TwitterOAuth2Provider::class, $config
);
}
.the method works and its return the redirect url successfully but failed to logged in to twitter page for unknown reason, which make me think is there a way to cleanly switch Socialite provider version between Oauth1 and Oauth2. Or is there any alternative to provide a callback with user identifier instead ?
the url return after i ditch the method in vendor SocialiteManager.php
But failed to log in to twitter app for unknown reason
thanks in advance, it's my first question and i've been looking for the answer since yesterday but found no specific way to switch Socialite provider between version 1 and version 2
Laravel\Socialite\SocialiteManager.php looks for an oauth key in your twitter credentials to determine whether to use OAuth1 or Oauth2
/**
* Create an instance of the specified driver.
*
* #return \Laravel\Socialite\One\AbstractProvider
*/
protected function createTwitterDriver()
{
$config = $this->config->get('services.twitter');
if (($config['oauth'] ?? null) === 2) {
return $this->createTwitterOAuth2Driver();
}
return new TwitterProvider(
$this->container->make('request'), new TwitterServer($this->formatConfig($config))
);
}
Simply add an oauth key with a value of 2 to your Twitter credentials in the config/services.php file.
'twitter' => [
'client_id' => env('TWITTER_CLIENT_ID'),
'client_secret' => env('TWITTER_CLIENT_SECRET'),
'redirect' => env('TWITTER_REDIRECT_URL'),
'oauth' => 2
],
If you are switching from oauth1 do not forget to update your credentials to use OAuth 2.0 Client ID and Client Secret which you can get from your Twitter developer account.

yii2: problems using google/apiclient for oAuth and token authentication

My project is built on Yii2 and uses google/apiclient for the purposes of login to the web interface. There's also an android app which connects to the API and uses bearer authentication against Google tokens (which I believe pulled in firebase/jwt). This has worked fine since early 2018 until the week commencing 10th September 2018. No code was changed in my system.
Since then, attempting to login to the web interface (oAuth) gives
yii\authclient\InvalidResponseException: Request failed with code: 400, message: {
"error" : "redirect_uri_mismatch",
"error_description" : "Bad Request"
The site is correctly listed in the Google developer console (where I've also not changed anything) so the redirect_uri_mismatch is not expected.
Upgrading google/apiclient allows me to login to the web interface but breaks the app's token auth, giving:
Your request was made with invalid credentials
I can provide the full stack trace if required, however, I'm hoping someone else has encountered the same and can point me in the right direction. Using firebase/jwt v4 in the live system allows token auth to function but using v4 in test with the upgraded google/apiclient fails the auth with the same credentials error as above.
Can anyone provide any guidance please?
To get refresh token on first login you need to set "?access_type=offline" in "authUrl". And then save it somewhere, in database, files etc.
If you have no database or if you ok with prompting access rights every time on login in you can add "&approval_prompt=force" to same URL. Thats will enforce Google send you refresh token every time you are logging in, but also enforces showing access rights prompt every time.
class MyGoogleClient extends yii\authclient\clients\Google
{
/**
* Set to true if you want to get refresh token always on login
* #var type
*/
public $enforceRefreshToken = false;
/**
* {#inheritdoc}
*/
public function init()
{
parent::init();
if (is_array($this->scope)) {
$this->scope = implode(' ', $this->scope);
}
$additionalParams = [];
if ($this->autoRefreshAccessToken) {
$additionalParams['access_type'] = 'offline';
}
if ($this->enforceRefreshToken) {
$additionalParams['approval_prompt'] = 'force';
}
if (!empty($additionalParams)) {
$this->authUrl = $this->composeUrl($this->authUrl, $additionalParams);
}
}
/**
* {#inheritdoc}
* #return string return URL.
*/
protected function defaultReturnUrl()
{
return Yii::$app->getUrlManager()->createAbsoluteUrl([Yii::$app->controller->getRoute(), 'authclient' => 'google']);
}
}

Laravel broadcasting auth route simply returns "true"

I have my pusher key set and initialized within Laravel 5.3. When I test it on my local environment, it works. When I try to run the exact same code on our production environment, I get this error:
Pusher : Error : {"type":"WebSocketError","error":{"type":"PusherError","data":{"code":null,"message":"Auth info required to subscribe to private-App.User.16"}}}
I've confirmed the Pusher key is identical on both my local and production.
The WS initializes on both environments the same:
wss://ws.pusherapp.com/app/264P9d412196d622od64d?protocol=7&client=js&version=4.1.0&flash=false
The only difference that I can see, is that when our production server contacts the Laravel "broadcasting/auth" route, it simply receives true in the response body.
When my local contacts "broadcasting/auth" it gets this in the response:
{auth: "22459d41299d6228d64d:df5d393fe37df0k3832fa5556098307f145d7e483c07974d8e7b2609200483f8"}
Within my BroadcastServiceProvider.php:
public function boot()
{
Broadcast::routes();
// Authenticate the user's personal channel.
Broadcast::channel('App.User.*', function (User $user, $user_id) {
return (int)$user->id === (int)$user_id;
});
}
What could cause the broadcast/auth route to return simply true instead of the expected auth?
If you check PusherBroadcaster.php file, you will see that the response can be "mixed".
I think the documentation is saying about the default broadcast only.
The channel method accepts two arguments: the name of the channel and
a callback which returns true or false indicating whether the user is
authorized to listen on the channel.
This is the validAuthenticationResponse method inside PusherBroadcast.
/**
* Return the valid authentication response.
*
* #param \Illuminate\Http\Request $request
* #param mixed $result
* #return mixed
*/
public function validAuthenticationResponse($request, $result)
{
if (Str::startsWith($request->channel_name, 'private')) {
return $this->decodePusherResponse(
$this->pusher->socket_auth($request->channel_name, $request->socket_id)
);
}
return $this->decodePusherResponse(
$this->pusher->presence_auth(
$request->channel_name, $request->socket_id, $request->user()->getAuthIdentifier(), $result)
);
}
Just to give you another example, this is inside RedisBroadcast.
if (is_bool($result)) {
return json_encode($result);
}
Short explanation about this "auth request":
BroadcastManager instantiate all "available drivers" (Pusher, Redis, Log,etc) , and create the "auth" route (using BroadcastController + authenticate method).
When you call "auth", this will happen:
Call "broadc.../auth" route.
BroadcastManager will instantiate the proper driver (in your case Pusher)
PusherBroadcaster can throw an exception AccessDeniedHttpException if the user is not authenticated (the "user session" - Auth::user() is not defined/null) and is trying to access a private (or presence) channel type.
If the user is trying to access a private/presence channel and the user is authenticated (Auth::check()), Laravel will check if the auth. user can access the channel. (Check: verifyUserCanAccessChannel method).
After that, validAuthenticationResponse method will be called. This method will make a request to pusher with the user credentials and return an array. This array contains Pusher response (socket auth: https://github.com/pusher/pusher-http-php/blob/03d3417748fc70a889c97271e25e282ff1ff0ae3/src/Pusher.php#L586 / Presence Auth: https://github.com/pusher/pusher-http-php/blob/03d3417748fc70a889c97271e25e282ff1ff0ae3/src/Pusher.php#L615) which is a string.
Short answer:
Soo.. Pusher require this auth response. Otherwise you won't be able to connect/identify the user (wss://ws.pusherapp.com....).
Edit This is from the version 5.5 docs, not applicable here.
I think the issue maybe with using the '*' wildcard in the channel's name.
I use the following in both local and production:
Broadcast::channel("servers.{id}", function (Authenticatable $user, $id) {
return (int)$user->getAuthIdentifier() === (int)$id;
});
The problem happens because you did not set the proper BROADCAST_DRIVER in your production .env file (which is redis by default).
# before
BROADCAST_DRIVER=redis
# after
BROADCAST_DRIVER=pusher

Call a method from another class in another application with FOSTRestBundle in Symfony

I have the class file ex: Stats.php when i want to give an array with information from another method in another application and class file ex: Information.php.
File Stats.php
public function getStats()
{
$myInformations = // here i want to get information from Information.php
.
.
.
return $myInformations;
}
In different application.
File Informations.php
/**
* Get all locales
* #FOS\View()
* #FOS\Get("/locales")
* #param ParamFetcherInterface $paramFetcher
* #return mixed
*/
public function getLocales(ParamFetcherInterface $paramFetcher)
{
.
.
.
return $locales;
}
How I call function getLocales from url: http://myhost.com/api/locales in function getStatus()?
When you are using the GuzzleBundle as mentioned in your comment. You can just inject a client into the class containing getStats() or by making it ContainerAware and retrieving the guzzle-client by its service id from the container. If you don't have to set default options for your client, i.e. you just want to access a url you assume is always available for all environments from all places you could just create a new client with default values:
$guzzleClient = new GuzzleHttp\Client();
Making a request with the client is described in the guzzle docs in the Quickstart in section Sending Requests:
$response = $guzzleClient->get('http://myhost.com/api/locales')
When the request was successful you can retrieve the locales by calling:
$content = $response->getBody()->getContent();
Either casting getBody() to string or using getContent() is important here. Again refer to Guzzle's documentation particularly the section on using responses.
For example if you send a json-encoded string you can do something like:
$encodedLocales = $response->getBody()->getContent();
$decodedLocales = json_decode($encodedLocales, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(json_last_error_msg());
}
There are many things that should be said about this approach such as it's fragile because it relies on a working network connection possibly to a different server, it's not easily testable, transfer exceptions are not handled gracefully, etc. pp. but for a simple proof of concept or asa starting point this should suffice.

Categories