Skip to content

Commit

Permalink
Merge branch 'next' into observation/FOUR-17536
Browse files Browse the repository at this point in the history
  • Loading branch information
gproly committed Aug 9, 2024
2 parents c25a852 + d5e41de commit d2ee34f
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 108 deletions.
26 changes: 20 additions & 6 deletions ProcessMaker/Http/Controllers/Auth/TwoFactorAuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,26 +143,40 @@ public function displayAuthAppQr(Request $request)
public function testSettings(Request $request)
{
$enabled = $request->json('enabled');
$message = [
'status' => 'success',
'message' => __('Configuration tested successfully.'),
];
$status = 200;

//The Two Step Method should be seleceted
if (count($enabled) === 0) {
$message = [
'status' => 'error',
'message' => __('The two-step method must be selected.')
];
$status = 500;
}

// Test Email Server, send email to current user
if (in_array('By email', $enabled)) {
$testEmailServer = $this->testEmailServer();
if ($testEmailServer !== true) {
return $testEmailServer;
$message = json_decode($testEmailServer->getContent(), true);
$status = $testEmailServer->getStatusCode() ?? 500;
}
}

// Test SMS Server
if (in_array('By message to phone number', $enabled)) {
$testSmsServer = $this->testSmsServer();
if ($testSmsServer !== true) {
return $testSmsServer;
$message = json_decode($testSmsServer->getContent(), true);
$status = $testSmsServer->getStatusCode() ?? 500;
}
}

return response()->json([
'status' => 'success',
'message' => ('Configuration tested successfully.'),
]);
return response()->json($message, $status);
}

private function testEmailServer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public function renderInflight(ModelerManager $manager, Process $process, $proce
/**
* Load PMBlock list
*/
private function getPmBlockList()
public function getPmBlockList()
{
$pmBlockList = null;
if (hasPackage('package-pm-blocks')) {
Expand Down
96 changes: 6 additions & 90 deletions ProcessMaker/Http/Middleware/SessionControlBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Jenssegers\Agent\Agent;
use ProcessMaker\Managers\SessionControlManager;
use ProcessMaker\Models\Setting;
use ProcessMaker\Models\User;
use Symfony\Component\HttpFoundation\Response;
Expand All @@ -25,17 +26,13 @@ class SessionControlBlock
public function handle(Request $request, Closure $next): Response
{
$user = $this->getUser($request);
$ip = $request->getClientIp() ?? $request->ip();
$hasUserSession = $request->session()->has('user_session');

if ($user && !$hasUserSession) {
// Check if the user's session should be blocked based on IP restriction
if ($this->blockedByIp($user, $request)) {
return $this->redirectToLogin();
}
// Check if the user's session should be blocked based on device restriction
if ($this->blockedByDevice($user, $request)) {
return $this->redirectToLogin();
}
$sessionManager = new SessionControlManager($user, $ip);

if ($user && !$hasUserSession && $sessionManager->isSessionBlocked()) {
return $sessionManager->redirectToLogin();
}

return $next($request);
Expand All @@ -51,85 +48,4 @@ private function getUser(Request $request): ?User
})->where('username', $request->input('username'))
->first();
}

private function blockedByIp(User $user, Request $request): bool
{
return Setting::configByKey(self::IP_RESTRICTION_KEY) === '1' && $this->blockSessionByIp($user, $request);
}

private function blockedByDevice(User $user, Request $request): bool
{
return Setting::configByKey(self::DEVICE_RESTRICTION_KEY) === '1'
&& $this->blockSessionByDevice($user, $request);
}

/**
* Checks if a user's session is a duplicate based on their IP address.
*
* @param User user
* @param Request request
*
* @return bool
*/
private function blockSessionByIp(User $user, Request $request): bool
{
// Get the user's most recent session
$session = $user->sessions->sortByDesc('created_at')->first();
// Get the user's current IP address
$ip = $request->getClientIp() ?? $request->ip();

return $session->ip_address === $ip;
}

/**
* Checks if the user's current session device matches the device used in the request
*
* @param User user
* @param Request request
*
* @return bool
*/
private function blockSessionByDevice(User $user, Request $request): bool
{
$agent = new Agent();
// Get the device details from the request
$agentDevice = $agent->device() ? $agent->device() : 'Unknown';
$requestDevice = $this->formatDeviceInfo($agentDevice, $agent->deviceType(), $agent->platform());
// Get the user's current IP address
$ip = $request->getClientIp() ?? $request->ip();
// Get the active user sessions
$sessions = $user->sessions()
->where([
['is_active', true],
['expired_date', null],
])
->orderBy('created_at', 'desc')
->get();

$openSessions = $sessions->reduce(function ($carry, $session) use ($requestDevice, $ip) {
$sessionDevice = $this->formatDeviceInfo(
$session->device_name, $session->device_type, $session->device_platform
);

if ($requestDevice !== $sessionDevice || $session->ip_address !== $ip) {
return $carry + 1;
} else {
return $carry - 1;
}
}, 0);

return $openSessions > 0;
}

private function formatDeviceInfo(string $deviceName, string $deviceType, string $devicePlatform): string
{
return Str::slug($deviceName . '-' . $deviceType . '-' . $devicePlatform);
}

private function redirectToLogin(): Response
{
return redirect()
->route('login')
->with('login-error', __('You are already logged in'));
}
}
3 changes: 2 additions & 1 deletion ProcessMaker/Listeners/UserSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public function handle(object $event): void
$configDevice = Setting::configByKey('session-control.device_restriction');

// Get the IP address and device information
$ip = request()->getClientIp() ?? request()->ip();
$ip = request()->header('X-Forwarded-For') ?? request()->getClientIp() ?? request()->ip();

$agentDevice = $agent->device() ? $agent->device() : 'Unknown';
$agentDeviceType = $agent->deviceType();
$agentPlatform = $agent->platform();
Expand Down
121 changes: 121 additions & 0 deletions ProcessMaker/Managers/SessionControlManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace ProcessMaker\Managers;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Jenssegers\Agent\Agent;
use ProcessMaker\Models\Setting;
use ProcessMaker\Models\User;
use Symfony\Component\HttpFoundation\Response;

/**
* Helpers that control if a user session is blocked by some session policy
*/
class SessionControlManager
{
const IP_RESTRICTION_KEY = 'session-control.ip_restriction';
const DEVICE_RESTRICTION_KEY = 'session-control.device_restriction';

private ?User $user;
private string $clientIp;

public function __construct(?User $user, string $clientIp)
{
$this->user = $user;
$this->clientIp = $clientIp;
}

/**
*
* If the session is blocked by some of the ProcessMaker policies, returns true, false otherwise
*
* @return bool
*/
public function isSessionBlocked()
{
// Check if the user's session should be blocked based on IP restriction
if ($this->blockedByIp()) {
return true;
}
// Check if the user's session should be blocked based on device restriction
if ($this->blockedByDevice()) {
return true;
}

return false;
}


/**
* Checks if a user's session is a duplicate based on their IP address.
*
* @return bool
*/
public function blockSessionByIp(): bool
{
// Get the user's most recent session
$session = $this->user->sessions->sortByDesc('created_at')->first();
// Get the user's current IP address
return $session->ip_address === $this->clientIp;
}

/**
* Checks if the user's current session device matches the device used in the request
*
* @return bool
*/
public function blockSessionByDevice(): bool
{
$agent = new Agent();
// Get the device details from the request
$agentDevice = $agent->device() ? $agent->device() : 'Unknown';
$requestDevice = $this->formatDeviceInfo($agentDevice, $agent->deviceType(), $agent->platform());
// Get the active user sessions
$sessions = $this->user->sessions()
->where([
['is_active', true],
['expired_date', null],
])
->orderBy('created_at', 'desc')
->get();

$openSessions = $sessions->reduce(function ($carry, $session) use ($requestDevice) {
$sessionDevice = $this->formatDeviceInfo(
$session->device_name, $session->device_type, $session->device_platform
);

if ($requestDevice !== $sessionDevice || $session->ip_address !== $this->clientIp) {
return $carry + 1;
} else {
return $carry - 1;
}
}, 0);

return $openSessions > 0;
}

public function blockedByIp(): bool
{
return Setting::configByKey(self::IP_RESTRICTION_KEY) === '1' && $this->blockSessionByIp();
}

public function blockedByDevice(): bool
{
return Setting::configByKey(self::DEVICE_RESTRICTION_KEY) === '1'
&& $this->blockSessionByDevice();
}

public function redirectToLogin(): Response
{
return redirect()
->route('login')
->with('login-error', __('You are already logged in'));
}

private function formatDeviceInfo(string $deviceName, string $deviceType, string $devicePlatform): string
{
return Str::slug($deviceName . '-' . $deviceType . '-' . $devicePlatform);
}
}
5 changes: 5 additions & 0 deletions resources/js/components/shared/FilterTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -504,4 +504,9 @@ export default {
background-color: #E8F0F9;
color: #1572C2;
}
.custom-wrap {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PMDropdownSuggest v-model="idSavedSearch"
:options="options"
@onInput="onInput"
@onSelectedOption="$emit('onSelectedOption',$event)"
@onSelectedOption="onSelectedOption"
:state="stateIdSavedSearch"
:placeholder="$t('Type here to search')">
</PMDropdownSuggest>
Expand Down Expand Up @@ -73,6 +73,7 @@
tabName: "",
filter: "",
pmql: "",
advanced_filter: null,
columns: [],
idSavedSearch: null,
seeTabOnMobile: false,
Expand Down Expand Up @@ -117,6 +118,7 @@
name: this.tabName,
filter: this.filter,
pmql: this.pmql,
advanced_filter: this.advanced_filter,
columns: this.columns,
idSavedSearch: this.idSavedSearch,
seeTabOnMobile: this.seeTabOnMobile
Expand Down Expand Up @@ -144,7 +146,8 @@
response?.data?.data?.forEach(item => {
this.options.push({
text: item.title,
value: item.id
value: item.id,
advanced_filter: item.advanced_filter
});
});
});
Expand All @@ -155,13 +158,18 @@
this.tabName = tab.name;
this.filter = tab.filter;
this.pmql = tab.pmql;
this.advanced_filter = tab.advanced_filter;
this.columns = tab.columns;
this.hideSelectSavedSearch = tab.type === "myCases" || tab.type === "myTasks";
if (!this.hideSelectSavedSearch) {
await this.requestSavedSearch("");
this.idSavedSearch = tab.idSavedSearch;
}
this.seeTabOnMobile = tab.seeTabOnMobile === true;
},
onSelectedOption(option) {
this.advanced_filter = option.advanced_filter;
this.$emit('onSelectedOption', option);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions resources/js/processes-catalogue/components/ProcessTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
:fetch-on-created="false"
:saved-search="item.idSavedSearch"
no-results-message="launchpad"
:advancedFilterProp="item.advanced_filter"
@in-overdue="setInOverdueMessage">
</tasks-list>
</template>
Expand Down Expand Up @@ -175,6 +176,7 @@
name: this.$t("My Cases"),
filter: "",
pmql: `(user_id = ${ProcessMaker.user.id}) AND (process_id = ${this.process.id})`,
advanced_filter: null,
columns: [
{
label: "Case #",
Expand Down Expand Up @@ -222,6 +224,7 @@
name: this.$t("My Tasks"),
filter: "",
pmql: `(user_id = ${ProcessMaker.user.id}) AND (process_id = ${this.process.id})`,
advanced_filter: null,
columns: window.Processmaker.defaultColumns || [],
seeTabOnMobile: true
}
Expand Down
Loading

0 comments on commit d2ee34f

Please sign in to comment.