Skip to content

Commit

Permalink
Merge pull request #5255 from ProcessMaker/feature/FOUR-10253
Browse files Browse the repository at this point in the history
Feature/four 10253: The edit password needs to enable according the permission
  • Loading branch information
pmPaulis committed Sep 5, 2023
2 parents 26111a4 + 4682d71 commit 76fd103
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 47 deletions.
1 change: 1 addition & 0 deletions ProcessMaker/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class Kernel extends HttpKernel
'external.connection' => \ProcessMaker\Http\Middleware\ValidateExternalConnection::class,
'client' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
'template-authorization' => \ProcessMaker\Http\Middleware\TemplateAuthorization::class,
'edit_username_password' => \ProcessMaker\Http\Middleware\ValidateEditUserAndPasswordPermission::class,
];

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace ProcessMaker\Http\Middleware;

use Closure;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use ProcessMaker\Models\User;

class ValidateEditUserAndPasswordPermission
{
public function handle(Request $request, Closure $next)
{
$user = $request->route('user');
$fields = $request->json()->all();
if (($fields['username'] !== $user->getAttribute('username') || in_array('password', $fields)) &&
!Auth::user()->hasPermission('edit-user-and-password') && !Auth::user()->is_administrator) {
throw new AuthorizationException(__('Not authorized to update the username and password.'));

}

return $next($request);
}
}
2 changes: 2 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@
"None": "None",
"Normal": "Normal",
"Not Authorized": "Not Authorized",
"Not authorized to update the username and password.": "Not authorized to update the username and password.",
"Notifications (API)": "Notifications (API)",
"Notifications Inbox": "Notifications Inbox",
"Notifications": "Notifications",
Expand Down Expand Up @@ -983,6 +984,7 @@
"Time": "Time",
"Timeout": "Timeout",
"Timing Control": "Timing Control",
"To change the current username and password please contact your administrator.": "To change the current username and password please contact your administrator.",
"To Do Tasks": "To Do Tasks",
"To Do": "To Do",
"to": "to",
Expand Down
67 changes: 40 additions & 27 deletions resources/views/shared/users/sidebar.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,48 @@
</div>
</div>
<div class="card card-body mt-3">
<h5 class="mb-3 font-weight-bold">{{__('Login Information')}}</h5>
<div class="form-group">
{!! Form::label('username', __('Username') . '<small class="ml-1">*</small>', [], false) !!}
{!! Form::text('username', null, ['id' => 'username', 'rows' => 4, 'class'=> 'form-control', 'v-model'
=> 'formData.username', 'autocomplete' => 'off', 'v-bind:class' => '{\'form-control\':true, \'is-invalid\':errors.username}', 'required', 'aria-required' => 'true']) !!}
<div class="invalid-feedback" role="alert" v-if="errors.username">@{{errors.username[0]}}</div>
</div>
<div class="form-group">
<small class="form-text text-muted">
{{__('Leave the password blank to keep the current password:')}}
</small>
</div>
<div class="form-group">
{!! Form::label('password', __('New Password')) !!}
<vue-password v-model="formData.password" :disable-toggle=true>
<div slot="password-input" slot-scope="props">
{!! Form::password('password', ['id' => 'password', 'rows' => 4, 'class'=> 'form-control', 'v-model'
=> 'formData.password', 'autocomplete' => 'new-password', '@input' => 'props.updatePassword($event.target.value)',
'v-bind:class' => '{\'form-control\':true, \'is-invalid\':errors.password}']) !!}
<fieldset :disabled="{{ \Auth::user()->hasPermission('edit-user-and-password') || \Auth::user()->is_administrator ? 'false' : 'true' }}">
<legend>
<h5 class="mb-3 font-weight-bold">{{__('Login Information')}}</h5>
</legend>
<div class="form-group">
{!! Form::label('username', __('Username') . '<small class="ml-1">*</small>', [], false) !!}
{!! Form::text('username', null, ['id' => 'username', 'rows' => 4, 'class'=> 'form-control', 'v-model'
=> 'formData.username', 'autocomplete' => 'off', 'v-bind:class' => '{\'form-control\':true, \'is-invalid\':errors.username}', 'required', 'aria-required' => 'true']) !!}
<div class="invalid-feedback" role="alert" v-if="errors.username">@{{errors.username[0]}}</div>
</div>
@can('edit-user-and-password')
<div class="form-group">
<small class="form-text text-muted">
{{__('Leave the password blank to keep the current password:')}}
</small>
</div>
</vue-password>
</div>
@endcan
<div class="form-group">
{!! Form::label('password', __('New Password')) !!}
<vue-password v-model="formData.password" :disable-toggle=true>
<div slot="password-input" slot-scope="props">
{!! Form::password('password', ['id' => 'password', 'rows' => 4, 'class'=> 'form-control', 'v-model'
=> 'formData.password', 'autocomplete' => 'new-password', '@input' => 'props.updatePassword($event.target.value)',
'v-bind:class' => '{\'form-control\':true, \'is-invalid\':errors.password}']) !!}
</div>
</vue-password>
</div>

<div class="form-group">
{!! Form::label('confPassword', __('Confirm Password')) !!}
{!! Form::password('confPassword', ['id' => 'confPassword', 'rows' => 4, 'class'=> 'form-control', 'v-model'
=> 'formData.confPassword', 'autocomplete' => 'new-password', 'v-bind:class' => '{\'form-control\':true, \'is-invalid\':errors.password}']) !!}
<div class="invalid-feedback" :style="{display: (errors.password) ? 'block' : 'none' }" role="alert" v-if="errors.password">@{{errors.password[0]}}</div>
</div>
<div class="form-group">
{!! Form::label('confPassword', __('Confirm Password')) !!}
{!! Form::password('confPassword', ['id' => 'confPassword', 'rows' => 4, 'class'=> 'form-control', 'v-model'
=> 'formData.confPassword', 'autocomplete' => 'new-password', 'v-bind:class' => '{\'form-control\':true, \'is-invalid\':errors.password}']) !!}
<div class="invalid-feedback" :style="{display: (errors.password) ? 'block' : 'none' }" role="alert" v-if="errors.password">@{{errors.password[0]}}</div>
</div>
@cannot('edit-user-and-password')
<div class="form-group">
<small class="form-text text-muted">
{{__('To change the current username and password please contact your administrator.')}}
</small>
</div>
@endcannot
</fieldset>

@if (!\Request::is('profile/edit'))
<div class="form-group">
Expand Down
3 changes: 2 additions & 1 deletion routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use ProcessMaker\Http\Controllers\Api\UserTokenController;
use ProcessMaker\Http\Controllers\Process\ModelerController;
use ProcessMaker\Http\Controllers\TestStatusController;
use ProcessMaker\Http\Middleware\ValidateEditUserAndPasswordPermission;

Route::middleware('auth:api', 'setlocale', 'bindings', 'sanitize')->prefix('api/1.0')->name('api.')->group(function () {
// Users
Expand All @@ -43,7 +44,7 @@
Route::get('users/{user}/get_pinnned_controls', [UserController::class, 'getPinnnedControls'])->name('users.getPinnnedControls'); // Permissions handled in the controller
Route::post('users', [UserController::class, 'store'])->name('users.store')->middleware('can:create-users');
Route::put('users/restore', [UserController::class, 'restore'])->name('users.restore')->middleware('can:create-users');
Route::put('users/{user}', [UserController::class, 'update'])->name('users.update'); // Permissions handled in the controller
Route::put('users/{user}', [UserController::class, 'update'])->name('users.update')->middleware('edit_username_password'); // Permissions handled in the controller
Route::put('users/{user}/update_pinned_controls', [UserController::class, 'updatePinnedControls'])->name('users.updatePinnnedControls'); // Permissions handled in the controller
Route::delete('users/{user}', [UserController::class, 'destroy'])->name('users.destroy')->middleware('can:delete-users');
Route::put('password/change', [ChangePasswordController::class, 'update'])->name('password.update');
Expand Down
121 changes: 102 additions & 19 deletions tests/Feature/Api/UsersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tests\Feature\Api;

use Database\Seeders\PermissionSeeder;
use Faker\Factory as Faker;
use Illuminate\Http\UploadedFile;
use ProcessMaker\Models\Setting;
Expand Down Expand Up @@ -40,6 +41,39 @@ class UsersTest extends TestCase
'created_at',
];

public function getUpdatedData()
{
$faker = Faker::create();

return [
'username' => 'newusername',
'email' => $faker->email(),
'firstname' => $faker->firstName(),
'lastname' => $faker->lastName(),
'phone' => $faker->phoneNumber(),
'cell' => $faker->phoneNumber(),
'fax' => $faker->phoneNumber(),
'address' => $faker->streetAddress(),
'city' => $faker->city(),
'state' => $faker->stateAbbr(),
'postal' => $faker->postcode(),
'country' => $faker->country(),
'timezone' => $faker->timezone(),
'status' => $faker->randomElement(['ACTIVE', 'INACTIVE']),
'birthdate' => $faker->dateTimeThisCentury()->format('Y-m-d'),
'password' => $faker->sentence(10),
];
}

protected function withUserSetup()
{
$this->user->is_administrator = true;
$this->user->save();

(new PermissionSeeder)->run($this->user);
}


/**
* Test verify the parameter required for create form
*/
Expand Down Expand Up @@ -343,25 +377,7 @@ public function testUpdateUser()
$verify = $this->apiCall('GET', $url);

// Post saved success
$response = $this->apiCall('PUT', $url, [
'username' => 'updatemytestusername',
'email' => $faker->email(),
'firstname' => $faker->firstName(),
'lastname' => $faker->lastName(),
'phone' => $faker->phoneNumber(),
'cell' => $faker->phoneNumber(),
'fax' => $faker->phoneNumber(),
'address' => $faker->streetAddress(),
'city' => $faker->city(),
'state' => $faker->stateAbbr(),
'postal' => $faker->postcode(),
'country' => $faker->country(),
'timezone' => $faker->timezone(),
'status' => $faker->randomElement(['ACTIVE', 'INACTIVE']),
'birthdate' => $faker->dateTimeThisCentury()->format('Y-m-d'),
'password' => $faker->password(8) . 'A' . '1',
'force_change_password' => $faker->boolean(),
]);
$response = $this->apiCall('PUT', $url, $this->getUpdatedData());

// Validate the header status code
$response->assertStatus(204);
Expand Down Expand Up @@ -694,4 +710,71 @@ public function testCreateUserValidateUsername()
$response->assertStatus(422);
}
}

/**
* Update username and password
* If is an admin user can edit username and password himself
*/
public function testUpdateUserAdmin()
{
$url = self::API_TEST_URL . '/' . $this->user->id;

// Load the starting user data
$verify = $this->apiCall('GET', $url);

// Post saved success
$response = $this->apiCall('PUT', $url, $this->getUpdatedData());

// Validate the header status code
$response->assertStatus(204);

// Load the updated user data
$verifyNew = $this->apiCall('GET', $url);

// Check that it has changed
$this->assertNotEquals($verify, $verifyNew);
}

/**
* Update username and password
* If is a user without permission can not edit and a user with permission can edit himself
*/
public function testUpdateUserNotAdmin()
{
// Without permission
$this->user = User::factory()->create(['status' => 'ACTIVE']);
$this->user->is_administrator = false;
$this->user->save();
$this->user->refresh();
$this->flushSession();

$url = self::API_TEST_URL . '/' . $this->user->id;

// Load the starting user data
$verify = $this->apiCall('GET', $url);

$response = $this->apiCall('PUT', $url, $this->getUpdatedData());

// Validate the header status code
$response->assertStatus(403);

// With permission
$this->user->giveDirectPermission('edit-user-and-password');
$this->user->save();
$this->user->refresh();
$this->flushSession();

// Post saved success
$response = $this->apiCall('PUT', $url, $this->getUpdatedData());

// Validate the header status code
$response->assertStatus(204);

// Load the updated user data
$verifyNew = $this->apiCall('GET', $url);

// Check that it has changed
$this->assertNotEquals($verify, $verifyNew);
}

}

0 comments on commit 76fd103

Please sign in to comment.