How to allow action using security component? - security

I am getting this error
Call to undefined method SecurityComponent::allowedActions()
When I try to allow singup action in controller like this
public function beforeFilter() {
parent::beforeFilter();
$this->Security->allowedActions(array('sign-up'));
$this->Auth->allow('login','signup','index','activate','logout','forgot','reset','display');
if($this->Auth->user('id')) {
$this->set('logged_in', true);
} else {
$this->set('logged_in', false);
}
}
public $components = array('RequestHandler');
if i remove
$this->Security->allowedActions(array('sign-up'));
when I submit signup form, It shows your request has ben blackholed

There is no such method, allowedActions is a property of the SecurityComponent.
http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#SecurityComponent::$allowedActions
$this->Security->allowedActions = array('sign-up');
Also you are using signup in AuthComponent::allow(), so make sure sign-up is really the correct name of the action (which I really doubt as this would be invalid PHP syntax).

Related

struggling with an ASP.NET MVC5 routing issue

So, I have an MVC5 site that uses the default routing template {controller}/{action}/{id} and this works fine. Most everything in the site requires a login (i.e. [Authorize] attribute is used almost everywhere), and this works fine.
Well, now I have a need to allow anonymous access to select pages when a certain kind of link pattern is used: App/{token}/{action}. The {token} is a random string associated with something in my database. I can issue and deactivate these tokens at will.
I got this new App/{token}/{action} routing working by implementing a custom RouteBase that parses the incoming URL for these tokens, and, crucially, adds the the token value to the RouteData.DataTokens so that my App controller can make use of it without needing an explicit action argument for it. So, I added this new route to the route table ahead of the default routing like this:
// new route here
routes.Add("AppToken", new AnonAppAccessRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Here is the problem/question: adding this now route has now made my default route stop working -- everything is now going through AnonAppAccessRoute which of course is meant to work only for a few things. I don't understand how to make my AnonAppAccessRoute apply only to URLs with a certain pattern. The MapRoute method accepts a URL pattern, but Adding a route doesn't seem to let you put a filter on it. What am I missing? I've looked around quite a bit at various blogs and documentation about routing, but I've not found good info about using the DataTokens collection (which I feel is important to my approach), and I'm not seeing a good explanation of the difference between Adding a route explicitly vs calling MapRoute.
Here's the code of my custom RouteBase:
public class AnonAppAccessRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string[] pathElements = httpContext.Request.Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (pathElements.Length > 0)
{
string token = TryGetArrayElement(pathElements, 1);
if (!string.IsNullOrEmpty(token))
{
result = new RouteData(this, new MvcRouteHandler());
result.DataTokens.Add("appToken", token);
result.Values.Add("controller", "App");
result.Values.Add("action", TryGetArrayElement(pathElements, 2, "Index"));
}
}
return result;
}
private string TryGetArrayElement(string[] array, int index, string defaultValue = null)
{
try
{
return array[index];
}
catch
{
return defaultValue;
}
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
I got this to work by dropping the custom RouteBase and instead used this MapRoute call like this:
routes.MapRoute(
name: "AppAnon",
url: "App/{token}/{action}",
defaults: new { controller = "App", action = "Index" }
);
Then, in my App controller, I did this in the Initialize override:
protected AppToken _appToken = null;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
string token = requestContext.RouteData.Values["token"]?.ToString();
_appToken = Db.FindWhere<AppToken>("[Token]=#token", new { token });
if (!_appToken?.IsActive ?? false) throw new Exception("The token is not found or inactive.");
}
This way, my "token" is available to all controller actions via the _appToken variable, and already validated. I did not need to use RouteData.DataTokens. Note that my Db.FindWhere statement is ORM-specific and not really related to the question -- it's just how I look up a database record.

JSF HttpSession attribute becomes null after refresh/redirection

I'm testing a simple log in with httpsession, so after i authenticate the user i add a user attribute to the http session :
#ManagedBean
#SessionScoped
public class loginView {
....
public String connect() {
FacesContext context = FacesContext.getCurrentInstance();
if (authenticated) {
context.getExternalContext().getSessionMap().put("user", login);
return "/home/NewFile?faces-redirect=true";
} else {
context.addMessage(null, new FacesMessage("Unknown login, try again"));
login = "";
pwd = "";
return null;
}
}
}
When i call this function from the login view it redirects to NewFile.xhtml as it's supposed to do. And inside the said xhtml i display the "user" attribute using #{user}. So far everything is working fine but when i refresh the page (NewFile.xhtml) or when i redirect to another page and try to display "user" attribute i get null, is this behavior expected ? does refreshing or redirecting creates another httpsession ? or is it just deleting the attribute i added ?
After some research i succeeded in solving my problem, turns out it's just a stupid mistake of my part. so i thought i should leave the answer here instead of deleting the question. So here goes :
After some looking around I have found out that this had to do with the cookies, so i did track the HTTP traffic using chrome's F12 and and it was the server sending new cookies each time I refresh/navigate. And after some more searching and testing i've found out what was causing the session to invalidate, so i was calling a logout function (that invalidates the session) this way : <h:button outcome="view.logout()"/> turns out outcome executes the function before loading the page so i had to change it to <p:commandButton action="view.logout()"/>

How to redirect to an action method of another area within OnActionExecuting

In My MVC5 application I have 3 areas. My project structure as following
I have implemented an ActionFilter class to validate whether user has granted the permission for particular action methods. My ActionFilter class stay out of areas folder. I want to check user permission within the OnActionExecuting method and redirect to PermissionDenied action method which has implemented on ErrorControl. However it does not recognize within areas and gives an error message mentioning "No controller and action method found within the area"
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(!GrantPermission(filterContext))
{
Controller contr = (BaseController)filterContext.Controller;
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "area", "" },
{ "controller", "Error" },
{ "action", "PermissionDenied" }
});
filterContext.Result.ExecuteResult(contr.ControllerContext);
}
base.OnActionExecuting(filterContext);
}
Can anyone help me to get this solved. this one already has ruined my day.
Area is not a route value. It is put into the RouteData.DataTokens dictionary instead. But there is no way to set it from RedirectToRouteResult.
Instead, you could use the UrlHelper to generate the URL much as you would in an ActionLink. The UrlHelper will work out what the virtual path of your Area is. Then, you can just use RedirectResult to get to that URL.
if (!GrantPermission(filterContext))
{
Controller contr = (BaseController)filterContext.Controller;
var urlHelper = new UrlHelper(filterContext.RequestContext);
var redirectUrl = urlHelper.Action("PermissionDenied", "Error", new { area = "" });
filterContext.Result = new RedirectResult(redirectUrl);
filterContext.Result.ExecuteResult(contr.ControllerContext);
}
base.OnActionExecuting(filterContext);
NOTE: The correct way to authorize an action is to use either IAuthorizationFilter or better yet, inherit AuthorizeAttribute. Authorization filters run before action filters do. Also, they will execute the result handler automatically for you.

When are user roles refreshed and how to force it?

First off, I'm not using FOSUserBundle and I can't because I'm porting a legacy system which has its own Model layer (no Doctrine/Mongo/whatsoever here) and other very custom behavior.
I'm trying to connect my legacy role system with Symfony's so I can use native symfony security in controllers and views.
My first attempt was to load and return all of the user's roles in the getRoles() method from the Symfony\Component\Security\Core\User\UserInterface. At first, it looked like that worked. But after taking a deeper look, I noticed that these roles are only refreshed when the user logs in. This means that if I grant or revoke roles from a user, he will have to log out and back in for the changes to take effect. However, if I revoke security roles from a user, I want that to be applied immediately, so that behavior isn't acceptable to me.
What I want Symfony to do is to reload a user's roles on every request to make sure they're up-to-date. I have implemented a custom user provider and its refreshUser(UserInterface $user) method is being called on every request but the roles somehow aren't being refreshed.
The code to load / refresh the user in my UserProvider looks something like this:
public function loadUserByUsername($username) {
$user = UserModel::loadByUsername($username); // Loads a fresh user object including roles!
if (!$user) {
throw new UsernameNotFoundException("User not found");
}
return $user;
}
(refreshUser looks similar)
Is there a way to make Symfony refresh user roles on each request?
So after a couple of days trying to find a viable solution and contributing to the Symfony2 user mailing list, I finally found it. The following has been derived from the discussion at https://groups.google.com/d/topic/symfony2/NDBb4JN3mNc/discussion
It turns out that there's an interface Symfony\Component\Security\Core\User\EquatableInterface that is not intended for comparing object identity but precisely to
test if two objects are equal in security and re-authentication context
Implement that interface in your user class (the one already implementing UserInterface). Implement the only required method isEqualTo(UserInterface $user) so that it returns false if the current user's roles differ from those of the passed user.
Note: The User object is serialized in the session. Because of the way serialization works, make sure to store the roles in a field of your user object, and do not retrieve them directly in the getRoles() Method, otherwise all of that won't work!
Here's an example of how the specific methods might look like:
protected $roles = null;
public function getRoles() {
if ($this->roles == null) {
$this->roles = ...; // Retrieve the fresh list of roles
// from wherever they are stored here
}
return $this->roles;
}
public function isEqualTo(UserInterface $user) {
if ($user instanceof YourUserClass) {
// Check that the roles are the same, in any order
$isEqual = count($this->getRoles()) == count($user->getRoles());
if ($isEqual) {
foreach($this->getRoles() as $role) {
$isEqual = $isEqual && in_array($role, $user->getRoles());
}
}
return $isEqual;
}
return false;
}
Also, note that when the roles actually change and you reload the page, the profiler toolbar might tell you that your user is not authenticated. Plus, looking into the profiler, you might find that the roles didn't actually get refreshed.
I found out that the role refreshing actually does work. It's just that if no authorization constraints are hit (no #Secure annotations, no required roles in the firewall etc.), the refreshing is not actually done and the user is kept in the "unauthenticated" state.
As soon as you hit a page that performs any kind of authorization check, the user roles are being refreshed and the profiler toolbar displays the user with a green dot and "Authenticated: yes" again.
That's an acceptable behavior for me - hope it was helpful :)
In your security.yml (or the alternatives):
security:
always_authenticate_before_granting: true
Easiest game of my life.
From a Controller, after adding roles to a user, and saving to the database, simply call:
// Force refresh of user roles
$token = $this->get('security.context')->getToken()->setAuthenticated(false);
Take a look here, set always_authenticate_before_granting to true at security.yml.
I achieve this behaviour by implementing my own EntityUserProvider and overriding loadByUsername($username) method :
/**
* Load an user from its username
* #param string $username
* #return UserInterface
*/
public function loadUserByUsername($username)
{
$user = $this->repository->findOneByEmailJoinedToCustomerAccount($username);
if (null === $user)
{
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
}
//Custom function to definassigned roles to an user
$roles = $this->loadRolesForUser($user);
//Set roles to the user entity
$user->setRoles($roles);
return $user;
}
The trick is to call setRoles each time you call loadByUsername ... Hope it helps
Solution is to hang a subscriber on a Doctrine postUpdate event. If updated entity is User, same user as logged, then I do authenticate using AuthenticationManager service. You have to inject service container (or related services) to subscriber, of course. I prefer to inject whole container to prevent a circular references issue.
public function postUpdate(LifecycleEventArgs $ev) {
$entity = $ev->getEntity();
if ($entity instanceof User) {
$sc = $this->container->get('security.context');
$user = $sc->getToken()->getUser();
if ($user === $entity) {
$token = $this->container->get('security.authentication.manager')->authenticate($sc->getToken());
if ($token instanceof TokenInterface) {
$sc->setToken($token);
}
}
}
}
Sorry i cant reply in comment so i replay to question. If someone new in symfony security try to get role refresh work in Custom Password Authentication then inside function authenticateToken :
if(count($token->getRoles()) > 0 ){
if ($token->getUser() == $user ){
$passwordValid=true;
}
}
And do not check for passwords from DB/LDAP or anywhere. If user come in system then in $token are just username and had no roles.
I've been battling this for Symfony4, and I think I've finally settled down to a solution.
The thing is that in my case, the roles depend on the "company" the user is working with. It may be a CEO in one company, but an operator in another one, and the menus, permissions, etc. depend on the company. When switching companies, the user must not re-login.
Finally I've done the following:
Set the firewall to stateless.
In the FormAuthentication class, I set an attribute in the session explicitely, with the username.
I set up another Guard, which essentially take this attribute and loads the user for it from the database, for every single request.
class FormAuthenticator extends AbstractFormLoginAuthenticator
{
/** Constructor omitted */
public function supports(Request $request)
{
return 'app_login' === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'nomusuari' => $request->request->get('nomusuari'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['nomusuari']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $userProvider->loadUserByUsername($credentials['nomusuari']);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Invalid user/password');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
$valid = $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
return $valid;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$request->getSession()->set("user_username",$token->getUsername());
return new RedirectResponse(
$this->urlGenerator->generate("main")
);
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}
The SessionAuthenticator (returns JSON, you may have to adapt it):
class SessionAuthenticator extends AbstractGuardAuthenticator
{
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request)
{
return $request->getSession()->has("user_username");
}
/**
* Called on every request. Return whatever credentials you want to
* be passed to getUser() as $credentials.
*/
public function getCredentials(Request $request)
{
return $request->getSession()->get("user_username","");
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
if (null === $credentials) {
// The token header was empty, authentication fails with HTTP Status
// Code 401 "Unauthorized"
return null;
}
// if a User is returned, checkCredentials() is called
/*return $this->em->getRepository(User::class)
->findOneBy(['apiToken' => $credentials])
;*/
return $userProvider->loadUserByUsername($credentials);
}
public function checkCredentials($credentials, UserInterface $user)
{
// Check credentials - e.g. make sure the password is valid.
// In case of an API token, no credential check is needed.
// Return `true` to cause authentication success
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = [
// you might translate this message
'message' => 'Authentication Required'
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
return false;
}
}
Finally, my security.yaml:
main:
anonymous:
stateless: true
guard:
entry_point: App\Security\FormAuthenticator
authenticators:
- App\Security\SessionAuthenticator
- App\Security\FormAuthenticator
Working fine. I can see the changes in the toolbar, and the Roles are refreshed.
HTH,
Esteve

Kohana 3: How can I pass full control to another action within my controller?

In my controller, I have a before() function that calls parent::before() and then does some additional processing once the parent returns. based on a specific condition, I want to "save" the original request and pass execution to a specific action. Here is my before() function.
public function before() {
parent::before();
$this->uri = Request::Instance()->uri;
$match = ORM::factory('survey_tester')
->where('eid','=',$this->template->user->samaccountname)
->find();
if (!$match->loaded()) {
self::action_tester("add",$this->template->user);
}
}
And the action that is being called..
public function action_tester($op=null,$user=null) {
$testers = ORM::factory('survey_tester')->find_all();
$tester = array();
$this->template->title = 'Some new title';
$this->template->styles = array('assets/css/survey/survey.css' => 'screen');
$this->template->scripts = array('assets/js/survey/tester.js');
$tester['title'] = $this->template->title;
$tester['user'] = $this->template->user;
switch ($op) {
case "add":
$tester = ORM::factory('survey_tester');
$tester->name = $user->displayname;
$tester->email = $user->mail;
$tester->division = $user->division;
$tester->eid = $user->samaccountname;
if ($tester->save()) {
$this->template->content = new View('pages/survey/tester_add', $admin);
} else {
$this->template->content = new View('pages/survey/tester_error', $admin);
}
break;
default:
break;
}
}
This all seems to work fine. This is designed to prompt the user for a specific piece of information that is not provided by $user (populated by LDAP) if this is the first time they are hitting the controller for any reason.
The problem is the views are not rendering. Instead control passes back to whatever action was originally requested. This controller is called survey. If i browse to http://my.site.com/survey and login with new user info, the record gets written and i get the action_index views instead of my action_tester views.
I cannot figure out what I am doing wrong here. Any ideas will be appreciated. Thank you.
EDIT: I managed to get this working (sort-of) by using $this->request->action = 'tester'; but I'm not sure how to add/set new params for the request yet.
The issue is that you are calling your method (action_tester), but then Kohana is still going to call the original action after the before method is called, which is going to change the response content overwriting the changed made in action_tester().
You can change the action being called (after before is called) inside your before() method:
$this->request->action('action_tester');
After the before method is called, it should then call the new Action (action_tester) rather than the old one, but then you need to do something about the way you are passing your parameters then.
Or you could just redirect the request upon some condition:
if($something) {
$this->request->redirect('controller/tester');
}
This doesn't seem like a nice way to do it anyway.

Resources