4 Replies - 943 Views - Last Post: 13 July 2018 - 05:41 PM

#1 ArtificialSoldier   User is offline

  • D.I.C Lover
  • member icon

Reputation: 2397
  • View blog
  • Posts: 7,304
  • Joined: 15-January 14

Authentication in Apache

Posted 13 July 2018 - 11:26 AM

All of our servers are running Apache, and our application has a feature to require someone to be logged in in order to access uploaded files. Right now we use htaccess for that - all requests in a certain directory get routed through a PHP script that can authenticate, and then send the file.

The majority of the time this works fine, but there are issues with some video files for some reason where, when this feature is on, the browser will send a request for a portion of the video and that request takes 5 minutes to finish. It doesn't actually time out - it ends up with a regular 206 response with the actual content, but for whatever reason the browser or server doesn't complete the request for 5 minutes. There aren't any PHP errors either. The major difference with the headers is some additional cache control headers when this is enabled but it doesn't seem like that would cause a 5 minute wait.

Ideally, I'd like to remove all of the PHP logic for dealing with the actual serving of the file, and have PHP only there for the authentication portion. After the user is authenticated, I'd like Apache to continue handling the request as it normally would. I don't know if that's possible or how though. Would it make sense to build a custom Apache module for this, so all of this is handled in the normal request processing pipeline instead of at the end? Is there another option?

Our authentication is happening only in the session, we don't check the database for all of these requests. So this needs to be aware of PHP sessions and capable of checking for a particular value in the session. Once we include the necessary files this is all we do to authenticate:

$logged_in = isset($_SESSION['userid']) && $_SESSION['userid'] > 0;


We also need to be capable of checking whether or not files exist on disk. In case something happens where the feature gets disabled, but the htaccess file doesn't get updated to remove the rewrite rule, the code checks for the existence of a file on disk to tell it whether the feature is enabled or not.

The vast majority of the code in this PHP file is dealing with the request like Apache should - checking for range headers and only returning partial content, checking cache headers to see if the file is newer or if we just quit with a last-modified header, etc, i.e. the stuff that Apache is good at. I'd like to continue to have Apache do that part of it, but I'm not aware of a way with PHP to "give back" the request to Apache and have it process it like the htaccess file isn't rewriting the request.

Is This A Good Question/Topic? 0
  • +

Replies To: Authentication in Apache

#2 modi123_1   User is offline

  • Suitor #2
  • member icon



Reputation: 15262
  • View blog
  • Posts: 61,187
  • Joined: 12-June 08

Re: Authentication in Apache

Posted 13 July 2018 - 11:55 AM

So I got the flow down..

You have an app that needs a file.
Someone logs into this app or creates a session?
The app calls out for a file in this special directory.
HTACCESS sees the call, pumps it to the php code for authentification of the session id.
the php code also does the file return
Was This Post Helpful? 0
  • +
  • -

#3 ArtificialSoldier   User is offline

  • D.I.C Lover
  • member icon

Reputation: 2397
  • View blog
  • Posts: 7,304
  • Joined: 15-January 14

Re: Authentication in Apache

Posted 13 July 2018 - 12:30 PM

More or less, yeah. We have a directory where all uploaded files get saved. A while ago someone noticed that, with the correct URL, anyone could access those files, and they wanted to prevent that.

So about 10% of this PHP file is authentication, and the rest is basically doing what Apache should with regard to headers and whatnot. I'd like to have Apache continue doing that part instead of PHP.

For reference, this is the actual code. There might be a bug here that is causing the 5-minute wait, and I'll look into that, but ideally I'd like to have the actual file serving part be done by Apache, with all of its modules and headers and whatnot. The htaccess file just says that for any URL in the resources directory, send the request to this PHP file and send it the original requested filename.

<?php

/*
This file is used for authorizing access to the resources directory.  This file is enabled by the .htaccess file inside the resources directory.
*/

//require_once 'include/global.init.php';
// Changes so that this file does not need database access
require_once 'include/global.config.php';

// When a user switches from LMS 8 to LMS 7, a warning was being generated in the log file:
//  `PHP Warning: Class __PHP_Incomplete_Class has no unserializer`
//
// The following code from https://github.com/zendframework/zf2/issues/3880 fixes that issue.
$current_dir = getcwd();
$zend_library_path = '/lms/public/../vendor/zendframework/zendframework/library/Zend';
chdir($zend_library_path);
include $zend_library_path . '/Loader/AutoloaderFactory.php';
Zend\Loader\AutoloaderFactory::factory([
    'Zend\Loader\StandardAutoloader' => [
        'autoregister_zf' => true
    ]
]);
chdir($current_dir);

session_name(!empty($config['session_name']) ? $config['session_name'] : 'lms8');
session_start();
$logged_in = isset($_SESSION['userid']) && $_SESSION['userid'] > 0;

$auth_file = $config['lms_root'] . $config['ds'] . 'resources' . $config['ds'] . 'auth';
$restrict_access = file_exists($auth_file);
$restrict_php = file_exists($auth_file . '_php');

if (!isset($_GET['_tc_lms_f'])) {
    exit('No file requested.');
}

// send a header identifying that this script is being used
header('X-Lms: auth');

$target = $_GET['_tc_lms_f'];
unset($_GET['_tc_lms_f']);

$base = $config['lms_root'] . $config['ds'] . 'resources';
$file = $base . $config['ds'] . $target;

// check to make sure if selected file is in the resources directory, so they can't request an arbitrary file from the server
$absfile = realpath($file);

if (isset($config['linked_root_dir'])) {
    $base = $config['linked_root_dir'] . $config['ds'] . 'resources';
}
if ($absfile === false || substr($absfile, 0, strlen($base)) != $base) {
    header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found");
    exit('HTTP 404: the file ' . $target . ' was not found');
}

$headers = apache_request_headers();
$method = $_SERVER['REQUEST_METHOD'];

// check if the option is even enabled; the htaccess file might be there but the option might be disabled
if (!$restrict_access || $logged_in) {
    if (file_exists($file)) {
        $parts = explode('.', $target);
        $ext = strtolower(array_pop($parts));

        // they have requested a PHP file - check for the appropriate options, remove LMS resources, and include the file
        if ($ext == 'php' && (!$restrict_access || !$restrict_php)) {
            ini_set('display_errors', 1);
            ini_set('log_errors', 0);
            //unset($sess, $db, $config, $lang, $uf, $opts, $cache, $global); // these should no longer be included
            chdir(dirname($file));
            include($file);
        } else {
            // all other file types, or PHP files also if the option to run them is disabled
            header('Cache-Control: ');
            header('Pragma: ');
            header('Expires: ');

            // check for caching
            // Checking if the client is validating his cache and if it is current.
            if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == filemtime($file))) {
                // Client's cache IS current, so we just respond '304 Not Modified'.
                header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT', true, 304);
                exit();
            } else {
                // file not cached or cache outdated, we respond '200 OK' (or 206) and output the file.
                if (!isset($headers['Range'])) {
                    $response_code = 200;
                } else {
                    $response_code = 206;
                }

                header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT', true, $response_code);
                header('Content-Type: ' . get_mime_type($file));
                if ($method == 'GET' || $method == 'HEAD') {
                    header('Accept-Ranges: bytes');
                }

                $filesize = filesize($file);
                $start_read_pos = 0;
                $max_read_chunk = $filesize;
                $content_length = $filesize;

                if (isset($headers['Range'])) {
                    $range_chunks = explode('=', $headers['Range']);
                    // the first element should be "bytes"
                    $range = $range_chunks[1]; // e.g. 1024-2048, or 1024-
                    $range_chunks = explode('-', $range);
                    $start_read_pos = intval($range_chunks[0]);
                    $end_read_pos = isset($range_chunks[1]) ? intval($range_chunks[1]) : 0;
                    if ($end_read_pos == 0) {
                        $end_read_pos = $filesize - 1;
                    }
                    $max_read_chunk = $end_read_pos - $start_read_pos + 1;
                    $content_length = $max_read_chunk;
                }

                header('Content-Length: ' . $content_length);
                if (isset($headers['Range'])) {
                    header('Content-Range: bytes ' . $start_read_pos . '-' . $end_read_pos . '/' . $filesize);
                }

                // only output the content if this is not a head request
                if ($method != 'HEAD') {

                    // read the file data

                    if ($ext != 'php') {
                        $fh = fopen($file, 'rb');
                        if ($fh) {
                            if ($start_read_pos != 0) {
                                fread($fh, $start_read_pos); // discard this many bytes
                            }

                            echo fread($fh, $max_read_chunk);

                            fclose($fh);
                        }
                    } else {
                        // don't show the PHP code
                        echo str_pad('PHP code', filesize($file)); // pad to match the content length header
                    }

                    exit();
                }
            }
        }
    } else {
        header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found");
        exit('HTTP 404: The file ' . $target . ' was not found.');
    }
} else {
    ?>
    <!doctype html>
    <html>
    <head>
        <title><?php echo $config['lms_title']; ?></title>
    </head>
    <body>
    Error: you must be logged in to access the requested content. <a href="<?php echo $config['http_root']; ?>">Log in here</a>.
    </body>
    </html>
    <?php
    exit();
}

// read from Apache's own list of mime types
function get_mime_type($filename, $mimePath = '/usr/local/apache/conf')
{
    $fileext = substr(strrchr($filename, '.'), 1);
    if (empty($fileext)) {
        return (false);
    }
    $regex = "/^([\w\+\-\.\/]+)\s+(\w+\s)*($fileext\s)/i";
    $lines = file("$mimePath/mime.types");
    foreach ($lines as $line) {
        if (substr($line, 0, 1) == '#') {
            continue;
        } // skip comments
        $line = rtrim($line) . " ";
        if (!preg_match($regex, $line, $matches)) {
            continue;
        } // no match to the extension
        return ($matches[1]);
    }
    return ('application/octet-stream'); // no match at all
}


Was This Post Helpful? 0
  • +
  • -

#4 ArtificialSoldier   User is offline

  • D.I.C Lover
  • member icon

Reputation: 2397
  • View blog
  • Posts: 7,304
  • Joined: 15-January 14

Re: Authentication in Apache

Posted 13 July 2018 - 04:48 PM

I'm thinking a custom Apache module might be the only way, just wondering if there's an alternative.
Was This Post Helpful? 0
  • +
  • -

#5 modi123_1   User is offline

  • Suitor #2
  • member icon



Reputation: 15262
  • View blog
  • Posts: 61,187
  • Joined: 12-June 08

Re: Authentication in Apache

Posted 13 July 2018 - 05:41 PM

Yeah that is dipping outside my knowledge base. Hopefully someone can lend a hand or an ear.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1