Route URI to a specific folder in CakePHP - php

Introduction :
I have this path in my CakePHP : app/webroot/storage/5/C/_demo/omar.txt , this one will be redirected to http://localhost/storage/5/C/_demo/omar.txt
My problem :
I want this path http://localhost/storage/5/C/_demo/omar.txt to be something like this http://localhost/storage/uehKDj44/C/_demo/omar.txt , where uehKDj44 is number 5 , the reason why I'm doing like that is I don't want anyone to change number 5 to any number so they can't access otherwise they login first , let's say if 5 belongs to a user will be open , but if not won't be open , is there any better way to secure it better than that ?
Thanks

Storing restricted data as plain files inside of the htdocs folder (or webroot for CakePHP), where they can be requested without further authorization is always a risky business and should be avoided.
I cannot determine what kind of data you store in the .txt file, but I assume it is not fetched from the database and then saved. My understanding is that the link to the file is displayed to a logged in (authorized) user.
Proposal of a more secure solution:
Move the files outside of the webroot folder and create a constant with the absolute path to that folder (USERDATA_PATH). Remember to set read permission for web server user (www-data for Apache)
Create a model, e.g. UserData with an underlying database table storing a relation between a user and a magic hash (e.g. 1 => 'uehKDj44', 2 => 'ds83skf' etc.). You can also store file names to make it a bit less complicated.
Create a controller UserDataController with an action serveFile which will take the secret key as a parameter, match it with the file and output the file to the user.
public function serveFile($hash= null) {
try
{
$data = $this->UserData->findByHash($hash);
if (!$data) {
throw new Exception('No match found');
}
// Load the data from file
$this->set('output', file_get_contents(USERDATA_PATH.DS.$data['UserData']['filename']));
}
catch (Exception $ex)
{
// No match found - display error message / log
}
}
Then in view:
header('Content-type: text/plain');
echo $output;
This should do the trick.

Related

Save temporary data with CakePhp

I am looking for a solution in CakePhp, to store and read temporary datas :
I read some XML from others websites in order to display some news in my website, but on each page load, it does a call to the other xml websites.
Is there a way (memcached like) to save temp. data in CakePhp in order to store data for 1 hour and read temp. data to display them in my webpages ; then 1 hour after update them (with cron) ?
Thanks.
CakePHP Caching seems what you'd want.
WHICH cache you use (Redis, Memcache...etc) would be up to you though. Set your cache to last an hour, and you're all set. (read more about cache on the link above).
If you're on CakePHP 2.5+, you can use the remember method described here.
public function newest() {
$model = $this;
return Cache::remember('newest_posts', function() use ($model){
// get your data from whatever source here, and return it
return $model->getMyData();
}, 'long');
}
Basically, this just checks to see if the cache key exists, and if not, runs some code in order to populate it again.
If you're below 2.5, you can do the same basic thing, but without the remember:
public function newest() {
$result = Cache::read('newest_posts', 'long');
if (!$result) {
// get your data from whatever source here, and write it
Cache::write('newest_posts', $this->getMyData(), 'long');
}
return $result;
}
If you don't have a cache engine installed or are aren't wanting to mess w/ your own server, there are companies that you can use for cache, and you can just set your cache settings to connect to them. ObjectRocket (Redis) is the one I know offhand, but I'm sure there are plenty.
One of many awesome things about CakePHP, is that in this case, your code doesn't change regardless of Cache type/location/configuration you choose.

How to validate a directory name before calling mkdir with PHP?

I would like to create a function that will create a directory to store photos in the file system. Suppose there is a directory called "albums" in the root directory. There is a form on the page that allows the user to create a new album, which will create a subdirectory under "albums".
Also I want the ability to have nested photo albums. When creating a new album, the admin can provide the album name with the forward slash (/) separators to recursively create this folder structure.
I am using directories because I want the ability to manage the photo albums via direct FTP access to the server for dealing with massive photo albums. Each photo directory can have an optional album.properties file to have any meta data associated with the album itself (such as a thumbnail to use for the album).
if (!is_admin()) die();
$album_name = $_GET['album_name'];
if ($album_name) {
$directory = "/albums/" . $album_name;
// TODO: How can I validate $album_name?
if (file_exists($directory)) {
echo "Album already exists, please choose a different name.";
} else if (mkdir($album_name), 0777, true) { // Recursive is true
echo "Album successfully created.";
} else {
// TODO: Is there a way to output a more detailed explanation?
echo "The album could not be created.";
}
}
See the TODO markers in the PHP code. Also if you have any advice regarding my approach so far, that would be useful.
Note that it was only recently that I decided to use directories and property files instead of database tables to store meta data associated with the albums/photos because I only recently found out that the client wished to use FTP.
I will still need to have database tables, though, to allow the admin to assign privileges to already existing albums. For example, the admin can share a particular album with the public or other non-admin users. The /albums directory will not be directly accessible, instead another .php will allow download of photos or read meta data, but only to permissioned users.
Edit:
Adding more clarification on what I want:
To validate that the $album_name is a valid directory name, if it is not valid I want to output an error message. Also I would like to explain to the user how to choose a valid directory name.
I want to output a more detailed message if mkdir fails, to tell the user what he/she can do to correct the problem.
You can use a regex or just check to see if the mkdir was successful.
See the answers on this questions for reference:
Check a string for a valid directory name
Edit 1:
To check if the mkdir function will succeed you can first use php functions such as is_writable to check if you have permission to write in that location. If not you can let your user know (but if thats the case you should probably be considering why they are trying to write somewhere they dont have permission). Otherwise if the mk_dir function fails you will be able to log the failure and check the issue later. Other than that you probably shouldnt tell the user much without first investigating the problem

Jquery File Upload change session id

I've been trying to solve this problem for days without success.
I am using blueimp Jquery File Upload and everything works fine but I need to save my pictures in different folders depending on a parameter send by url like /index.php?customer=160
<input type="hidden" name="customer" value="<?php print $_GET["id_cliente"];?>">
I created the hidden field in the form and got it in the uploadhanndler.php.
/files/'.$_POST['customer'].'
, here everything goes ok, the file is saved on the folder I wanted, but the next time I open the window /index.php?customer=160, the files listed are the files/ folder not the files/160/ folder or the number I want to list.
I realized I could use the PHP user directories, and the files are beeing saved in a folder like this 6afa0f7338b14aeed39eb7656f364b4e that comes from the session_id(), I tried then to change the session_id() with the number of the folder I want this way at the begining of the /index.php?customer=160
session_start();
session_id($_GET['customer']);
but the files are still beeing saved in folder 6afa0f7338b14aeed39eb7656f364b4e, and when I print the session_id() is 160.
PHP user directories is a good method to achieve what I want? what I am doing wrong?
Thanks in advance and excuse my poor english.
The code below will show you how to save a session ID called customer_id and then use it as a folder name. Please let me know if you need any more help.
// start (or restart) the session
session_start();
// set the customer id session var
$_SESSION['customer_id'] = // set this variable something that you've retrieved from the DB
// create a folder if it doesn't already exist
$dir = '/httpdocs/mySite/customers/' . $_SESSION['customer_id'];
if (!file_exists($dir)) {
mkdir($dir, 0777, true);
}
even it has been a while since a last answer has been given to this question, I'd like to give a small update on that issue. Since version 5.17 of the jquery-file-upload library ther is a support for user-directories.
Within the UploadHandler.php you will find the option 'user_dirs' => false. Just set it to 'true' and you will have user-directories based on session-id.
If you want to have your own user-directories not based on session-ids, but e.g. based on user-id (your own defined Session key) you can proceed in the following way:
within the index.php file (as stated in the Demo of the jquery-file-upload library) you place following
right after "require('UploadHandler.php');"
class CustomUploadHandler extends UploadHandler {
protected function get_user_id() {
#session_start();
return $_SESSION[*<your own user id session key>*];
}
}
followed by:
$upload_handler = new CustomUploadHandler(array(
'user_dirs' => true
));
...and thats it...now you have your own user-directories for your uploaded files

Securely serving files from Amazon S3

I have an app that uploads user files to S3. At the moment, the ACL for the folders and files is set to private.
I have created a db table (called docs) that stores the following info:
id
user_id
file_name (original file as specified by the user)
hash_name (random hash used to save the file on amazon)
So, when a user wants to download a file, I first check in the db table that they have access to file. I'd prefer to not have the file first downloaded to my server and then sent to the user - I'd like them to be able to grab the file directly from Amazon.
Is it OK to rely on a very very long hashname (making it basically impossible for anyone to randomly guess a filename)? In this case, I can set the ACL for each file to public-read.
Or, are there other options that I can use to serve the files whilst keeping them private?
Remember, once the link is out there, nothing prevents a user from sharing that link with others. Then again, nothing prevents the user from saving the file elsewhere and sharing a link to the copy of the file.
The best approach depends on your specific needs.
Option 1 - Time Limited Download URL
If applicable to your scenario, you can also create expiring (time-limited) custom links to the S3 contents. That would allow the user to download content for a limited amount of time, after which they would have to obtain a new link.
http://docs.amazonwebservices.com/AmazonS3/latest/dev/S3_QSAuth.html
Option 2 - Obfuscated URL
If you value avoiding running the file through your web server over the risk that a URL, however obscure, might be intentionally shared, then use the hard-to-guess link name. This would allow a link to remain valid "forever", which means the link can be shared "forever".
Option 3 - Download through your server
If you are concerned about the link being shared and certainly want users to authenticate through your website, then serve the content through your website after verifying user credentials.
This option also allows the link to remain valid "forever" but require the user to log in (or perhaps just have an authentication cookie in the browser) to access the link.
I just want to post the PHP solution with code, if anybody has the same problem.
Here's the code I used:
$aws_access_key_id = 'AKIAIOSFODNN7EXAMPLE';
$aws_secret_key = 'YourSecretKey12345';
$aws_bucket = 'bucket';
$file_path = 'directory/image.jpg';
$timeout = '+10 minutes';
// get the URL!
$url = get_public_url($aws_access_key_id,$aws_secret_key,$aws_bucket,$file_path,$timeout);
// print the URL!
echo($url);
function get_public_url($keyID, $s3Key, $bucket, $filepath, $timeout)
{
$expires = strtotime($timeout);
$stringToSign = "GET\n\n\n{$expires}\n/{$aws_bucket}/{$file_path}";
$signature = urlencode(hex2b64(hmacsha1($s3Key, utf8_encode($stringToSign))));
$url = "https://{$bucket}.s3.amazonaws.com/{$file_path}?AWSAccessKeyId={$keyID}&Signature={$signature}&Expires={$expires}";
return $url;
}
function hmacsha1($key,$data)
{
$blocksize=64;
$hashfunc='sha1';
if (strlen($key)>$blocksize)
$key=pack('H*', $hashfunc($key));
$key=str_pad($key,$blocksize,chr(0x00));
$ipad=str_repeat(chr(0x36),$blocksize);
$opad=str_repeat(chr(0x5c),$blocksize);
$hmac = pack(
'H*',$hashfunc(
($key^$opad).pack(
'H*',$hashfunc(
($key^$ipad).$data
)
)
)
);
return bin2hex($hmac);
}
function hex2b64($str)
{
$raw = '';
for ($i=0; $i < strlen($str); $i+=2)
{
$raw .= chr(hexdec(substr($str, $i, 2)));
}
return base64_encode($raw);
}

Symfony 2, passing variable

So how can I pass a variable into a script that is not connected with symfony?
For example, I have variable $a, which I render in template a.html.php
How can I use this $a in some example.php script?
More close: I wanna make image uploading with TinyMCE image manager; but each user should have their own directory (which corresponds to user id). So I should pass variable user_id to config.php file of image manager.
So this directory depends on the user's session! How can I make different dirs to upload images for each user? Or can you advise me how to deal with this problem?
Maybe to use another text editor? Which one can I connect with symfony or to use different directories for different users?
You can store information in the session if you want to share it with other scripts. Make a var_dump($_SESSION) in example.php to see what you already have.
$formPath = 'nameBundle:name:name.html.twig';
$session = $controler->getRequest()->getSession();
$session->set('formPath', $formPath);
$session->set('neededVariables', $neededVariables);
return $controler->redirect($controler->generateUrl('show_result'));
I used this to handle data and read data function
public function showResultAction() {
$session = $this->getRequest()->getSession();
$formPath = $session->get('formPath');
$neededVariables = $session->get('neededVariables');
if ($formPath or $neededVariables)
return $this->render($formPath,$neededVariables); else
throw $this->createNotFoundException('The product does not exist');
}

Categories