Get security config in Symfony2? - fosuserbundle

I'd like to access the security config as it's configured (by default) in security.yml, and in particular actually I need the route name or (even better) generated URL to login. When using FOS User (which I'm using right now) its called "fos_user_security_login" with "/login" URL. I need it to compare with an event's request's (requsted) URL on Kernel's listened events.
I could hardcode this setting check in my Kernel Listener class, like this:
public function onKernelResponse(\Symfony\Component\HttpKernel\Event\FilterResponseEvent $event)
{
if ($originalResponse->headers->has('location')
&& $originalResponse->headers->get('location') === $router->generate('fos_user_security_login', array(), true))
{
//...
}
}
But what if I changed this setting in future to some another one, e.g. to some "/user/login" path with my custom login handler? This is why I'd like to read the security setting for login.
How can I do this in Symfony?

If I were you I would refrain from reading the security settings for this as you can have multiple firewalls with multiple logins and your listener would thus have to listen to all of these (which might not be what you want) or artificially restrict to hardcoded firewalls. Also this will tie your implementation to Symfony's security-component, which you should avoid.
An easily reusable approach would be, to add the URL or route name you want to check for as argument to your listener and pass it via Symfony's Service Container and then just compare request with that value:
class LoginListener
{
/**
* #var string
*/
protected $loginUrl;
/**
* #param string $loginUrl
*/
public function __construct($loginUrl)
{
// You can even fallback to default if you like:
if (empty($loginUrl)) {
$loginUrl = '/login';
}
$this->loginUrl = $loginUrl;
}
// [...] your comparison just against $this->loginUrl
}
You can then use your bundle's configuration to pass the right argument to that listener.
This way you can easily reuse it outside of Symfony e.g. in Silex without being tied to Symfony's Security-component. Also if you want to check against multiple urls, you can just make it an array and specify the different login urls, e.g. when you have multiple login-mechanisms.
edit: In your bundle's Configuration you can check for parameters and define your fallbacks or an error message or whatever (see Getting and Setting Container Parameters).
edit:
in parameters.yml:
custom_login_path: /my_login
in security.yml:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: security.csrf.token_manager # Use form.csrf_provider instead for Symfony <2.4
login_path: %custom_login_path%
in routing.yml:
fos_user:
resource: "#FOSUserBundle/Resources/config/routing/all.xml"
# Make sure this follows "fos_user" so it takes precendece over the default
fos_user_security_login:
path: %custom_login_path%
defaults: { _controller: FOSUserBundle:Security:login }
in config.yml:
# config for listener in your bundle
my_bundle:
login_path: %custom_login_path%

Related

How to use a CookieCollection in multiple functions

I'm setting up a web page using cookies to determine if the user already logged in, using a cookie containing his id. Problem is : The cookie is either not written or the cookie collection is not updated.
I've tried reading the documentation, but it does not define the usage of CookieCollection.
Here's the function where i write my cookie :
function displayData(){
$id = $this->getRequest()->getSession()->read('id');
$cookies = CookieCollection::createFromServerRequest($this->getRequest());
if(!$cookies->has('id')){
$cookie = (new Cookie('id'))
->withValue($id)
->withExpiry(new DateTime('+999 year'))
->withPath('/')
->withDomain('break-first.eu')
->withSecure(true)
->withHttpOnly(true);
$cookies = $cookies->add($cookie);
}
// Other stuff
}
And where I try reading it :
function index(){
$cookies = $this->getRequest()->getCookieCollection();
dd($cookies);
}
I expect having a cookie named "id", but I don't have it. Only CAKEPHP and pll_language are showing up.
First things first, CakePHP provides authentication functionality with cookie authentication, you may want to have a look at that instead of driving a custom solution.
Cookbook > Plugins > Authentication
That being said, what you're doing there will create a cookie collection object, which however is just that, a lone object somewhere in space, it won't affect the state of your application, in order for that to happen you have to actually modify the response object.
However what you're trying to do there doesn't require cookie collections in the first place, you can simply read and write cookies directly via the methods provided by the request and response objects, like:
// will be `null` in case the cookie doesn't exist
$cookie = $this->getRequest()->getCookie('id');
// responses are immutable, they need to be reassinged
this->setResponse(
$this->getResponse()->withCookie(
(new Cookie('id'))
->withValue($id)
->withExpiry(new DateTime('+999 year'))
->withPath('/')
->withDomain('break-first.eu')
->withSecure(true)
->withHttpOnly(true)
)
);
And if you where to use a cookie collection for whatever reason, then you'd use withCookieCollection() to pass it into the response:
$this->setResponse($this->getResponse()->withCookieCollection($cookies));
If you run into strict typing errors, you could for example create a custom reponse class with an overridden Response::convertCookieToArray() method and cast the string to an integer there (make sure that PHP_INT_MAX covers your target date timestamp, 32-Bit incompatibility is why the fix that landed in CakePHP 4.x, probably won't come to 3.x), something like:
src/Http/Response.php
namespace App\Http;
use Cake\Http\Cookie\CookieInterface;
use Cake\Http\Response as CakeResponse;
class Response extends CakeResponse
{
protected function convertCookieToArray(CookieInterface $cookie)
{
$data = parent::convertCookieToArray($cookie);
$data['expire'] = (int)$data['expire'];
return $data;
}
}
You can pass that into the app in your webroot/index.php file, as the second argument of the $server->run() call:
// ...
$server->emit($server->run(null, new \App\Http\Response()));
See also
Cookbook > Request & Response Objects > Request > Cookies
Cookbook > Request & Response Objects > Response > Setting Cookies

How can CakePHP Authorization plugin authorize access to indexes?

I'm converting my app to CakePHP 3.6, and working now on using the new Authorization plugin. I'm not sure how to check authorization for things like indexes or other reports, where there is no "resource" to pass to the can() or authorize() functions.
For now, I've built a ControllerResolver, loosely copied from the ORMResolver, which accepts controller objects and finds policies based on the singularized controller name, so that they're named the same as the Entity policies I'm building. (That is, my UserPolicy can have canIndex and canEdit functions, the former found via the controller and the latter via the entity.)
This works fine in controller actions where I can call $this->Authorize->authorize($this);, but it doesn't work in views, where I'd like to be able to do things like:
if ($this->Identity->can('index', *something*)) {
echo $this->Html->link('List', ['action' => 'index']);
}
so as to only show links to people who are allowed to run those actions.
Anyone know if there's a reason why the system implicitly requires that the "resource" passed into authorization functions be an object? (For example, the plugin component calls get_class($resource) in the case of a failed authorization, without first checking that the provided resource is in fact an object.) Allowing a string (e.g. \App\Controller\UsersController::class) would make my life easy. Very happy to put together a PR for this if it's just an oversight.
But authorizing indexes seems like a pretty obvious function, so I wonder if I've missed something. Maybe I'm supposed to pass the table object, and split the authorization between an entity policy and a table policy? But using table objects in views just for this purpose seems like a violation of separation of concerns. Maybe uses of the plugin to date have been things where indexes are always public?
to do this you can use the authorizeModel, as stated in the documentation https://github.com/cakephp/authorization/blob/master/docs/Component.md#automatic-authorization-checks. Basically is adding the auhtorizeModel parameters when you load the component at AppController.php
$this->loadComponent('Authorization.Authorization', [
'skipAuthorization' => ['login','token'],
'authorizeModel' => ['index','add'],
]);
When you configure an action to be authorized by model the authorization service uses the TablePolicy, so if you want to authorize the index action for Books you need to create the BooksTablePolicy and implement the method
<?php
namespace App\Policy;
use App\Model\Table\BooksTable;
use Authorization\IdentityInterface;
/**
* Books policy
*/
class BooksTablePolicy
{
public function scopeIndex($user, $query)
{
return $query->where(['Books.user_id' => $user->id]);
}
public function canIndex(IdentityInterface $identity)
{
// here you can resolve true or false depending of the identity required characteristics
$identity['can_index']=true;
return $identity['can_index'];
}
}
This will be validated before the request reaches your controller so you do not need to authorize anything there. Nevertheless if you want to apply an scope policy as you can see in this example:
public function index()
{
$user = $this->request->getAttribute('identity');
$query = $user->applyScope('index', $this->Books->find()->contain('Users'));
$this->set('books', $this->paginate($query));
}

CakePHP 3.5 Writing & Encrypting Cookie

Background: I have just upgraded to CakePHP 3.5.17.
I had a code that write cookie. However, it seems that I am missing a few steps to encrypt it. Can somebody shed some lights where are the missing steps? At the moment, the web browser is getting the value of the cookie but it is not encrypted. Note I have also set the cookieKey on my app.php
I've also included this steps in the link provided below
https://book.cakephp.org/3.0/en/development/application.html#adding-http-stack
//In src/Controller/UsersController.php
use Cake\I18n\Time;
use Cake\Http\Cookie\Cookie;
use Cake\Http\Cookie\CookieCollection;
use Cake\Core\Configure;
use App\Application;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
use Cake\Http\Middleware\EncryptedCookieMiddleware;
public function writecookie() {
$cookie = new Cookie(
'goodday', // name
'YES', // value
(Time::now())->modify('+1 year'), // expiration time, if applicable
'/', // path, if applicable
'', // domain, if applicable
false, // secure only?
true // http only ?
);
$middlewareQueue = new MiddlewareQueue();
$cookiesEncrypted = new EncryptedCookieMiddleware(
['goodday'],
Configure::read('Security.cookieKey')
);
$cookiesEncrypted = $middlewareQueue->add($cookiesEncrypted);
$this->response = $this->response->withCookie($cookie); //value is still YES in the web browser cookie storage
}
After further debugging, I noticed that in class EncryptedCookieMiddleware. It is stating that Cookies in request data will be decrypted, while cookies in response headers will be encrypted automatically. If the response is a Cake\Http\Response, the cookie data set with withCookie() and `cookie()`` will also be encrypted. But for me it doesn't automatically encrypt?
You may want to make yourself more familiar with how middlewares work, you're not supposed to use them in your controller, they're supposed to be "wrapped around" your application and interact with the requests that are sent to the app, and the responses that the app sends back.
You register them in your applications Application::middleware() method, in the Server.buildMiddleware event, or when connecting routes.
// src/Application.php
// ...
use Cake\Http\Middleware\EncryptedCookieMiddleware;
class Application extends BaseApplication
{
public function middleware($middlewareQueue)
{
// ...
$middlewareQueue->add(new EncryptedCookieMiddleware(/* ... */));
return $middlewareQueue;
}
}
See also
Cookbook > Middleware
Cookbook > Middleware > Using Middleware
Cookbook > Routing > Connecting Scoped Middleware

Modify default endpoint configuration

Example:
to("xslt:mapping.xsl?saxon=true&transformerCacheSize=5")
When I use Saxon, I will set this property all over the place. Having a String constant for them or creating my own xslt endpoint does not seem to be the proper way.
Is there something I can set those properties for all xslt endpoints?
You can configure it by subscribing to the CamelContext's startupListener and setting the XsltEndpoints' parameters (be careful, because the following example does set every XsltEndpoint endpoint's saxon and transformerCacheSize properties). Example (ctx is an instance of CamelContext):
ctx.addStartupListener((ctx, alreadyStarted) -> {
ctx.getEndpoints().forEach(e -> {
if(e instanceof XsltEndpoint) {
((XsltEndpoint) e).setTransformerCacheSize(5);
((XsltEndpoint) e).setSaxon(true);
}
});
});
In case of Spring Boot saxon=true can be configured using the application.properties file (XsltComponentConfiguration). AFAIK the transformerCacheSize cannot be configured from the properties file, because it is a parameter of the XsltEndpoint.
# application.properties
camel.component.xslt.saxon = true
If the configuration is always the same, you can declare a direct endpoint to handle all requests.
from("direct:my-xslt")
.to("xslt:mapping.xsl?saxon=true&transformerCacheSize=5")
And then from your other routes:
.to("direct:my-xslt")
Direct endpoints run in the same thread, so after all its just a way of isolating parts of the route that do a specific job.
As a bonus if you need to do any kind of transformation/log before/after your xslt that apply to all routes, you can simply do it in your direct route.

How can I use cookies for authentication in CakePHP?

I am trying to use a cookie that is set by other page in my domain to authenticate the user.
Say I have needpassword.example.com written using cakephp,
and the cookie is generated by auth.example.com (using a Perl CGI program).
To login in to needpassword.example.com, I need to redirect to auth.example.com to set the cookie, and then use CakePHP to parse the cookie.
How do I parse this cookie? And how do I modify the Auth component to do these?
And how can I override the Auth class to instead go to the auth.example.com to authenticate, and not using the User model? By overriding the identify method in Auth.php?
Many thanks.
Since your needs sound outwith AuthComponent's originally intended design you have two options.
Firstly, if it really doesn't fit your needs, you could create and maintain your very own AuthComponent. Do this by copying /cake/libs/controller/components/auth.php to /app/controller/components/auth.php.
This would allow you to rewrite the component completely, but the downside is you will no longer receive updates to AuthComponent when you upgrade cake.
Secondly, you can extend just about anything in CakePHP using the following pattern:
// save as: /app/controllers/components/app_auth.php
App::import('Component', 'Auth');
class AppAuthComponent extends AuthComponent {
function identify($user = null, $conditions = null) {
// do stuff
return parent::indentify($user, $conditions);
}
}
.. and replace all instances of AuthComponent in your controllers with your AppAuthComponent.
You only need to define the methods you wish to replace.
You can run methods from the original AuthComponent (even ones you have redefined) at any point during your methods using parent::...
The method arguments should remain in the same order as the original API for consistency.
If you wish to add more method arguments, put them after the API ones, eg:
function identify($user = null, $conditions = null, $custom = array()) { ... }
This approach allows you to make application-specific customisation while still using the latest methods defined in the core where necessary.
Presuming I understand your question... As long as auth.example.com sets the cookie with the domain ".example.com" the users browser will send it along with the request to needpassword.example.com and you will be able to access it in your PHP script with the following:
$auth = $_COOKIE['auth'];
You can then make changes to the cookie with the following:
setcookie( "auth", "value", time() + 300, "/", ".example.com" );
(Note: time() + 300 sets the cookies expiry date to 5 minutes in the future, you may want to change this)

Resources