# overrides can be made. Defaults to disabled.
APP_THEME=false
+# Trusted Proxies
+# Used to indicate trust of systems that proxy to the application so
+# certain header values (Such as "X-Forwarded-For") can be used from the
+# incoming proxy request to provide origin detail.
+# Set to an IP address, or multiple comma seperated IP addresses.
+# Can alternatively be set to "*" to trust all proxy addresses.
+APP_PROXIES=null
+
# Database details
# Host can contain a port (localhost:3306) or a separate DB_PORT option can be used.
DB_HOST=localhost
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Request;
class ActivityService
{
*/
protected function newActivityForUser(string $type): Activity
{
+ $ip = request()->ip() ?? '';
return $this->activity->newInstance()->forceFill([
'type' => strtolower($type),
'user_id' => user()->id,
+ 'ip' => config('app.env') === 'demo' ? '127.0.0.1' : $ip,
]);
}
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Models\Entity;
-use DB;
use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
class TagRepo
{
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
-use Log;
+use Illuminate\Support\Facades\Log;
class UserRepo
{
namespace BookStack\Console\Commands;
use BookStack\Entities\Tools\SearchIndex;
-use DB;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
class RegenerateSearch extends Command
{
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
-use Validator;
+use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
namespace BookStack\Providers;
-use Blade;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Entities\BreadcrumbsViewComposer;
use BookStack\Util\CspService;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Database\Eloquent\Relations\Relation;
+use Illuminate\Support\Facades\Blade;
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Laravel\Socialite\Contracts\Factory as SocialiteFactory;
-use Schema;
-use URL;
class AppServiceProvider extends ServiceProvider
{
namespace BookStack\Providers;
-use Auth;
use BookStack\Api\ApiTokenGuard;
use BookStack\Auth\Access\ExternalBaseUserProvider;
use BookStack\Auth\Access\Guards\LdapSessionGuard;
use BookStack\Auth\Access\LdapService;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
namespace BookStack\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
-use Route;
+use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
+use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
-use Log;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class AttachmentService
namespace BookStack\Uploads;
use BookStack\Exceptions\ImageUploadException;
-use DB;
use ErrorException;
use Exception;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
use Illuminate\Contracts\Filesystem\Filesystem as Storage;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Intervention\Image\Exception\NotSupportedException;
use Intervention\Image\ImageManager;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Str;
class CreateJointPermissionsTable extends Migration
{
// Ensure unique name
while (DB::table('roles')->where('name', '=', $publicRoleData['display_name'])->count() > 0) {
- $publicRoleData['display_name'] = $publicRoleData['display_name'] . str_random(2);
+ $publicRoleData['display_name'] = $publicRoleData['display_name'] . Str::random(2);
}
$publicRoleId = DB::table('roles')->insertGetId($publicRoleData);
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddActivitiesIpColumn extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table('activities', function (Blueprint $table) {
+ $table->string('ip', 45)->after('user_id');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('activities', function (Blueprint $table) {
+ $table->dropColumn('ip');
+ });
+ }
+}
'audit_table_user' => 'User',
'audit_table_event' => 'Event',
'audit_table_related' => 'Related Item or Detail',
+ 'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Activity Date',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
<a href="{{ sortUrl('/settings/audit', $listDetails, ['sort' => 'key']) }}">{{ trans('settings.audit_table_event') }}</a>
</th>
<th>{{ trans('settings.audit_table_related') }}</th>
+ <th>{{ trans('settings.audit_table_ip') }}</th>
<th>
<a href="{{ sortUrl('/settings/audit', $listDetails, ['sort' => 'created_at']) }}">{{ trans('settings.audit_table_date') }}</a></th>
</tr>
<div class="px-m">{{ $activity->detail }}</div>
@endif
</td>
+ <td>{{ $activity->ip }}</td>
<td>{{ $activity->created_at }}</td>
</tr>
@endforeach
$resp->assertSeeText($chapter->name);
$resp->assertDontSeeText($page->name);
}
+
+ public function test_ip_address_logged_and_visible()
+ {
+ config()->set('app.proxies', '*');
+ $editor = $this->getEditor();
+ /** @var Page $page */
+ $page = Page::query()->first();
+
+ $this->actingAs($editor)->put($page->getUrl(), [
+ 'name' => 'Updated page',
+ 'html' => '<p>Updated content</p>',
+ ], [
+ 'X-Forwarded-For' => '192.123.45.1'
+ ])->assertRedirect($page->refresh()->getUrl());
+
+ $this->assertDatabaseHas('activities', [
+ 'type' => ActivityType::PAGE_UPDATE,
+ 'ip' => '192.123.45.1',
+ 'user_id' => $editor->id,
+ 'entity_id' => $page->id,
+ ]);
+
+ $resp = $this->asAdmin()->get('/settings/audit');
+ $resp->assertSee('192.123.45.1');
+ }
+
+ public function test_ip_address_not_logged_in_demo_mode()
+ {
+ config()->set('app.proxies', '*');
+ config()->set('app.env', 'demo');
+ $editor = $this->getEditor();
+ /** @var Page $page */
+ $page = Page::query()->first();
+
+ $this->actingAs($editor)->put($page->getUrl(), [
+ 'name' => 'Updated page',
+ 'html' => '<p>Updated content</p>',
+ ], [
+ 'X-Forwarded-For' => '192.123.45.1',
+ 'REMOTE_ADDR' => '192.123.45.2',
+ ])->assertRedirect($page->refresh()->getUrl());
+
+ $this->assertDatabaseHas('activities', [
+ 'type' => ActivityType::PAGE_UPDATE,
+ 'ip' => '127.0.0.1',
+ 'user_id' => $editor->id,
+ 'entity_id' => $page->id,
+ ]);
+ }
}
use BookStack\Entities\Models\Page;
use BookStack\Notifications\ConfirmEmail;
use BookStack\Notifications\ResetPassword;
-use DB;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
use Tests\TestResponse;
use BookStack\Actions\ActivityType;
use BookStack\Auth\SocialAccount;
use BookStack\Auth\User;
-use DB;
+use Illuminate\Support\Facades\DB;
use Laravel\Socialite\Contracts\Factory;
use Laravel\Socialite\Contracts\Provider;
use Mockery;
use BookStack\Auth\User;
use BookStack\Notifications\UserInvite;
use Carbon\Carbon;
-use DB;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str;
-use Notification;
use Tests\TestCase;
class UserInviteTest extends TestCase
namespace Tests;
-use Auth;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Auth\Role;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\View;
class PublicActionTest extends TestCase
use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
-use DB;
use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\DB;
class RecycleBinTest extends TestCase
{
use BookStack\Entities\Tools\PageContent;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
-use File;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
+use Illuminate\Support\Facades\File;
use League\CommonMark\ConfigurableEnvironmentInterface;
class ThemeTest extends TestCase