Added application base url: Redirector now uses URLs instead of relative paths

main
Igor Scheller 5 years ago committed by msquare
parent 6b1c22a743
commit 795a0631cb

@ -23,6 +23,9 @@ return [
// Set to development to enable debugging messages
'environment' => env('ENVIRONMENT', 'production'),
// Application URL and base path to use instead of the auto detected one
'url' => env('APP_URL', null),
// Header links
// Available link placeholders: %lang%
'header_items' => [

@ -38,11 +38,11 @@ $route->addGroup(
function (RouteCollector $route) {
// Schedule
$route->addGroup(
'-schedule',
'/schedule',
function (RouteCollector $route) {
$route->get('', 'Admin\\Schedule\\ImportSchedule@index');
$route->post('-load', 'Admin\\Schedule\\ImportSchedule@loadSchedule');
$route->post('-import', 'Admin\\Schedule\\ImportSchedule@importSchedule');
$route->post('/load', 'Admin\\Schedule\\ImportSchedule@loadSchedule');
$route->post('/import', 'Admin\\Schedule\\ImportSchedule@importSchedule');
}
);
$route->addGroup(

@ -48,7 +48,7 @@ class ImportSchedule extends BaseController
protected $session;
/** @var string */
protected $url = 'admin-schedule';
protected $url = '/admin/schedule';
/** @var GuzzleClient */
protected $guzzle;
@ -520,7 +520,7 @@ class ImportSchedule extends BaseController
/**
* @param ScheduleUrl $scheduleUrl
* @param string[] $events
* @return QueryBuilder[]|DatabaseCollection|ScheduleShift[]
* @return QueryBuilder[]|DatabaseCollection|Collection|ScheduleShift[]
*/
protected function getScheduleShiftsByGuid(ScheduleUrl $scheduleUrl, array $events)
{
@ -533,7 +533,7 @@ class ImportSchedule extends BaseController
/**
* @param ScheduleUrl $scheduleUrl
* @param string[] $events
* @return QueryBuilder[]|DatabaseCollection|ScheduleShift[]
* @return QueryBuilder[]|DatabaseCollection|Collection|ScheduleShift[]
*/
protected function getScheduleShiftsWhereNotGuid(ScheduleUrl $scheduleUrl, array $events)
{

@ -117,7 +117,7 @@ function make_navigation()
'admin_shifts' => 'Create shifts',
'admin_rooms' => 'Rooms',
'admin_groups' => 'Grouprights',
'admin-schedule' => ['schedule.import', 'schedule.import'],
'admin/schedule' => ['schedule.import', 'schedule.import'],
'admin_log' => 'Log',
'admin_event_config' => 'Event config',
];

@ -17,7 +17,7 @@
<div class="row">
{% block row_content %}
<form method="POST" action="{{ url('/admin-schedule-load') }}">
<form method="POST" action="{{ url('/admin/schedule/load') }}">
{{ csrf() }}
<div class="col-md-12">

@ -4,7 +4,7 @@
{% block title %}{{ __('schedule.import.load.title') }}{% endblock %}
{% block row_content %}
<form method="POST" action="{{ url('/admin-schedule-import') }}">
<form method="POST" action="{{ url('/admin/schedule/import') }}">
{{ csrf() }}
{{ f.hidden('schedule-url', schedule_url) }}
{{ f.hidden('shift-type', shift_type) }}

@ -5,9 +5,9 @@ namespace Engelsystem\Controllers;
use Carbon\Carbon;
use Engelsystem\Config\Config;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Models\User\User;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
@ -21,8 +21,8 @@ class AuthController extends BaseController
/** @var SessionInterface */
protected $session;
/** @var UrlGeneratorInterface */
protected $url;
/** @var Redirector */
protected $redirect;
/** @var Config */
protected $config;
@ -39,20 +39,20 @@ class AuthController extends BaseController
/**
* @param Response $response
* @param SessionInterface $session
* @param UrlGeneratorInterface $url
* @param Redirector $redirect
* @param Config $config
* @param Authenticator $auth
*/
public function __construct(
Response $response,
SessionInterface $session,
UrlGeneratorInterface $url,
Redirector $redirect,
Config $config,
Authenticator $auth
) {
$this->response = $response;
$this->session = $session;
$this->url = $url;
$this->redirect = $redirect;
$this->config = $config;
$this->auth = $auth;
}
@ -104,7 +104,7 @@ class AuthController extends BaseController
$user->last_login_at = new Carbon();
$user->save(['touch' => false]);
return $this->response->redirectTo($this->config->get('home_site'));
return $this->redirect->to($this->config->get('home_site'));
}
/**
@ -114,6 +114,6 @@ class AuthController extends BaseController
{
$this->session->invalidate();
return $this->response->redirectTo($this->url->to('/'));
return $this->redirect->to('/');
}
}

@ -10,17 +10,24 @@ class Redirector
/** @var Response */
protected $response;
/** @var UrlGeneratorInterface */
protected $url;
/**
* @param Request $request
* @param Response $response
* @param UrlGeneratorInterface $url
*/
public function __construct(Request $request, Response $response)
public function __construct(Request $request, Response $response, UrlGeneratorInterface $url)
{
$this->request = $request;
$this->response = $response;
$this->url = $url;
}
/**
* Redirects to a path, generating a full URL
*
* @param string $path
* @param int $status
* @param array $headers
@ -28,7 +35,7 @@ class Redirector
*/
public function to(string $path, int $status = 302, array $headers = []): Response
{
return $this->response->redirectTo($path, $status, $headers);
return $this->response->redirectTo($this->url->to($path), $status, $headers);
}
/**

@ -3,22 +3,26 @@
namespace Engelsystem\Http;
/**
* Provides urls when rewriting on the webserver is enabled. (default)
* Provides URLs
*
* The urls have the form <app url>/<path>?<parameters>
*/
class UrlGenerator implements UrlGeneratorInterface
{
/**
* Create a URL for the given path, using the applications base url if configured
*
* @param string $path
* @param array $parameters
* @return string url in the form [app url]/[path]?[parameters]
*/
public function to($path, $parameters = [])
public function to(string $path, array $parameters = []): string
{
$path = '/' . ltrim($path, '/');
$request = app('request');
$uri = $request->getUriForPath($path);
$uri = $path;
if (!$this->isValidUrl($uri)) {
$uri = $this->generateUrl($path);
}
if (!empty($parameters) && is_array($parameters)) {
$parameters = http_build_query($parameters);
@ -27,4 +31,37 @@ class UrlGenerator implements UrlGeneratorInterface
return $uri;
}
/**
* Check if the URL is valid
*
* @param string $path
* @return bool
*/
public function isValidUrl(string $path): bool
{
return preg_match('~^(?:\w+:(//)?|#)~', $path) || filter_var($path, FILTER_VALIDATE_URL) !== false;
}
/**
* Prepend the auto detected or configured app base path and domain
*
* @param $path
* @return string
*/
protected function generateUrl(string $path): string
{
$path = '/' . ltrim($path, '/');
$baseUrl = config('url');
if ($baseUrl) {
$uri = rtrim($baseUrl, '/') . $path;
} else {
/** @var Request $request */
$request = app('request');
$uri = $request->getUriForPath($path);
}
return $uri;
}
}

@ -12,5 +12,5 @@ interface UrlGeneratorInterface
* @param array $parameters
* @return string
*/
public function to($path, $parameters = []);
public function to(string $path, array $parameters = []): string;
}

@ -2,19 +2,19 @@
namespace Engelsystem\Renderer\Twig\Extensions;
use Engelsystem\Http\UrlGenerator;
use Engelsystem\Http\UrlGeneratorInterface;
use Twig\Extension\AbstractExtension as TwigExtension;
use Twig\TwigFunction;
class Assets extends TwigExtension
{
/** @var UrlGenerator */
/** @var UrlGeneratorInterface */
protected $urlGenerator;
/**
* @param UrlGenerator $urlGenerator
* @param UrlGeneratorInterface $urlGenerator
*/
public function __construct(UrlGenerator $urlGenerator)
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}

@ -2,19 +2,19 @@
namespace Engelsystem\Renderer\Twig\Extensions;
use Engelsystem\Http\UrlGenerator;
use Engelsystem\Http\UrlGeneratorInterface;
use Twig\Extension\AbstractExtension as TwigExtension;
use Twig\TwigFunction;
class Url extends TwigExtension
{
/** @var UrlGenerator */
/** @var UrlGeneratorInterface */
protected $urlGenerator;
/**
* @param UrlGenerator $urlGenerator
* @param UrlGeneratorInterface $urlGenerator
*/
public function __construct(UrlGenerator $urlGenerator)
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}

@ -2,17 +2,19 @@
namespace Engelsystem\Test\Unit\Controllers\Admin;
use Engelsystem\Config\Config;
use Engelsystem\Controllers\Admin\NewsController;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGenerator;
use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\News;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Collection;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Http\Message\ServerRequestInterface;
@ -161,7 +163,7 @@ class NewsControllerTest extends TestCase
->willReturn($canEditHtml);
$this->response->expects($this->once())
->method('redirectTo')
->with('/news/' . $id)
->with('http://localhost/news/' . $id)
->willReturn($this->response);
/** @var NewsController $controller */
@ -195,7 +197,7 @@ class NewsControllerTest extends TestCase
]);
$this->response->expects($this->once())
->method('redirectTo')
->with('/news')
->with('http://localhost/news')
->willReturn($this->response);
/** @var NewsController $controller */
@ -220,7 +222,8 @@ class NewsControllerTest extends TestCase
parent::setUp();
$this->initDatabase();
$this->request = new Request();
$this->request = Request::create('http://localhost');
$this->app->instance('request', $this->request);
$this->app->instance(Request::class, $this->request);
$this->app->instance(ServerRequestInterface::class, $this->request);
@ -235,6 +238,10 @@ class NewsControllerTest extends TestCase
$this->auth = $this->createMock(Authenticator::class);
$this->app->instance(Authenticator::class, $this->auth);
$this->app->bind(UrlGeneratorInterface::class, UrlGenerator::class);
$this->app->instance('config', new Config());
(new News([
'title' => 'Foo',
'text' => '<b>foo</b>',

@ -7,9 +7,9 @@ use Engelsystem\Config\Config;
use Engelsystem\Controllers\AuthController;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\User\Settings;
use Engelsystem\Models\User\User;
@ -35,10 +35,10 @@ class AuthControllerTest extends TestCase
/** @var Response|MockObject $response */
$response = $this->createMock(Response::class);
/** @var SessionInterface|MockObject $session */
/** @var UrlGeneratorInterface|MockObject $url */
/** @var Redirector|MockObject $redirect */
/** @var Config $config */
/** @var Authenticator|MockObject $auth */
list(, $session, $url, $config, $auth) = $this->getMocks();
list(, $session, $redirect, $config, $auth) = $this->getMocks();
$session->expects($this->atLeastOnce())
->method('get')
@ -50,7 +50,7 @@ class AuthControllerTest extends TestCase
->with('pages/login')
->willReturn($response);
$controller = new AuthController($response, $session, $url, $config, $auth);
$controller = new AuthController($response, $session, $redirect, $config, $auth);
$controller->login();
}
@ -64,10 +64,10 @@ class AuthControllerTest extends TestCase
$request = new Request();
/** @var Response|MockObject $response */
$response = $this->createMock(Response::class);
/** @var UrlGeneratorInterface|MockObject $url */
/** @var Redirector|MockObject $redirect */
/** @var Config $config */
/** @var Authenticator|MockObject $auth */
list(, , $url, $config, $auth) = $this->getMocks();
list(, , $redirect, $config, $auth) = $this->getMocks();
$session = new Session(new MockArraySessionStorage());
/** @var Validator|MockObject $validator */
$validator = new Validator();
@ -101,13 +101,13 @@ class AuthControllerTest extends TestCase
$this->assertArraySubset(['errors' => collect(['some.bar.error', 'auth.not-found'])], $data);
return $response;
});
$response->expects($this->once())
->method('redirectTo')
$redirect->expects($this->once())
->method('to')
->with('news')
->willReturn($response);
// No credentials
$controller = new AuthController($response, $session, $url, $config, $auth);
$controller = new AuthController($response, $session, $redirect, $config, $auth);
$controller->setValidator($validator);
try {
$controller->postLogin($request);
@ -142,23 +142,23 @@ class AuthControllerTest extends TestCase
{
/** @var Response $response */
/** @var SessionInterface|MockObject $session */
/** @var UrlGeneratorInterface|MockObject $url */
/** @var Redirector|MockObject $redirect */
/** @var Config $config */
/** @var Authenticator|MockObject $auth */
list($response, $session, $url, $config, $auth) = $this->getMocks();
list($response, $session, $redirect, $config, $auth) = $this->getMocks();
$session->expects($this->once())
->method('invalidate');
$url->expects($this->once())
$redirect->expects($this->once())
->method('to')
->with('/')
->willReturn('https://foo.bar/');
->willReturn($response);
$controller = new AuthController($response, $session, $url, $config, $auth);
$controller = new AuthController($response, $session, $redirect, $config, $auth);
$return = $controller->logout();
$this->assertEquals(['https://foo.bar/'], $return->getHeader('location'));
$this->assertEquals($response, $return);
}
/**
@ -169,14 +169,14 @@ class AuthControllerTest extends TestCase
$response = new Response();
/** @var SessionInterface|MockObject $session */
$session = $this->getMockForAbstractClass(SessionInterface::class);
/** @var UrlGeneratorInterface|MockObject $url */
$url = $this->getMockForAbstractClass(UrlGeneratorInterface::class);
/** @var Redirector|MockObject $redirect */
$redirect = $this->createMock(Redirector::class);
$config = new Config(['home_site' => 'news']);
/** @var Authenticator|MockObject $auth */
$auth = $this->createMock(Authenticator::class);
$this->app->instance('session', $session);
return [$response, $session, $url, $config, $auth];
return [$response, $session, $redirect, $config, $auth];
}
}

@ -8,6 +8,8 @@ use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGenerator;
use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\News;
use Engelsystem\Models\NewsComment;
@ -231,6 +233,7 @@ class NewsControllerTest extends TestCase
$this->initDatabase();
$this->request = new Request();
$this->app->instance('request', $this->request);
$this->app->instance(Request::class, $this->request);
$this->app->instance(ServerRequestInterface::class, $this->request);
@ -247,6 +250,10 @@ class NewsControllerTest extends TestCase
$this->auth = $this->createMock(Authenticator::class);
$this->app->instance(Authenticator::class, $this->auth);
$this->app->bind(UrlGeneratorInterface::class, UrlGenerator::class);
$this->app->instance('config', new Config());
foreach ($this->data as $news) {
(new News($news))->save();
}

@ -5,6 +5,8 @@ namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class RedirectorTest extends TestCase
@ -17,7 +19,9 @@ class RedirectorTest extends TestCase
{
$request = new Request();
$response = new Response();
$redirector = new Redirector($request, $response);
$url = $this->getUrlGenerator();
$redirector = new Redirector($request, $response, $url);
$return = $redirector->to('/test');
$this->assertEquals(['/test'], $return->getHeader('location'));
@ -37,17 +41,43 @@ class RedirectorTest extends TestCase
{
$request = new Request();
$response = new Response();
$redirector = new Redirector($request, $response);
$url = $this->getUrlGenerator();
$redirector = new Redirector($request, $response, $url);
$return = $redirector->back();
$this->assertEquals(['/'], $return->getHeader('location'));
$this->assertEquals(302, $return->getStatusCode());
$request = $request->withHeader('referer', '/old-page');
$redirector = new Redirector($request, $response);
$redirector = new Redirector($request, $response, $url);
$return = $redirector->back(303, ['foo' => 'bar']);
$this->assertEquals(303, $return->getStatusCode());
$this->assertEquals(['/old-page'], $return->getHeader('location'));
$this->assertEquals(['bar'], $return->getHeader('foo'));
}
/**
* @return UrlGeneratorInterface|MockObject
*/
protected function getUrlGenerator()
{
/** @var UrlGeneratorInterface|MockObject $url */
$url = $this->getMockForAbstractClass(UrlGeneratorInterface::class);
$url->expects($this->atLeastOnce())
->method('to')
->willReturnCallback([$this, 'returnPath']);
return $url;
}
/**
* Returns the provided path
*
* @param string $path
* @return string
*/
public function returnPath(string $path)
{
return $path;
}
}

@ -2,15 +2,17 @@
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Application;
use Engelsystem\Container\Container;
use Engelsystem\Config\Config;
use Engelsystem\Http\Request;
use Engelsystem\Http\UrlGenerator;
use PHPUnit\Framework\TestCase;
use Engelsystem\Test\Unit\TestCase;
class UrlGeneratorTest extends TestCase
{
public function provideLinksTo()
/**
* @return array
*/
public function provideLinksTo(): array
{
return [
['/foo/path', '/foo/path', 'http://foo.bar/foo/path', [], 'http://foo.bar/foo/path'],
@ -22,6 +24,7 @@ class UrlGeneratorTest extends TestCase
/**
* @dataProvider provideLinksTo
* @covers \Engelsystem\Http\UrlGenerator::to
* @covers \Engelsystem\Http\UrlGenerator::generateUrl
*
* @param string $path
* @param string $willReturn
@ -31,21 +34,68 @@ class UrlGeneratorTest extends TestCase
*/
public function testTo($urlToPath, $path, $willReturn, $arguments, $expectedUrl)
{
$app = new Container();
$urlGenerator = new UrlGenerator();
Application::setInstance($app);
$request = $this->getMockBuilder(Request::class)
->getMock();
$request->expects($this->once())
->method('getUriForPath')
->with($path)
->willReturn($willReturn);
$this->app->instance('request', $request);
$this->app->instance('config', new Config());
$app->instance('request', $request);
$urlGenerator = new UrlGenerator();
$url = $urlGenerator->to($urlToPath, $arguments);
$this->assertEquals($expectedUrl, $url);
}
/**
* @covers \Engelsystem\Http\UrlGenerator::to
*/
public function testToWithValidUrl()
{
$url = new UrlGenerator();
$this->app->instance('config', new Config());
$this->assertEquals('https://foo.bar/batz', $url->to('https://foo.bar/batz'));
$this->assertEquals('https://some.url?lorem=ipsum', $url->to('https://some.url', ['lorem' => 'ipsum']));
$this->assertEquals('mailto:foo@bar.batz', $url->to('mailto:foo@bar.batz'));
$this->assertEquals('#some-anchor', $url->to('#some-anchor'));
}
/**
* @covers \Engelsystem\Http\UrlGenerator::to
* @covers \Engelsystem\Http\UrlGenerator::generateUrl
*/
public function testToWithApplicationURL()
{
$this->app->instance('config', new Config(['url' => 'https://foo.bar/base/']));
$url = new UrlGenerator();
$this->assertEquals('https://foo.bar/base/test', $url->to('test'));
$this->assertEquals('https://foo.bar/base/test', $url->to('/test'));
$this->assertEquals('https://foo.bar/base/lorem?ipsum=dolor', $url->to('/lorem', ['ipsum' => 'dolor']));
$this->app->instance('config', new Config(['url' => 'https://foo.bar/base']));
$this->assertEquals('https://foo.bar/base/test', $url->to('test'));
$this->assertEquals('https://foo.bar/base/test', $url->to('/test'));
}
/**
* @covers \Engelsystem\Http\UrlGenerator::isValidUrl
*/
public function testIsValidUrl()
{
$url = new UrlGenerator();
$this->assertTrue($url->isValidUrl('https://foo.bar'));
$this->assertTrue($url->isValidUrl('#foo-bar'));
$this->assertTrue($url->isValidUrl('tel:+123456'));
$this->assertTrue($url->isValidUrl('ftp://foo@bar.batz'));
$this->assertFalse($url->isValidUrl('foo/bar'));
$this->assertFalse($url->isValidUrl('foo/uff://bar'));
$this->assertFalse($url->isValidUrl('lorem/ipsum#dolor'));
}
}

@ -2,7 +2,7 @@
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Application;
use Engelsystem\Config\Config;
use Engelsystem\Http\Exceptions\HttpException;
use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Psr7ServiceProvider;
@ -10,12 +10,13 @@ use Engelsystem\Http\RedirectServiceProvider;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\ResponseServiceProvider;
use Engelsystem\Http\UrlGeneratorServiceProvider;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Middleware\ErrorHandler;
use Engelsystem\Test\Unit\Middleware\Stub\ReturnResponseMiddlewareHandler;
use Engelsystem\Test\Unit\TestCase;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
@ -186,19 +187,20 @@ class ErrorHandlerTest extends TestCase
);
$request->setSession($session);
/** @var Application $app */
$app = app();
$app->instance(Session::class, $session);
$app->bind(SessionInterface::class, Session::class);
(new ResponseServiceProvider($app))->register();
(new Psr7ServiceProvider($app))->register();
(new RedirectServiceProvider($app))->register();
$this->app->instance(Session::class, $session);
$this->app->bind(SessionInterface::class, Session::class);
$this->app->instance('request', $request);
$this->app->instance('config', new Config());
(new ResponseServiceProvider($this->app))->register();
(new Psr7ServiceProvider($this->app))->register();
(new RedirectServiceProvider($this->app))->register();
(new UrlGeneratorServiceProvider($this->app))->register();
$errorHandler = new ErrorHandler($twigLoader);
$return = $errorHandler->process($request, $handler);
$this->assertEquals(302, $return->getStatusCode());
$this->assertEquals('/', $return->getHeaderLine('location'));
$this->assertEquals('http://localhost/', $return->getHeaderLine('location'));
$this->assertEquals([
'errors' => [
'validation' => [
@ -214,10 +216,10 @@ class ErrorHandlerTest extends TestCase
], $session->all());
$request = $request->withAddedHeader('referer', '/foo/batz');
$app->instance(Request::class, $request);
$this->app->instance(Request::class, $request);
$return = $errorHandler->process($request, $handler);
$this->assertEquals('/foo/batz', $return->getHeaderLine('location'));
$this->assertEquals('http://localhost/foo/batz', $return->getHeaderLine('location'));
}
/**

Loading…
Cancel
Save