working with session on zf2 (recover container) - session

PRECEDENTS:
using a custom hack OF ZfcUserLdap to authenticate against a LDAP server (include zfcUser too as dependency)
the hack is due the Ldap server uses a ldapc wrapper, so the bind and search process doesn't belong to Ldap standards but through a ldapc library
the login/password box works great against the Ldap server by modifying the bind and findbyuser methods
NEED:
add country selection at login step
check if the user has the permission to work with this country (so to have the country here has sense, don't need ACL, it will be check through LDAP user groups)
store the selected country to use along the whole application
WORK IN PROGRESS:
add SELECT dropdown with available countries to login box [OK]
get the country selected at the login form [OK]
-> at authenticate method on ZfcUserLdap\Authentication\Adapter\Ldap.php class I get correctly the country set at the form
PROBLEM:
how to store the country into a session variable,
-> since zfcUser has an Storage defined and the country is defined at the login step, I would like to use that Storage
I will appreciate any kind of clarification or tips to accomplish this task.
SOLUTION:
The logic is more at zfcUserLdap module, since the auth is against an LDAP Server.
I added to the Entity extended at zfcUserLdap a new property, country that is set to the Entity object along the findByUsername method.
public function findByUsername($username, $country = null)
{
$pUser = $this->ldap->findByUsername($username);
if (isObjectNotNull($pUser))
{
$this->entity->setDisplayName(getLdapUserFirstName($pUser) . ' ' . getLdapUserLastName($pUser));
$this->entity->setEmail(getLdapUserMail($pUser));
$this->entity->setId(getLdapUserUid($pUser));
$this->entity->setUsername(getLdapUserUid($pUser));
$this->entity->setLdapcObject($pUser);
$this->entity->setUserCountry($country);
return $this->entity;
}
else {
return null;
}
}
To have the country here will be useful because the authentication process might check if the username has permission to work within that country. I'll need to add that check later.
Like this, the country is part of the entity object, so I can get the country at the same way I was able to get the username.
For now, I have create a View Helper very similar to ZfcUserDisplayName. I just update the get metohd to get the country property.
$countryName = $user->getUserCountry();
I plan to create a Controller Plugin to get the country from any Controller.

ZFCUser has an authenticate event that you should leverage for this. IN your Module's main bootstrap:
$sm = $e->getApplication()->getServiceManager();
$zfcAuthEvents = $e->getApplication()->getServiceManager()->get('ZfcUser\Authentication\Adapter\AdapterChain')->getEventManager();
$zfcAuthEvents->attach( 'authenticate', function( $authEvent ) use( $sm ){
try
{
// use $authEvent->getIdentity() to get country and stick it in a session
return true;
}
catch( \Exception $x )
{
// handle it
}
});
How you store in session is up to you, there's 400 ways to skin that cat.

Related

Laravel JSON:API - Allow users to create objects related only to themselves

I have a JSON API set up where I would like to allow users to only create addresses for themselves.
In the docs it shows that there is no request to check against in the Policy when creating a resource.
The relationship validation also only works for updates, not for creation, so I can't quite see how I could say "only authorise users to create an address if the relationship is to their own user ID" in a similar way to the update methods.
Example Policy:
class AddressPolicy
{
use HandlesAuthorization;
public function update(User $requestingUser, Address $address): bool
{
// User may update their own address if they do not have permission to edit all addresses
return $requestingUser->is($address->user) || $requestingUser->can('edit addresses');
}
public function create(User $requestingUser): bool
{
// Check if requestingUser is creating an address for themselves?
return $requestingUser->can('create addresses');
}
}
The only option I can see is to create a custom controller action for this, but it feels like it should be possible to do this via Policy validation or similar.
I ended up adjusting the permissions and implementing a solution in an Address controller:
if ($currentUser->getKey() === $creatingForUserId && $currentUser->can('create own addresses')) {
return;
}

How do I allow a 3rd party (twilio) access to current user account in laravel

I am making an application using laravel and twilio that gets feedback about student performance. The logic as follows.
A user, in my case the Student(called resident) logs in and uses a
web page form to send an eval request to a teacher (called
attending). This step starts a session and saves teacher info and
student info.
A random question is picked from a database and saved to the session.
The phone number of the teacher is pulled from a database and the random question is pulled from session and sent to the teacher on SMS using twilio.
The teacher responds with yes, no, or DNS (did not see) via Twilio SMS.
The teacher's response along with the student name, the teacher name and the question asked are saved to a database.
My application works up until step 5. The problem is that a new session is being started when the teacher responds via SMS. So everything after the response is saved to a new session. I can't get access to the original session. I think I need a way to automatically grant the teacher access to the student(ie. user's account). This seems to be a problem with it being a 3rd party application. Can this be done or is there another way to accomplish this?
Below is the code I am using for the response. It is not able to access the session that contains the residentName, the firstQuestion, or the attending_name data. It puts null for those values and uploads null to the database. How do I get access to the initial session in this situation?
namespace App\Http\Controllers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Session;
use Twilio\Rest\Client;
use Twilio\Twiml;
use App\Question;
use App\Answer;
class AskFirstQuestionsController extends Controller
{
public function qOneResponse(Request $request) {
$responderNumber = $request->input('From');
session()->put('responderNumber', $responderNumber);
session()->save();
$responderAnswer = strtolower($request->input('Body'));
$residentName = session::get('residentName');
$firstQuestion = session::get('first_question');
$attending_name = session::get('attending_name');
if (strpos($responderAnswer, 'yes') !== false) {
$answer = new
Answer(['attending'=>$attending_name,'resident_name'=>$residentName,'question_body'
=>$firstQuestion, 'answer_yes'=>1]);
$answer->save();
$smsResponse = "Great! Please help us reinforce this action by providing specific feedback
to the resident about what they did. Thank You for teaching!";
} else if (strpos($responderAnswer, 'no') !== false) {
$answer = new
answer::create(['attending'=>$attending_name,'resident_name'=>$resident_name,'question_body'
=>$firstQuestion, 'answer_no'=>1]);
$answer->save();
$smsResponse = "Ugh, ok...we will work on this. If you feel comfortable, please help us by
providing specific feedback to the resident about what they need to work on. Thank You for
teaching!";
} else if (strpos($responderAnswer, 'dns') !== false) {
$answer = new
answer::create(['attending'=>$attending_name,'resident_name'=>$resident_name,'question_body'
=>$firstQuestion, 'answer_dns'=>1]);
$answer->save();
$smsResponse = "How about trying a different question?";
} else {
$smsResponse = 'Please answer yes, no or dns.';
}
return response($this->respond($smsResponse))->header('Content-Type', 'application/xml');
}
public function respond($smsResponse) {
//get responderNumber and use it below
$responderNumber = session::get('responderNumber');
$response = new Twiml();
$response->message($smsResponse, ['to' => $responderNumber]);
return $response;
}
Do I need to do some type of multiauth approach and somehow grant the teacher automatic access to the student's account (user account)? Or do I have to re-write the logic so that the response-request lifecycle closes and then try to write to the database (maybe it will then use the original session data?)? Or is there a simpler way? Please help. I have been stuck for more than a week.
Twilio developer evangelist here.
I'm not a Laravel developer, but session objects in web application frameworks like this are normally tied to a cookie that either stores the contents of the session or an ID for the session which points to the contents in a database in order to add state to a user's session within a browser.
When Twilio receives an incoming SMS message the webhook that is sent to your server is not connected to the browser session that the user is part of, so you cannot access the same data.
Instead of using the session, you should store this as part of your actual database so that you can look up the details from the database when you receive the SMS.

ASP.NET Core 2.2 - Action Filter db Query Question

I have users in our app, who are mapped to companies. When a user logs in and starts to make requests I want a way to validate if that user is currently mapped to the company for access to company resources.
The idea I had was to create a whole controller just to manage all of this, but someone mentioned ActionFilters as a much better and cleaner option, I have to agree after looking at it.
The idea is to have the controller setup as:
controller - action - CompanyId - ReportId
So any request to root the system would just look up if there are any companies mapped to that logged in user.
But if the request included CompanyId then they'd go to that company's “portal” account page. It's really any request that includes CompanyId where I want the actionFilter to make a determination on if that user is allowed access.
Request comes in...
There is a CompanyId in the request!
ActionFilter:
Look up in db for all users assigned to that CompanyId. Is current user within that list? No? = kick'em out.
I tried to type in a code example, but the system told me to manually indent each line by 4 spaces, I was doing it from memory anyways so no idea how helpful it would have been anyways.
You could get your action parameters in your action filter and then get your database via HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>().Refer to here.
public class TestActionFilter:Attribute,IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
//If companyId is action parameter
var companyId= context.ActionArguments["companyId"].ToString();
//If companyId1 is query string
var companyId1= context.HttpContext.Request.Query["companyId1"].ToString();
//If companyId2 is in request header
var companyId2= context.HttpContext.Request.Headers["companyId2"].ToString();
//get your dbcontext
var db = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
//EF core logic
//...
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
You could use it on action directly using [TestActionFilter] attribute or set as global filter
services.AddMvc(options =>
{
options.Filters.Add(new TestActionFilter()); // an instance
});

Updating a session and using it within my view

I have a wish list, that is throughout the shopping pages. I need to know if this makes sense/the proper way of structuring.
Store the wish list as a session, when a user adds/deletes a new item it updates the session by an ajax call that just returns true/false if successful. On the partial view of the wish list component, I check for the session and cast it to my viewModel (which the session is based on) or serialize it for my knockout.
Let me know if this makes sense, otherwise I can post some code samples
It's hard to say without having a look at your basic structure, and not knowing you exact needs.
I don't know if you know this, but you can actually access the Session directly in Views:
#{
var wishlist = (WishList)HttpContext.Current.Session["Wishlist"];
}
It's fine to use Ajax to update it server side; and then you can return a partial view from the controller, to use however you like in the Ajax success call.
I hope this makes sense.
To begin with, if the wishlist is only supposed to exist for the duration of their visit then storing it in a session would be the best thing to do. However if the wishlist is supposed to live longer than a single visit and should be available to the user upon their return then I would suggest storing it in the database against the user's credentials/account (this is presuming they have an account).
As for the session itself, whilst you can access session data from a view I would not suggest it as you start to have a dependency on the session and before long you'll have code such as this scattered throughout your views.
var wishlist = (WishList)HttpContext.Current.Session["Wishlist"];
What happens when you want to change the way the wishlist works and instead have it database driven as you'd now like to persist the wishlist? You'll have to go through all of your views updating the references to the session.
Instead I would opt for registering your session with your IoC container of choice and injecting it using dependency injection, here is a simple example of how to register the session with StructureMap:
public class WebsiteRegistry : Registry
{
public WebsiteRegistry()
{
this.For<IUserWishlist>().HybridHttpOrThreadLocalScoped().Use(() => GetUserWishlistFromSession());
}
public static IUserWishlist GetUserWishlistFromSession()
{
var session = HttpContext.Current.Session;
if (session["WishList"] != null)
{
return session["WishList"] as IUserWishlist;
}
/* Create new empty session object */
session["WishList"] = new UserWishlist();
return session["WishList"] as IUserWishlist;
}
}
Now you're able to inject your wishlist into your controller and pass the data to your view via a view model. And as you're now programming against an interface instead of an implementation you could easily change how the wishlist is persisted without needing to change any code that references the wishlist.
public class WishlistController : Controller {
private readonly IUserWishlist userWishlist;
public void WishlistController(IUserWishlist userWishlist) {
this.userWishlist= userWishlist;
}
public ActionResult ViewProfile()
{
...
var viewModel = new UserWishlistViewModel {
wishlist = this.userWishlist.GetWishList()
}
...
}
}
I've written a more detailed example up in a blog post that might be of interest which can be read here. I hope this helps!

Symfony2 shared users across multiple apps

I have multiple symfony2 applications which share common entities, but use different database settings. Each of these databases has tables user, user_role and role.
Here's the catch: I would like that user to be able to login to app1 by visiting www.myproject.com/app1/login and after changing URL to /app2/ to use existing token ONLY if identical user exists in app2's database (same username, password and salt). Currently it checks only for same username which is, you must agree, quite inconvenient...
I can't really see when refreshUser() is being called... :-/
All apps use same User and Role entities and UserRepository.
Any help would be much appreciated!
UserRepository:
class UserRepository extends EntityRepository implements \Symfony\Component\Security\Core\User\UserProviderInterface{
/** #var User */
private $user;
public function loadUserByUsername($username) {
/** #var $Q \Doctrine\ORM\Query */
$Q = $this->getEntityManager()
->createQuery('SELECT u FROM CommonsBundle:User u WHERE u.username = :username')
->setParameters(array(
'username' => $username
));
$user = $Q->getOneOrNullResult();
if ( $user == null ){
throw new UsernameNotFoundException("");
}
return $this->user = $user;
}
public function refreshUser(UserInterface $user) {
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class) {
return $class === 'CommonsBundle\Entity\User';
}
public function findById($id){
return $this->getEntityManager()
->createQuery('SELECT u FROM CommonsBundle:User u WHERE u.id = :id')
->setParameters(array(
'id' => $id
))
->getOneOrNullResult();
}
}
User#equals(UserInterface):
I know there is a prettier way to write this method but I will rewrite it after see this working :)
public function equals(UserInterface $user)
{
if (!$user instanceof User) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->getSalt() !== $user->getSalt()) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
Your question made me think. When using symfony2 security, you got one problem: Either a session is valid, meaning the user is authenticated as either anonymous or real user, or the session is invalid.
So, with this in mind, I don't see your approach working as you would like it, because let's say user1 logs in and is using app1. Now he switches to app2 and is not in the database, meaning he should not have access. What to do now? Invalidate the session? This would mean he has to log in again in app1.
If you would use subdomains, you could tie your session to that subdomain, but this would mean the user has to log in again for each application.
There is another problem: It seems like symfony2 stores the id of the user into the session, so without access to the app1 database, you cannot know what the password and the roles of the user in the app1 database are and cannot check for it.
I guess the security of symfony2 was simply not made for such behaviour. It expects the session to relate to the same user within your whole application.
I don't think that symfony2 is the big problem here but the overall handling with php. Let's think for one moment what I would suggest without symfony2:
When a user logs in, store user and roles into a specific array in the session, like:
user.app1 = array('username','password',array('role1','role2'))
Now, on each request to app1 I would check if user.app1 is in the session and read the roles from there. If not, I would check for user.app2, user.app3 and so on. If I find none, redirect to login. If I find one, I would query the database to find the user with the same username and compare the other values. If match, store everything into the database. If not, check next user from session.
I looked up the symfony security reference, and you got some extension points, so maybe you can work from there on. The form_login got a success_handler, so adding the array to the session as suggested above should be done there. The firewall itself has some parameters like request_matcher and entry_point which could be used to add additional checks like the ones I mentioned above. All are defined as services, so injecting the entity manager and the security context should be no problem.
I personally think the design itself is not optimal here and you might be better of refactoring your code to either use one user for all apps and different roles (remember that you can define many entity managers and use different databases) or even consolidating all databases and storing everything into one database, using acl to prevent users from viewing the "wrong" content.

Resources