Added hotlink protection and restricting access to the original image to the photo's owner
This commit is contained in:
parent
068ed2018a
commit
08f13b28cb
@ -57,12 +57,14 @@ class ConfigHelper
|
||||
'allow_self_registration' => true,
|
||||
'app_name' => trans('global.app_name'),
|
||||
'date_format' => $this->allowedDateFormats()[0],
|
||||
'hotlink_protection' => false,
|
||||
'items_per_page' => 12,
|
||||
'items_per_page_admin' => 10,
|
||||
'recaptcha_enabled_registration' => false,
|
||||
'recaptcha_secret_key' => '',
|
||||
'recaptcha_site_key' => '',
|
||||
'require_email_verification' => true,
|
||||
'restrict_original_download' => true,
|
||||
'sender_address' => sprintf('hostmaster@%s', (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost')),
|
||||
'sender_name' => (is_null($currentAppName) ? trans('global.app_name') : $currentAppName),
|
||||
'smtp_server' => 'localhost',
|
||||
|
@ -26,6 +26,11 @@ class DbHelper
|
||||
return $albums;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an album using its URL alias.
|
||||
* @param string $urlAlias URL alias of the album to fetch.
|
||||
* @return Album|null
|
||||
*/
|
||||
public static function loadAlbumByUrlAlias($urlAlias)
|
||||
{
|
||||
return Album::where('url_alias', $urlAlias)->first();
|
||||
|
@ -49,9 +49,11 @@ class DefaultController extends Controller
|
||||
];
|
||||
$checkboxKeys = [
|
||||
'allow_self_registration',
|
||||
'hotlink_protection',
|
||||
'recaptcha_enabled_registration',
|
||||
'require_email_verification',
|
||||
'restrict_original_download',
|
||||
'smtp_encryption',
|
||||
'recaptcha_enabled_registration'
|
||||
];
|
||||
$updateKeys = [
|
||||
'app_name',
|
||||
|
@ -16,6 +16,7 @@ use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
@ -173,6 +174,7 @@ class PhotoController extends Controller
|
||||
|
||||
$photo = new Photo();
|
||||
$photo->album_id = $album->id;
|
||||
$photo->user_id = Auth::user()->id;
|
||||
$photo->name = pathinfo($photoFile->getClientOriginalName(), PATHINFO_FILENAME);
|
||||
$photo->file_name = $photoFile->getClientOriginalName();
|
||||
$photo->storage_file_name = $savedFile->getFilename();
|
||||
@ -223,7 +225,7 @@ class PhotoController extends Controller
|
||||
{
|
||||
if ($fileInfo->isDir())
|
||||
{
|
||||
if ($fileInfo->getFilename() == '__MACOSX')
|
||||
if ($fileInfo->getFilename() == '__MACOSX' || substr($fileInfo->getFilename(), 0, 1) == '.')
|
||||
{
|
||||
@rmdir($fileInfo->getPathname());
|
||||
}
|
||||
@ -246,6 +248,7 @@ class PhotoController extends Controller
|
||||
|
||||
$photo = new Photo();
|
||||
$photo->album_id = $album->id;
|
||||
$photo->user_id = Auth::user()->id;
|
||||
$photo->name = pathinfo($photoFile->getFilename(), PATHINFO_FILENAME);
|
||||
$photo->file_name = $photoFile->getFilename();
|
||||
$photo->storage_file_name = $savedFile->getFilename();
|
||||
|
@ -2,12 +2,26 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
|
||||
/**
|
||||
* Gets either the authenticated user, or a user object representing the anonymous user.
|
||||
* @return User
|
||||
*/
|
||||
protected function getUser()
|
||||
{
|
||||
$user = Auth::user();
|
||||
return (is_null($user)
|
||||
? User::anonymous()
|
||||
: $user);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use App\Helpers\DbHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AlbumController extends Controller
|
||||
@ -22,7 +23,7 @@ class AlbumController extends Controller
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->authorize('album.view', $album);
|
||||
$this->authorizeForUser($this->getUser(), 'album.view', $album);
|
||||
|
||||
$photos = $album->photos()
|
||||
->orderBy(DB::raw('COALESCE(taken_at, created_at)'))
|
||||
@ -33,13 +34,4 @@ class AlbumController extends Controller
|
||||
'photos' => $photos
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @return Album
|
||||
*/
|
||||
private static function loadAlbum($urlAlias)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,14 @@ namespace App\Http\Controllers\Gallery;
|
||||
|
||||
use App\Album;
|
||||
use App\Facade\Theme;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Helpers\DbHelper;
|
||||
use app\Http\Controllers\Admin\AlbumController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Middleware\VerifyCsrfToken;
|
||||
use App\Photo;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class PhotoController extends Controller
|
||||
@ -21,14 +24,38 @@ class PhotoController extends Controller
|
||||
App::abort(404);
|
||||
return null;
|
||||
}
|
||||
$this->authorize('album.view', $album);
|
||||
|
||||
$albumSource = $album->getAlbumSource();
|
||||
$this->authorizeForUser($this->getUser(), 'album.view', $album);
|
||||
|
||||
if (UserConfig::get('hotlink_protection'))
|
||||
{
|
||||
$referrer = $request->headers->get('Referer');
|
||||
|
||||
if (!is_null($referrer))
|
||||
{
|
||||
$hostname = parse_url($referrer, PHP_URL_HOST);
|
||||
if (strtolower($hostname) != strtolower($request->getHttpHost()))
|
||||
{
|
||||
App::abort(403);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
App::abort(403);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$thumbnail = $request->get('t');
|
||||
$photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename);
|
||||
|
||||
return response()->file($albumSource->getPathToPhoto($photo, $thumbnail));
|
||||
$thumbnail = $request->get('t');
|
||||
if (is_null($thumbnail))
|
||||
{
|
||||
Gate::forUser($this->getUser())->authorize('photo.download_original', $photo);
|
||||
}
|
||||
|
||||
return response()->file($album->getAlbumSource()->getPathToPhoto($photo, $thumbnail));
|
||||
}
|
||||
|
||||
public function show($albumUrlAlias, $photoFilename)
|
||||
@ -40,12 +67,15 @@ class PhotoController extends Controller
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->authorize('album.view', $album);
|
||||
$this->authorizeForUser($this->getUser(), 'album.view', $album);
|
||||
|
||||
$photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename);
|
||||
|
||||
$isOriginalAllowed = Gate::forUser($this->getUser())->allows('photo.download_original', $photo);
|
||||
|
||||
return Theme::render('gallery.photo', [
|
||||
'album' => $album,
|
||||
'is_original_allowed' => $isOriginalAllowed,
|
||||
'photo' => $photo
|
||||
]);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ class Photo extends Model
|
||||
*/
|
||||
protected $fillable = [
|
||||
'album_id',
|
||||
'user_id',
|
||||
'name',
|
||||
'description',
|
||||
'file_name',
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Album;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Photo;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
|
||||
@ -30,8 +32,18 @@ class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
return (!$album->is_private || $album->user_id == $user->id);
|
||||
});
|
||||
Gate::define('admin-access', function ($user) {
|
||||
Gate::define('admin-access', function ($user)
|
||||
{
|
||||
return $user->is_admin;
|
||||
});
|
||||
Gate::define('photo.download_original', function ($user, Photo $photo)
|
||||
{
|
||||
if (!UserConfig::get('restrict_original_download'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return ($user->id == $photo->user_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,15 @@ class User extends Authenticatable
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
public static function anonymous()
|
||||
{
|
||||
$user = new User();
|
||||
$user->id = -1;
|
||||
$user->name = 'Anonymous';
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddPhotoUserColumn extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('photos', function (Blueprint $table) {
|
||||
$table->unsignedInteger('user_id');
|
||||
|
||||
$table->foreign('user_id')
|
||||
->references('id')->on('users')
|
||||
->onDelete('no action');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('photos', function (Blueprint $table) {
|
||||
$table->dropForeign('photos_user_id_foreign');
|
||||
$table->dropColumn('user_id');
|
||||
});
|
||||
}
|
||||
}
|
7
public/themes/base/css/app.css
vendored
7
public/themes/base/css/app.css
vendored
@ -0,0 +1,7 @@
|
||||
.no-margin-bottom {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.no-padding {
|
||||
padding: 0;
|
||||
}
|
4
public/themes/bootstrap3/theme.css
vendored
4
public/themes/bootstrap3/theme.css
vendored
@ -12,6 +12,10 @@ body {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.content-body {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
div.breadcrumb {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
@ -25,7 +25,9 @@ return [
|
||||
'no_albums_text' => 'You have no photo albums yet. Click the button below to create one.',
|
||||
'no_albums_title' => 'No Photo Albums',
|
||||
'open_album' => 'Open album',
|
||||
'settings_image_protection' => 'Image Protection',
|
||||
'settings_link' => 'Settings',
|
||||
'settings_recaptcha' => 'reCAPTCHA settings',
|
||||
'settings_save_action' => 'Update Settings',
|
||||
'settings_saved_message' => 'The settings were updated successfully.',
|
||||
'settings_test_email_action' => 'Send a test e-mail',
|
||||
|
@ -14,6 +14,10 @@ return [
|
||||
'realname_label' => 'Your name:',
|
||||
'register_action' => 'Create account',
|
||||
'remember_me_label' => 'Remember me',
|
||||
'settings_hotlink_protection' => 'Prevent hot-linking to images',
|
||||
'settings_hotlink_protection_help' => 'With this option enabled, direct linking to images is not allowed. Photos can only be viewed through Blue Twilight.',
|
||||
'settings_restrict_originals_download' => 'Restrict access to original images',
|
||||
'settings_restrict_originals_download_help' => 'With this option enabled, only the photo\'s owner can download the original high-resolution images.',
|
||||
'upload_action' => 'Upload',
|
||||
'save_action' => 'Save Changes'
|
||||
];
|
@ -15,7 +15,7 @@
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<div class="col-xs-12 col-sm-8 content-body">
|
||||
@include (Theme::viewName('partials.admin_sysinfo_widget'))
|
||||
</div>
|
||||
|
||||
|
@ -143,7 +143,7 @@
|
||||
</div>
|
||||
|
||||
<fieldset style="margin-top: 30px;">
|
||||
<legend>reCAPTCHA settings</legend>
|
||||
<legend>@lang('admin.settings_recaptcha')</legend>
|
||||
|
||||
<div class="form-group">
|
||||
{!! Form::label('recaptcha_site_key', 'Site key:', ['class' => 'control-label']) !!}
|
||||
@ -155,6 +155,26 @@
|
||||
{!! Form::text('recaptcha_secret_key', old('recaptcha_secret_key'), ['class' => 'form-control']) !!}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset style="margin-top: 20px;">
|
||||
<legend>@lang('admin.settings_image_protection')</legend>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="restrict_original_download" @if (UserConfig::get('restrict_original_download'))checked="checked"@endif>
|
||||
<strong>@lang('forms.settings_restrict_originals_download')</strong><br/>
|
||||
@lang('forms.settings_restrict_originals_download_help')
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox" style="margin-top: 20px;">
|
||||
<label>
|
||||
<input type="checkbox" name="hotlink_protection" @if (UserConfig::get('hotlink_protection'))checked="checked"@endif>
|
||||
<strong>@lang('forms.settings_hotlink_protection')</strong><br/>
|
||||
@lang('forms.settings_hotlink_protection_help')
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
@extends('themes.base.layout')
|
||||
@section('title', 'Welcome')
|
||||
@section('title', $photo->name)
|
||||
|
||||
@section('breadcrumb')
|
||||
<div class="breadcrumb">
|
||||
@ -25,55 +25,64 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<a href="{{ $photo->thumbnailUrl() }}"><img src="{{ $photo->thumbnailUrl('fullsize') }}" alt="" class="img-thumbnail"/></a>
|
||||
<div class="col-xs-12 col-sm-8 content-body">
|
||||
@if ($is_original_allowed)
|
||||
<a href="{{ $photo->thumbnailUrl() }}">
|
||||
@endif
|
||||
<img src="{{ $photo->thumbnailUrl('fullsize') }}" alt="" class="img-thumbnail"/>
|
||||
@if ($is_original_allowed)
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<p>Information about this photo:</p>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Information about this photo:</div>
|
||||
<div class="panel-body no-padding">
|
||||
<table class="table table-striped photo-metadata no-margin-bottom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="metadata_name">File name:</td>
|
||||
<td class="metadata_value">{{ $photo->file_name }}</td>
|
||||
</tr>
|
||||
|
||||
<table class="table table-striped photo-metadata">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="metadata_name">File name:</td>
|
||||
<td class="metadata_value">{{ $photo->file_name }}</td>
|
||||
</tr>
|
||||
@if (strlen($photo->taken_at) > 0)
|
||||
<tr>
|
||||
<td class="metadata_name">Date taken:</td>
|
||||
<td class="metadata_value">{{ date(UserConfig::get('date_format'), strtotime($photo->taken_at)) }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
|
||||
@if (strlen($photo->taken_at) > 0)
|
||||
<tr>
|
||||
<td class="metadata_name">Date taken:</td>
|
||||
<td class="metadata_value">{{ date(UserConfig::get('date_format'), strtotime($photo->taken_at)) }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@if (strlen($photo->camera_make) > 0)
|
||||
<tr>
|
||||
<td class="metadata_name">Camera make:</td>
|
||||
<td class="metadata_value">{{ $photo->camera_make }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
|
||||
@if (strlen($photo->camera_make) > 0)
|
||||
<tr>
|
||||
<td class="metadata_name">Camera make:</td>
|
||||
<td class="metadata_value">{{ $photo->camera_make }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@if (strlen($photo->camera_model) > 0)
|
||||
<tr>
|
||||
<td class="metadata_name">Camera model:</td>
|
||||
<td class="metadata_value">{{ $photo->camera_model }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
|
||||
@if (strlen($photo->camera_model) > 0)
|
||||
<tr>
|
||||
<td class="metadata_name">Camera model:</td>
|
||||
<td class="metadata_value">{{ $photo->camera_model }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
|
||||
@if (strlen($photo->camera_software) > 0)
|
||||
<tr>
|
||||
<td class="metadata_name">Camera software:</td>
|
||||
<td class="metadata_value">{{ $photo->camera_software }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
@if (strlen($photo->camera_software) > 0)
|
||||
<tr>
|
||||
<td class="metadata_name">Camera software:</td>
|
||||
<td class="metadata_value">{{ $photo->camera_software }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user