Feature testing authentication with hidden property - php

How can I to test user authentication? My test:
public function successLogin()
{
$user = User::factory()->create(['login' => '123']);
$user->makeVisible(['password']);
$this->post(route('login'), $user->toArray());
$this->assertAuthenticated(); // not asserts
}
I saw this decision
$this->User->makeVisible(['password']);
But what is this proterty? Model? It is undefined.

The decision was change post request of auth to attempt method:
public function successLogin()
{
$user = User::factory()->create(['password' => \Hash::make('pass')]);
\Auth::attempt(['login' => $user->login, 'password' => 'pass']);
$this->assertAuthenticated();
}
public function failedLogin()
{
User::factory()->create(['login' => 'username1', 'password' => \Hash::make('pass')]);
\Auth::attempt(['login' => 'username2', 'password' => 'pass']);
$this->assertGuest();
}

Related

cakephp authenticate with ldap and match to local user (or create one)

I'm trying to configure ldap authentication on cakephp 3.8 using the new cakephp/authentication plugin, and I'm not sure how to match the authenticated ldap user with a local entity.
My config closely follows the documentation and is available in full here.
in my Application.php the Application class implements both the AuthenticationServiceProviderInterface and the AuthorizationServiceProviderInterface
public function getAuthenticationService(ServerRequestInterface $request,
ResponseInterface $response)
{
$service = new AuthenticationService();
$service->loadIdentifier('Authentication.Password', [...]),
$service->loadIdentifier('Authentication.Ldap', [
'fields' => [
'username' => 'username',
'password' => 'password'
],
'host' => 'ldap.forumsys.com',
'port' => '389',
'bindDN' => function($username) {
return 'uid='.$username.',DC=example,DC=com';
},
'options' => [LDAP_OPT_PROTOCOL_VERSION => 3]
]);
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password'
],
'loginUrl' => '/users/login'
]);
return $service;
}
In my middleware, I'm trying decorate the identity with authorization stuff. When authenticating using the regular local system the $identity is a App\Model\Entity\User, but when logging in with a ldap user it's a Authentication\Identity
So when I call setAuthorization
'identityDecorator' => function (AuthorizationServiceInterface $authorization,
ArrayAccess $identity) {
$identity->setAuthorization($authorization);
}
it fails with a Call to undefined method Authentication\Identity::setAuthorization() since all I have in $identity is
object(Authentication\Identity) {
'config' => [
'fieldMap' => [
'id' => 'id'
]
],
'data' => object(ArrayObject) {
username => 'einstein'
}
}
How would I match an authenticated ldap user with their local counterpart, and transform from Authentication\Identity to App\Model\Entity\User?
The final goal is to also optionally generate local users from ldap data if they don't exist.
middleware attempt
Application.php
public function middleware($middlewareQueue)
{
...
$middlewareQueue->add($authentication);
$middlewareQueue->add($ldap_matcher);
$middlewareQueue->add($authorization);
return $middlewareQueue;
}
LdapMatcherMiddleware.php
class LdapMatcherMiddleware
{
public function __invoke(ServerRequestInterface $request,
ResponseInterface $response, $next)
{
$identity = $request->getAttribute('identity');
if ($identity !== null) {
$identity = $this->buildIdentity($identity);
$request = $request->withAttribute('identity', $identity);
}
$response = $next($request, $response);
return $response;
}
public function buildIdentity($identity)
{
$Users = TableRegistry::getTableLocator()->get('Users');
$username = $identity->getOriginalData()['username'];
$user = $Users->find()->where(['username' => $username])->first();
if (is_null($identity)) {
$user = $this->createLocalUserFromLdap($identity);
}
return $user;
}
public function createLocalUserFromLdap($identity)
{
$Users = TableRegistry::getTableLocator()->get('Users');
$user = $Users->newEntity([
'username' => $identity->getOriginalData()['username']
]);
$Users->save($user);
return $user;
}
}
How would I match an authenticated ldap user with their local counterpart, and transform from Authentication\Identity to App\Model\Entity\User?
I would add another middleware after the authentication middleware and do that step there.
$users = TableRegistry::getTableLocator()->get('Users');
$entity = $users->newEntity($identity->getOriginalData());
Then do whatever you need to do for authorization with this entity.
The final goal is to also optionally generate local users from ldap data if they don't exist.
Just implement your logic somewhere and get the entity the same way as shown above from the identity.
$users = TableRegistry::getTableLocator()->get('Users');
$entity = $users->newEntity($identity->getOriginalData());
// Check if it exists inside this method and if not just create it
$users->createLocalUserFromLDAP($entity);

How to mock user creation if password is hidden in Laravel 5.5 unit tests

I have a unit acceptance test where I am mocking the creation of a user.
class UserAcceptanceApiTest extends TestCase
{
use WithoutMiddleware;
public function setUp()
{
parent::setUp();
$this->User = factory(App\Models\User::class)->make([
'id' => '999',
'name' => 'Name',
'email' => 'test#example.com',
'password' => bcrypt('password'),
]);
$this->User = factory(App\Models\User::class)->make([
'id' => '999',
'name' => 'Name',
'email' => 'test#example.com',
'password' => bcrypt('password'),
]);
$user = factory(App\Models\User::class)->make();
$this->actor = $this->actingAs($user);
}
public function testStore()
{
$response = $this->actor->call('POST', 'api/users', $this->User->toArray());
$this->assertEquals(200, $response->getStatusCode());
$this->seeJson(['id' => 999]);
}
}
I get the following exception "Field 'password' doesn't have a default value.
This is because in my User model I have the following:
protected $hidden = ['password', 'remember_token'];
So it automatically removes the password field from the JSON.
Is there a way I can override this only for this test? As I want to keep the password as a hidden attribute.
public function testStore()
{
$this->User->makeVisible(['password']);
$response = $this->actor->call('POST', 'api/users', $this->User->toArray());
$this->assertEquals(200, $response->getStatusCode());
$this->seeJson(['id' => 999]);
}

Laravel 5.3: UnitTest on API - multiple function lead to error

Hope someone can help me out here. I bootstrapped a Laravel 5.3 application and I'm writing JUnitTest in order to verifiy my API.
However, as long as I create only one function in the Test Class everything works fine:
class MobileAppTest extends TestCase {
function __construct()
{
parent::setUp();
}
public function testUserLogin()
{
$this->json('POST', '/mobile/auth', ['username' => 'empty', 'password' => 'demo'])
->seeJson([
'success' => true
]);
$this->json('POST', '/mobile/auth', ['username' => 'wrongUser', 'password' => 'demo'])
->seeJson([
'success' => false,
'state' => 2
]);
}}
When I start to add another function like this:
class MobileAppTest extends TestCase{
function __construct()
{
parent::setUp();
}
public function testUserLogin()
{
$this->json('POST', '/mobile/auth', ['username' => 'empty', 'password' => 'demo'])
->seeJson([
'success' => true
]);
$this->json('POST', '/mobile/auth', ['username' => 'wrongUser', 'password' => 'demo'])
->seeJson([
'success' => false,
'state' => 2
]);
}
public function testSecond()
{
}}
Suddenly the parameters of the HTTP POST call become empty. I have no idea why this is happening. On the server-log there is the following message poping up:
[2017-05-10 11:09:15] testing.INFO: array (
)
[2017-05-10 11:09:15] testing.ERROR: ErrorException: Undefined index: username
in app/Http/Controllers/Controller.php:36
But on the second call the parameters are present:
[2017-05-10 11:09:15] testing.INFO: array (
'username' => 'wrongUser',
'password' => 'demo',
)
Thanks for helping me out here.

Laravel Passport password grant returns Invalid Credentials Exception

I am trying to setup a SPA that consumes a Laravel API protected with Passport.
I started by creating a new Laravel app specifically for this and I then followed the instructions for setting up passport and set up a password grant client.
I can successfully create a new user, save the user to the database, and log the user in. After that, I try to use the newly created user's information along with the password grant clients id and secret to create an access token. At this point I receive the exception.
I read through the log and I saw where the exception was being thrown. Inside League\OAuth2\Server\Grant\PasswordGrant the validateUser method has the following:
if ($user instanceof UserEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidCredentials();
}
Seeing this I implemented the UserEntityInterface on my user model and implemented the getIdentifier method but I still receive the Exception. I'm really not too sure where to go from here, any help would be greatly appreciated. Below is some of my code.
Here is my Registration controller:
class RegisterController extends Controller
{
private $tokenService;
public function __construct(AccessTokenService $tokenService)
{
//$this->middleware('guest');
$this->tokenService = $tokenService;
}
public function register(Request $request)
{
$this->validateWith($this->validator($request->all()));
Log::debug('Validated');
$user = $this->create($request->all());
$this->guard()->login($user);
$this->tokenService->boot(Auth::user());
return response()->json($this->tokenService->getNewAccessToken(), 200);
}
protected function guard()
{
return Auth::guard();
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
'password_confirmation' => 'required'
]);
}
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
And these are the relevant portions of AccessTokenService:
public function getNewAccessToken() {
$http = new Client();
$client = \Laravel\Passport\Client::where('id', 6)->first();
Log::debug($client->getAttribute('secret'));
Log::debug($this->user->getAttribute('email'));
Log::debug($this->user->getAuthPassword());
$response = $http->post('homestead.app/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 6,
'client_secret' => $client->getAttribute('secret'),
'username' => $this->user->getAttribute('email'),
'password' => $this->user->getAuthPassword(),
'scope' => '*'
]]);
unset($client);
$status = $response->getStatusCode();
$body = $response->getBody();
Log::debug($body->getContents());
Log::debug($status);
switch($status)
{
case 200:case 201:
case 202:
$tokens = array(
"user_id" => $this->user->getAttribute('id'),
"access_token" => $body['access_token'],
"refresh_token" => $body['refresh_token']
);
$output = ["access_token" => $this->storeTokens($tokens), 'status_code' => $status];
break;
default:
$output = ["access_token" => '', 'status_code' => $status];
break;
}
return $output;
}
private function storeTokens(array $tokens) {
UserToken::create([
"user_id" => $tokens['user_id'],
"access_token" => bcrypt($tokens['access_token']),
"refresh_token" => bcrypt($tokens['refresh_token'])
]);
return $tokens['access_token'];
}
So I figured out the issue. When I was requesting the access token I was passing in the user's email and password but I was passing the hashed password when I needed to pass in the unhashed password.
My request for an access token looked like this:
$response = $http->post('homestead.app/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 6,
'client_secret' => $client->getAttribute('secret'),
'username' => $this->user->getAttribute('email'),
'password' => $this->user->getAuthPassword(), //Here is the problem
'scope' => '*'
]]);
By passing the Request to the function using the unhashed password like this solved the problem:
$response = $http->post('homestead.app/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 6,
'client_secret' => $client->getAttribute('secret'),
'username' => $request['email'],
'password' => $request['password'],
'scope' => '*'
]]);
In my case it was magic
when i changed username from email format to simple (alphanumeric only) format it works.
please tell the reason if anyone have for my case.
Thanks

Laravel Auth attempt failing

I really try to debug my issues on my own before I bring them here, but I seriously cannot find a solution to my laravel auth problem, though it seems to be a common issue.
My authentication will not login. It always returns false and I don't understand why.
I've read through some other questions here, and their solutions haven't solved my particular situation.
My User model implements UserInterface and Remindable Interface.
My password is hashed upon creating it to the database.
My password field in my database is varchar 100, which should be more than enough to hash the password.
The user I'm logging is has been created and activated in the database.
Thank you so much for any insight.
User Model
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
protected $fillable = array('email', 'username', 'password', 'password_temp', 'code', 'active');
public $timestamps = false;
protected $softDelete = false;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'Users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = 'password';
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
}
Account Controller
class AccountController extends BaseController {
public function getLogin() {
return View::make('account.login');
}
public function postLogin() {
$validator = Validator::make(Input::all(),
array(
'email' => 'required',
'password' => 'required'
)
);
if($validator->fails()) {
return Redirect::route('login')
->withErrors($validator);
} else {
$auth = Auth::attempt(array(
'email' => Input::get('email'),
'password' => Input::get('password'),
'active' => 1
));
if($auth) {
return Redirect::route('Create-Account');
}
}
return Redirect::route('login')
->with('global', 'There was a problem logging you in. Please check your credentials and try again.');
}
public function getCreate() {
return View::make('account.create');
}
public function getviewReturn() {
return View::make('account.return');
}
public function postCreate() {
$validator = Validator::make(Input::all(),
array(
'email' => 'required|max:50|email|unique:Users',
'username' => 'required|max:15|min:4|unique:Users',
'password' => 'required|min:6',
'password2' => 'required|same:password'
)
);
if ($validator->fails()) {
return Redirect::route('Post-Create-Account')
->withErrors($validator)
->withInput();
}
else {
$email = Input::get('email');
$username = Input::get('username');
$password = Input::get('email');
$code = str_random(60);
$user = User::create(array(
'email' => $email,
'username' => $username,
'password' => Hash::make($password),
'code' => $code,
'active' => 0));
});
return Redirect::to('account/return')
Routes
Route::group(array('before' => 'guest'), function() {
Route::group(array('before' => 'csrf'), function() {
Route::post('/account/create', array(
'as' => 'Post-Create-Account',
'uses' => 'AccountController#postCreate'
));
Route::post('/account/login', array(
'as' => 'postlogin',
'uses' => 'AccountController#postLogin'
));
});
Route::get('/account/login', array(
'as' => 'login',
'uses' => 'AccountController#getLogin'
));
Route::get('/account/create', array(
'as' => 'Create-Account',
'uses' => 'AccountController#getCreate'
));
Route::get('/account/activate/{code}', array(
'as' => 'Activate-Account',
'uses' => 'AccountController#getActivate'
When creating the user you've done
$password = Input::get('email');
It should be
$password = Input::get('password');
so if you try and login with the "email" as the password - it will work! :)
So if you change this
else {
$email = Input::get('email');
$username = Input::get('username');
$password = Input::get('email');
$code = str_random(60);
$user = User::create(array(
'email' => $email,
'username' => $username,
'password' => Hash::make($password),
'code' => $code,
'active' => 0));
});
to this
else {
$user = User::create(array(
'email' => Input::get('email'),
'username' => Input::get('username'),
'password' => Hash::make(Input::get('password');),
'code' => str_random(60),
'active' => 0));
});
that cleans up your code and fixes the issue.
Your code looks right to me, so you have to check some things:
1) A manual attempt works for you?
dd( Auth::attempt(['email' => 'youremail', 'password' => 'passw0rt']) );
2) The user hash checks manually?
$user = User::find(1);
var_dump( Hash::check($user->password, 'passw0rt') );
dd( Hash::check($user->password, Input::get('password')) );
Try to add primaryKey field in your user model. It should be something like that:
protected $primaryKey = 'user_id';
I think Apache version problem. You need to update Apache2.4.

Categories