3

Access to my application (in order)

  1. Whitelist ip address
    • redirect to 404 on invalid ip
  2. Check if last activity was > 2 hours ago
    • redirect to login page and expire session
  3. Check if user is logged in, by looking at user data in $_SESSION
    • redirect to login page if not valid

Index.php

(notice it is very similar to this question):

/**
 * Set Timezone
 */
date_default_timezone_set('Zulu');

/**
 * Include globals and config files
 */
require_once('env.php');

/*
 * Closure for providing lazy initialization of DB connection
 */
$db = new Database();

/* 
 * Creates basic structures, which will be 
 * used for interaction with model layer.
 */
$serviceFactory = new ServiceFactory(new RepositoryFactory($db), new EntityFactory);
$serviceFactory->setDefaultNamespace('\\MyApp\\Service');

$request = new Request();
$session = new Session();
$session->start();
$router = new Router($request, $session);

/*
 * Whitelist IP addresses
 */
if (!$session->isValidIp()) {
    $router->import($whitelist_config);

/*
 * Check if Session is expired or invalid
 */
} elseif (!$session->isValid()) {
    $router->import($session_config);

/*
 * Check if user is logged in
 */
} elseif (!$session->loggedIn()) {
    $router->import($login_config);
} else {
    $router->import($routes_config);
}

/*
 * Find matched route, or throw 400 header.
 *
 * If matched route, add resource name 
 * and action to the request object.
 */
$router->route();

/* 
 * Initialization of View 
 */
$class = '\\MyApp\\View\\' . $request->getResourceName();
$view = new $class($serviceFactory);

/*
 * Initialization of Controller
 */
$class = '\\MyApp\\Controller\\' . $request->getResourceName();
$controller = new $class($serviceFactory, $view);

/*
 * Execute the necessary command on the controller
 */
$command = $request->getCommand();
$controller->{$command}($request);

/*
 * Produces the response
 */
echo $view->render();

The $router->import() function takes a json file with the route configuration and creates the routes (Haven't decided if I'm going to keep that). My router is a modified version of Klein.

My Question

Is this a proper implementation of how to check the session data?

I would prefer to check that the user data in the session can be found in the database, but I would need to use a Service for that, and Services should only be accessed by the controller(?). I wouldn't know which controller to send the user to since the route configuration would change if the user was logged in.

For example, if someone was trying to go to www.myapp.com/orders/123, I would send them to the Orders controller if they were logged in, or the Session controller (to render the login page) if they weren't.

I have read the ACL Implementation from this question. But, unless I'm mistaken, that is for controlling access for users who are already logged in, not for users who aren't logged in. If this is not the case, could someone please explain how I would implement my ACL to check this?

I greatly appreciate any help since the search for this answer has given me really mixed solutions, and most of them I don't like or don't seem like clean solutions. Like a Session Manager, which is basically what I'm doing, but pretending not to. =/

UPDATED index.php (my solution)

/**
 * Set Timezone
 */
date_default_timezone_set('Zulu');

/**
 * Include globals and config files
 */
require_once('env.php');

/*
 * Closure for providing lazy initialization of DB connection
 */
$db = new Database();

/* 
 * Creates basic structures, which will be 
 * used for interaction with model layer.
 */
$serviceFactory = new ServiceFactory(new MapperFactory($db), new DomainFactory);
$serviceFactory->setDefaultNamespace('\\MyApp\\Service');

include CONFIG_PATH.'routes.php';

$request = new Request();
$router = new Router($routes,$request);

/*
 * Find matched route.
 *
 * If matched route, add resource name 
 * and command to the request object.
 */
$router->route();

$session = $serviceFactory->create('Session');

/*
 * Whitelist Ip address, check if user is 
 * logged in and session hasn't expired.
 */
$session->authenticate();

/*
 * Access Control List
 */
include CONFIG_PATH.'acl_settings.php';

$aclFactory = new AclFactory($roles,$resources,$rules);
$acl = $aclFactory->build();

$user = $session->currentUser();
$role = $user->role();
$resource = $request->getResourceName();
$command = $request->getCommand();

// User trying to access unauthorized page
if (!$acl->isAllowed($role, $resource, $command) {
    $request->setResourceName('Session');
    $request->setCommand('index');
    if ($role === 'blocked') {
        $request->setResourceName('Error');
    }
}

/* 
 * Initialization of View 
 */
$class = '\\MyApp\\View\\' . $request->getResourceName();
$view = new $class($serviceFactory, $acl);

/*
 * Initialization of Controller
 */
$class = '\\MyApp\\Controller\\' . $request->getResourceName();
$controller = new $class($serviceFactory, $view, $acl);

/*
 * Execute the necessary command on the controller
 */
$command = $request->getCommand();
$controller->{$command}($request);

/*
 * Produces the response
 */
$view->{$command}
$view->render();

I start the session and authorize the user in the Session model. The session's currentUser will have a role of 'guest' if not logged in, or 'blocked' if it's IP address is not in the whitelist. I wanted to implement the Controller wrapper as suggested teresko's previous ACL post, but I needed something that would redirect the user's instead. I send them to their homepage (Session#index) if they try to access a page they aren't allowed to, or to Error#index if they are blocked. Session#index will let the View decide whether or not to display the homepage for a logged in user, or the login page if they aren't logged in (by checking the user's role). Maybe not the best solution, but doesn't seem too terrible.

Community
  • 1
  • 1
Katrina
  • 1,922
  • 2
  • 24
  • 42
  • 1
    I would say that session is form of persistence. And thus - it should probably go into various mappers. As for that ancient ACL post, you can make it that `$currentUser` is anonymous, when unauthenticated. And have specific access rules for those anonymous users. Oh .. and those answers are a bit dated :( – tereško Apr 23 '15 at 00:46
  • 2
    @tereško Just please write a book already so I can read it. They may be outdated, but it still made me understand the concepts better than most tutorials out there. Anyway, so you're saying I should have a mapper for session? Or I should implement session in various mappers? – Katrina Apr 23 '15 at 13:32
  • @tereško re-read your comment. Obviously you meant multiple mappers, hence the word "various". Also, I really like the 'anonymous user' thanks. – Katrina Apr 24 '15 at 14:52

1 Answers1

3

Single Responsibility

Your session object is doing too many things. Sessions are more or less just for persistence across requests. The session shouldn't be doing any authentication logic. You store an identifier for the logged in user in the session, but the actual validation, logging in/out should be done in an authentication service.

Route Management

Importing different routes based on the users authentication status will not scale well and will be a pain to debug later when you have a lot more routes. It would be better to define all your routes in one place and use the authentication service to redirect if not authorized. I'm not very familiar with that router but looking at the documentation you should be able to so something like

$router->respond(function ($request, $response, $service, $app) { 
    $app->register('auth', function() {
        return new AuthService();
    }
});

Then on routes you need to be logged in for you can do something like

$router->respond('GET', '/resource', function ($request, $response, $service, $app) {
    if( ! $app->auth->authenticate() )
        return $response->redirect('/login', 401);
    // ...
});
jfadich
  • 6,110
  • 2
  • 20
  • 31
  • So, would I need to add the `if ( ! $app->auth->authenticate() )` and the redirect for every resource? Is it okay that the app object, which isn't a controller, interacts with a service? Also, would I implement an ACL in the app object as well? Something like `$app->register('acl' function() { return new ACL(); }` and then later ... `if (! $app->acl->allowedResource($resource) ) ` ... redirect? – Katrina Apr 23 '15 at 11:21
  • For the authentication you can put it in every secured route but it would probably be easier to put it in a route filter/group or middleware depending on the structure of the rest of your app. The solution you gave for ACLs should work. The app object is acting as a container in this instance, it's not really "interacting" with the service at all beyond storing it for easy access. Think of services as your interface with business logic. Your Http logic should simply be a pathway to your business logic. Http logic includes controllers and routers. – jfadich Apr 23 '15 at 21:57
  • Thank you. I think I was just getting confused because I didn't want my business logic for authentication to be leaked into my controllers. I will update my answer with my changed index.php for others to see. I took a different approach than what we discussed, but it's a similar concept. Accepted your answer. – Katrina Apr 24 '15 at 14:51