Using OAuth2 and ZF3-MVC to protect REST API - zend-framework

I'm trying to get https://github.com/zfcampus/zf-oauth2 working with my ZF3-MVC Application (ok, one solution could be to wait Apigility update).
I have successfully implemented the oauth2-server-php (https://github.com/bshaffer/oauth2-server-php), its zf-oauth2 module support (https://github.com/zfcampus/zf-oauth2) and adapted zf-oauth2 client for ZF3 (https://github.com/API-Skeletons/zf-oauth2-client).
However, I'm totaly stuck now trying to protect my API y following zf-oauth2 module's recommandation:
You can protect your API using the following code (for instance, at the top of a controller):
if (!$this->server->verifyResourceRequest(OAuth2Request::createFromGlobals()))
{
// Not authorized return 401 error
$this->getResponse()->setStatusCode(401);
return;
}
where $this->server is an instance of OAuth2\Server (see the AuthController.php).
I've read this post (Using ZF2 Oauth2) but it's not compliant with ZF3. I guess there's a more efficient way rather than copying/pasting zf-oauth2 module's controller and factory to instantiate the server from scratch.
Would anyone have a clue on how to implement the instance of OAuth2\Server in my API controller?

I finally did it by my own. As I spent a significant amount time on this and saw that others where also looking for a solution, here is how I did it.
At first, I suggest you read https://docs.zendframework.com/tutorials/in-depth-guide/models-and-servicemanager/ if you're not familiar with Dependency Injection and Factories (this was my case).
module.config.php
// In module/YourModule/config/module.config.php:
namespace YourAppNamespace;
use Zend\ServiceManager\Factory\InvokableFactory;
return [
'controllers' => [
'factories' => [
Controller\YourController::class => Factory\YourControllerFactory::class,
],
],
'service_manager' => [ /** Your Service Manager Config **/ ]
'router' => [ /** Your Router Config */ ]
'view_manager' => [ /** Your ViewManager Config */ ],
];
YourControllerFactory.php
// In module/YourModule/src/Controller/YourControllerFactory.php:
namespace YourAppNamespace\Factory;
use YourAppNamespace\Controller\YourController;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class YourControllerFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param null|array $options
*
* #return YourController
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$controllerPluginManager = $container;
$serviceManager = $controllerPluginManager->get('ServiceManager');
// Requires zf-campus/zf-oauth2
$server = $serviceManager->get('ZF\OAuth2\Service\OAuth2Server');
$provider = $serviceManager->get('ZF\OAuth2\Provider\UserId');
return new YourController($server, $provider);
}
}
YourController.php
// In module/YourModule/src/Controller/YourController.php:
namespace YourAppNamespace\Controller;
use ZF\OAuth2\Controller\AuthController;
use OAuth2\Request as OAuth2Request;
use ZF\OAuth2\Provider\UserId\UserIdProviderInterface;
class YourController extends AuthController
{
public function __construct($serverFactory, UserIdProviderInterface $userIdProvider)
{
parent::__construct($serverFactory, $userIdProvider);
}
public function indexAction()
{
$server = call_user_func($this->serverFactory, "oauth");
if (!$server->verifyResourceRequest(OAuth2Request::createFromGlobals())) {
// Failure
$response = $server->getResponse();
return $this->getApiProblemResponse($response);
}
// Success
echo json_encode(array('success' => true, 'message' => 'It works!'));
}
}
Hope it helps!

Related

TYPO3/Extbase How to create unique slug within create action?

I have slug field in my TCA and in general it works, when adding via Backend > List module, even if I won't input any value the unique eval ensures that slug will be unique, so when I'll create many rows with the same name Foo TYPO3 backend will enshure that it will resolve to unique slugs like foo, foo-1, foo-2, etc. Kudos!:
'slug' => [
'exclude' => true,
'label' => 'Slug',
'displayCond' => 'VERSION:IS:false',
'config' => [
'type' => 'slug',
'generatorOptions' => [
'fields' => ['name'],
'fieldSeparator' => '/',
'replacements' => [
'/' => '',
],
],
'fallbackCharacter' => '-',
'eval' => 'unique',
'default' => '',
'appearance' => [
'prefix' => \BIESIOR\Garage\UserFunctions\SlugPrefix::class . '->getPrefix'
],
],
],
However when creating a new object from my form within new/create actions (typical Extbase CRUD from extension_builder as you can see) like:
public function createAction(Car $newCar)
{
$this->addFlashMessage(
'The object was created. Please be aware that this action is publicly accessible unless you implement an access check. See https://docs.typo3.org/typo3cms/extensions/extension_builder/User/Index.html',
'',
\TYPO3\CMS\Core\Messaging\AbstractMessage::WARNING);
$this->carRepository->add($newCar);
$this->redirect('list');
}
of course slug is note set.
My first idea is to duplicate the logic of TCA type='slug' and just add this functionality with some own JS, AJAX and PHP, however that sounds as overload and time consumption. Especially that I don't want the user to care about slug part at all. Is there any simple API for lookup for a unique slug of the given table that can be used in custom action instead?
Note this question is not about how to handle it with JS, that's just concept. I would like to skip this part for FE user at all, he doesn't need to know what the slug is. Just during creating a new object, I want to get unique value like foo-123 instead.
In addition to Jonas Eberles answer here's another example which also respects the eval configuration of the slug field (can be uniqueInSite, uniqueInPid or simply unique).
use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
use TYPO3\CMS\Core\DataHandling\SlugHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
...
public function createAction(Car $newCar)
{
$this->carRepository->add($newCar);
GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class)->persistAll();
$record = $this->carRepository->findByUidAssoc($newCar->getUid())[0];
$tableName = 'tx_garage_domain_model_car';
$slugFieldName = 'slug';
// Get field configuration
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$slugFieldName]['config'];
$evalInfo = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
// Initialize Slug helper
/** #var SlugHelper $slugHelper */
$slugHelper = GeneralUtility::makeInstance(
SlugHelper::class,
$tableName,
$slugFieldName,
$fieldConfig
);
// Generate slug
$slug = $slugHelper->generate($record, $record['pid']);
$state = RecordStateFactory::forName($tableName)
->fromArray($record, $record['pid'], $record['uid']);
// Build slug depending on eval configuration
if (in_array('uniqueInSite', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
} else if (in_array('uniqueInPid', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
} else if (in_array('unique', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInTable($slug, $state);
}
$newCar->setSlug($slug);
$this->carRepository->update($newCar);
}
with custom finder in the repository to fetch assoc array instead of the mapped object for $racord argument
public function findByUidAssoc($uid)
{
$query = $this->createQuery();
$query->matching(
$query->equals('uid', $uid)
);
return $query->execute(true)[0];
}
Note that the record needs to be persisted before executing above code.
References:
SlugHelper::generate
SlugHelper::buildSlugForUniqueInSite
SlugHelper::buildSlugForUniqueInPid
SlugHelper::buildSlugForUniqueInTable
According to answers from Elias and Jonas, I created a class which simplifies things especially when you have more models to handle
typo3conf/ext/sitepackage/Classes/Utility/SlugUtility.php
<?php
namespace VENDOR\Sitepackage\Utility; // <- to be replaced with your namespace
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
use TYPO3\CMS\Core\DataHandling\SlugHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/***
*
* This file is part of the "Sitepackage" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* (c) 2020 Marcus Biesioroff <biesior#gmail.com>
* Concept by: Elias Häußler
* Jonas Eberle
*
***/
class SlugUtility
{
/**
* #param int $uid UID of record saved in DB
* #param string $tableName Name of the table to lookup for uniques
* #param string $slugFieldName Name of the slug field
*
* #return string Resolved unique slug
* #throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
*/
public static function generateUniqueSlug(int $uid, string $tableName, string $slugFieldName): string
{
/** #var Connection $connection */
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
$queryBuilder = $connection->createQueryBuilder();
$record = $queryBuilder
->select('*')
->from($tableName)
->where('uid=:uid')
->setParameter(':uid', $uid)
->execute()
->fetch();
if (!$record) return false;
// Get field configuration
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$slugFieldName]['config'];
$evalInfo = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
// Initialize Slug helper
/** #var SlugHelper $slugHelper */
$slugHelper = GeneralUtility::makeInstance(
SlugHelper::class,
$tableName,
$slugFieldName,
$fieldConfig
);
// Generate slug
$slug = $slugHelper->generate($record, $record['pid']);
$state = RecordStateFactory::forName($tableName)
->fromArray($record, $record['pid'], $record['uid']);
// Build slug depending on eval configuration
if (in_array('uniqueInSite', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
} else if (in_array('uniqueInPid', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
} else if (in_array('unique', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInTable($slug, $state);
}
return $slug;
}
}
Usage in any place, like controller. Scheduler task, repository, etc. Keep in mind that record should be saved before (it may be created by Extbase, or just with plain SQL), just need to have created uid and be valid TYPO3 record.
use VENDOR\Sitepackage\Utility\SlugUtility;
use \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
...
$pageSlug = SlugUtility::generateUniqueSlug(
5, // int $uid UID of record saved in DB
'pages', // string $tableName Name of the table to lookup for uniques
'slug' // string $slugFieldName Name of the slug field
)
// or
$uniqueSlug = SlugUtility::generateUniqueSlug(
123,
'tx_garage_domain_model_car',
'slug'
);
// or according to the original question,
// if you created new model object with Extbase,
// persist it, create unique slug with SlugUtility
// set the slug property to the created model object and finally update
public function createAction(Car $newCar)
{
$this->carRepository->add($newCar);
GeneralUtility::makeInstance(PersistenceManager::class)->persistAll();
$uniqueSlug = SlugUtility::generateUniqueSlug(
$newCar->getUid(),
'tx_garage_domain_model_car',
'slug'
);
if($uniqueSlug) {
$newCar->setSlug($uniqueSlug);
$this->carRepository->update($newCar);
}
$this->redirect('list');
}
// no need for second call to persistAll()
// as Extbase will call it at action's finalizing.
// etc.
You can use the SlugHelper directly. The API was obviously not made very fluent for that use case but it works...
$this->carRepository->add($newCar);
// probably you need to persist first - I am not sure if this is really necessary
$this->objectManager()->get(
\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class
)->persistAll();
$table = 'tx_garage_domain_model_car';
$field = 'slug';
// a stripped down record with just the necessary fields is enough
$record = ['name' => $newCar->getName()];
$pid = $this->settings->persistence->...
$slugHelper = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
\TYPO3\CMS\Core\DataHandling\SlugHelper::class,
$table,
$field,
$GLOBALS['TCA'][$table]['columns'][$field]['config']
);
$newCar->slug = $slugHelper->generate($record, $pid);

Deprecated: ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer

I am getting below error message while using service manager.
How can i resolve this via different approach like constuct....
Deprecated: You are retrieving the service locator from within the
class Users\Controller\LoginController. Please be aware that
ServiceLocatorAwareInterface is deprecated and will be removed in
version 3.0, along with the ServiceLocatorAwareInitializer. You will
need to update your class to accept all dependencies at creation,
either via constructor arguments or setters, and use a factory to
perform the injections. in
C:\wamp64\www\ZendSkeletonApplication-master\vendor\zendframework\zend-mvc\src\Controller\AbstractController.php
on line 258
Below code i have added in module.php
public function getServiceConfig() {
return array(
'abstract_factories' => array(),
'aliases' => array(),
'factories' => array(
// FORMS
'LoginForm' => function ($sm) {
$form = new \Users\Form\LoginForm();
$form->setInputFilter($sm->get('LoginFilter'));
return $form;
},
)
)
}
and from login controller, index action i calling below code
$form = $this->getServiceLocator()->get('LoginForm');
$viewModel = new ViewModel(array('form' => $form));
return $viewModel;
Any help is highly appreciated.
Currently i am using Zend framework 2.5.1 Version
In Zend framework 2.3 Version it was working fine.
Update
Now i am using below code in my controller
// Add this property:
private $table;
// Add this constructor:
public function __construct(LoginForm $table) {
$this->table = $table;
}
and in module.php
// FORMS
Model\AlbumTable::class => function ($sm) {
$form = new \Users\Form\LoginForm();
$form->setInputFilter($sm->get('LoginFilter'));
return Model\AlbumTable;
},
But still i am getting below error
Catchable fatal error: Argument 1 passed to
Users\Controller\LoginController::__construct() must be an instance of
Users\Form\LoginForm, none given, called in
C:\wamp64\www\ZendSkeletonApplication-master\vendor\zendframework\zend-servicemanager\src\AbstractPluginManager.php
on line 252 and defined in
C:\wamp64\www\ZendSkeletonApplication-master\module\Users\src\Users\Controller\LoginController.php
on line 22
There was a lot of problem in the use of serviceLocator in ZF2, Zend tech' did a great job by removing the serviceLocatorAware from the framework, and remove the serviceManager from controllers.
Why ?
Just because some entry and experienced developpers used it in an ugly way, and way too much.
From my point of view, the serviceLocator is meant to be used only in factories.
That's why i keep advising other developper to create factories, without using anonymous function.
Here an example of a controller's factory (not the same as service's factories) : https://github.com/Grafikart/BlogMVC/blob/master/ZendFramework2/module/Blog/src/Blog/Factory/PostControllerFactory.php
And its config line https://github.com/Grafikart/BlogMVC/blob/master/ZendFramework2/module/Blog/config/module.config.controllers.php#L8
And here a Service's factory
<?php
namespace Blog\Factory;
use Blog\Service\CategoryService;
use Doctrine\Common\Persistence\ObjectManager;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class CategoryServiceFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return CategoryService
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
/** #var ObjectManager $em */
$em = $serviceLocator->get('orm_em');
return new CategoryService($em);
}
}
You can do a factory for almost all of your component, even form, you just need to declare those as factories in your config like this :
You can replace the key form_elements by :
controllers
service_manager
view_helpers
validators
It will works the same way :
'form_elements' => array(
'factories' => array(
'Application\Item\Form\Fieldset\ProfileFieldset' =>
'Application\Item\Factory\ProfileFieldsetFactory',
),
'invokables' => array(
'EntityForm' => 'Application\Entities\Form\EntityForm',
'PropertyForm' => 'Application\Item\Form\PropertyForm',
'ProfileForm' => 'Application\Item\Form\ProfileForm',
),
'initializers' => array(
'ObjectManagerInitializer' => 'Application\Initializers\ObjectManagerInitializer',
),
),
Your last error means that your controller is not correctly instanciated, you not give the LoginForm instance, maybe because you didn't create a factory ? Is your controller declared as an invokables ?
For an in depth discussion on deprecating the ServiceLocatorAwareInterface, please read this article by Matthew Weier O'Phinney. Basically, you should avoid hidden dependencies in your controllers by simply setter injecting them through factories as mentioned previously by Hooli.

How to get the url for admin page (including the key) in magento 2x custom module controller

I need the page url in Magento 2x including key in my custom module controller.
here something similar but this is for magento 1x. i need for magento 2x.
for magento 1x : Mage::helper('adminhtml')->getUrl('adminhtml/framexport/index') but i need similar for magento 2x.
The right way is, inject the UrlInterface in you model block or whatever class constructor
Then call the getUrl() function
class SomeClass extends \Some\Other\Class
{
protected $_backendUrl;
public function __construct(
...........
...........
\Magento\Backend\Model\UrlInterface $backendUrl,
...........
) {
$this->_backendUrl = $backendUrl;
}
public function someFunction()
{
$params = array('some'=>'url_parameters');
$url = $this->_backendUrl->getUrl("the/url/path", $params);
}
}
You can easily get Admin url By calling
$this->getUrl('adminhtml/module/action');
Please not that "Context" type of object is loaded in the $this object
You can get admin url as follows:
public function __construct(\Magento\Backend\Helper\Data $HelperBackend
) {
$this->HelperBackend = $HelperBackend;
}
/**
*
* #param \Magento\Framework\Event\Observer $observer
* #return void
*/
public function getAdminUrl()
{
echo $this->HelperBackend->getHomePageUrl();
}
Somehow adminhtml/module creates an extra admin slug which does not work.
My solution is:
// $this->urlBuilder is defined in __constructor() \Magento\Framework\UrlInterface $urlBuilder
$query = [
'method' => 'confirm',
'id' => $order->getEntityId()
];
$url = $this->urlBuilder->getUrl('module', $query);
It works well for secure URLs (with keys) and non-default /admin URLs, such as /backend.

How to use Yii2 debugger when developing RESTful application?

Like in the guide, I have created RESTful controller UserController.
namespace app\controllers;
use yii\rest\ActiveController;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
}
And when I make request GET /users, it works.
But I have no idea what queries does Yii2 execute behind the scene, and I do not know how long do they last.
Can I somehow use Yii2 debugger to debug and profile queries ? If not, what is the alternative for this ?
To see requests in Debugger for APIs
Add this in you API config file -
$config = [
'id' => 'app-api',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
......
....
]
if (YII_ENV_DEV) {
// configuration adjustments for 'dev' environment
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = [
'class' => 'yii\debug\Module',
'allowedIPs' => ['your_ip_address'], // accessible to this ip address only
];
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = [
'class' => 'yii\gii\Module',
];
}
return $config;
In web/index.php of API folder -
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
Access debugger by below URL-
http://localhost/yii2-app/api/web/debug/default/view
To change API's default actions like - create,update,view,index,delete write below code in controller
/* Declare actions supported by APIs (Added in api/modules/v1/components/controller.php too) */
public function actions(){
$actions = parent::actions();
unset($actions['create']);
unset($actions['update']);
unset($actions['delete']);
unset($actions['view']);
unset($actions['index']);
return $actions;
}
/* Declare methods supported by APIs */
protected function verbs(){
return [
'create' => ['POST'],
'update' => ['PUT', 'PATCH','POST'],
'delete' => ['DELETE'],
'view' => ['GET'],
'index'=>['GET'],
];
}
public function actionCreate(){echo "in create action";die;}

Zend Framework - Router - Creating Aliases

I'm building a Zend Framework 1.11.11 application and would like to make the routes and content database driven.
I've written a FrontController Plugin that retrieves the 'paths' from the database and creates an entry in the Router for each one, with the associated controller and action.
However, I'd like to be able to use 'aliases' - a URL that behaves like a normal URL, but is an alias.
For example, if I create the following:
// Create the Zend Route
$entry = new Zend_Controller_Router_Route_Static(
$route->getUrl(), // The string/url to match
array('controller' => $route->getControllers()->getName(),
'action' => $route->getActions()->getName())
);
// Add the route to the router
$router->addRoute($route->getUrl(), $entry);
Then a route for /about/ for example can goto the staticController, indexAction.
However, what's the best way for me to create an alias of this route? So if I went to /abt/ it would render the same Controller and Action?
To me it doesn't make sense to recreate the same route as I'll be using the route as the page 'identifier' to then load content from the database for the page...
you can extend static router:
class My_Route_ArrayStatic extends Zend_Controller_Router_Route_Static
{
protected $_routes = array();
/**
* Prepares the array of routes for mapping
* first route in array will become primary, all others
* aliases
*
* #param array $routes array of routes
* #param array $defaults
*/
public function __construct(array $routes, $defaults = array())
{
$this->_routes = $routes;
$route = reset($routes);
parent::__construct($route, $defaults);
}
/**
* Matches a user submitted path with a previously specified array of routes
*
* #param string $path
* #param boolean $partial
* #return array|false
*/
public function match($path, $partial = false)
{
$return = false;
foreach ($this->_routes as $route) {
$this->setRoute($route);
$success = parent::match($path, $partial);
if (false !== $success) {
$return = $success;
break;
}
}
$this->setRoute(reset($this->_routes));
return $return;
}
public function setRoute($route)
{
$this->_route = trim($route, '/');
}
}
and add new router this way:
$r = My_Route_ArrayStatic(array('about', 'abt'), $defaults);