Yii file permission verify doesn't work - .htaccess

I have a question about yii.
I have some videos and the view action is performed based on some privileges. Some videos may be hidden, just for some user categories or for all. I can handle this from yii filter or something, but the real problem is what happen if someone knows the video file path, i can't handle the permissions in this case.
My solution is ok, but I have one problem - when someone access direct a file I redirect it (from htaccess) to a page, when I verify the user permission based on it's id. When I try to access the file from url everything is ok, the permission filter works fine, but when I the file is called from the player something it's wrong. From what I saw, the Yii::app()->user->id is empty.
Any recommendation?
This is my UserIdentity class:
class UserIdentity extends CUserIdentity
{
private $_id;
/**
* Authenticates a user.
* #return boolean whether authentication succeeds.
*/
public function authenticate()
{
$user=Utilizator::model()->findByAttributes(array('username'=>$this->username));
if($user===null)
{
$this->errorCode=self::ERROR_USERNAME_INVALID;
}
else
{
if($user->password!==$user->encrypt($this->password))
{
$this->errorCode=self::ERROR_PASSWORD_INVALID;
}
else
{
$this->_id = $user->id;
$this->errorCode=self::ERROR_NONE;
}
}
return !$this->errorCode;
}
public function getId()
{
return $this->username;
}
}

Related

BreezeJS SaveChanges() security issue

I'm using BreezeJS and have a question regarding how data is saved. Here's my code and comments
[Authorize]
/*
* I want to point out the security hole here. Any Authorized user is able to pass to this method
* a saveBundle which will be saved to the DB. This saveBundle can contain anything, for any user,
* or any table.
*
* This cannot be stopped at the client level as this method can be called from Postman, curl, or whatever.
*
* The only way I can see to subvert this attack would be to examine the saveBundle and verify
* no data is being impacted that is not owned or related directly to the calling user.
*
* Brute force could be applied here because SaveResult contains Errors and impacted Entities.
*
*/
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return _efContext.SaveChanges(saveBundle);
}
To limit access to a callers ability to retrieve data I first extract from the access_token the user_id and limit all my queries to include this in a where clause, making it somewhat impossible for a user to retrieve another users data.
But that would not stop a rogue user who had a valid access_token from calling SaveChanges() in a brute force loop with incremental object ids.
Am I way off on this one? Maybe I'm missing something.
Thanks for any help.
Mike
The JObject saveBundle that the client passes to the SaveChanges method is opaque and hard to use. The Breeze ContextProvider converts that to a map of entities and passes it to the BeforeSaveEntities method. BeforeSaveEntities is a method you would implement on your ContextProvider subclass, or in a delegate that you attach to the ContextProvider, e.g.:
var cp = new MyContextProvider();
cp.BeforeSaveEntitiesDelegate += MySaveValidator;
In your BeforeSaveEntities or delegate method, you would check to see if the entities can be saved by the current user. If you find an entity that shouldn't be saved, you can either remove it from the change set, or throw an error and abort the save:
protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(
Dictionary<Type, List<EntityInfo>> saveMap)
{
var user = GetCurrentUser();
var entityErrors = new List<EFEntityError>();
foreach (Type type in saveMap.Keys)
{
foreach (EntityInfo entityInfo in saveMap[type])
{
if (!UserCanSave(entityInfo, user))
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Forbidden)
{ ReasonPhrase = "Not authorized to make these changes" });
}
}
}
return saveMap;
}
You will need to determine whether the user should be allowed to save a particular entity. This could be based on the role of the user and/or some other attribute, e.g. users in the Sales role can only save Client records that belong to their own SalesRegion.

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

Don't save URL in history, any header or meta-tag?

Is there any HTTP-headers or meta-tags one can use to avoid getting a URL into the browser history?
For example, I don't want
http://domain.td/show/super-secret-unique-token-that-is-private
to show up in the browser URL bar, when I start typing "domain.t".
Currently I have a (POST) search form on the website to load the tokens, and they don't come up. But later I want to load the tokens via links, from let's say an album.
I don't think you can.
You can save the token as a cookie, or use it as a GET param but make it expire every 15 minutes or so (and regenerate a new one on every page load). Also check for the same user agent, and if you want to go down the IP road, IP address (however it can give false positives, I wouldn't recommend it).
Decided to use a map that I save in the browser session. This way i can pass the tokenKey throgh the URL and get the variable back afterwards.
I wrote this little extended class of Zend_Session_Namespace and added 'add' and 'get' functions.
<?php
class My_Session_Tokens extends Zend_Session_Namespace {
protected $_namespace = "Tokens";
public function __construct($namespace = 'Tokens', $singleInstance = false)
{
parent::__construct($namespace, $singleInstance);
}
public function add($token) {
if($tokenKey = $this->hasToken($token)) {
return $tokenKey;
}
do { $tokenKey = uniqid(); } while(isset($this->$tokenKey));
$this->$tokenKey = $token;
return $tokenKey;
}
public function get($tokenKey) {
if(isset($tokenKey)) {
return $this->$tokenKey;
}
return null;
}
public function hasToken($token) {
foreach($this as $key => $val) {
if($val === $token) return $key;
}
return false;
}
}

Where to check user email does not already exist?

I have an account object that creates a user like so;
public class Account
{
public ICollection<User> Users { get; set; }
public User CreateUser(string email)
{
User user = new User(email);
user.Account = this;
Users.Add(user);
}
}
In my service layer when creating a new user I call this method. However there is a rule that the users email MUST be unique to the account, so where does this go? To me it should go in the CreateUser method with an extra line that just checks that the email is unique to the account.
However if it were to do this then ALL the users for the account would need to be loaded in and that seems like a bit of an overhead to me. It would be better to query the database for the users email - but doing that in the method would require a repository in the account object wouldn't it? Maybe the answer then is when loading the account from the repository instead of doing;
var accountRepository.Get(12);
//instead do
var accountRepository.GetWithUserLoadedOnEmail(12, "someone#example.com");
Then the account object could still check the Users collection for the email and it would have been eagerly loaded in if found.
Does this work? What would you do?
I'm using NHibernate as an ORM.
First off, I do not think you should use exceptions to handle "normal" business logic like checking for duplicate email addresses. This is a well document anti-pattern and is best avoided. Keep the constraint on the DB and handle any duplicate exceptions because they cannot be avoid, but try to keep them to a minimum by checking. I would not recommend locking the table.
Secondly, you've put the DDD tag on this questions, so I'll answer it in a DDD way. It looks to me like you need a domain service or factory. Once you have moved this code in a domain service or factory, you can then inject a UserRepository into it and make a call to it to see if a user already exists with that email address.
Something like this:
public class CreateUserService
{
private readonly IUserRepository userRepository;
public CreateUserService(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
public bool CreateUser(Account account, string emailAddress)
{
// Check if there is already a user with this email address
User userWithSameEmailAddress = userRepository.GetUserByEmailAddress(emailAddress);
if (userWithSameEmailAddress != null)
{
return false;
}
// Create the new user, depending on you aggregates this could be a factory method on Account
User newUser = new User(emailAddress);
account.AddUser(newUser);
return true;
}
}
This allows you to separate the responsiblities a little and use the domain service to coordinate things. Hope that helps!
If you have properly specified the constraints on the users table, the add should throw an exception telling you that there is already a duplicate value. You can either catch that exception in the CreateUser method and return null or some duplicate user status code, or let it flow out and catch it later.
You don't want to test if it exists in your code and then add, because there is a slight possibility that between the test and the add, someone will come along and add the same email with would cause the exception to be thrown anyway...
public User CreateUser(string email)
{
try
{
User user = new User(email);
user.Account = this;
user.Insert();
catch (SqlException e)
{
// It would be best to check for the exception code from your db...
return null;
}
}
Given that "the rule that the users email MUST be unique to the account", then the most important thing is to specify in the database schema that the email is unique, so that the database INSERT will fail if the email is duplicate.
You probably can't prevent two users adding the same email nearly-simultaneously, so the next thing is that the code should handle (gracefully) an INSERT failure cause by the above.
After you've implemented the above, seeing whether the email is unique before you do the insert is just optional.

Resources