feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run

- Base: wintercms/winter branch 1.2 (full framework)
- Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS
- Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication
- Partials: hero (offline-first), features, modes (offline/nube toggle),
  screenshots, pricing (3 planes), comparison, FAQ, CTA
- Plugin VivesPOS.Site with ContactForm
- Dockerfile: PHP 8.2 Apache, port 80, healthcheck
- Added winter/wn-pages, blog, sitemap, seo plugins
- Active theme set to vivespos
This commit is contained in:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

View File

@@ -0,0 +1,480 @@
<?php
namespace Backend\Tests\Models;
use Backend\Facades\BackendAuth;
use Backend\Models\User;
use Backend\Models\UserGroup;
use Backend\Tests\Fixtures\Models\UserFixture;
use System\Tests\Bootstrap\PluginTestCase;
use Winter\Storm\Auth\AuthorizationException;
use Winter\Storm\Database\Model;
class UserAuthorizationTest extends PluginTestCase
{
protected User $targetUser;
public function setUp(): void
{
parent::setUp();
// Create a real target user in the database for operations that need it
Model::unguard();
$this->targetUser = User::create([
'first_name' => 'Target',
'last_name' => 'User',
'login' => 'targetuser',
'email' => 'target@test.com',
'password' => 'TestPassword1',
'password_confirmation' => 'TestPassword1',
'is_activated' => true,
'is_superuser' => false,
]);
Model::reguard();
}
//
// canBeManagedByUser()
//
public function testCanBeManagedByUserReturnsTrueForCli(): void
{
// No authenticated user (CLI context)
BackendAuth::logout();
$this->assertTrue($this->targetUser->canBeManagedByUser());
}
public function testCanBeManagedByUserReturnsFalseWithoutPermission(): void
{
$actor = (new UserFixture)->withPermission('backend.manage_users', false);
$this->assertFalse($this->targetUser->canBeManagedByUser($actor));
}
public function testCanBeManagedByUserReturnsTrueWithPermission(): void
{
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->assertTrue($this->targetUser->canBeManagedByUser($actor));
}
public function testCanBeManagedByUserReturnsFalseForNonSuperuserTargetingSuperuser(): void
{
$this->targetUser->is_superuser = true;
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->assertFalse($this->targetUser->canBeManagedByUser($actor));
}
public function testCanBeManagedByUserReturnsTrueForSuperuserTargetingSuperuser(): void
{
$this->targetUser->is_superuser = true;
$actor = (new UserFixture)->asSuperUser()->withPermission('backend.manage_users', true);
$this->assertTrue($this->targetUser->canBeManagedByUser($actor));
}
public function testCanBeManagedByUserChecksPreviousSuperuserStatus(): void
{
// Simulate a record that WAS a superuser (getOriginal returns true)
$this->targetUser->syncOriginal();
$this->targetUser->is_superuser = true;
$this->targetUser->syncOriginal();
$this->targetUser->is_superuser = false;
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
// getOriginal('is_superuser') is true, so non-superuser can't manage
$this->assertFalse($this->targetUser->canBeManagedByUser($actor));
}
public function testCanBeManagedByUserFallsBackToAuthenticatedUser(): void
{
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
// No explicit user passed — falls back to authenticated user
$this->assertTrue($this->targetUser->canBeManagedByUser());
}
//
// beforeCreate() / beforeUpdate()
//
public function testUpdateAllowsCliContext(): void
{
BackendAuth::logout();
$this->targetUser->first_name = 'Updated';
$this->targetUser->save();
$this->assertEquals('Updated', $this->targetUser->first_name);
}
public function testUpdateAllowsSelfNonEscalatingChanges(): void
{
$this->actingAs($this->targetUser);
$this->targetUser->first_name = 'NewName';
$this->targetUser->save();
$this->assertEquals('NewName', $this->targetUser->first_name);
}
public function testUpdateBlocksSelfEscalation(): void
{
$this->actingAs($this->targetUser);
$this->targetUser->is_superuser = true;
$this->expectException(AuthorizationException::class);
$this->targetUser->save();
}
public function testUpdateBlocksUnauthorizedUserModifyingOthers(): void
{
$actor = new UserFixture;
$this->actingAs($actor);
$this->targetUser->first_name = 'Hacked';
$this->expectException(AuthorizationException::class);
$this->targetUser->save();
}
public function testUpdateAllowsManageUsersActorModifyingOthers(): void
{
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
$this->targetUser->first_name = 'AdminUpdated';
$this->targetUser->save();
$this->assertEquals('AdminUpdated', $this->targetUser->first_name);
}
public function testSaveWithNoChangesRequiresNoPermission(): void
{
$actor = new UserFixture;
$this->actingAs($actor);
// The guard fires on create/update, which only happen when there is
// something to write — a no-op save is allowed for anyone (#1464)
try {
$this->targetUser->save();
} catch (AuthorizationException $e) {
$this->fail('Should not throw AuthorizationException for a save with no changes');
}
$this->assertTrue(true);
}
public function testCreateBlocksUnauthorizedUser(): void
{
$actor = new UserFixture;
$this->actingAs($actor);
$user = new User;
$user->first_name = 'New';
$user->last_name = 'User';
$user->login = 'newuser';
$user->email = 'new@test.com';
$user->password = 'TestPassword1';
$user->password_confirmation = 'TestPassword1';
$this->expectException(AuthorizationException::class);
$user->save();
}
public function testUpdateBlocksNonSuperuserModifyingSuperuser(): void
{
// Make the target a superuser via direct DB update to bypass model events
$this->targetUser->newQuery()->where('id', $this->targetUser->id)->update(['is_superuser' => true]);
// Re-fetch the model fresh to avoid stale password hash triggering validation
$superTarget = User::find($this->targetUser->id);
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
$superTarget->first_name = 'Hacked';
$this->expectException(AuthorizationException::class);
$superTarget->save();
}
public function testUpdateSuperuserCanModifyAnyone(): void
{
$actor = (new UserFixture)->asSuperUser();
$this->actingAs($actor);
$this->targetUser->first_name = 'SuperUpdated';
$this->targetUser->save();
$this->assertEquals('SuperUpdated', $this->targetUser->first_name);
}
//
// Group membership (model.relation.* guard)
//
protected function makeGroup(): UserGroup
{
return UserGroup::create([
'name' => 'Test Group',
'code' => 'test-group',
]);
}
public function testGroupAttachAllowsManageUsersActor(): void
{
$group = $this->makeGroup();
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
$this->targetUser->groups()->add($group);
$this->assertEquals(1, $this->targetUser->groups()->count());
}
public function testGroupAttachBlocksUnauthorizedUser(): void
{
$group = $this->makeGroup();
$actor = new UserFixture;
$this->actingAs($actor);
$this->expectException(AuthorizationException::class);
$this->targetUser->groups()->add($group);
}
public function testGroupDetachBlocksUnauthorizedUser(): void
{
$group = $this->makeGroup();
$this->targetUser->groups()->add($group);
$actor = new UserFixture;
$this->actingAs($actor);
$this->expectException(AuthorizationException::class);
$this->targetUser->groups()->remove($group);
}
public function testGroupSyncAssignmentBlocksUnauthorizedUser(): void
{
$group = $this->makeGroup();
$actor = new UserFixture;
$this->actingAs($actor);
// Assigning a relation value queues a sync that runs during save without
// dirtying any attributes — the relation guard must still catch it
$this->targetUser->groups = [$group->id];
$this->expectException(AuthorizationException::class);
$this->targetUser->save();
}
public function testPluginAddedRelationIsNotGuarded(): void
{
$group = $this->makeGroup();
// Simulate a plugin-added relation on the user model; only the relations
// core owns (listed in $permissionGuardedRelations) require the permission
$this->targetUser->belongsToMany['memberships'] = [
UserGroup::class,
'table' => 'backend_users_groups',
];
$actor = new UserFixture;
$this->actingAs($actor);
$this->targetUser->memberships()->add($group);
$this->assertEquals(1, $this->targetUser->memberships()->count());
}
public function testSelfGroupChangeAllowed(): void
{
$group = $this->makeGroup();
$this->actingAs($this->targetUser);
// Groups carry no permissions out of the box, so changing your own
// membership is not an escalation vector and needs no permission
$this->targetUser->groups()->add($group);
$this->assertEquals(1, $this->targetUser->groups()->count());
}
//
// beforeDelete()
//
public function testBeforeDeleteAllowsCliContext(): void
{
BackendAuth::logout();
$this->targetUser->delete();
$this->assertTrue($this->targetUser->trashed());
}
public function testBeforeDeleteBlocksUnauthorizedUser(): void
{
$actor = new UserFixture;
$this->actingAs($actor);
$this->expectException(AuthorizationException::class);
$this->targetUser->delete();
}
public function testBeforeDeleteAllowsManageUsersActor(): void
{
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
$this->targetUser->delete();
$this->assertTrue($this->targetUser->trashed());
}
public function testBeforeDeleteBlocksNonSuperuserDeletingSuperuser(): void
{
$this->targetUser->newQuery()->where('id', $this->targetUser->id)->update(['is_superuser' => true]);
$this->targetUser->refresh();
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
$this->expectException(AuthorizationException::class);
$this->targetUser->delete();
}
public function testBeforeDeleteSuperuserCanDeleteAnyone(): void
{
$actor = (new UserFixture)->asSuperUser();
$this->actingAs($actor);
$this->targetUser->delete();
$this->assertTrue($this->targetUser->trashed());
}
//
// beforeRestore()
//
public function testBeforeRestoreAllowsCliContext(): void
{
BackendAuth::logout();
$this->targetUser->delete();
$this->targetUser->restore();
$this->assertFalse($this->targetUser->trashed());
}
public function testBeforeRestoreBlocksUnauthorizedUser(): void
{
BackendAuth::logout();
$this->targetUser->delete();
$actor = new UserFixture;
$this->actingAs($actor);
$this->expectException(AuthorizationException::class);
$this->targetUser->restore();
}
public function testBeforeRestoreAllowsManageUsersActor(): void
{
BackendAuth::logout();
$this->targetUser->delete();
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
$this->targetUser->restore();
$this->assertFalse($this->targetUser->trashed());
}
public function testBeforeRestoreBlocksNonSuperuserRestoringSuperuser(): void
{
$this->targetUser->newQuery()->where('id', $this->targetUser->id)->update(['is_superuser' => true]);
$this->targetUser->refresh();
BackendAuth::logout();
$this->targetUser->delete();
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
$this->expectException(AuthorizationException::class);
$this->targetUser->restore();
}
public function testBeforeRestoreSuperuserCanRestoreAnyone(): void
{
BackendAuth::logout();
$this->targetUser->delete();
$actor = (new UserFixture)->asSuperUser();
$this->actingAs($actor);
$this->targetUser->restore();
$this->assertFalse($this->targetUser->trashed());
}
//
// unsuspend()
//
public function testUnsuspendBlocksUnauthorizedUser(): void
{
$actor = new UserFixture;
$this->actingAs($actor);
$this->expectException(AuthorizationException::class);
$this->targetUser->unsuspend();
}
public function testUnsuspendAllowsManageUsersActor(): void
{
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
// Should not throw — the throttle record may not exist but that's fine
// for testing the authorization gate
try {
$this->targetUser->unsuspend();
} catch (AuthorizationException $e) {
$this->fail('Should not throw AuthorizationException for manage_users actor');
}
$this->assertTrue(true);
}
public function testUnsuspendAllowsCliContext(): void
{
BackendAuth::logout();
try {
$this->targetUser->unsuspend();
} catch (AuthorizationException $e) {
$this->fail('Should not throw AuthorizationException in CLI context');
}
$this->assertTrue(true);
}
//
// getResetPasswordCode()
//
public function testGetResetPasswordCodeAllowsSelfService(): void
{
$this->actingAs($this->targetUser);
$code = $this->targetUser->getResetPasswordCode();
$this->assertNotEmpty($code);
}
public function testGetResetPasswordCodeBlocksUnauthorizedUserForOthers(): void
{
$actor = new UserFixture;
$this->actingAs($actor);
$this->expectException(AuthorizationException::class);
$this->targetUser->getResetPasswordCode();
}
public function testGetResetPasswordCodeAllowsManageUsersActorForOthers(): void
{
$actor = (new UserFixture)->withPermission('backend.manage_users', true);
$this->actingAs($actor);
$code = $this->targetUser->getResetPasswordCode();
$this->assertNotEmpty($code);
}
public function testGetResetPasswordCodeAllowsCliContext(): void
{
BackendAuth::logout();
$code = $this->targetUser->getResetPasswordCode();
$this->assertNotEmpty($code);
}
}