Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolving open basedir restrictions with font files in avatar generat… #2796

Merged
merged 5 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/Core/Configuration/laravelConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Application Service Providers...
*/
\Leantime\Core\Providers\AppServiceProvider::class,
\Leantime\Core\Providers\LoadMacros::class,

\Leantime\Core\Providers\Cache::class, //\Illuminate\Cache\CacheServiceProvider::class,
\Leantime\Core\Providers\Redis::class,
Expand Down
13 changes: 11 additions & 2 deletions app/Core/Fileupload.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ private function uploadLocal(): bool
/**
* displayImageFile - display image file
*/
public function displayImageFile(string $imageName, string $fullPath = ''): Response
public function displayImageFile(string $imageName, string $fullPath = '', $allowSvg = false): Response
{
$mimes = [
'jpg' => 'image/jpg',
Expand All @@ -339,6 +339,10 @@ public function displayImageFile(string $imageName, string $fullPath = ''): Resp
'png' => 'image/png',
];

if ($allowSvg) {
$mimes['svg'] = 'image/svg+xml';
}

$responseFailure = new Response(file_get_contents(ROOT.'/dist/images/doc.png'));
$sLastModified = filemtime(ROOT.'/dist/images/doc.png');
$responseFailure->headers->set('Content-Type', 'image/png');
Expand Down Expand Up @@ -387,7 +391,12 @@ public function displayImageFile(string $imageName, string $fullPath = ''): Resp
$path_parts = pathinfo($fullPath);
$ext = $path_parts['extension'];

if (! in_array($ext, ['jpg', 'jpeg', 'gif', 'png'])) {
$fileExtensions = ['jpg', 'jpeg', 'gif', 'png'];
if ($allowSvg) {
$fileExtensions[] = 'svg';
}

if (! in_array($ext, $fileExtensions)) {
throw new HttpResponseException($responseFailure);
}

Expand Down
26 changes: 26 additions & 0 deletions app/Core/Providers/LoadMacros.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Leantime\Core\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Stringable;
use Leantime\Core\Support\StringableMacros;

class LoadMacros extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Stringable::mixin(new StringableMacros);
}
}
36 changes: 36 additions & 0 deletions app/Core/Support/StringableMacros.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Leantime\Core\Support;

/**
* @mixin \Illuminate\Support\Stringable
*/
class StringableMacros
{
/**
* Cleans a string by removing special characters and optionally spaces.
*
* @param bool $removeSpaces Whether to remove spaces from the string.
* @return callable A function that cleans a string based on the given parameter.
*/
public function alphaNumeric($removeSpaces = false)
{
return function () use ($removeSpaces) {
/** @var \Illuminate\Support\Stringable $this */
$cleaned = preg_replace('/[^A-Za-z0-9 ]/', '', (string) $this);

if ($removeSpaces) {
$cleaned = str_replace(' ', '', $cleaned);
} else {
// Step 2: Replace multiple spaces with a single space
$cleaned = preg_replace('/\s+/', ' ', $cleaned);
}

// Step 3: Trim leading and trailing spaces
$cleaned = trim($cleaned);

return $cleaned;
};

}
}
2 changes: 1 addition & 1 deletion app/Domain/Api/Controllers/Projects.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function get(array $params): Response

return match ($svg['type']) {
'uploaded' => $file->displayImageFile($svg['filename']),
'generated' => $file->displayImageFile('avatar', $svg['filename']),
'generated' => $file->displayImageFile('avatar', $svg['filename'], true),
};
}

Expand Down
2 changes: 1 addition & 1 deletion app/Domain/Api/Controllers/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function get(array $params): Response

return match ($svg['type']) {
'uploaded' => $file->displayImageFile($svg['filename']),
'generated' => $file->displayImageFile('avatar', $svg['filename']),
'generated' => $file->displayImageFile('avatar', $svg['filename'], true),
};
}

Expand Down
17 changes: 13 additions & 4 deletions app/Domain/Projects/Repositories/Projects.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use DatePeriod;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
use LasseRafn\Initials\Initials;
use Leantime\Core\Configuration\Environment;
Expand Down Expand Up @@ -1134,8 +1135,8 @@ public function getProjectAvatar($id): array|SVG
$stmn->closeCursor();
}
try {
$avatar = (new InitialAvatar)
->fontName('Verdana')
$avatar = app()->make(InitialAvatar::class)
->font(APP_ROOT.'/public/dist/fonts/roboto/Roboto-Medium.ttf')
->background('#555555')
->color('#fff');

Expand All @@ -1152,13 +1153,21 @@ public function getProjectAvatar($id): array|SVG

/** @var Initials $initialsClass */
$initialsClass = app()->make(Initials::class);
$initialsClass->allowSpecialCharacters(false);
$initialsClass->name($value['name']);
$imagename = $initialsClass->getInitials();
$imagename = Str::of($imagename)->alphaNumeric(true);

if (! file_exists($filename = APP_ROOT.'/cache/avatars/'.$imagename.'.svg')) {
if (is_dir(storage_path('framework/cache/avatars')) === false) {
mkdir(storage_path('framework/cache/avatars'));
}

if (! file_exists($filename = storage_path('framework/cache/avatars/user-'.$imagename.'.svg'))) {
$image = $avatar->name($value['name'])->generateSvg();

if (! is_writable(APP_ROOT.'/cache/avatars/')) {
if (! is_writable(storage_path('framework/cache/avatars/'))) {
Log::warning("Can't write to avatars folders");

return $image;
}

Expand Down
18 changes: 14 additions & 4 deletions app/Domain/Users/Repositories/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
use LasseRafn\Initials\Initials;
use Leantime\Core\Configuration\Environment;
Expand Down Expand Up @@ -612,8 +613,8 @@ public function getProfilePicture($id): array|SVG

try {

$avatar = (new InitialAvatar)
->fontName('Verdana')
$avatar = app()->make(InitialAvatar::class)
->font(APP_ROOT.'/public/dist/fonts/roboto/Roboto-Medium.ttf')
->background('#00a887')
->color('#fff');

Expand All @@ -634,13 +635,22 @@ public function getProfilePicture($id): array|SVG

/** @var Initials $initialsClass */
$initialsClass = app()->make(Initials::class);
$initialsClass->allowSpecialCharacters(false);
$initialsClass->name($name);

$imagename = $initialsClass->getInitials();
$imagename = Str::of($imagename)->alphaNumeric(true);

if (is_dir(storage_path('framework/cache/avatars')) === false) {
mkdir(storage_path('framework/cache/avatars'));
}

if (! file_exists($filename = APP_ROOT.'/cache/avatars/user-'.$imagename.'.svg')) {
if (! file_exists($filename = storage_path('framework/cache/avatars/user-'.$imagename.'.svg'))) {
$image = $avatar->name($name)->generateSvg();

if (! is_writable(APP_ROOT.'/cache/avatars/')) {
if (! is_writable(storage_path('framework/cache/avatars/'))) {
Log::warning("Can't write to avatars folders");

return $image;
}

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "leantime/leantime",
"description": "Open source project management system",
"version": "3.3",
"version": "3.3.1",
"type": "project",
"license": "AGPL-3.0",
"config": {
Expand Down
2 changes: 1 addition & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ get-version:
@echo $(VERSION)

phpstan:
./vendor/bin/phpstan analyse -c .phpstan/phpstan.neon -v --debug --memory-limit 2G
./vendor/bin/phpstan analyse -c .phpstan/phpstan.neon --memory-limit 2G

update-carbon-macros:
./vendor/bin/carbon macro Leantime\\Core\\Support\\CarbonMacros app/Core/Support/CarbonMacros.php
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "leantime",
"version": "3.3.0",
"version": "3.3.1",
"description": "Open source innovation management system",
"dependencies": {
"@assuradeurengilde/fontawesome-iconpicker": "^3.2.3",
Expand Down
30 changes: 15 additions & 15 deletions public/dist/mix-manifest.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{
"/js/compiled-htmx.3.3.0.min.js": "/js/compiled-htmx.3.3.0.min.js",
"/js/compiled-htmx-headSupport.3.3.0.min.js": "/js/compiled-htmx-headSupport.3.3.0.min.js",
"/css/main.3.3.0.min.css": "/css/main.3.3.0.min.css",
"/css/editor.3.3.0.min.css": "/css/editor.3.3.0.min.css",
"/css/app.3.3.0.min.css": "/css/app.3.3.0.min.css",
"/js/compiled-footer.3.3.0.min.js": "/js/compiled-footer.3.3.0.min.js",
"/js/compiled-app.3.3.0.min.js": "/js/compiled-app.3.3.0.min.js",
"/js/compiled-frameworks.3.3.0.min.js": "/js/compiled-frameworks.3.3.0.min.js",
"/js/compiled-framework-plugins.3.3.0.min.js": "/js/compiled-framework-plugins.3.3.0.min.js",
"/js/compiled-global-component.3.3.0.min.js": "/js/compiled-global-component.3.3.0.min.js",
"/js/compiled-calendar-component.3.3.0.min.js": "/js/compiled-calendar-component.3.3.0.min.js",
"/js/compiled-table-component.3.3.0.min.js": "/js/compiled-table-component.3.3.0.min.js",
"/js/compiled-editor-component.3.3.0.min.js": "/js/compiled-editor-component.3.3.0.min.js",
"/js/compiled-gantt-component.3.3.0.min.js": "/js/compiled-gantt-component.3.3.0.min.js",
"/js/compiled-chart-component.3.3.0.min.js": "/js/compiled-chart-component.3.3.0.min.js",
"/js/compiled-htmx.3.3.1.min.js": "/js/compiled-htmx.3.3.1.min.js",
"/js/compiled-htmx-headSupport.3.3.1.min.js": "/js/compiled-htmx-headSupport.3.3.1.min.js",
"/css/main.3.3.1.min.css": "/css/main.3.3.1.min.css",
"/css/editor.3.3.1.min.css": "/css/editor.3.3.1.min.css",
"/css/app.3.3.1.min.css": "/css/app.3.3.1.min.css",
"/js/compiled-footer.3.3.1.min.js": "/js/compiled-footer.3.3.1.min.js",
"/js/compiled-app.3.3.1.min.js": "/js/compiled-app.3.3.1.min.js",
"/js/compiled-frameworks.3.3.1.min.js": "/js/compiled-frameworks.3.3.1.min.js",
"/js/compiled-framework-plugins.3.3.1.min.js": "/js/compiled-framework-plugins.3.3.1.min.js",
"/js/compiled-global-component.3.3.1.min.js": "/js/compiled-global-component.3.3.1.min.js",
"/js/compiled-calendar-component.3.3.1.min.js": "/js/compiled-calendar-component.3.3.1.min.js",
"/js/compiled-table-component.3.3.1.min.js": "/js/compiled-table-component.3.3.1.min.js",
"/js/compiled-editor-component.3.3.1.min.js": "/js/compiled-editor-component.3.3.1.min.js",
"/js/compiled-gantt-component.3.3.1.min.js": "/js/compiled-gantt-component.3.3.1.min.js",
"/js/compiled-chart-component.3.3.1.min.js": "/js/compiled-chart-component.3.3.1.min.js",
"/images/03-1.png": "/images/03-1.png",
"/images/32px.png": "/images/32px.png",
"/images/40px.png": "/images/40px.png",
Expand Down
Loading