Skip to content

Commit

Permalink
Merge branch 'next' into feature/FOUR-13174
Browse files Browse the repository at this point in the history
  • Loading branch information
nolanpro committed Jan 19, 2024
2 parents bdd4fb7 + 2907765 commit ebf3c29
Show file tree
Hide file tree
Showing 17 changed files with 178 additions and 38 deletions.
9 changes: 8 additions & 1 deletion ProcessMaker/Http/Controllers/Admin/GroupController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,21 @@ public function edit(Group $group)
$all_permissions = Permission::all();
$permissionGroups = $all_permissions->sortBy('title')->groupBy('group')->sortKeys();

// Get the 2FA settings
$settings = [
'2fa_enabled' => config('password-policies.2fa_enabled', false),
'2fa_methods' => config('password-policies.2fa_method', []),
];

$addons = $this->getPluginAddons('edit', compact(['group']));

return view('admin.groups.edit', compact(
'group',
'permissionNames',
'all_permissions',
'permissionGroups',
'addons'
'addons',
'settings',
));
}

Expand Down
2 changes: 2 additions & 0 deletions ProcessMaker/Http/Controllers/Admin/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ function ($result, $item) {
$enabled2FA = config('password-policies.2fa_enabled', false);
$global2FAEnabled = config('password-policies.2fa_method', []);
$user->preferences_2fa = $user->getValid2FAPreferences();
$is2FAEnabledForGroup = $user->in2FAGroupOrIndependent();

$addons = $this->getPluginAddons('edit', compact(['user']));
$addonsSettings = $this->getPluginAddons('edit.settings', compact(['user']));
Expand All @@ -99,6 +100,7 @@ function ($result, $item) {
'status',
'enabled2FA',
'global2FAEnabled',
'is2FAEnabledForGroup',
'addons',
'addonsSettings',
));
Expand Down
6 changes: 6 additions & 0 deletions ProcessMaker/Http/Controllers/Api/GroupController.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ public function index(Request $request)
public function store(Request $request)
{
$request->validate(Group::rules());

// Check if 2FA is enabled and set the enabled_2fa flag
if (config('password-policies.2fa_enabled', false)) {
$request->merge(['enabled_2fa' => true]);
}

$group = new Group();
$group->fill($request->input());
$group->saveOrFail();
Expand Down
7 changes: 7 additions & 0 deletions ProcessMaker/Http/Controllers/Api/SettingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use ProcessMaker\Http\Resources\ApiCollection;
use ProcessMaker\Http\Resources\ApiResource;
use ProcessMaker\Jobs\ImportSettings;
use ProcessMaker\Models\Group;
use ProcessMaker\Models\Setting;
use Throwable;

Expand Down Expand Up @@ -211,6 +212,12 @@ public function update(Setting $setting, Request $request)
$original = array_intersect_key($setting->getOriginal(), $setting->getDirty());
$setting->save();

if ($setting->key === 'password-policies.2fa_enabled') {
// Update all groups with the new 2FA setting
Group::where('enabled_2fa', '!=', $setting->config)
->update(['enabled_2fa' => $setting->config]);
}

// Register the Event
SettingsUpdated::dispatch($setting, $setting->getChanges(), $original);

Expand Down
17 changes: 17 additions & 0 deletions ProcessMaker/Http/Controllers/Auth/TwoFactorAuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,21 @@ private function testSmsServer()

return true;
}

/**
* Check if 2FA is enabled for the current user based on the groups he belongs to
*
* @return bool
*/
public static function check2faByGroups()
{
try {
$user = Auth::user();
return $user->in2FAGroupOrIndependent();
} catch (Exception $e) {
session()->put(self::TFA_ERROR, $e->getMessage());
}

return false;
}
}
3 changes: 2 additions & 1 deletion ProcessMaker/Http/Controllers/ProfileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ function ($result, $item) {
$enabled2FA = config('password-policies.2fa_enabled', false);
$global2FAEnabled = config('password-policies.2fa_method', []);
$currentUser->preferences_2fa = $currentUser->getValid2FAPreferences();
$is2FAEnabledForGroup = $currentUser->in2FAGroupOrIndependent();

$addons = $this->getPluginAddons('edit', []);

return view('profile.edit',
compact('currentUser', 'states', 'timezones', 'countries', 'datetimeFormats', 'availableLangs',
'status', 'enabled2FA', 'global2FAEnabled', 'addons'));
'status', 'enabled2FA', 'global2FAEnabled', 'is2FAEnabledForGroup', 'addons'));
}

/**
Expand Down
5 changes: 1 addition & 4 deletions ProcessMaker/Http/Middleware/SessionControlBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,7 @@ private function blockSessionByDevice(User $user, Request $request): bool
$ip = $request->getClientIp() ?? $request->ip();
// Get the user's most recent session
$session = $user->sessions()
->where([
['is_active', true],
['ip_address', '!=', $ip],
])
->where('is_active', true)
->orderBy('created_at', 'desc')
->first();

Expand Down
3 changes: 2 additions & 1 deletion ProcessMaker/Http/Middleware/TwoFactorAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public function handle($request, Closure $next)
// Check if 2FA is enabled, a method is set, and the current session is not yet validated
if (config('password-policies.2fa_enabled', false) &&
!empty(config('password-policies.2fa_method', [])) &&
!session()->get(TwoFactorAuthController::TFA_VALIDATED, false)
!session()->get(TwoFactorAuthController::TFA_VALIDATED, false) &&
TwoFactorAuthController::check2faByGroups()
) {
// If not validated display the 2FA code screen
return redirect()->route('2fa');
Expand Down
5 changes: 1 addition & 4 deletions ProcessMaker/Listeners/UserSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ public function handle(object $event): void

if ($configDevice === '2') {
$user->sessions()
->where([
['is_active', true],
['ip_address', '!=', $ip],
])
->where('is_active', true)
->where(function (Builder $query) use ($agentDevice, $agentDeviceType, $agentPlatform, $ip) {
$query->where('device_name', '!=', $agentDevice)
->orWhere('device_type', '!=', $agentDeviceType)
Expand Down
5 changes: 5 additions & 0 deletions ProcessMaker/Models/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ class Group extends ProcessMakerModel
'description',
'manager_id',
'status',
'enabled_2fa',
];

protected $casts = [
'enabled_2fa' => 'boolean',
];

public static function rules($existing = null)
Expand Down
25 changes: 25 additions & 0 deletions ProcessMaker/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ProcessMaker\Models;

use Exception;
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
Expand Down Expand Up @@ -533,4 +534,28 @@ public function getValid2FAPreferences(): array

return !empty($aux) ? array_values($aux) : $global2FAEnabled;
}

public function in2FAGroupOrIndependent()
{
$userGroups = $this->groups;
$groupCount = $userGroups->count();

if ($groupCount === 0) {
return true;
}

$groupsWith2fa = $userGroups->where('enabled_2fa', true);

// Check if the only group has 2fa enabled, if so, ask for 2fa
$hasSingleGroupWith2fa = $groupCount === 1 && $groupsWith2fa->count() === 1;
// Check if at least one group has 2fa enabled, if so, ask for 2fa
$hasMultipleGroupsWithAtLeastOne2fa = $groupCount > 1 && $groupsWith2fa->count() > 0;
// Check if all groups don't have 2fa enabled, if so, ask for 2fa if the 2fa setting is enabled
$independent = $groupCount === 0;

if ($hasSingleGroupWith2fa || $hasMultipleGroupsWithAtLeastOne2fa || $independent) {
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('groups', function (Blueprint $table) {
$table->boolean('enabled_2fa')->default(false)->after('status');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('groups', function (Blueprint $table) {
$table->dropColumn('enabled_2fa');
});
}
};
16 changes: 8 additions & 8 deletions resources/js/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ window.Vue.component("filter-table", FilterTable);

let translationsLoaded = false;
const mdates = JSON.parse(
document.head.querySelector("meta[name=\"i18n-mdate\"]").content,
document.head.querySelector("meta[name=\"i18n-mdate\"]")?.content,
);

// Make $t available to all vue instances
Expand Down Expand Up @@ -271,9 +271,9 @@ if (appUrl) {
if (userID) {
window.ProcessMaker.user = {
id: userID.content,
datetime_format: formatDate.content,
calendar_format: formatDate.content,
timezone: timezone.content,
datetime_format: formatDate?.content,
calendar_format: formatDate?.content,
timezone: timezone?.content,
fullName: userFullName?.content,
avatar: userAvatar?.content,
};
Expand All @@ -298,10 +298,10 @@ if (window.Processmaker && window.Processmaker.broadcasting) {

if (userID) {
// Session timeout
const timeoutScript = document.head.querySelector("meta[name=\"timeout-worker\"]").content;
window.ProcessMaker.AccountTimeoutLength = parseInt(eval(document.head.querySelector("meta[name=\"timeout-length\"]").content));
window.ProcessMaker.AccountTimeoutWarnSeconds = parseInt(document.head.querySelector("meta[name=\"timeout-warn-seconds\"]").content);
window.ProcessMaker.AccountTimeoutEnabled = document.head.querySelector("meta[name=\"timeout-enabled\"]") ? parseInt(document.head.querySelector("meta[name=\"timeout-enabled\"]").content) : 1;
const timeoutScript = document.head.querySelector("meta[name=\"timeout-worker\"]")?.content;
window.ProcessMaker.AccountTimeoutLength = parseInt(eval(document.head.querySelector("meta[name=\"timeout-length\"]")?.content));
window.ProcessMaker.AccountTimeoutWarnSeconds = parseInt(document.head.querySelector("meta[name=\"timeout-warn-seconds\"]")?.content);
window.ProcessMaker.AccountTimeoutEnabled = document.head.querySelector("meta[name=\"timeout-enabled\"]") ? parseInt(document.head.querySelector("meta[name=\"timeout-enabled\"]")?.content) : 1;
window.ProcessMaker.AccountTimeoutWorker = new Worker(timeoutScript);
window.ProcessMaker.AccountTimeoutWorker.addEventListener("message", (e) => {
if (e.data.method === "countdown") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
readonly
size="sm">
</b-form-input>
<b-form-datepicker v-model="input"
<b-form-datepicker v-model="selectedDate"
button-only
right
size="sm"
Expand All @@ -17,6 +17,15 @@
button-variant="outline-secondary"
class="pm-column-filter-op-button">
</b-form-datepicker>
<b-form-timepicker v-model="selectedTime"
button-only
show-seconds
right
size="sm"
boundary="window"
button-variant="outline-secondary"
class="pm-column-filter-op-button">
</b-form-timepicker>
</div>
</template>

Expand All @@ -27,44 +36,62 @@
],
data() {
return {
input: ""
input: "",
selectedDate: "",
selectedTime: ""
};
},
watch: {
value: {
handler(newValue) {
this.input = newValue;
this.dateToDatetime();
},
immediate: true
},
input() {
this.emitInput();
},
selectedDate() {
this.setInput();
},
selectedTime() {
this.setInput();
}
},
mounted() {
if (this.input === "") {
this.input = this.currentDate();
}
this.selectedDate = this.getCurrentDate(this.input);
this.selectedTime = this.getCurrentTime(this.input);
},
methods: {
emitInput() {
this.dateToDatetime();
this.$emit("input", this.input);
},
dateToDatetime() {
if (this.input && this.input !== "" && !/\d{2}:\d{2}:\d{2}/.test(this.input)) {
this.input = this.input + " 00:00:00";
}
setInput() {
this.input = this.selectedDate + " " + this.selectedTime;
},
currentDate() {
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
month = month < 10 ? "0" + month : month;
day = day < 10 ? "0" + day : day;
return year + "-" + month + "-" + day;
return date.toISOString().split("T")[0];
},
isDatetime(string) {
const date = new Date(string);
return !isNaN(date.getTime()) && /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(string);
},
getCurrentDate(newValue) {
if (this.isDatetime(newValue)) {
let s = newValue.trim().split(" ");
return s[0];
} else {
return this.currentDate();
}
},
getCurrentTime(newValue) {
if (this.isDatetime(newValue)) {
let s = newValue.trim().split(" ");
return s.length > 1 ? s[1] : "00:00:00";
} else {
return "00:00:00";
}
}
}
};
Expand All @@ -87,4 +114,4 @@
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
</style>
</style>
2 changes: 2 additions & 0 deletions resources/js/templates/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ new Vue({
name: "",
responseId: null,
request: {},
redirectTo: null,
wizardTemplateUuid: null,
};
},
mounted() {
Expand Down
Loading

0 comments on commit ebf3c29

Please sign in to comment.