'Admin User', 'email' => 'admin@company.com', 'role' => 'admin', ]); $user = User::create([ 'name' => 'Standard User', 'email' => 'user@sentientgeeks.com', 'role' => 'user', ]); // Unauthenticated user is redirected to login $this->get('/controlpannel/dashboard') ->assertRedirect(route('login')); // Admin can access admin dashboard $this->actingAs($admin) ->get('/controlpannel/dashboard') ->assertStatus(200); // Standard user gets allowed standard user access to dashboard (our middleware handles this) $this->actingAs($user) ->get('/controlpannel/dashboard') ->assertRedirect(route('dashboard')); } /** * Test blocked users are forced to logout and redirected. */ public function test_blocked_user_cannot_access_dashboard_and_gets_logged_out(): void { $user = User::create([ 'name' => 'Standard User', 'email' => 'user@sentientgeeks.com', 'role' => 'user', 'is_blocked' => true, ]); // Attempting to access dashboard logs them out and redirects $this->actingAs($user) ->get('/dashboard') ->assertRedirect(route('login')); $this->assertFalse(auth()->check()); } /** * Test restricted services are hidden from specific users via deny overrides. */ public function test_restricted_service_is_not_displayed_to_the_restricted_user(): void { $user = User::create([ 'name' => 'Standard User', 'email' => 'user@sentientgeeks.com', 'role' => 'user', ]); $app1 = App::create([ 'name' => 'Outlook', 'url' => 'https://outlook.office.com', 'color' => '#0078d4', 'tag' => 'Microsoft 365' ]); $app2 = App::create([ 'name' => 'Keka HR', 'url' => 'https://sentientgeeks.keka.com', 'color' => '#f27059', 'tag' => 'HR Portal' ]); // Create a role and associate both apps $role = Role::create(['name' => 'Employee']); $role->apps()->sync([$app1->id, $app2->id]); // Assign role to user $user->roles()->sync([$role->id]); // Restrict user from Keka HR using override UserAppOverride::create([ 'user_id' => $user->id, 'app_id' => $app2->id, 'type' => 'deny', ]); $response = $this->actingAs($user) ->get('/dashboard'); $response->assertStatus(200); $response->assertSee('Outlook'); $response->assertDontSee('Keka HR'); } /** * Test additional services are displayed to specific users via allow overrides. */ public function test_additional_service_is_displayed_to_the_user(): void { $user = User::create([ 'name' => 'Standard User', 'email' => 'user@sentientgeeks.com', 'role' => 'user', ]); $app1 = App::create([ 'name' => 'Outlook', 'url' => 'https://outlook.office.com', 'color' => '#0078d4', 'tag' => 'Microsoft 365' ]); $app2 = App::create([ 'name' => 'Keka HR', 'url' => 'https://sentientgeeks.keka.com', 'color' => '#f27059', 'tag' => 'HR Portal' ]); // Create a role that only has Outlook $role = Role::create(['name' => 'Intern']); $role->apps()->sync([$app1->id]); // Assign role to user $user->roles()->sync([$role->id]); // Add Keka HR as extra service using override UserAppOverride::create([ 'user_id' => $user->id, 'app_id' => $app2->id, 'type' => 'allow', ]); $response = $this->actingAs($user) ->get('/dashboard'); $response->assertStatus(200); $response->assertSee('Outlook'); $response->assertSee('Keka HR'); } /** * Test admin user cannot authenticate via Microsoft OAuth callback. */ public function test_admin_cannot_authenticate_via_microsoft_oauth_callback(): void { $admin = User::create([ 'name' => 'Admin User', 'email' => 'admin@company.com', 'role' => 'admin', 'microsoft_id' => 'admin-microsoft-id-999' ]); $mockSocialite = \Mockery::mock('Laravel\Socialite\Contracts\Provider'); $mockUser = \Mockery::mock('Laravel\Socialite\Two\User'); $mockUser->shouldReceive('getId')->andReturn('admin-microsoft-id-999'); $mockUser->shouldReceive('getEmail')->andReturn('admin@company.com'); $mockUser->shouldReceive('getName')->andReturn('Admin User'); $mockUser->shouldReceive('getAvatar')->andReturn(null); $mockSocialite->shouldReceive('user')->andReturn($mockUser); \Laravel\Socialite\Facades\Socialite::shouldReceive('driver')->with('microsoft')->andReturn($mockSocialite); $response = $this->get('/auth/microsoft/callback'); $response->assertRedirect(route('admin.login')); $response->assertSessionHas('error', 'Security protocol: Administrators are restricted to email and password authentication only.'); $this->assertFalse(auth()->check()); } public function test_user_logout_redirects_to_microsoft_logout_page(): void { $user = User::create([ 'name' => 'Standard User', 'email' => 'user@sentientgeeks.com', 'role' => 'user', ]); $response = $this->actingAs($user) ->get('/logout'); // supports GET for testing $tenant = config('services.microsoft.tenant', 'common'); $expectedRedirect = "https://login.microsoftonline.com/{$tenant}/oauth2/v2.0/logout?" . http_build_query([ 'post_logout_redirect_uri' => route('login'), 'logout_hint' => 'user@sentientgeeks.com', ]); $response->assertRedirect($expectedRedirect); $this->assertFalse(auth()->check()); } /** * Test Microsoft Front-channel logout callback terminates local session. */ public function test_microsoft_front_channel_logout_clears_session(): void { $user = User::create([ 'name' => 'Standard User', 'email' => 'user@sentientgeeks.com', 'role' => 'user', ]); $this->actingAs($user); $this->assertTrue(auth()->check()); $response = $this->get('/auth/microsoft/logout'); $response->assertStatus(200); $response->assertSee('Logged out from SingleLogin'); $this->assertFalse(auth()->check()); } /** * Test that launching any service (like Keka) routes through Microsoft SSO flow * if the user is Microsoft-linked. */ public function test_sso_launch_redirects_to_microsoft_for_all_services_if_user_is_microsoft_linked(): void { $user = User::create([ 'name' => 'Standard User', 'email' => 'user@sentientgeeks.com', 'role' => 'user', 'microsoft_id' => 'user-microsoft-id-123', ]); $app = App::create([ 'name' => 'Keka HR', 'url' => 'https://sentientgeeks.keka.com', 'color' => '#f27059', 'tag' => 'HR Portal' ]); // Assign role with the app to user $role = Role::create(['name' => 'Employee']); $role->apps()->sync([$app->id]); $user->roles()->sync([$role->id]); $mockSocialite = \Mockery::mock('Laravel\Socialite\Contracts\Provider'); $mockSocialite->shouldReceive('redirectUrl')->with(route('sso.callback'))->andReturnSelf(); $mockSocialite->shouldReceive('with')->with([ 'login_hint' => $user->email, 'domain_hint' => 'organizations', 'prompt' => 'none' ])->andReturnSelf(); $mockSocialite->shouldReceive('redirect') ->andReturn(redirect('https://login.microsoftonline.com/common/oauth2/v2.0/authorize')); \Laravel\Socialite\Facades\Socialite::shouldReceive('driver')->with('microsoft')->andReturn($mockSocialite); $response = $this->actingAs($user) ->get(route('sso.launch', $app->id)); $response->assertRedirect('https://login.microsoftonline.com/common/oauth2/v2.0/authorize'); } /** * Test user with multiple roles including Admin logs in via general route, * sees user dashboard, displays all roles, and has Go to Admin Dashboard target_blank link. */ public function test_user_multiple_roles_admin_link(): void { $user = User::create([ 'name' => 'Multi Role User', 'email' => 'multi@sentientgeeks.com', 'role' => 'user', ]); $pmRole = Role::create(['name' => 'Project Manager']); $adminRole = Role::create(['name' => 'Admin']); $user->roles()->sync([$pmRole->id, $adminRole->id]); // User dashboard can be accessed $response = $this->actingAs($user) ->get('/dashboard'); $response->assertStatus(200); // At the top they will see other role i.e. admin $response->assertSee('Project Manager, Admin'); // And they will see a link Go to Admin Dashboard which has target="_blank" $response->assertSee('href="' . route('admin.dashboard') . '" target="_blank" class="nav-action-btn admin"', false); $response->assertSee('Go to Admin Dashboard'); // And they can access the Admin Dashboard $responseAdmin = $this->actingAs($user) ->get('/controlpannel/dashboard'); $responseAdmin->assertStatus(200); } /** * Test that new user registration / login defaults to the configured role. */ public function test_new_user_registration_default_role(): void { // 1. Initially configuration fallback is Developer $devRole = Role::create(['name' => 'Developer']); $otherRole = Role::create(['name' => 'Team Lead']); // Mock Socialite callback for a brand new user $mockSocialite = \Mockery::mock('Laravel\Socialite\Contracts\Provider'); $mockUser = \Mockery::mock('Laravel\Socialite\Two\User'); $mockUser->shouldReceive('getId')->andReturn('new-microsoft-id-888'); $mockUser->shouldReceive('getEmail')->andReturn('newuser@sentientgeeks.com'); $mockUser->shouldReceive('getName')->andReturn('New User'); $mockUser->shouldReceive('getAvatar')->andReturn(null); $mockSocialite->shouldReceive('user')->andReturn($mockUser); \Laravel\Socialite\Facades\Socialite::shouldReceive('driver')->with('microsoft')->andReturn($mockSocialite); // Login as new user $response = $this->get('/auth/microsoft/callback'); $response->assertRedirect(route('dashboard')); $newUser = User::where('email', 'newuser@sentientgeeks.com')->first(); $this->assertNotNull($newUser); $this->assertTrue($newUser->hasRole('Developer')); $this->assertFalse($newUser->hasRole('Team Lead')); } /** * Test that new user registration / login defaults to the custom configured role. */ public function test_new_user_registration_custom_default_role(): void { $devRole = Role::create(['name' => 'Developer']); $otherRole = Role::create(['name' => 'Team Lead']); // Admin updates the configuration $admin = User::create([ 'name' => 'Admin User', 'email' => 'admin@company.com', 'role' => 'admin', ]); $this->withoutMiddleware(\Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class); $this->actingAs($admin) ->post(route('admin.settings.default-role'), [ 'default_role' => 'Team Lead', ]) ->assertRedirect(route('admin.dashboard')); $this->assertEquals('Team Lead', \App\Models\Setting::get('default_role')); // Mock Socialite callback for second new user $mockSocialite2 = \Mockery::mock('Laravel\Socialite\Contracts\Provider'); $mockUser2 = \Mockery::mock('Laravel\Socialite\Two\User'); $mockUser2->shouldReceive('getId')->andReturn('new-microsoft-id-999'); $mockUser2->shouldReceive('getEmail')->andReturn('anotheruser@sentientgeeks.com'); $mockUser2->shouldReceive('getName')->andReturn('Another User'); $mockUser2->shouldReceive('getAvatar')->andReturn(null); $mockSocialite2->shouldReceive('user')->andReturn($mockUser2); \Laravel\Socialite\Facades\Socialite::shouldReceive('driver')->with('microsoft')->andReturn($mockSocialite2); // Login as second new user $response2 = $this->get('/auth/microsoft/callback'); $response2->assertRedirect(route('dashboard')); $anotherUser = User::where('email', 'anotheruser@sentientgeeks.com')->first(); $this->assertNotNull($anotherUser); $this->assertTrue($anotherUser->hasRole('Team Lead')); $this->assertFalse($anotherUser->hasRole('Developer')); } /** * Test granting and revoking admin privileges to/from other users. */ public function test_admin_toggle_privileges(): void { $admin = User::create([ 'name' => 'Admin User', 'email' => 'admin@company.com', 'role' => 'admin', ]); $targetUser = User::create([ 'name' => 'Regular Dev', 'email' => 'dev@sentientgeeks.com', 'role' => 'user', ]); $this->withoutMiddleware(\Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class); // 1. Grant admin privilege $response = $this->actingAs($admin) ->post(route('admin.users.toggle-admin', $targetUser->id)); $response->assertRedirect(route('admin.dashboard')); $targetUser = $targetUser->fresh(); $this->assertTrue($targetUser->isAdmin()); $this->assertEquals('admin', $targetUser->role); $this->assertTrue($targetUser->hasRole('Admin')); // 2. Revoke admin privilege $response2 = $this->actingAs($admin) ->post(route('admin.users.toggle-admin', $targetUser->id)); $response2->assertRedirect(route('admin.dashboard')); $targetUser = $targetUser->fresh(); $this->assertFalse($targetUser->isAdmin()); $this->assertEquals('user', $targetUser->role); $this->assertFalse($targetUser->hasRole('Admin')); // 3. Security: Prevent toggling self $response3 = $this->actingAs($admin) ->post(route('admin.users.toggle-admin', $admin->id)); $response3->assertRedirect(route('admin.dashboard')); $this->assertTrue($admin->fresh()->isAdmin()); } /** * Test user personal custom apps CRUD and isolation. */ public function test_user_personal_custom_apps(): void { $user1 = User::create([ 'name' => 'User One', 'email' => 'user1@sentientgeeks.com', 'role' => 'user', ]); $user2 = User::create([ 'name' => 'User Two', 'email' => 'user2@sentientgeeks.com', 'role' => 'user', ]); $this->withoutMiddleware(\Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class); // 1. Create personal app $response = $this->actingAs($user1) ->post(route('dashboard.personal-apps.store'), [ 'name' => 'My Test App', 'url' => 'https://example.com/test', 'color' => '#123456', 'tag' => 'Testing', 'desc' => 'Some description' ]); $response->assertRedirect(route('dashboard')); $this->assertDatabaseHas('personal_apps', [ 'user_id' => $user1->id, 'name' => 'My Test App', 'url' => 'https://example.com/test', 'color' => '#123456' ]); $personalApp = \App\Models\PersonalApp::where('name', 'My Test App')->first(); $this->assertNotNull($personalApp); // 2. View dashboard and see the custom app $viewResponse = $this->actingAs($user1)->get('/dashboard'); $viewResponse->assertStatus(200); $viewResponse->assertSee('MY TEST APP'); // Displayed upper case in UI // 3. Update personal app $updateResponse = $this->actingAs($user1) ->post(route('dashboard.personal-apps.update', $personalApp->id), [ 'name' => 'Updated Test App', 'url' => 'https://example.com/updated', 'color' => '#654321', 'tag' => 'UpdatedTag', 'desc' => 'New description' ]); $updateResponse->assertRedirect(route('dashboard')); $this->assertDatabaseHas('personal_apps', [ 'id' => $personalApp->id, 'name' => 'Updated Test App', 'url' => 'https://example.com/updated', 'color' => '#654321' ]); // 4. Security: User 2 cannot update User 1's custom app $hackerResponse = $this->actingAs($user2) ->post(route('dashboard.personal-apps.update', $personalApp->id), [ 'name' => 'Hacked App', 'url' => 'https://hacked.com', ]); // Let's assert a 404 status because user2 doesn't own it (Auth::user()->personalApps()->findOrFail($id) throws ModelNotFoundException which is rendered as 404) $hackerResponse->assertStatus(404); // 5. Delete personal app $deleteResponse = $this->actingAs($user1) ->delete(route('dashboard.personal-apps.destroy', $personalApp->id)); $deleteResponse->assertRedirect(route('dashboard')); $this->assertDatabaseMissing('personal_apps', [ 'id' => $personalApp->id ]); } /** * Test silent Microsoft SSO redirection for Convex CRM. */ public function test_convexcrm_silent_sso_redirection(): void { $user = User::create([ 'name' => 'Convex CRM User', 'email' => 'convexcrm@sentientgeeks.com', 'role' => 'user', 'microsoft_id' => 'mock-ms-id-777', ]); $app = \App\Models\App::create([ 'name' => 'Convex CRM', 'url' => 'https://demo-convexcrm.convexsol.co/', 'color' => '#123456', 'tag' => 'CRM' ]); // Enable access to this app $devRole = Role::firstOrCreate(['name' => 'Developer'], ['description' => 'Developer role']); $devRole->apps()->syncWithoutDetaching([$app->id]); $user->roles()->syncWithoutDetaching([$devRole->id]); // Trigger SSO launch for corporate app $response = $this->actingAs($user) ->get(route('sso.launch', $app->id)); // It should initiate Microsoft OAuth redirect to refresh the session // And save rewritten target URL (Convex CRM Microsoft login redirect endpoint) in session $this->assertEquals( 'https://demo-convexcrm.convexsol.co/admin/authentication/microsoft_login', session('sso_target_url') ); // Test personal custom app version $pApp = $user->personalApps()->create([ 'name' => 'My Custom Convex', 'url' => 'https://demo-convexcrm.convexsol.co', ]); $responsePersonal = $this->actingAs($user) ->get(route('sso.personal-launch', $pApp->id)); $this->assertEquals( 'https://demo-convexcrm.convexsol.co/admin/authentication/microsoft_login', session('sso_target_url') ); } }