FosUserBundle integration with FosRestBundle - php

Does anyone have an example or any idea how one would implement the FOSRestBundle together along with the FOSUserBundle. I have a Web app already developed with Symfony 2 and the FOSUserBundle, but I would like to add the FOSRestBundle for an api layer. I want to be able to pass it a username and password and receive some type of token from the FOSUserBundle that represents the logged in user that I can then pass and forth between other api calls. Does anyone know of a good way to do this?

FOSUserBundle should be natively "restful" meaning that it may follow the REST recommandations.
However, it is not designed to work natively with FOSRestBundle, the simplest way to do that is to override the UsersController in your Bundle and adapt your actions.
For example, to allow RESTFul registration, you may write the following action:
public function postUsersAction()
{
$form = $this->container->get('fos_user.registration.form');
$formHandler = $this->container->get('fos_user.registration.form.handler');
$confirmationEnabled = $this->container->getParameter('fos_user.registration.confirmation.enabled');
$process = $formHandler->process($confirmationEnabled);
if ($process) {
$user = $form->getData();
$authUser = false;
if ($confirmationEnabled) {
} else {
$authUser = true;
}
$response = new Response();
if ($authUser) {
/* #todo Implement authentication */
//$this->authenticateUser($user, $response);
}
$response->setStatusCode(Codes::HTTP_CREATED);
$response->headers->set(
'Location',
$this->generateUrl(
'api_users_get_user',
array('user' => $user->getId()),
true
)
);
return $response;
}
return RestView::create($form, Codes::HTTP_BAD_REQUEST);
}

Related

Authentication with Doctrine in Zend Framework 2 Good Practices

Lets say that my page has 10 sections, in 6 of them I have to check if the user is logged in, and if not, redirect him to the "login/register" page.
I found myself repeating this code in the controller of those 6 pages:
public function actionthatneedsauthAction()
{
$sl = $this->getServiceLocator();
$authService = $sl->get('doctrine.authenticationservice.orm_default');
$user = $authService->getStorage()->read(); //is the user logged in?
if ($user) { //auth successful
//-------------/*CODE FOR THIS SPECIFIC CONTROLLER GOES HERE*/--------
return new ViewModel(array(
'user' => $user,
'somethingelse' => $somethingelse
));
} else { //auth denied
return $this->redirect()->toRoute(user, array('action' => 'login'));
}
}
I tried to encapsulate that into a service, called islogged (this is a model, not a controller), but I couldn't make it work because I couldn't find a way to redirect to a controller from inside a model, I only know how to redirect to a controller via another controller.
So in my usermanager.php I had a function like this one:
public function islogged()
{
$sl = $this->getServiceLocator();
$authService = $sl->get('doctrine.authenticationservice.orm_default');
$user = $authService->getStorage()->read(); //is the user logged in?
if ($user) { //auth successful
return $user;
} else {
/*
redirect to the login screen, dont know how to do it,
this code doesnt work here:
return $this->redirect()->toRoute(NULL, array(
'controller' => 'user',
'action' => 'login'
));
*/
}
}
so the idea was that in my controllers I only had to write:
$user = islogged();
and all the code repetition I mentioned won't be necessary anymore.
Is it a good practice what I tried to do with the usermanager.php islogged function?
If it is a good practice, how am I supposed to redirect to a controller from inside a model?
If is not a good practice, which will be the way to avoid all that code repetition that I'm having in my controllers?
I know that I can put the authentication step into the onboostrap() but in that case the auth will be triggered for all of my pages, and I just want it in some of them.
I would advise you to implement Doctrine Authentication with official DoctrineModule Authentication described in the docs folder of the repo.
Read this - Link to DoctrineModule Authentication
Then you can handle your Authentication check via the zf2 own Controller and View Helpers identity. See example in the docs here.
I use this ACL Module on my apps: https://github.com/ZF-Commons/zfc-rbac
My Customer overview controller then looks as so:
<?php
namespace RoleBasedCustomer\Controller;
use RoleBasedUser\Service\AuthenticationService;
use RoleBasedUser\Service\UserService;
use RoleBasedUser\Controller\AbstractMultiModelController;
class OverviewController extends AbstractMultiModelController
{
public function __construct(
AuthenticationService $authService,
UserService $userService
) {
$this->authService = $authService;
$this->userService = $userService;
}
public function indexAction()
{
if ( ! $this->authService->hasIdentity() ) {
return $this->redirect()->toRoute('customer/login');
}
}
}
The only thing i had to do is replace these two lines:
$authService = $this->getServiceLocator()
->get('doctrine.authenticationservice.orm_default');
$user = $authService->getStorage()->read(); //is the user logged in?
with this one:
$user = $this->identity();

iOS app communicate with Laravel webapp

I’m working on a project based on an iOS app (native) who use a webapp (Laravel framework) to communicate.
For exemple, ios user should use Laravel login to use the application.
The laravel part of the project is done and work good on a computer (login,register etc…)
But now i’m thinking how will i communicate with my futur ios App and my webapp using laravel framework. I dont know any ways to do that, maybe i need a special framwork on my ios app ?
I have no idea, can you help me ?
Thanks in advance
This is a loaded question.... My personal preference is to set up a set of API controllers so you can control them independently and version them.
1) Create a sub-set of controllers # /app/controllers/api/v1
2) Give them all a namespace of api/v1
<?php namespace api\v1;
3) Import whatever classes you need into the new namespace
<?php namespace api\v1;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response;
use Usage;
use Auth;
4) Install an oAuth2 package
5) Set up routes that generate and validate tokens and place your protected routes in a route group. (my example below.)
Route::group(['prefix' => 'api/v1', 'before' => 'apiErrors'], function()
{
Route::post('accessToken', function()
{
return AuthorizationServer::performAccessTokenFlow();
});
Route::group(['before' => 'oauth|setUser'], function()
{
Route::resource('usages', 'api\v1\UsagesController');
Route::resource('connections', 'api\v1\ConnectionsController');
Route::resource('users', 'api\v1\UsersController');
});
});
6) Set up your new api controllers to return data in a manner that a mobile app can use (JSON)
public function index()
{
$usages = Usage::with('device.model.manufacturer')
->where('user_id', Auth::user()->id)
->get();
return Response::json($usages, $this->responseCode, $this->accessControl);
}
thanks for your complete answer ! but i have done an simple API controller without oAuth2 package. My controller for the moment just return true or false if login is okay and it works good. here my code for other people...
public function trylogin() {
if (Auth::attempt(array('email'=>Input::get('email'), 'password'=>Input::get('password'))) || Auth::attempt(array('username'=>Input::get('username'), 'password'=>Input::get('password')))) {
return Response::json(array('status' => 'OK'));
} else {
return Response::json(array('status' => 'FAIL'));
}
}
here my api routes
Route::resource('api/v1', 'ApiController');
Route::get('api/v1_hello', 'ApiController#sayhello');
Route::get('api/v1_login', 'ApiController#trylogin')
what do you think about security managment ? i can make my own token system validation on ios ?
EDIT
i finally found a solution, here the function in my ApiController :
you just need to send to your api from iOS the token generate by Facebook or Google connexion. and in my case add a network parameter.
public function registerOrLoginFromSocialNetWorkV1(){
if (Input::get('email') && Input::get('sn_id')) {
//sn = social network
if (User::where('email','=', Input::get('email'))->count() != 0) {
$user = User::where('email','=', Input::get('email'))->first();
$user->fb_id = Input::get('sn_id');
$user->save();
//return 'email already used';
}
else{
if (User::where('fb_id','=', Input::get('sn_id'))->count() == 0) {
$user = new User;
$user->firstname = Input::get('firstname');
$user->lastname = Input::get('lastname');
$user->username = Input::get('username');
$user->email = Input::get('email');
$user->fb_id = Input::get('sn_id');
$user->fk_role = 3;
$user->yearofbirth = Input::get('birthday');
//$user->yearofbirth = substr($me['birthday'],6,9);
if (Input::get('sex') == 'male') {
$user->sex = 1;
}
else{
$user->sex = 0;
}
$user->save();
Userslog::log('api_register_social_network');
}
else{
$user = User::where('fb_id','=', Input::get('sn_id'))->first();
if (!$user->yearofbirth){
$user->yearofbirth = Input::get('birthday');
$user->save();
}
}
}
//dd($user);
Auth::login($user);
$follows = Follow::where('user_id','=',$user->id)->get();
return Response::json(array('user' => $user,'follows' => $follows));
}
else{
return 'error';
}
}

Symfony2 Deny User Login Based on Custom Status

I've followed the guide for implementing authentication/authorization and I can login.
I have one main difference though from what's in the guide. Instead of an isActive property I have a status table in my database.
I'm at a loss as to how I would deny/accept logins based on the values in the status table rather than the isActive property referenced in the guide.
I'm not sure what code to post because it works as it does in the guide and I'm pretty sure the Symfony security system handles all the authentication stuff where I can't see it.
Even if you just point me in the right direction I would be grateful.
Edit:
Using ChadSikorra's advice I came up with this code to implement the AdvancedUserInterface functions:
public function isAccountNonExpired()
{
$status = $this->getTblStatus()->getStatustext();
switch ($status){
case "expired":
return false;
default:
return true;
}
}
public function isAccountNonLocked()
{
$status = $this->getTblStatus()->getStatustext();
switch ($status){
case "locked":
return false;
case "suspended":
return false;
case "registered":
return false;
default:
return true;
}
}
public function isCredentialsNonExpired()
{
return $this->pwdexpired;
}
public function isEnabled()
{
$status = $this->getTblStatus()->getStatustext();
if($status != 'active')
return false
else
return true;
}
The next question I have then is how do I handle the exceptions that are thrown when a user has one of the statuses?
Based on what I have so far I think this is doable by catching the errors in the loginAction. What I don't know how to do is identify the errors, but I'll keep digging.
/**
* #Route("/Login", name="wx_exchange_login")
* #Template("WXExchangeBundle:User:login.html.twig")
* User login - Open to public
* Authenticates users to the system
*/
public function loginAction(Request $request)
{
$session = $request->getSession();
if ($this->get('security.context')->isGranted('IS_AUTHENTICATED_REMEMBERED'))
{
// redirect authenticated users to homepage
return $this->redirect($this->generateUrl('wx_exchange_default_index'));
}
// get the login error if there is one
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(
SecurityContext::AUTHENTICATION_ERROR
);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
if($error instanceof LockedException)
{
}
return $this->render(
'WXExchangeBundle:User:login.html.twig',
array(
// last username entered by the user
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
)
);
}
I am now able to check for the type of Exception, but I'm at a loss as to how to get the specific status so that I can redirect to the correct place. This is the last piece of the puzzle.
You could add mapping to your custom status table on the user entity, like so:
/**
* #ORM\OneToOne(targetEntity="AccountStatus")
* #ORM\JoinColumn(name="status_id", referencedColumnName="id", nullable=true)
*/
private $accountStatus;
This would also require creating an entity describing the custom status table. Then you could use this mapping in your user entity by implementing Symfony\Component\Security\Core\User\AdvancedUserInterface as referenced in the guide you linked. Then implement the isEnabled function something like this...
public function isEnabled()
{
return $this->getAccountStatus()->getIsActive(); /* Or whatever you named it */
}
EDIT:
Based on the API Doc for AdvancedUserInterface, if you want to do custom logic for handling the different statuses you'll need to register an exception listener...
If you need to perform custom logic for any of these situations, then
you will need to register an exception listener and watch for the
specific exception instances thrown in each case. All exceptions are a
subclass of AccountStatusException
There's a pretty good Cookbook article for creating something like this here. The basic process in this instance would be to create the class for the listener...
src/Acme/DemoBundle/EventListener/AcmeExceptionListener.php
namespace Acme\DemoBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\DisabledException;
use Symfony\Component\Security\Core\Exception\LockedException;
class AcmeExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof DisabledException) {
// Customize your response object to display the exception details
$response = new Response();
$response->setContent('<html><body><h1>Custom disabled page!</h1></body></html>');
// Send the modified response object to the event
$event->setResponse($response);
}
elseif ($exception instanceof LockedException) {
// Or render a custom template as a subrequest instead...
$kernel = $event->getKernel();
$response = $kernel->forward('AcmeDemoBundle:AccountStatus:locked', array(
'exception' => $exception,
));
$event->setResponse($response);
}
// ... and so on
}
}
The above are just basic examples, but it gives you the gist anyway. Technically I guess you could also make custom exceptions by extending AccountStatusException and then throw them in your logic for your AdvancedUserInterface implementation. Then you would know exactly which status you are catching. Anyway, then make sure to register the listener as a service.
app/config/config.yml
services:
kernel.listener.your_listener_name:
class: Acme\DemoBundle\EventListener\AcmeExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
Another way to go about this would be to implement some sort of a custom User Checker. See this question: Symfony2 custom user checker based on accepted eula

Zend Framework 2 - ZFCUser - How to exclude landing page from auth

I'm using ZF2 in combination with ZFCUser and bjyauthorize. I have a landing page which should be globally accessable. All other pages need to be behind a login.
At first I blamed bjyauthorize for not letting guest users access my landing page. But after some discussions it seems that ZFCUser is blocking the way.
My question is: How can I tell ZFCUser not to block one page/action?
Edit:
My Application/Module.php looks like in this post. When I add my app myApp to the whitlist, I can access my landing page but all other actions from myApp as well.
Any ideas how to alter the condition that I can match the URL or just whitlist my frontend-action?
Maybe I could add a second route to my landing page. But that's not a clean solution, right?
If you insist on checking authentication in the onBoostrap method you could do something like this:
class Module
{
protected $whitelist = array(
'zfcuser/login' => array('login'),
'your-landing-route' => array('your-landing-action'),
);
public function onBootstrap($e)
{
$app = $e->getApplication();
$em = $app->getEventManager();
$sm = $app->getServiceManager();
$list = $this->whitelist;
$auth = $sm->get('zfcuser_auth_service');
$em->attach(MvcEvent::EVENT_ROUTE, function($e) use ($list, $auth) {
$match = $e->getRouteMatch();
// No route match, this is a 404
if (!$match instanceof RouteMatch) {
return;
}
// Route and action is whitelisted
$routeName = $match->getMatchedRouteName();
$action = $match->getParam("action");
if(array_key_exists($routeName,$list) && in_array($action,$list[$routeName])) {
return;
}
// User is authenticated
if ($auth->hasIdentity()) {
return;
}
// Redirect to the user login page, as an example
$router = $e->getRouter();
$url = $router->assemble(array(), array(
'name' => 'zfcuser/login'
));
$response = $e->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
return $response;
}, -100);
}
}
I've just changed the code a little but so your white list also contains specific actions. Then we can check the action parameter to be a little bit more specific with your white listing.
I don't know if this is the best way to do it, I'm just showing you how you can do it.
I don't think you even need to check authentication when using BjyAuthorize as you can just use resource checks. If a user has anything other than a guest role then they are a real user and are authenticated. Again, I'm not 100% on that but I do know that I don't use ZfcUser authentication checks in my application which uses BjyAuthorize. I just use route guards to specify the role level needed for a aparticular route.
Maybe somebody else could clarify this?

FosUserBundle and custom user registration

I am trying to figure out the registration process of the FosUserBundle and have been unable to do so.
I want to be able to register a user manually using custom fields and have been unable to see that in the code.
I have the registerAction in FosUserBundle and following it anywhere does not show me where the information is actually stored in the database:
public function registerAction()
{
$form = $this->container->get('fos_user.registration.form');
$formHandler = $this->container->get('fos_user.registration.form.handler');
$confirmationEnabled = $this->container->getParameter('fos_user.registration.confirmation.enabled');
$process = $formHandler->process($confirmationEnabled);
if ($process) {
$user = $form->getData();
if ($confirmationEnabled) {
$this->container->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
$route = 'fos_user_registration_check_email';
} else {
$this->authenticateUser($user);
$route = 'fos_user_registration_confirmed';
}
$this->setFlash('fos_user_success', 'registration.flash.user_created');
$url = $this->container->get('router')->generate($route);
return new RedirectResponse($url);
}
return $this->container->get('templating')->renderResponse('FOSUserBundle:Registration:register.html.'.$this->getEngine(), array(
'form' => $form->createView(),
'theme' => $this->container->getParameter('fos_user.template.theme'),
));
}
How do I register a user manually?
Thanks
Have you read this in the FOSUserBundle docs?
Using the UserManager
Apparently the service UserManager has the responsibility to actually save and update the user, you could create your own UserManager by following the guide.
Edit: Symfony has incorporated the documentation for the FOSUserBundle into their own documentation, as so the new link for the documentation is:
https://symfony.com/doc/master/bundles/FOSUserBundle/index.html

Categories