Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save srghma/ed861ef96ef50b25ad75762a36551592 to your computer and use it in GitHub Desktop.
Save srghma/ed861ef96ef50b25ad75762a36551592 to your computer and use it in GitHub Desktop.
ELFINDER TOKEN AUTHENTICATION, rails, tinymce, elfinder, nginx

This is an example of using elfinder with bcrypt tokens.

It works like this:

  • backend and elfinder knows about secret token
  • user can access elfinder on localhost:8000/elFinder/elfinder.html, but cant write without token
  • backend adds hashed token to url only on some pages (admin panel for example)
  • tinymce opens elfinder.html in iframe with hashed token, elfinder.html iframe pass this token to connector, connector validates token
  • I use nginx to bypass cors issue

Sorry can provide more elaborate exmaple (proprietary software)

version: '2.3'
volumes:
elfinder_data:
services:
elfinder:
build:
context: .
dockerfile: docker/Dockerfiles/elfinder.Dockerfile
volumes:
- docker/elfinder/connector.php:/var/www/html/elFinder/php/connector.php
- docker/elfinder/index.html:/var/www/html/elFinder/elfinder.html
# data
- elfinder_data:/var/www/html/elFinder/files
elfinder_be_glue:
image: nginx
volumes:
- docker/elfinder_be_glue/nginx.conf.template:/tmp/nginx.conf.template
ports:
- 8080:80
environment:
- NGINX_LISTEN=80
- NGINX_SERVER_NAME=_
- ELFINDER_SECRET: foo
# envsubst will try to substitute all variables starting with $, substitute only some variables
# https://github.com/dokku/dokku/issues/1059#issuecomment-239660290
command: /bin/bash -c "envsubst '$$NGINX_SERVER_NAME,$$NGINX_LISTEN' < /tmp/nginx.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
depends_on:
- be
- elfinder
## overriddings
be:
image: ruby:2.4
...
ports:
- 3000:3000
command: bundle exec passenger start
environment:
- ELFINDER_URL: elFinder/elfinder.html
- ELFINDER_SECRET: foo
depends_on:
- postgres
- sidekiq
- redis
# vi:syntax=nginx
# These are some "magic" Nginx configuration options that aid in making
# WebSockets work properly with Passenger Standalone. Please learn more
# at http://nginx.org/en/docs/http/websocket.html
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen ${NGINX_LISTEN};
server_name ${NGINX_SERVER_NAME};
# TODO
# root ${NGINX_STATIC_FILES_LOCATION};
location / {
proxy_pass http://be:3000;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_buffering off;
}
# redirect to elfinder and prevent cors issue
location /elFinder {
proxy_pass http://elfinder:80/elFinder;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# deny access to .htaccess files, for elfinder
location ~ /\.ht {
deny all;
}
}
<?php
error_reporting(0); // Set E_ALL for debuging
/* error_reporting(E_ALL); // Set E_ALL for debuging */
// load composer autoload before load elFinder autoload If you need composer
//require './vendor/autoload.php';
// elFinder autoload
require './autoload.php';
// ===============================================
// Enable FTP connector netmount
elFinder::$netDrivers['ftp'] = 'FTP';
// ===============================================
// // Required for Dropbox network mount
// // Installation by composer
// // `composer require kunalvarma05/dropbox-php-sdk`
// // Enable network mount
// elFinder::$netDrivers['dropbox2'] = 'Dropbox2';
// // Dropbox2 Netmount driver need next two settings. You can get at https://www.dropbox.com/developers/apps
// // AND reuire regist redirect url to "YOUR_CONNECTOR_URL?cmd=netmount&protocol=dropbox2&host=1"
// define('ELFINDER_DROPBOX_APPKEY', '');
// define('ELFINDER_DROPBOX_APPSECRET', '');
// ===============================================
// // Required for Google Drive network mount
// // Installation by composer
// // `composer require google/apiclient:^2.0`
// // Enable network mount
// elFinder::$netDrivers['googledrive'] = 'GoogleDrive';
// // GoogleDrive Netmount driver need next two settings. You can get at https://console.developers.google.com
// // AND reuire regist redirect url to "YOUR_CONNECTOR_URL?cmd=netmount&protocol=googledrive&host=1"
// define('ELFINDER_GOOGLEDRIVE_CLIENTID', '');
// define('ELFINDER_GOOGLEDRIVE_CLIENTSECRET', '');
// // Required case of without composer
// define('ELFINDER_GOOGLEDRIVE_GOOGLEAPICLIENT', '/path/to/google-api-php-client/vendor/autoload.php');
// ===============================================
// // Required for Google Drive network mount with Flysystem
// // Installation by composer
// // `composer require nao-pon/flysystem-google-drive:~1.1 nao-pon/elfinder-flysystem-driver-ext`
// // Enable network mount
// elFinder::$netDrivers['googledrive'] = 'FlysystemGoogleDriveNetmount';
// // GoogleDrive Netmount driver need next two settings. You can get at https://console.developers.google.com
// // AND reuire regist redirect url to "YOUR_CONNECTOR_URL?cmd=netmount&protocol=googledrive&host=1"
// define('ELFINDER_GOOGLEDRIVE_CLIENTID', '');
// define('ELFINDER_GOOGLEDRIVE_CLIENTSECRET', '');
// ===============================================
// // Required for One Drive network mount
// // * cURL PHP extension required
// // * HTTP server PATH_INFO supports required
// // Enable network mount
// elFinder::$netDrivers['onedrive'] = 'OneDrive';
// // GoogleDrive Netmount driver need next two settings. You can get at https://dev.onedrive.com
// // AND reuire regist redirect url to "YOUR_CONNECTOR_URL/netmount/onedrive/1"
// define('ELFINDER_ONEDRIVE_CLIENTID', '');
// define('ELFINDER_ONEDRIVE_CLIENTSECRET', '');
// ===============================================
// // Required for Box network mount
// // * cURL PHP extension required
// // Enable network mount
// elFinder::$netDrivers['box'] = 'Box';
// // Box Netmount driver need next two settings. You can get at https://developer.box.com
// // AND reuire regist redirect url to "YOUR_CONNECTOR_URL"
// define('ELFINDER_BOX_CLIENTID', '');
// define('ELFINDER_BOX_CLIENTSECRET', '');
// ===============================================
// // Zoho Office Editor APIKey
// // https://www.zoho.com/docs/help/office-apis.html
// define('ELFINDER_ZOHO_OFFICE_APIKEY', '');
// ===============================================
// // Zip Archive editor
// // Installation by composer
// // `composer require barryvdh/elfinder-flysystem-driver league/flysystem-ziparchive`
// define('ELFINDER_DISABLE_ZIPEDITOR', false); // set `true` to disable zip editor
// ===============================================
// AUTH PART HERE
$secret = $_ENV["ELFINDER_SECRET_TOKEN"];
# read url param
$rubyEncodedAndHashedToken = $_GET['token'];
$base64_decode_strict = false;
$rubyHashedToken = base64_decode($rubyEncodedAndHashedToken, $base64_decode_strict);
# change token to php compatible form
# more - https://stackoverflow.com/questions/20980859/using-bcrypt-ruby-to-validate-hashed-passwords-using-version-2y
$hashedToken = preg_replace('/$\$2a\$/', '$2y$', $rubyHashedToken);
$tokenValid = password_verify($secret, $hashedToken);
/**
* Simple function to demonstrate how to control file access using "accessControl" callback.
* This method will disable accessing files/folders starting from '.' (dot)
*
* @param string $attr attribute name (read|write|locked|hidden)
* @param string $path absolute file path
* @param string $data value of volume option `accessControlData`
* @param object $volume elFinder volume driver object
* @param bool|null $isDir path is directory (true: directory, false: file, null: unknown)
* @param string $relpath file path relative to volume root directory started with directory separator
* @return bool|null
**/
function rwaccess($attr, $path, $data, $volume, $isDir, $relpath) {
return strpos(basename($path), '.') === 0 // if file/folder begins with '.' (dot)
? !($attr == 'read' || $attr == 'write') // set read+write to false, other (locked+hidden) set to true
: null; // else elFinder decide it itself
}
function roaccess($attr, $path, $data, $volume, $isDir, $relpath) {
return strpos(basename($path), '.') === 0 // if file/folder begins with '.' (dot)
? !($attr == 'read' || $attr == 'write') // set read+write to false, other (locked+hidden) set to true
: ($attr == 'read' || $attr == 'locked'); // else read only
}
$accessControlFnName = $tokenValid ? 'rwaccess' : 'roaccess';
// Documentation for connector options:
// https://github.com/Studio-42/elFinder/wiki/Connector-configuration-options
$opts = array(
// 'debug' => true,
'roots' => array(
// Items volume
array(
'driver' => 'LocalFileSystem', // driver for accessing file system (REQUIRED)
'path' => '../files/', // path to files (REQUIRED)
'URL' => dirname($_SERVER['PHP_SELF']) . '/../files/', // URL to files (REQUIRED)
'trashHash' => 't1_Lw', // elFinder's hash of trash folder
'winHashFix' => DIRECTORY_SEPARATOR !== '/', // to make hash same to Linux one on windows too
'uploadDeny' => array('all'), // All Mimetypes not allowed to upload
'uploadAllow' => array('image', 'text/plain'),// Mimetype `image` and `text/plain` allowed to upload
'uploadOrder' => array('deny', 'allow'), // allowed Mimetype `image` and `text/plain` only
'accessControl' => $accessControlFnName // disable and hide dot starting files (OPTIONAL)
),
// Trash volume
array(
'id' => '1',
'driver' => 'Trash',
'path' => '../files/.trash/',
'tmbURL' => dirname($_SERVER['PHP_SELF']) . '/../files/.trash/.tmb/',
'winHashFix' => DIRECTORY_SEPARATOR !== '/', // to make hash same to Linux one on windows too
'uploadDeny' => array('all'), // Recomend the same settings as the original volume that uses the trash
'uploadAllow' => array('image', 'text/plain'),// Same as above
'uploadOrder' => array('deny', 'allow'), // Same as above
'accessControl' => $accessControlFnName // Same as above
)
)
);
// run elFinder
$connector = new elFinderConnector(new elFinder($opts));
$connector->run();
# based on https://github.com/Studio-42/elFinder/releases
FROM php:7.2.3-apache
ENV ELFINDER_DOWNLOAD="https://github.com/Studio-42/elFinder/archive/2.1.32.zip"
RUN apt-get update && \
apt-get install unzip && \
apt-get clean
RUN curl -L -O "${ELFINDER_DOWNLOAD}" && \
unzip *.zip && \
rm -f *.zip
RUN mv elFinder-* elFinder && \
chown -R www-data:www-data elFinder && \
mv elFinder/php/connector.minimal.php-dist elFinder/php/connector.minimal.php && \
rm -rf elFinder/files
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>elFinder</title>
<meta name="robots" content="noindex">
<meta name="googlebot" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2"/>
<script data-main="./main.default.js"
src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.3.2/require.min.js"></script>
<script>
// pass token from url to connector
var token = (new URL(window.location)).searchParams.get('token')
var url = 'php/connector.php?token=' + token
define('elFinderConfig', {
defaultOpts: {
url: url,
getFileCallback : function(file, fm) {
// pass selected file data to TinyMCE
parent.tinymce.activeEditor.windowManager.getParams().oninsert(file, fm);
// close popup window
parent.tinymce.activeEditor.windowManager.close();
},
commandsOptions: {
edit: {
extraOptions: {}
},
quicklook: {
googleDocsMimes: [
'application/pdf',
'image/tiff',
'application/vnd.ms-office',
'application/msword',
'application/vnd.ms-word',
'application/vnd.ms-excel',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
]
}
}
},
managers: {
'elfinder': {}
}
});
</script>
</head>
<body>
<div id="elfinder"></div>
</body>
</html>
require 'bcrypt'
module ElfinderUrlWithToken
module_function
def elfinder_url_with_token
url = ENV['ELFINDER_URL']
secret = ENV['ELFINDER_SECRET_TOKEN']
hashed_token = ::BCrypt::Password.create(secret).to_s
# because encode64 has newlines
encoded_and_hashed_token = Base64.s6-app/assets/jatrict_encode64(hashed_token)
"#{url}?token=#{encoded_and_hashed_token}"
end
end
window.app_elfinderUrl = "<%= ElfinderUrlWithToken.elfinder_url_with_token %>"
function initMce() {
tinyMCE.init({
selector: '.tinymce',
height: 500,
plugins:
'print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists textcolor wordcount imagetools contextmenu colorpicker textpattern help',
toolbar1:
'formatselect | bold italic strikethrough forecolor backcolor | link | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | removeformat',
image_advtab: true,
file_picker_callback: elFinderBrowser,
})
}
function elFinderBrowser(callback, value, meta) {
tinyMCE.activeEditor.windowManager.open(
{
file: window.app_elfinderUrl, // use an absolute path!
title: 'TinyMCE with elFinder',
width: 900,
height: 450,
resizable: 'yes',
},
{
oninsert(file, fm) {
// URL normalization
let { url } = file
const reg = /\/[^/]+?\/\.\.\//
while (url.match(reg)) {
url = url.replace(reg, '/')
}
// Make file info
const info = `${file.name} (${fm.formatSize(file.size)})`
// Provide file and text for the link dialog
if (meta.filetype === 'file') {
callback(url, { text: info, title: info })
}
// Provide image and alt text for the image dialog
if (meta.filetype === 'image') {
callback(url, { alt: info })
}
// Provide alternative source and posted for the media dialog
if (meta.filetype === 'media') {
callback(url)
}
},
},
)
}
$(document).on('turbolinks:load', initMce)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment