How to allow local scripts to run, with Auth enabled? - cakephp

I am using SWFUpload and some other scripts that print and download PDF's.
The thing is that the machine tries to access a page in order to generate the data for the scripts, but it hits the Authorization requirements.
I am using a custom made ip check for this scripts and put them in $this->Auth->allow().
I would like to allow all requests from 127.0.0.1.
What is the correct workflow, for custom scripts?
Should I allow() all methods and checkIP() or should I make a user for the system and authorize him. I find the last one a little bit to much just for a local connection!

Ok, so I figured out the solution, as it follows:
I wrote an ip check method in core class Security
public static function checkIPBased($userId = false, $ipPool = array()) {
$permission = FALSE;
//CHECK BY IP IN IP POOL
$ipAllowed = array_merge($ipPool, array(
'localhost',
'127.0.0.1',
'::1',
));
foreach ($ipAllowed as $ip):
if (strpos($_SERVER['REMOTE_ADDR'], $ip) === 0)
$permission = true;
endforeach;
//CHECK IF THE USER IS ACCESING HIS ORDER
if ($userId && (($userId == AuthComponent::user('id')) || ((int) AuthComponent::user('group_id') === 1)))
$permission = true;
return $permission;
}
After that I wrote in AppController in beforeFilter() the following code:
if (Security::checkIPBased())
$this->Auth->allow();
This way I am allowing the machine to access all methods while still using Auth Component!

Related

Authenticating guest/anonymous users using JWT

I'm building an SPA that a user can upload some files. The front-end is written in AngularJS while the back-end is using an API in Laravel 5.7. The authentication is implemented using the jwt-auth library.
So far I have implemented this for registered users where each user has a personal directory on the server where he/she uploads the files. The difference between the registered and the anonymous users is that the files of the anonymous will be deleted after a while.
Now, I want to do the same for anonymous/guest users (if the press the button continue as a guest). So what I tried first in the authContrroller.php side is to use something like this:
public function authentication(Request $rrequest) {
$credentials = $request->only('email', 'password');
// Guest authentication
if( $credentials['email'] === 'guest' && $credentials['password'] === 'guest' )
{
$payload = auth()->factory()->claims(['sub' => $this->createRandomDir()])->make();
$token = auth()->manager()->encode($payload);
// OR
$factory = JWTFactory::customClaims([
'sub' => $this->createRandomDir(),
]);
$payload = $factory->make();
$token = JWTAuth::encode($payload);
}
// Registered user authentication authentication
else
{
if (! $token = auth()->setTTL(60)->attempt($credentials))
return response()->json(['error' => 'invalid_credentials'], 400);
}
return response()->json(compact('token'));
}
The idea was to create a random directory and enclose it inside the payload and use it on the next requests.
But in the case of the guest, the server returns as a token an empty object. Possibly because there wasn't a user in the DB.
Another idea that I'm thinking of is to create a random user (add it to the DB) and assign to it a random directory each time a user needs to use the app as guest/anonymous. The only thing that I'm afraid on that approach is that if there are thousands of guests then thousands random users should be created on the DB.
So what do you think? Is there any other and more efficient way to handle this?
Any idea is welcomed.

PrestaShop - Login programmatically

I am writing a android and windows native app. The native app stores the login details as reated for mulitple other web apps, and logs them into this when browsing to them from the native app.
one of the buttons in my app open a prestashop site for a authenticated user. How can i set the username and password and log that user in to the site programmitcally, giving the illusion and user experience that he has been seemlessly authenticated and accessed to his shop.
I know this is an old question, but theres another way which i find better for the purpose.
You include the AuthController from the controllers folder, set your post-parameters and execute the postProcess() method. After this, you can check the "$authController->errors" array for errors. If it's empty - the login was successful.
Example:
public function hookDisplayHeader()
{
if ($this->context->cookie->isLogged())
{
return;
} else {
$acceptLogin = false;
if( isset( $_POST["email"] ) && isset( $_POST["passwd"] ) )
{
$acceptLogin = $this->attemptLogin($_POST["email"],$_POST["passwd"]);
}
if( $acceptLogin )
return;
die( $this->display(__FILE__, 'logintemplate.tpl') );
}
}
protected function attemptLogin($email, $password)
{
include _PS_FRONT_CONTROLLER_DIR_ . "AuthController.php";
$auth = new AuthController();
$auth->isAjax = true;
$_POST["email"] = $email;
$_POST["passwd"] = $password;
$_POST["SubmitLogin"] = true;
$auth->postProcess();
if( count($auth->errors) > 0 )
{
$this->context->smarty->assign( "errors", $auth->errors );
return false;
} else {
return true;
}
}
Edit: This no longer works with Prestashop 1.6. As of PS 1.6 $auth->postProcess() either redirects or sends the ajaxs response immediately. There is no way to circumvent this. If you want to do something after login, you have to make two ajax calls.
Basically do the same as the PrestaShop login form does, which is (for v1.5 at least):
Sending a POST request to http(s)://yourshop.com/index.php?controller=authentication with the following parameters:
email: your customer's email address
passwd: your customer's password
back: name of the controller you want to be redirected to after success (ex: my-account)
SubmitLogin: put anything there, it just needs to be true, so that the controller knows it's a login action
If it doesn't work, your version may work differently and you will have to check the network tab of your favourite developer tool, to see what kind of request is sent with which parameters.

Building realtime app using Laravel and Latchet websocket

I'm building a closed app (users need to authenticate in order to use it). I'm having trouble in identifying the currently authenticated user from my Latchet session. Since apache does not support long-lived connections, I host Latchet on a separate server instance. This means that my users receive two session_id's. One for each connection. I want to be able to identify the current user for both connections.
My client code is a SPA based on AngularJS. For client WS, I'm using the Autobahn.ws WAMP v1 implementation. The ab framework specifies methods for authentication: http://autobahn.ws/js/reference_wampv1.html#session-authentication, but how exactly do I go about doing this?
Do I save the username and password on the client and retransmit these once login is performed (which by the way is separate from the rest of my SPA)? If so, won't this be a security concearn?
And what will receive the auth request server side? I cannot find any examples of this...
Please help?
P.S. I do not have reputation enough to create the tag "Latchet", so I'm using Ratchet (which Latchet is built on) instead.
Create an angularjs service called AuthenticationService, inject where needed and call it with:
AuthenticationService.check('login_name', 'password');
This code exists in a file called authentication.js. It assumes that autobahn is already included. I did have to edit this code heavily removing all the extra crap I had in it,it may have a syntax error or two, but the idea is there.
angular.module(
'top.authentication',
['top']
)
.factory('AuthenticationService', [ '$rootScope', function($rootScope) {
return {
check: function(aname, apwd) {
console.log("here in the check function");
$rootScope.loginInfo = { channel: aname, secret: apwd };
var wsuri = 'wss://' + '192.168.1.11' + ':9000/';
$rootScope.loginInfo.wsuri = wsuri;
ab.connect(wsuri,
function(session) {
$rootScope.loginInfo.session = session;
console.log("connected to " + wsuri);
onConnect(session);
},
function(code,reason) {
$rootScope.loginInfo.session = null;
if ( code == ab.CONNECTION_UNSUPPORTED) {
console.log(reason);
} else {
console.log('failed');
$rootScope.isLoggedIn = 'false';
}
}
);
function onConnect(sess) {
console.log('onConnect');
var wi = $rootScope.loginInfo;
sess.authreq(wi.channel).then(
function(challenge) {
console.log("onConnect().then()");
var secret = ab.deriveKey(wi.secret,JSON.parse(challenge).authextra);
var signature = sess.authsign(challenge, secret);
sess.auth(signature).then(onAuth, ab.log);
},ab.log
);
}
function onAuth(permission) {
$rootScope.isLoggedIn = 'true';
console.log("authentication complete");
// do whatever you need when you are logged in..
}
}
};
}])
then you need code (as you point out) on the server side. I assume your server side web socket is php coding. I can't help with that, haven't coded in php for over a year. In my case, I use python, I include the autobahn gear, then subclass WampCraServerProtocol, and replace a few of the methods (onSessionOpen, getAuthPermissions, getAuthSecret, onAuthenticated and onClose) As you can envision, these are the 'other side' of the angular code knocking at the door. I don't think autobahn supports php, so, you will have to program the server side of the authentication yourself.
Anyway, my backend works much more like what #oberstat describes. I establish authentication via old school https, create a session cookie, then do an ajax requesting a 'ticket' (which is a temporary name/password which i associate with the web authenticated session). It is a one use name/password and must be used in a few seconds or it disappears. The point being I don't have to keep the user's credentials around, i already have the cookie/session which i can create tickets that can be used. this has a neat side affect as well, my ajax session becomes related to my web socket session, a query on either is attributed to the same session in the backend.
-g
I can give you a couple of hints regarding WAMP-CRA, which is the authentication mechnism this is referring:
WAMP-CRA does not send passwords over the wire. It works by a challenge-response scheme. The client and server have a shared secret. To authenticate a client, the server will send a challenge (something random) that the client needs to sign - using the secret. And only the signature is sent back. The client might store the secret in browser local storage. It's never sent.
In a variant of above, the signing of the challenge the server sends is not directly signed within the client, but the client might let the signature be created from an Ajax request. This is useful when the client was authenticated using other means already (e.g. classical cookie based), and the signing can then be done in the classical web app that was authenticating.
Ok, Greg was kind enough to provide a full example of the client implementation on this, so I wont do anything more on that. It works with just a few tweaks and modifications to almost any use-case I can think of. I will mark his answer as the correct one. But his input only covered the theory of the backend implementation, so I will try to fill in the blanks here for postparity.
I have to point out though, that the solution here is not complete as it does not give me a shared session between my SPA/REST connection and my WS connection.
I discovered that the authentication request transmitted by autobahn is in fact a variant of RPC and for some reason has hardcoded topic names curiously resembling regular url's:
- 'http://api.wamp.ws/procedure#authreq' - for auth requests
- 'http://api.wamp.ws/procedure#auth' - for signed auth client responses
I needed to create two more routes in my Laravel routes.php
// WS CRA routes
Latchet::topic('http://api.wamp.ws/procedure#authreq', 'app\\socket\\AuthReqController');
Latchet::topic('http://api.wamp.ws/procedure#auth', 'app\\socket\\AuthReqController');
Now a Latchet controller has 4 methods: subscribe, publish, call and unsubscribe. Since both the authreq and the auth calls made by autobahn are RPC calls, they are handled by the call method on the controller.
The solution first proposed by oberstet and then backed up by Greg, describes a temporary auth key and secret being generated upon request and held temporarily just long enough to be validated by the WS CRA procedure. I've therefore created a REST endpoint which generates a persisted key value pair. The endpoint is not included here, as I am sure that this is trivial.
class AuthReqController extends BaseTopic {
public function subscribe ($connection, $topic) { }
public function publish ($connection, $topic, $message, array $exclude, array $eligible) { }
public function unsubscribe ($connection, $topic) { }
public function call ($connection, $id, $topic, array $params) {
switch ($topic) {
case 'http://api.wamp.ws/procedure#authreq':
return $this->getAuthenticationRequest($connection, $id, $topic, $params);
case 'http://api.wamp.ws/procedure#auth':
return $this->processAuthSignature($connection, $id, $topic, $params);
}
}
/**
* Process the authentication request
*/
private function getAuthenticationRequest ($connection, $id, $topic, $params) {
$auth_key = $params[0]; // A generated temporary auth key
$tmpUser = $this->getTempUser($auth_key); // Get the key value pair as persisted from the temporary store.
if ($tmpUser) {
$info = [
'authkey' => $tmpUser->username,
'secret' => $tmpUser->secret,
'timestamp' => time()
];
$connection->callResult($id, $info);
} else {
$connection->callError($id, $topic, array('User not found'));
}
return true;
}
/**
* Process the final step in the authentication
*/
private function processAuthSignature ($connection, $id, $topic, $params) {
// This should do something smart to validate this response.
// The session should be ours right now. So store the Auth::user()
$connection->user = Auth::user(); // A null object is stored.
$connection->callResult($id, array('msg' => 'connected'));
}
private function getTempUser($auth_key) {
return TempAuth::findOrFail($auth_key);
}
}
Now somewhere in here I've gone wrong. Cause if I were supposed to inherit the ajax session my app holds, I would be able to call Auth::user() from any of my other WS Latchet based controllers and automatically be presented with the currently logged in user. But this is not the case. So if somebody see what I'm doing wrong, give me a shout. Please!
Since I'm unable to get the shared session, I'm currently cheating by transmitting the real username as a RPC call instead of performing a full CRA.

CakePHP perform Auth before Constructing Models?

In my CakePHP application I have multi-tenancy which is provided through isolated databases (each tenant has their own, tenant-specific database).
There is also a 'global' database which contains users and tenancy information. The 'tenants' table contains the name of which database the particular tenant occupies. Each user contains a single tenant_id.
Structure:
global_db:
users (contains tenant_id foreign key)
tenants (contains tenant-specific database name, ie: 'isolated_tenant1_db')
isolated_tenant1_db:
orders
jobs
customers
isolated_tenant2_db:
orders
jobs
customers
This system works correctly when the user is logged in via forms / sessions. When they login through /Users/login their tenancy is verified, stored in Session, and database parameters are loaded so their own 'isolated' models can use this dynamic connection.
However, issues arise when the user tries to login via Basic Auth, and directly request the controller function they want to access. For example /Orders/view/1.xml.
In this case, CakePHP attempts to construct the 'Order' Model before the user has been logged in, and therefore before any tenancy information is available - which means it has no idea what database to connect to in order to access orders.
From putting debug() statements around the place I can see that the order in which models / controllers / auth are constructed / executed is as follows (when executing /Orders/view/1.xml):
Model __construct: User
Controller __construct: OrdersController
Model __construct: Permission
Model __construct: Order
function: OrdersController/beforeFilter
AuthComponent __startup
Model __construct: Models related to Order
My problem is that AuthComponent::_startup is executed after Order Model has been constructed. I need to attempt to login the user (and get their database information) before this 'Order' model is constructed.
Questions:
What causes the User model to be constructed before anything else? (I also have the default CakePHP ACL enabled)
Where in the App can I put a call to Auth->login() to attempt login if the request contains BasicAuth headers, that will be executed prior to trying to load tenant-specific models? I assume putting this inside User __construct is a very bad idea.
== UPDATE 01/05/2014 ==
Inserting code samples.
bootstrap.php:
Checks whether the request is being made to api. subdomain:
// Determine whether the request is coming from the api.* subdomain, and if so set the API_REQUEST define to true.
if (preg_match('/^api\./i',$_SERVER['HTTP_HOST']))
{
define('API_REQUEST',true);
// Any links generated (in emails etc), will contain the full base url. If a cron job logged in via the API is generating
// those e-mails, then users will receive links to api.mydomain, instead of just mydomain.
$full_base_url = Router::fullBaseUrl();
$new_full_base_url = preg_replace('/\/\/api\./i', '//', $full_base_url);
Router::fullBaseUrl($new_full_base_url);
CakeLog::write('auth_base_url_debug', 'modified fullbaseurl from ' . $full_base_url . ' to ' . $new_full_base_url);
}
else
{
define('API_REQUEST',false);
}
AppController.php:
public $components = array(
'Security',
'Session',
'Acl',
'Auth' => array(
'className' => 'ExtendedAuth',
'authenticate' => array(
'FormAlias',
),
'authorize' => array(
'Actions' => array('actionPath' => 'controllers')
),
'loginRedirect' => array('controller' => 'Consignments', 'action' => 'index'),
'logoutRedirect' => array('controller' => 'Users', 'action' => 'login'),
),
//'Users.RememberMe',
);
function beforeFilter()
{
// Reroute all requests to API subdomain (ie: api.mydomain) to api_ prefixed actions.
// Also, enable Basic Authentication if the user is accessing via api.*
// If login fails, return 401 error instead of 302 redirect to login page.
if(API_REQUEST == true)
{
$this->params['action'] = 'api_'.$this->params['action']; // prefix the actions with api_
$this->Auth->authenticate = array('BasicAlias'); // Switch to using Basic Authentication
if($this->Auth->login() == false) // Attempt Basic Auth Login
{ // Login failed
CakeLog::write('auth_api', 'Unauthorized API request to: ' . $this->params['action']);
header("HTTP/1.0 401 Unauthorized"); // Force returning an Unauthorized header (401)
exit; // MUST BE CALLED TO PREVENT 302 BEING SENT!
}
}
}
It is important to note that BasicAlias Auth Component is not included in the $components within AppController, but used dynamically if the request is to the api.* subdomain. However, the order in which classes are constructed has no effect whether BasicAlias AuthComponent is included in $components, or used dynamically as shown above.
AppModel:
function __construct($id = false, $table = null, $ds = null)
{
if(($ds == null) && ($this->use_tenant_database == true))
{
// Create a connection to the tenants database and configure model to use this connection.
$Tenant = ClassRegistry::init('Tenant');
$db_name = $Tenant->checkAndCreateTenantDatabaseConnectionForCurrentUser();
if($db_name == false)
{
header("HTTP/1.0 500 Server Error"); // Force returning a Server Error Header (500)
debug('AppModel::$db_name = false, unable to proceed');
CakeLog::write('tenant_error', 'db_name = false, unable to connect.');
exit; // MUST BE CALLED TO PREVENT 302 BEING SENT!
}
// Point model to the tenant database connection:
$this->useDbConfig = $db_name;
}
parent::__construct($id, $table, $ds);
}
And then within any models which use a specific tenant database:
class Order extends AppModel
{
var $use_tenant_database = true;
...
}
Tenant.php:
/**
* Check whether a connection to the current users tenant database has already been created and if so, return its name.
* Otherwise, create the connection and return its name.
*
* #return boolean|Ambigous <mixed, multitype:, NULL, array, boolean>
*/
public function checkAndCreateTenantDatabaseConnectionForCurrentUser()
{
// Check whether we have the tenants database connection information available in the Configure variable:
if(Configure::check('Tenant.db_name') == true)
{ // the db_config is available in configure, use it!
$db_name = Configure::read('Tenant.db_name');
}
else
{ // The tenants db_name has not been set in the configure variable, we need to create a database connection and then
// set the configure variable.
$tenant_id = $this->getCurrentUserTenantId();
if($tenant_id == null)
{ // Unable to resolve the tenant_id, instead, connect to the default database.
debug('TRIED TO CONSTRUCT MODEL WITHOUT KNOWING TENANT DATABASE!!');
exit;
}
$db_name = $this->TenantDatabase->createConnection($tenant_id);
if($db_name == false)
{ // The database connection could not be created.
CakeLog::write('tenant_error', 'unable to find the database name for tenant_id: ' . $tenant_id);
return false;
}
Configure::write('Tenant.db_name', $db_name);
}
return $db_name;
}
So, if the user requests a URL for example:
http://api.mydomain.com/Orders/getAllPendingOrders
Where they have supplied BASIC auth credentials along with the request, then what happens is that classes are constructed / executed in the following order:
Model __construct: User
Controller __construct: OrdersController
Model __construct: Permission
Model __construct: Order
Model __construct: Tenant
Model __construct: TenantDatabase
function: OrdersController/beforeFilter
AuthComponent __startup --> This then performs the login.
Model __construct: other models.
The problem is: Order.php is being constructed the user has been logged in, which means when the code in AppModel.php is executed:
$db_name = $Tenant->checkAndCreateTenantDatabaseConnectionForCurrentUser();
It is unable to determine the users current tenancy.
I need to find out a workaround for this, either by somehow performing the login BEFORE Order.php is constructed, or hacking it so that if you attempt to construct a model which has $use_tenant_database = true, and the user is not logged in, then BasicAuth is performed at this point to try and login the user.. however this feels wrong to me.
You might want to have a look at Authorization (who’s allowed to access what) portion in Cake's documentation. Specifically look at the isAuthorized function and how it works.
You might need something like this in your Orders controller:
// app/Controller/OrdersController.php
public function isAuthorized($user) {
// All registered users can add posts
if ($this->action === 'add') {
return true;
}
// The owner of an order can edit and delete it
if (in_array($this->action, array('edit', 'delete'))) {
$orderId = (int) $this->request->params['pass'][0];
if ($this->Order->isOwnedBy($orderId, $user['id'])) {
return true;
}
}
return parent::isAuthorized($user);
}
Implement your logic in before filter Request Life-cycle callback in the app controller.
Controller::beforeFilter() :
This function is executed before every action in the controller. It’s a handy place to check for an active session or inspect user permissions.
http://book.cakephp.org/2.0/en/controllers.html
It turns out these models were being constructed by the 'Search.Prg' plugin, a CakeDC plugin for handling search / filtering of results. The initialize() function within the component was being executed and causing the model to be constructed prior to the user being logged in.
The way in which this was solved was to move the Basic Auth check / login process from AppController beforeFilter to ExtendedAuthComponent (my own custom authenciation component) initialize function.
The end code was this:
ExtendedAuthComponent.php
public function initialize(Controller $controller)
{
parent::initialize($controller); // Call parent initialization first, this sets up request and response variables.
$this->controller = $controller;
// Reroute all requests to API subdomain (ie: api.rms.roving.net.au) to api_ prefixed actions.
// Also, enable Basic Authentication if the user is accessing via api.*
// If login fails, return 401 error instead of 302 redirect to login page.
if(API_REQUEST == true)
{
$controller->params['action'] = 'api_'.$controller->params['action']; // prefix the actions with api_
if($this->loggedIn() == false) // Attempt Basic Auth Login
{ // Login failed
$this->authenticate = array('BasicAlias'); // Switch to using Basic Authentication
if($this->login() == false)
{
CakeLog::write('auth_api', 'Unauthorized API request to: ' . $this->params['action']);
header("HTTP/1.0 401 Unauthorized"); // Force returning an Unauthorized header (401)
exit; // MUST BE CALLED TO PREVENT 302 BEING SENT!
}
}
}
}
This causes the user to be logged in via Basic Auth before the Search.Prg components initialize() function is run, which means the users tenancy is determined before the model(s) are constructed, solving the problem.

CakeDC admin is accessible by all kinds of users/roles by default, make it secure

I've installed CakeDC Users plugin and I found out that role, is_admin don't function by default. If I login with regular username role=registered and is_admin=0, I can still go to /admin/users/add/. Why are there two types of checks, role and is_admin, what if role=administrator and is_admin=0, or vice-versa?
I am looking for a preferred solution to this problem so I could secure admin section and make use of user roles on different pages. Still, can't understand why is_admin is present, when role=administrator could take care of it all.
I solved the very same issue by adding the following piece of code in "app/Controller/AppController.php" in method "beforeFilter()" :
if (isset($this->params['prefix']) && $this->params['prefix'] == 'admin') {
if ($this->Session->check('Auth.User.role') and 'admin' !== $this->Session->read('Auth.User.role')) {
$this->Auth->deny('*');
return $this->flash('Non admin access unauthorized', '/');
}
}
While I admit this solution is not optimal, it sure does the trick!
This is not an issue of the plugin: You have to implement your auth application wide on your own. The plugin just gives you the basics but does not your job of customizing the app based on the requirements of your client. I recommend you to read this chapter http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html
The is_admin check AND role field are there for multiple reasons: Your user can have any role but only if they have is_admin checked they can access an admin area for example. is_admin alone does not allow you to have roles. Both fields are there to cover different scenarios. Again, the plugin is thought to be a kick start and base you can build on. That's what you have to do when you want to customize it.
There is an example that shows pretty much how to use whatever you need:
http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#using-controllerauthorize
class AppController extends Controller {
public $components = array(
'Auth' => array('authorize' => 'Controller'),
);
public function isAuthorized($user = null) {
// Any registered user can access public functions
if (empty($this->request->params['admin'])) {
return true;
}
// Only admins can access admin functions
if (isset($this->request->params['admin'])) {
return (bool)($user['role'] === 'admin');
}
// Default deny
return false;
}
}

Resources