mirror of
https://github.com/snipe/snipe-it.git
synced 2024-11-09 23:24:06 -08:00
Merge branch 'develop' into bug/sc-24884
This commit is contained in:
commit
53ccd196d7
|
@ -45,6 +45,7 @@ DB_SSL_KEY_PATH=null
|
|||
DB_SSL_CERT_PATH=null
|
||||
DB_SSL_CA_PATH=null
|
||||
DB_SSL_CIPHER=null
|
||||
DB_SSL_VERIFY_SERVER=null
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
|
||||
|
|
|
@ -36,6 +36,7 @@ DB_SSL_KEY_PATH=null
|
|||
DB_SSL_CERT_PATH=null
|
||||
DB_SSL_CA_PATH=null
|
||||
DB_SSL_CIPHER=null
|
||||
DB_SSL_VERIFY_SERVER=null
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
|
||||
|
|
|
@ -42,6 +42,7 @@ DB_SSL_KEY_PATH=null
|
|||
DB_SSL_CERT_PATH=null
|
||||
DB_SSL_CA_PATH=null
|
||||
DB_SSL_CIPHER=null
|
||||
DB_SSL_VERIFY_SERVER=null
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
|
||||
|
|
|
@ -390,7 +390,7 @@ class LdapSync extends Command
|
|||
$user->location_id = $location->id;
|
||||
}
|
||||
}
|
||||
|
||||
$location = null;
|
||||
$user->ldap_import = 1;
|
||||
|
||||
$errors = '';
|
||||
|
|
|
@ -662,25 +662,27 @@ class AssetsController extends Controller
|
|||
|
||||
$asset = $request->handleImages($asset);
|
||||
$model = $asset->model;
|
||||
|
||||
// Update custom fields
|
||||
$problems_updating_encrypted_custom_fields = false;
|
||||
if (($model) && (isset($model->fieldset))) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
$field_val = $request->input($field->db_column, null);
|
||||
|
||||
// Update custom fields
|
||||
if (($model) && (isset($model->fieldset))) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
$field_val = $request->input($field->db_column, null);
|
||||
|
||||
if ($request->has($field->db_column)) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$asset->{$field->db_column} = Crypt::encrypt($field_val);
|
||||
if ($request->has($field->db_column)) {
|
||||
if ($field->element == 'checkbox') {
|
||||
if(is_array($field_val)) {
|
||||
$field_val = implode(',', $field_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($field->element == 'checkbox') {
|
||||
if(is_array($field_val)) {
|
||||
$field_val = implode(',', $field_val);
|
||||
$asset->{$field->db_column} = $field_val;
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$field_val = Crypt::encrypt($field_val);
|
||||
} else {
|
||||
$problems_updating_encrypted_custom_fields = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$asset->{$field->db_column} = $field_val;
|
||||
}
|
||||
}
|
||||
|
@ -705,11 +707,13 @@ class AssetsController extends Controller
|
|||
|
||||
if ($asset->image) {
|
||||
$asset->image = $asset->getImageUrl();
|
||||
|
||||
if ($problems_updating_encrypted_custom_fields) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
|
||||
} else {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
|
||||
// commenting to make this clear, this was removed in favor of the above temporarily because it breaks jamf2snipe
|
||||
// return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.success')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
|
||||
}
|
||||
|
|
|
@ -273,6 +273,7 @@ class UsersController extends Controller
|
|||
$users = $users->withTrashed();
|
||||
}
|
||||
|
||||
// Apply companyable scope
|
||||
$users = Company::scopeCompanyables($users);
|
||||
|
||||
|
||||
|
@ -403,7 +404,10 @@ class UsersController extends Controller
|
|||
public function show($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
|
||||
$user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count')->findOrFail($id);
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('update', $user);
|
||||
|
||||
return (new UsersTransformer)->transformUser($user);
|
||||
}
|
||||
|
@ -423,6 +427,8 @@ class UsersController extends Controller
|
|||
$this->authorize('update', User::class);
|
||||
|
||||
$user = User::findOrFail($id);
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('update', $user);
|
||||
|
||||
/**
|
||||
* This is a janky hack to prevent people from changing admin demo user data on the public demo.
|
||||
|
@ -459,6 +465,7 @@ class UsersController extends Controller
|
|||
if (! Auth::user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
}
|
||||
|
||||
$user->permissions = $permissions_array;
|
||||
}
|
||||
|
||||
|
@ -481,6 +488,7 @@ class UsersController extends Controller
|
|||
|
||||
// Check if the request has groups passed and has a value
|
||||
if ($request->filled('groups')) {
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'groups.*' => 'integer|exists:permission_groups,id',
|
||||
]);
|
||||
|
@ -488,10 +496,19 @@ class UsersController extends Controller
|
|||
if ($validator->fails()){
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
|
||||
}
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
|
||||
// Only save groups if the user is a superuser
|
||||
if (Auth::user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
|
||||
// The groups field has been passed but it is null, so we should blank it out
|
||||
} elseif ($request->has('groups')) {
|
||||
$user->groups()->sync([]);
|
||||
|
||||
// Only save groups if the user is a superuser
|
||||
if (Auth::user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -512,37 +529,43 @@ class UsersController extends Controller
|
|||
public function destroy($id)
|
||||
{
|
||||
$this->authorize('delete', User::class);
|
||||
$user = User::findOrFail($id);
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
if (($user->assets) && ($user->assets->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
|
||||
}
|
||||
if ($user) {
|
||||
|
||||
if (($user->licenses) && ($user->licenses->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->licenses->count().' license(s) associated with them and cannot be deleted.'));
|
||||
}
|
||||
|
||||
if (($user->accessories) && ($user->accessories->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->accessories->count().' accessories associated with them.'));
|
||||
}
|
||||
|
||||
if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has '.$user->managedLocations()->count().' locations that they manage.'));
|
||||
}
|
||||
|
||||
if ($user->delete()) {
|
||||
|
||||
// Remove the user's avatar if they have one
|
||||
if (Storage::disk('public')->exists('avatars/'.$user->avatar)) {
|
||||
try {
|
||||
Storage::disk('public')->delete('avatars/'.$user->avatar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
if (($user->assets) && ($user->assets->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
|
||||
if (($user->licenses) && ($user->licenses->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->licenses->count() . ' license(s) associated with them and cannot be deleted.'));
|
||||
}
|
||||
|
||||
if (($user->accessories) && ($user->accessories->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->accessories->count() . ' accessories associated with them.'));
|
||||
}
|
||||
|
||||
if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->managedLocations()->count() . ' locations that they manage.'));
|
||||
}
|
||||
|
||||
if ($user->delete()) {
|
||||
|
||||
// Remove the user's avatar if they have one
|
||||
if (Storage::disk('public')->exists('avatars/' . $user->avatar)) {
|
||||
try {
|
||||
Storage::disk('public')->delete('avatars/' . $user->avatar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
|
||||
|
@ -560,6 +583,11 @@ class UsersController extends Controller
|
|||
{
|
||||
$this->authorize('view', User::class);
|
||||
$this->authorize('view', Asset::class);
|
||||
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('view', $user);
|
||||
|
||||
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model');
|
||||
|
||||
|
||||
|
@ -595,7 +623,10 @@ class UsersController extends Controller
|
|||
*/
|
||||
public function emailAssetList(Request $request, $id)
|
||||
{
|
||||
$this->authorize('update', User::class);
|
||||
$user = User::findOrFail($id);
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
$this->authorize('update', $user);
|
||||
|
||||
if (empty($user->email)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
|
||||
|
@ -619,6 +650,7 @@ class UsersController extends Controller
|
|||
$this->authorize('view', User::class);
|
||||
$this->authorize('view', Consumable::class);
|
||||
$user = User::findOrFail($id);
|
||||
$this->authorize('update', $user);
|
||||
$consumables = $user->consumables;
|
||||
return (new ConsumablesTransformer)->transformConsumables($consumables, $consumables->count(), $request);
|
||||
}
|
||||
|
@ -635,6 +667,7 @@ class UsersController extends Controller
|
|||
{
|
||||
$this->authorize('view', User::class);
|
||||
$user = User::findOrFail($id);
|
||||
$this->authorize('view', $user);
|
||||
$this->authorize('view', Accessory::class);
|
||||
$accessories = $user->accessories;
|
||||
|
||||
|
@ -655,6 +688,7 @@ class UsersController extends Controller
|
|||
$this->authorize('view', License::class);
|
||||
|
||||
if ($user = User::where('id', $id)->withTrashed()->first()) {
|
||||
$this->authorize('update', $user);
|
||||
$licenses = $user->licenses()->get();
|
||||
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
||||
}
|
||||
|
@ -678,6 +712,7 @@ class UsersController extends Controller
|
|||
if ($request->filled('id')) {
|
||||
try {
|
||||
$user = User::find($request->get('id'));
|
||||
$this->authorize('update', $user);
|
||||
$user->two_factor_secret = null;
|
||||
$user->two_factor_enrolled = 0;
|
||||
$user->saveQuietly();
|
||||
|
|
|
@ -38,7 +38,7 @@ class AssetModelsFilesController extends Controller
|
|||
|
||||
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file);
|
||||
|
||||
$model->logUpload($file_name, e($request->get('notes')));
|
||||
$model->logUpload($file_name, $request->get('notes'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', trans('general.file_upload_success'));
|
||||
|
|
|
@ -39,6 +39,12 @@ class AssetCheckinController extends Controller
|
|||
|
||||
$this->authorize('checkin', $asset);
|
||||
|
||||
// This asset is already checked in, redirect
|
||||
|
||||
if (is_null($asset->assignedTo)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
|
||||
}
|
||||
|
||||
return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ class AssetFilesController extends Controller
|
|||
foreach ($request->file('file') as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
|
||||
|
||||
$asset->logUpload($file_name, e($request->get('notes')));
|
||||
$asset->logUpload($file_name, $request->get('notes'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', trans('admin/hardware/message.upload.success'));
|
||||
|
|
|
@ -14,6 +14,7 @@ use App\Models\Setting;
|
|||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use App\View\Label as LabelView;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class LabelsController extends Controller
|
||||
|
@ -21,9 +22,9 @@ class LabelsController extends Controller
|
|||
/**
|
||||
* Returns the Label view with test data
|
||||
*
|
||||
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
|
||||
* @param string $labelName
|
||||
* @param string $labelName
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
|
||||
*/
|
||||
public function show(string $labelName)
|
||||
{
|
||||
|
@ -66,7 +67,7 @@ class LabelsController extends Controller
|
|||
$exampleAsset->model->category->id = 999999;
|
||||
$exampleAsset->model->category->name = trans('admin/labels/table.example_category');
|
||||
|
||||
$customFieldColumns = CustomField::all()->pluck('db_column');
|
||||
$customFieldColumns = CustomField::where('field_encrypted', '=', 0)->pluck('db_column');
|
||||
|
||||
collect(explode(';', Setting::getSettings()->label2_fields))
|
||||
->filter()
|
||||
|
|
|
@ -696,7 +696,7 @@ class ReportsController extends Controller
|
|||
->whereBetween('action_date',[$checkout_start, $checkout_end])
|
||||
->pluck('item_id');
|
||||
|
||||
$assets->whereIn('id',$actionlogassets);
|
||||
$assets->whereIn('assets.id',$actionlogassets);
|
||||
}
|
||||
|
||||
if (($request->filled('checkin_date_start'))) {
|
||||
|
|
|
@ -804,10 +804,9 @@ class SettingsController extends Controller
|
|||
*/
|
||||
public function getLabels()
|
||||
{
|
||||
return view('settings.labels', [
|
||||
'setting' => Setting::getSettings(),
|
||||
'customFields' => CustomField::all(),
|
||||
]);
|
||||
return view('settings.labels')
|
||||
->with('setting', Setting::getSettings())
|
||||
->with('customFields', CustomField::where('field_encrypted', '=', 0)->get());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -182,8 +182,13 @@ class UsersController extends Controller
|
|||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
if ($user = User::find($id)) {
|
||||
$this->authorize('update', $user);
|
||||
|
||||
$this->authorize('update', User::class);
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
|
||||
if ($user) {
|
||||
|
||||
$permissions = config('permissions');
|
||||
$groups = Group::pluck('name', 'id');
|
||||
|
||||
|
@ -210,106 +215,109 @@ class UsersController extends Controller
|
|||
*/
|
||||
public function update(SaveUserRequest $request, $id = null)
|
||||
{
|
||||
// We need to reverse the UI specific logic for our
|
||||
// permissions here before we update the user.
|
||||
$permissions = $request->input('permissions', []);
|
||||
app('request')->request->set('permissions', $permissions);
|
||||
$this->authorize('update', User::class);
|
||||
|
||||
// This is a janky hack to prevent people from changing admin demo user data on the public demo.
|
||||
// The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
|
||||
// Thanks, jerks. You are why we can't have nice things. - snipe
|
||||
|
||||
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
|
||||
return redirect()->route('users.index')->with('error', 'Permission denied. You cannot update user information for superadmins on the demo.');
|
||||
return redirect()->route('users.index')->with('error', trans('general.permission_denied_superuser_demo'));
|
||||
}
|
||||
|
||||
try {
|
||||
$user = User::findOrFail($id);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
}
|
||||
|
||||
$this->authorize('update', $user);
|
||||
// Figure out of this user was an admin before this edit
|
||||
$orig_permissions_array = $user->decodePermissions();
|
||||
$orig_superuser = '0';
|
||||
if (is_array($orig_permissions_array)) {
|
||||
if (array_key_exists('superuser', $orig_permissions_array)) {
|
||||
$orig_superuser = $orig_permissions_array['superuser'];
|
||||
// We need to reverse the UI specific logic for our
|
||||
// permissions here before we update the user.
|
||||
$permissions = $request->input('permissions', []);
|
||||
app('request')->request->set('permissions', $permissions);
|
||||
|
||||
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
|
||||
// User is valid - continue...
|
||||
if ($user) {
|
||||
$this->authorize('update', $user);
|
||||
|
||||
// Figure out of this user was an admin before this edit
|
||||
$orig_permissions_array = $user->decodePermissions();
|
||||
$orig_superuser = '0';
|
||||
if (is_array($orig_permissions_array)) {
|
||||
if (array_key_exists('superuser', $orig_permissions_array)) {
|
||||
$orig_superuser = $orig_permissions_array['superuser'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only save groups if the user is a super user
|
||||
if (Auth::user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
// Only save groups if the user is a superuser
|
||||
if (Auth::user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
|
||||
// Update the user
|
||||
if ($request->filled('username')) {
|
||||
// Update the user fields
|
||||
$user->username = trim($request->input('username'));
|
||||
}
|
||||
$user->email = trim($request->input('email'));
|
||||
$user->first_name = $request->input('first_name');
|
||||
$user->last_name = $request->input('last_name');
|
||||
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||
$user->locale = $request->input('locale');
|
||||
$user->employee_num = $request->input('employee_num');
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->jobtitle = $request->input('jobtitle', null);
|
||||
$user->phone = $request->input('phone');
|
||||
$user->location_id = $request->input('location_id', null);
|
||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||
$user->manager_id = $request->input('manager_id', null);
|
||||
$user->notes = $request->input('notes');
|
||||
$user->department_id = $request->input('department_id', null);
|
||||
$user->address = $request->input('address', null);
|
||||
$user->city = $request->input('city', null);
|
||||
$user->state = $request->input('state', null);
|
||||
$user->country = $request->input('country', null);
|
||||
// if a user is editing themselves we should always keep activated true
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->vip = $request->input('vip', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
|
||||
$user->email = trim($request->input('email'));
|
||||
$user->first_name = $request->input('first_name');
|
||||
$user->last_name = $request->input('last_name');
|
||||
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||
$user->locale = $request->input('locale');
|
||||
$user->employee_num = $request->input('employee_num');
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->jobtitle = $request->input('jobtitle', null);
|
||||
$user->phone = $request->input('phone');
|
||||
$user->location_id = $request->input('location_id', null);
|
||||
$user->company_id = Company::getIdForUser($request->input('company_id', null));
|
||||
$user->manager_id = $request->input('manager_id', null);
|
||||
$user->notes = $request->input('notes');
|
||||
$user->department_id = $request->input('department_id', null);
|
||||
$user->address = $request->input('address', null);
|
||||
$user->city = $request->input('city', null);
|
||||
$user->state = $request->input('state', null);
|
||||
$user->country = $request->input('country', null);
|
||||
// if a user is editing themselves we should always keep activated true
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->vip = $request->input('vip', 0);
|
||||
$user->website = $request->input('website', null);
|
||||
$user->start_date = $request->input('start_date', null);
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $request->input('location_id', null)]);
|
||||
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
$permissions_array = $request->input('permission');
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
if (! Auth::user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
$permissions_array['superuser'] = $orig_superuser;
|
||||
}
|
||||
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Handle uploaded avatar
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
if ($user->save()) {
|
||||
// Redirect to the user page
|
||||
return redirect()->route('users.index')
|
||||
->with('success', trans('admin/users/message.success.update'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $request->input('location_id', null)]);
|
||||
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
$permissions_array = $request->input('permission');
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
if (! Auth::user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
$permissions_array['superuser'] = $orig_superuser;
|
||||
}
|
||||
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Handle uploaded avatar
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
//\Log::debug(print_r($user, true));
|
||||
|
||||
// Was the user updated?
|
||||
if ($user->save()) {
|
||||
// Redirect to the user page
|
||||
return redirect()->route('users.index')
|
||||
->with('success', trans('admin/users/message.success.update'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -323,12 +331,13 @@ class UsersController extends Controller
|
|||
*/
|
||||
public function destroy($id = null)
|
||||
{
|
||||
try {
|
||||
// Get user information
|
||||
$user = User::findOrFail($id);
|
||||
// Authorize takes care of many of our logic checks now.
|
||||
$this->authorize('delete', User::class);
|
||||
|
||||
$this->authorize('delete', User::class);
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($id);
|
||||
|
||||
|
||||
if ($user) {
|
||||
// Check if we are not trying to delete ourselves
|
||||
if ($user->id === Auth::id()) {
|
||||
// Redirect to the user management page
|
||||
|
@ -362,16 +371,12 @@ class UsersController extends Controller
|
|||
|
||||
// Delete the user
|
||||
$user->delete();
|
||||
|
||||
// Prepare the success message
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.delete'));
|
||||
} catch (ModelNotFoundException $e) {
|
||||
// Prepare the error message
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -427,59 +432,25 @@ class UsersController extends Controller
|
|||
*/
|
||||
public function show($userId = null)
|
||||
{
|
||||
if (! $user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($userId)) {
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
|
||||
}
|
||||
// Make sure the user can view users at all
|
||||
$this->authorize('view', User::class);
|
||||
|
||||
$userlog = $user->userlog->load('item');
|
||||
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user = Company::scopeCompanyables($user)->find($userId);
|
||||
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $user);
|
||||
|
||||
return view('users/view', compact('user', 'userlog'))
|
||||
->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsuspend a user.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $id
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function getUnsuspend($id = null)
|
||||
{
|
||||
try {
|
||||
// Get user information
|
||||
$user = User::findOrFail($id);
|
||||
$this->authorize('update', $user);
|
||||
|
||||
// Check if we are not trying to unsuspend ourselves
|
||||
if ($user->id === Auth::id()) {
|
||||
// Prepare the error message
|
||||
$error = trans('admin/users/message.error.unsuspend');
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')->with('error', $error);
|
||||
}
|
||||
|
||||
// Do we have permission to unsuspend this user?
|
||||
if ($user->isSuperUser() && ! Auth::user()->isSuperUser()) {
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')->with('error', 'Insufficient permissions!');
|
||||
}
|
||||
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.unsuspend'));
|
||||
} catch (ModelNotFoundException $e) {
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
if ($user) {
|
||||
$userlog = $user->userlog->load('item');
|
||||
return view('users/view', compact('user', 'userlog'))->with('settings', Setting::getSettings());
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a view containing a pre-populated new user form,
|
||||
* populated with some fields from an existing user.
|
||||
|
@ -493,22 +464,34 @@ class UsersController extends Controller
|
|||
public function getClone(Request $request, $id = null)
|
||||
{
|
||||
$this->authorize('create', User::class);
|
||||
|
||||
// We need to reverse the UI specific logic for our
|
||||
// permissions here before we update the user.
|
||||
$permissions = $request->input('permissions', []);
|
||||
app('request')->request->set('permissions', $permissions);
|
||||
|
||||
try {
|
||||
// Get the user information
|
||||
$user_to_clone = User::withTrashed()->find($id);
|
||||
|
||||
$user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed();
|
||||
$user_to_clone = Company::scopeCompanyables($user_to_clone)->find($id);
|
||||
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $user_to_clone);
|
||||
|
||||
|
||||
if ($user_to_clone) {
|
||||
|
||||
|
||||
$user = clone $user_to_clone;
|
||||
|
||||
// Blank out some fields
|
||||
$user->first_name = '';
|
||||
$user->last_name = '';
|
||||
$user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0);
|
||||
$user->id = null;
|
||||
|
||||
// Get this user groups
|
||||
// Get this user's groups
|
||||
$userGroups = $user_to_clone->groups()->pluck('name', 'id');
|
||||
|
||||
// Get all the available permissions
|
||||
$permissions = config('permissions');
|
||||
$clonedPermissions = $user_to_clone->decodePermissions();
|
||||
|
@ -517,16 +500,14 @@ class UsersController extends Controller
|
|||
|
||||
// Show the page
|
||||
return view('users/edit', compact('permissions', 'userPermissions'))
|
||||
->with('user', $user)
|
||||
->with('groups', Group::pluck('name', 'id'))
|
||||
->with('userGroups', $userGroups)
|
||||
->with('clone_user', $user_to_clone);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
// Prepare the error message
|
||||
// Redirect to the user management page
|
||||
return redirect()->route('users.index')
|
||||
->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
->with('user', $user)
|
||||
->with('groups', Group::pluck('name', 'id'))
|
||||
->with('userGroups', $userGroups)
|
||||
->with('clone_user', $user_to_clone);
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -546,8 +527,20 @@ class UsersController extends Controller
|
|||
// Open output stream
|
||||
$handle = fopen('php://output', 'w');
|
||||
|
||||
User::with('assets', 'accessories', 'consumables', 'department', 'licenses', 'manager', 'groups', 'userloc', 'company')
|
||||
->orderBy('created_at', 'DESC')
|
||||
$users = User::with(
|
||||
'assets',
|
||||
'accessories',
|
||||
'consumables',
|
||||
'department',
|
||||
'licenses',
|
||||
'manager',
|
||||
'groups',
|
||||
'userloc',
|
||||
'company'
|
||||
)->orderBy('created_at', 'DESC');
|
||||
|
||||
// FMCS scoping
|
||||
Company::scopeCompanyables($users)
|
||||
->chunk(500, function ($users) use ($handle) {
|
||||
$headers = [
|
||||
// strtolower to prevent Excel from trying to open it as a SYLK file
|
||||
|
@ -565,7 +558,7 @@ class UsersController extends Controller
|
|||
trans('general.licenses'),
|
||||
trans('general.accessories'),
|
||||
trans('general.consumables'),
|
||||
trans('admin/users/table.groups'),
|
||||
trans('general.groups'),
|
||||
trans('general.notes'),
|
||||
trans('admin/users/table.activated'),
|
||||
trans('general.created_at'),
|
||||
|
@ -626,7 +619,11 @@ class UsersController extends Controller
|
|||
public function printInventory($id)
|
||||
{
|
||||
$this->authorize('view', User::class);
|
||||
$show_user = User::where('id', $id)->withTrashed()->first();
|
||||
$show_user = Company::scopeCompanyables(User::where('id', $id)->withTrashed()->first());
|
||||
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $show_user);
|
||||
|
||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', User::class)->with('model', 'model.category')->get();
|
||||
$accessories = $show_user->accessories()->get();
|
||||
$consumables = $show_user->consumables()->get();
|
||||
|
@ -651,16 +648,23 @@ class UsersController extends Controller
|
|||
{
|
||||
$this->authorize('view', User::class);
|
||||
|
||||
if (!$user = User::find($id)) {
|
||||
return redirect()->back()
|
||||
->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
|
||||
}
|
||||
if (empty($user->email)) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email'));
|
||||
$user = Company::scopeCompanyables(User::find($id));
|
||||
|
||||
// Make sure they can view this particular user
|
||||
$this->authorize('view', $user);
|
||||
|
||||
if ($user) {
|
||||
|
||||
if (empty($user->email)) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email'));
|
||||
}
|
||||
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
|
||||
}
|
||||
|
||||
$user->notify((new CurrentInventory($user)));
|
||||
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
|
||||
return redirect()->back()->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -672,19 +676,19 @@ class UsersController extends Controller
|
|||
*/
|
||||
public function sendPasswordReset($id)
|
||||
{
|
||||
if (($user = User::find($id)) && ($user->activated == '1') && ($user->email != '') && ($user->ldap_import == '0')) {
|
||||
if (($user = Company::scopeCompanyables(User::find($id))) && ($user->activated == '1') && ($user->email != '') && ($user->ldap_import == '0')) {
|
||||
$credentials = ['email' => trim($user->email)];
|
||||
|
||||
try {
|
||||
|
||||
Password::sendResetLink($credentials);
|
||||
|
||||
return redirect()->back()->with('success', trans('admin/users/message.password_reset_sent', ['email' => $user->email]));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', ' Error sending email. :( ');
|
||||
return redirect()->back()->with('error', trans('general.error_sending_email'));
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'User is not activated, is LDAP synced, or does not have an email address ');
|
||||
return redirect()->back()->with('error', trans('general.pwd_reset_not_sent'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ class ImageUploadRequest extends Request
|
|||
return [
|
||||
'image' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp,avif',
|
||||
'avatar' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp,avif',
|
||||
'favicon' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp,image/x-icon,image/vnd.microsoft.icon,ico',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -103,9 +104,9 @@ class ImageUploadRequest extends Request
|
|||
\Log::info('File name will be: '.$file_name);
|
||||
\Log::debug('File extension is: '.$ext);
|
||||
|
||||
if (($image->getMimeType() == 'image/avif') || ($image->getMimeType() == 'image/webp')) {
|
||||
// If the file is a webp or avif, we need to just move it since webp support
|
||||
// needs to be compiled into gd for resizing to be available
|
||||
if (($image->getMimeType() == 'image/vnd.microsoft.icon') || ($image->getMimeType() == 'image/x-icon') || ($image->getMimeType() == 'image/avif') || ($image->getMimeType() == 'image/webp')) {
|
||||
// If the file is an icon, webp or avif, we need to just move it since gd doesn't support resizing
|
||||
// icons or avif, and webp support and needs to be compiled into gd for resizing to be available
|
||||
Storage::disk('public')->put($path.'/'.$file_name, file_get_contents($image));
|
||||
|
||||
} elseif($image->getMimeType() == 'image/svg+xml') {
|
||||
|
|
|
@ -81,26 +81,6 @@ final class Company extends SnipeModel
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoping table queries, determining if a logged in user is part of a company, and only allows
|
||||
* that user to see items associated with that company
|
||||
*/
|
||||
private static function scopeCompanyablesDirectly($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
if (Auth::user()) {
|
||||
$company_id = Auth::user()->company_id;
|
||||
} else {
|
||||
$company_id = null;
|
||||
}
|
||||
|
||||
$table = ($table_name) ? $table_name."." : $query->getModel()->getTable().".";
|
||||
|
||||
if (\Schema::hasColumn($query->getModel()->getTable(), $column)) {
|
||||
return $query->where($table.$column, '=', $company_id);
|
||||
} else {
|
||||
return $query->join('users as users_comp', 'users_comp.id', 'user_id')->where('users_comp.company_id', '=', $company_id);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getIdFromInput($unescaped_input)
|
||||
{
|
||||
|
@ -141,25 +121,49 @@ final class Company extends SnipeModel
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the current user should have access to the model.
|
||||
* I hate this method and I think it should be refactored.
|
||||
*
|
||||
* @param $companyable
|
||||
* @return bool|void
|
||||
*/
|
||||
public static function isCurrentUserHasAccess($companyable)
|
||||
{
|
||||
// When would this even happen tho??
|
||||
if (is_null($companyable)) {
|
||||
return false;
|
||||
} elseif (! static::isFullMultipleCompanySupportEnabled()) {
|
||||
return true;
|
||||
} elseif (!$companyable instanceof Company && !\Schema::hasColumn($companyable->getModel()->getTable(), 'company_id')) {
|
||||
// This is primary for the gate:allows-check in location->isDeletable()
|
||||
// Locations don't have a company_id so without this it isn't possible to delete locations with FullMultipleCompanySupport enabled
|
||||
// because this function is called by SnipePermissionsPolicy->before()
|
||||
return true;
|
||||
} else {
|
||||
if (Auth::user()) {
|
||||
$current_user_company_id = Auth::user()->company_id;
|
||||
$companyable_company_id = $companyable->company_id;
|
||||
}
|
||||
|
||||
return $current_user_company_id == null || $current_user_company_id == $companyable_company_id || Auth::user()->isSuperUser();
|
||||
// If FMCS is not enabled, everyone has access, return true
|
||||
if (! static::isFullMultipleCompanySupportEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Again, where would this happen? But check that $companyable is not a string
|
||||
if (!is_string($companyable)) {
|
||||
$company_table = $companyable->getModel()->getTable();
|
||||
try {
|
||||
// This is primary for the gate:allows-check in location->isDeletable()
|
||||
// Locations don't have a company_id so without this it isn't possible to delete locations with FullMultipleCompanySupport enabled
|
||||
// because this function is called by SnipePermissionsPolicy->before()
|
||||
if (!$companyable instanceof Company && !\Schema::hasColumn($company_table, 'company_id')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::warning($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Auth::user()) {
|
||||
\Log::warning('Companyable is '.$companyable);
|
||||
$current_user_company_id = Auth::user()->company_id;
|
||||
$companyable_company_id = $companyable->company_id;
|
||||
return $current_user_company_id == null || $current_user_company_id == $companyable_company_id || Auth::user()->isSuperUser();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function isCurrentUserAuthorized()
|
||||
|
@ -190,6 +194,10 @@ final class Company extends SnipeModel
|
|||
&& ($this->users()->count() === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $unescaped_input
|
||||
* @return int|mixed|string|null
|
||||
*/
|
||||
public static function getIdForUser($unescaped_input)
|
||||
{
|
||||
if (! static::isFullMultipleCompanySupportEnabled() || Auth::user()->isSuperUser()) {
|
||||
|
@ -199,38 +207,6 @@ final class Company extends SnipeModel
|
|||
}
|
||||
}
|
||||
|
||||
public static function scopeCompanyables($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
// If not logged in and hitting this, assume we are on the command line and don't scope?'
|
||||
if (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser()) || (! Auth::check())) {
|
||||
return $query;
|
||||
} else {
|
||||
return static::scopeCompanyablesDirectly($query, $column, $table_name);
|
||||
}
|
||||
}
|
||||
|
||||
public static function scopeCompanyableChildren(array $companyable_names, $query)
|
||||
{
|
||||
if (count($companyable_names) == 0) {
|
||||
throw new Exception('No Companyable Children to scope');
|
||||
} elseif (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser())) {
|
||||
return $query;
|
||||
} else {
|
||||
$f = function ($q) {
|
||||
static::scopeCompanyablesDirectly($q);
|
||||
};
|
||||
|
||||
$q = $query->where(function ($q) use ($companyable_names, $f) {
|
||||
$q2 = $q->whereHas($companyable_names[0], $f);
|
||||
|
||||
for ($i = 1; $i < count($companyable_names); $i++) {
|
||||
$q2 = $q2->orWhereHas($companyable_names[$i], $f);
|
||||
}
|
||||
});
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
|
@ -261,4 +237,98 @@ final class Company extends SnipeModel
|
|||
{
|
||||
return $this->hasMany(Component::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* START COMPANY SCOPING FOR FMCS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Scoping table queries, determining if a logged in user is part of a company, and only allows the user to access items associated with that company if FMCS is enabled.
|
||||
*
|
||||
* This method is the one that the CompanyableTrait uses to contrain queries automatically, however that trait CANNOT be
|
||||
* applied to the user's model, since it causes an infinite loop against the authenticated user.
|
||||
*
|
||||
* @todo - refactor that trait to handle the user's model as well.
|
||||
*
|
||||
* @author [A. Gianotto] <snipe@snipe.net>
|
||||
* @param $query
|
||||
* @param $column
|
||||
* @param $table_name
|
||||
* @return mixed
|
||||
*/
|
||||
public static function scopeCompanyables($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
// If not logged in and hitting this, assume we are on the command line and don't scope?'
|
||||
if (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser()) || (! Auth::check())) {
|
||||
return $query;
|
||||
} else {
|
||||
\Log::debug('Fire scopeCompanyablesDirectly.');
|
||||
return static::scopeCompanyablesDirectly($query, $column, $table_name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoping table queries, determining if a logged in user is part of a company, and only allows
|
||||
* that user to see items associated with that company
|
||||
*/
|
||||
private static function scopeCompanyablesDirectly($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
// Get the company ID of the logged in user, or set it to null if there is no company assicoated with the user
|
||||
if (Auth::user()) {
|
||||
\Log::debug('Admin company is: '.Auth::user()->company_id);
|
||||
$company_id = Auth::user()->company_id;
|
||||
} else {
|
||||
$company_id = null;
|
||||
}
|
||||
|
||||
// Dynamically get the table name if it's not passed in, based on the model we're querying against
|
||||
$table = ($table_name) ? $table_name."." : $query->getModel()->getTable().".";
|
||||
\Log::debug('Model is: '.$query->getModel());
|
||||
|
||||
\Log::debug('Table is: '.$table);
|
||||
|
||||
// If the column exists in the table, use it to scope the query
|
||||
if (\Schema::hasColumn($query->getModel()->getTable(), $column)) {
|
||||
return $query->where($table.$column, '=', $company_id);
|
||||
} else {
|
||||
return $query->join('users as users_comp', 'users_comp.id', 'user_id')->where('users_comp.company_id', '=', $company_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I legit do not know what this method does, but we can't remove it (yet).
|
||||
*
|
||||
* This gets invoked by CompanyableChildScope, but I'm not sure what it does.
|
||||
*
|
||||
* @author [A. Gianotto] <snipe@snipe.net>
|
||||
* @param array $companyable_names
|
||||
* @param $query
|
||||
* @return mixed
|
||||
*/
|
||||
public static function scopeCompanyableChildren(array $companyable_names, $query)
|
||||
{
|
||||
\Log::debug('Company Names in scopeCompanyableChildren: '.print_r($companyable_names, true));
|
||||
|
||||
if (count($companyable_names) == 0) {
|
||||
throw new Exception('No Companyable Children to scope');
|
||||
} elseif (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser())) {
|
||||
return $query;
|
||||
} else {
|
||||
$f = function ($q) {
|
||||
\Log::debug('scopeCompanyablesDirectly firing ');
|
||||
static::scopeCompanyablesDirectly($q);
|
||||
};
|
||||
|
||||
$q = $query->where(function ($q) use ($companyable_names, $f) {
|
||||
$q2 = $q->whereHas($companyable_names[0], $f);
|
||||
|
||||
for ($i = 1; $i < count($companyable_names); $i++) {
|
||||
$q2 = $q2->orWhereHas($companyable_names[$i], $f);
|
||||
}
|
||||
});
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,8 +5,13 @@ namespace App\Models;
|
|||
trait CompanyableTrait
|
||||
{
|
||||
/**
|
||||
* Boot the companyable trait for a model.
|
||||
* This trait is used to scope models to the current company. To use this scope on companyable models,
|
||||
* we use the "use Companyable;" statement at the top of the mode.
|
||||
*
|
||||
* We CANNOT USE THIS ON USERS, as it causes an infinite loop and prevents users from logging in, since this scope will be
|
||||
* applied to the currently logged in (or logging in) user in addition to the user model for viewing lists of users.
|
||||
*
|
||||
* @see \App\Models\Company\Company::scopeCompanyables()
|
||||
* @return void
|
||||
*/
|
||||
public static function bootCompanyableTrait()
|
||||
|
|
|
@ -18,8 +18,14 @@ class FieldOption {
|
|||
// assignedTo directly on the asset is a special case where
|
||||
// we want to avoid returning the property directly
|
||||
// and instead return the entity's presented name.
|
||||
if ($dataPath[0] === 'assignedTo'){
|
||||
return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null;
|
||||
if ($dataPath[0] === 'assignedTo') {
|
||||
if ($asset->relationLoaded('assignedTo')) {
|
||||
// If the "assignedTo" relationship was eager loaded then the way to get the
|
||||
// relationship changes from $asset->assignedTo to $asset->assigned.
|
||||
return $asset->assigned ? $asset->assigned->present()->fullName() : null;
|
||||
}
|
||||
|
||||
return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null;
|
||||
}
|
||||
|
||||
return $dataPath->reduce(function ($myValue, $path) {
|
||||
|
|
|
@ -35,16 +35,50 @@ abstract class SnipePermissionsPolicy
|
|||
|
||||
public function before(User $user, $ability, $item)
|
||||
{
|
||||
// Lets move all company related checks here.
|
||||
if ($item instanceof \App\Models\SnipeModel && ! Company::isCurrentUserHasAccess($item)) {
|
||||
return false;
|
||||
}
|
||||
// If an admin, they can do all asset related tasks.
|
||||
/**
|
||||
* If an admin, they can do all item related tasks, but ARE constrained by FMCSA company access.
|
||||
* That scoping happens on the model level (except for the Users model) via the Companyable trait.
|
||||
*
|
||||
* This does lead to some inconsistencies in the responses, since attempting to edit assets,
|
||||
* accessories, etc (anything other than users) will result in a Forbidden error, whereas the users
|
||||
* area will redirect with "That user doesn't exist" since the scoping is handled directly on those queries.
|
||||
*
|
||||
* The *superuser* global permission gets handled in the AuthServiceProvider before() method.
|
||||
*
|
||||
* @see https://snipe-it.readme.io/docs/permissions
|
||||
*/
|
||||
|
||||
if ($user->hasAccess('admin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we got here by $this→authorize('something', $actualModel) then we can continue on Il but if we got here
|
||||
* via $this→authorize('something', Model::class) then calling Company:: isCurrentUserHasAccess($item) gets weird.
|
||||
* Bail out here by returning "nothing" and allow the relevant method lower in this class to be called and handle authorization.
|
||||
*/
|
||||
if (!$item instanceof Model){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Company::isCurrentUserHasAccess() method from the company model handles the check for FMCS already so we
|
||||
* don't have to do that here.
|
||||
*/
|
||||
if (!Company::isCurrentUserHasAccess($item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* These methods handle the generic view/create/edit/delete permissions for the model.
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function index(User $user)
|
||||
{
|
||||
return $user->hasAccess($this->columnName().'.view');
|
||||
|
|
|
@ -93,21 +93,28 @@ class AuthServiceProvider extends ServiceProvider
|
|||
Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
|
||||
Passport::withCookieSerialization();
|
||||
|
||||
// --------------------------------
|
||||
// BEFORE ANYTHING ELSE
|
||||
// --------------------------------
|
||||
// If this condition is true, ANYTHING else below will be assumed
|
||||
// to be true. This can cause weird blade behavior.
|
||||
|
||||
/**
|
||||
* BEFORE ANYTHING ELSE
|
||||
*
|
||||
* If this condition is true, ANYTHING else below will be assumed to be true.
|
||||
* This is where we set the superadmin permission to allow superadmins to be able to do everything within the system.
|
||||
*
|
||||
*/
|
||||
Gate::before(function ($user) {
|
||||
if ($user->isSuperUser()) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// --------------------------------
|
||||
// GENERAL GATES
|
||||
// These control general sections of the admin
|
||||
// --------------------------------
|
||||
|
||||
/**
|
||||
* GENERAL GATES
|
||||
*
|
||||
* These control general sections of the admin. These definitions are used in our blades via @can('blah) and also
|
||||
* use in our controllers to determine if a user has access to a certain area.
|
||||
*/
|
||||
|
||||
Gate::define('admin', function ($user) {
|
||||
if ($user->hasAccess('admin')) {
|
||||
return true;
|
||||
|
|
|
@ -278,7 +278,24 @@ class ValidationServiceProvider extends ServiceProvider
|
|||
|
||||
Validator::extend('is_unique_department', function ($attribute, $value, $parameters, $validator) {
|
||||
$data = $validator->getData();
|
||||
if ((array_key_exists('location_id', $data) && $data['location_id'] != null) && (array_key_exists('company_id', $data) && $data['company_id'] != null)) {
|
||||
|
||||
if (
|
||||
array_key_exists('location_id', $data) && $data['location_id'] !== null &&
|
||||
array_key_exists('company_id', $data) && $data['company_id'] !== null
|
||||
) {
|
||||
//for updating existing departments
|
||||
if(array_key_exists('id', $data) && $data['id'] !== null){
|
||||
$count = Department::where('name', $data['name'])
|
||||
->where('location_id', $data['location_id'])
|
||||
->where('company_id', $data['company_id'])
|
||||
->whereNotNull('company_id')
|
||||
->whereNotNull('location_id')
|
||||
->where('id', '!=', $data['id'])
|
||||
->count('name');
|
||||
|
||||
return $count < 1;
|
||||
}else // for entering in new departments
|
||||
{
|
||||
$count = Department::where('name', $data['name'])
|
||||
->where('location_id', $data['location_id'])
|
||||
->where('company_id', $data['company_id'])
|
||||
|
@ -288,9 +305,10 @@ class ValidationServiceProvider extends ServiceProvider
|
|||
|
||||
return $count < 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Validator::extend('not_array', function ($attribute, $value, $parameters, $validator) {
|
||||
|
|
|
@ -142,7 +142,32 @@ class Label implements View
|
|||
// Remove Duplicates
|
||||
$toAdd = $field
|
||||
->filter(fn($o) => !$myFields->contains('dataSource', $o['dataSource']))
|
||||
->first();
|
||||
// For fields that have multiple options, we need to combine them
|
||||
// into a single field so all values are displayed.
|
||||
->reduce(function ($previous, $current) {
|
||||
// On the first iteration we simply return the item.
|
||||
// If there is only one item to be processed for the row
|
||||
// then this effectively skips everything below this if block.
|
||||
if (is_null($previous)) {
|
||||
return $current;
|
||||
}
|
||||
|
||||
// At this point we are dealing with a row with multiple items being displayed.
|
||||
// We need to combine the label and value of the current item with the previous item.
|
||||
|
||||
// The end result of this will be in this format:
|
||||
// {labelOne} {valueOne} | {labelTwo} {valueTwo} | {labelThree} {valueThree}
|
||||
$previous['value'] = trim(implode(' | ', [
|
||||
implode(' ', [$previous['label'], $previous['value']]),
|
||||
implode(' ', [$current['label'], $current['value']]),
|
||||
]));
|
||||
|
||||
// We'll set the label to an empty string since we
|
||||
// injected the label into the value field above.
|
||||
$previous['label'] = '';
|
||||
|
||||
return $previous;
|
||||
});
|
||||
|
||||
return $toAdd ? $myFields->push($toAdd) : $myFields;
|
||||
}, new Collection());
|
||||
|
|
|
@ -96,6 +96,7 @@ return [
|
|||
PDO::MYSQL_ATTR_SSL_CERT => env('DB_SSL_CERT_PATH'), // /path/to/cert.pem
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('DB_SSL_CA_PATH'), // /path/to/ca.pem
|
||||
PDO::MYSQL_ATTR_SSL_CIPHER => env('DB_SSL_CIPHER'),
|
||||
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => env('DB_SSL_VERIFY_SERVER'), //true/false
|
||||
]) : [],
|
||||
],
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<?php
|
||||
return array (
|
||||
'app_version' => 'v6.3.4',
|
||||
'full_app_version' => 'v6.3.4 - build 13139-g6f9ba6ede',
|
||||
'build_version' => '13139',
|
||||
'full_app_version' => 'v6.3.4 - build 13226-g5229dd65c',
|
||||
'build_version' => '13226',
|
||||
'prerelease_version' => '',
|
||||
'hash_version' => 'g6f9ba6ede',
|
||||
'full_hash' => 'v6.3.4-234-g6f9ba6ede',
|
||||
'hash_version' => 'g5229dd65c',
|
||||
'full_hash' => 'v6.3.4-85-g5229dd65c',
|
||||
'branch' => 'develop',
|
||||
);
|
|
@ -4,6 +4,7 @@ namespace Database\Factories;
|
|||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Location;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
|
@ -354,7 +355,6 @@ class AssetFactory extends Factory
|
|||
return $this->state(['requestable' => false]);
|
||||
}
|
||||
|
||||
|
||||
public function noPurchaseOrEolDate()
|
||||
{
|
||||
return $this->afterCreating(function (Asset $asset) {
|
||||
|
@ -364,6 +364,17 @@ class AssetFactory extends Factory
|
|||
]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function hasEncryptedCustomField(CustomField $field = null)
|
||||
{
|
||||
return $this->state(function () use ($field) {
|
||||
return [
|
||||
'model_id' => AssetModel::factory()->hasEncryptedCustomField($field),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This allows bypassing model level validation if you want to purposefully
|
||||
* create an asset in an invalid state. Validation is turned back on
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Database\Factories;
|
||||
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\CustomFieldset;
|
||||
use App\Models\Depreciation;
|
||||
use App\Models\Manufacturer;
|
||||
|
@ -429,4 +430,13 @@ class AssetModelFactory extends Factory
|
|||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function hasEncryptedCustomField(CustomField $field = null)
|
||||
{
|
||||
return $this->state(function () use ($field) {
|
||||
return [
|
||||
'fieldset_id' => CustomFieldset::factory()->hasEncryptedCustomField($field),
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Database\Factories;
|
||||
|
||||
use App\Models\CustomFieldset;
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class CustomFieldsetFactory extends Factory
|
||||
|
@ -43,4 +44,13 @@ class CustomFieldsetFactory extends Factory
|
|||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function hasEncryptedCustomField(CustomField $field = null)
|
||||
{
|
||||
return $this->afterCreating(function (CustomFieldset $fieldset) use ($field) {
|
||||
$field = $field ?? CustomField::factory()->testEncrypted()->create();
|
||||
|
||||
$fieldset->fields()->attach($field, ['order' => '1', 'required' => false]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -19162,6 +19162,11 @@
|
|||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"signature_pad": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/signature_pad/-/signature_pad-4.2.0.tgz",
|
||||
"integrity": "sha512-YLWysmaUBaC5wosAKkgbX7XI+LBv2w5L0QUcI6Jc4moHYzv9BUBJtAyNLpWzHjtjKTeWOH6bfP4a4pzf0UinfQ=="
|
||||
},
|
||||
"simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
"papaparse": "^4.3.3",
|
||||
"select2": "4.0.13",
|
||||
"sheetjs": "^2.0.0",
|
||||
"signature_pad": "^4.2.0",
|
||||
"tableexport.jquery.plugin": "1.28.0",
|
||||
"tether": "^1.4.0",
|
||||
"vue-resource": "^1.5.2",
|
||||
|
|
Binary file not shown.
Binary file not shown.
BIN
public/css/dist/all.css
vendored
BIN
public/css/dist/all.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-black-dark.css
vendored
BIN
public/css/dist/skins/skin-black-dark.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-black-dark.min.css
vendored
BIN
public/css/dist/skins/skin-black-dark.min.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-blue-dark.css
vendored
BIN
public/css/dist/skins/skin-blue-dark.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-blue-dark.min.css
vendored
BIN
public/css/dist/skins/skin-blue-dark.min.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-green-dark.css
vendored
BIN
public/css/dist/skins/skin-green-dark.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-green-dark.min.css
vendored
BIN
public/css/dist/skins/skin-green-dark.min.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-orange-dark.css
vendored
BIN
public/css/dist/skins/skin-orange-dark.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-orange-dark.min.css
vendored
BIN
public/css/dist/skins/skin-orange-dark.min.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-purple-dark.css
vendored
BIN
public/css/dist/skins/skin-purple-dark.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-purple-dark.min.css
vendored
BIN
public/css/dist/skins/skin-purple-dark.min.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-red-dark.css
vendored
BIN
public/css/dist/skins/skin-red-dark.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-red-dark.min.css
vendored
BIN
public/css/dist/skins/skin-red-dark.min.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-yellow-dark.css
vendored
BIN
public/css/dist/skins/skin-yellow-dark.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-yellow-dark.min.css
vendored
BIN
public/css/dist/skins/skin-yellow-dark.min.css
vendored
Binary file not shown.
|
@ -1,24 +1,24 @@
|
|||
{
|
||||
"/js/build/app.js": "/js/build/app.js?id=ea5f3edebafdb29b616d23fa89106080",
|
||||
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374",
|
||||
"/css/build/overrides.css": "/css/build/overrides.css?id=ba34afc5bd6ee746744fd109672f715e",
|
||||
"/css/build/app.css": "/css/build/app.css?id=1b976575001241a8f97ec4fad7bd1f3d",
|
||||
"/css/build/overrides.css": "/css/build/overrides.css?id=3f1125ebfd37987dd0d6e2bf18bb41ce",
|
||||
"/css/build/app.css": "/css/build/app.css?id=ca42da2140f0b5be6c2d0fa84ae90108",
|
||||
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=dc383f8560a8d4adb51d44fb4043e03b",
|
||||
"/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
|
||||
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=e6e53eef152bba01a4c666a4d8b01117",
|
||||
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=07273f6ca3c698a39e8fc2075af4fa07",
|
||||
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=c1f33574ecb9d3e69d9b8fe5bd68e101",
|
||||
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=620b684d9dd9d3bb5fdda00a3a2467c3",
|
||||
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=23f2e8df6b60e8d9816e645cc5000874",
|
||||
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=2600828a93288ba791de9bf711171796",
|
||||
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
|
||||
"/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=7d92dea45d94be7e1d4e427c728d335d",
|
||||
"/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=e56aa0fcc7789f1efd1e14bc5e0b4159",
|
||||
"/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=6fe68325d5356197672c27bc77cedcb4",
|
||||
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=8ca888bbc050d9680cbb65021382acba",
|
||||
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=b061bb141af3bdb6280c6ee772cf8f4f",
|
||||
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=821b68e0a3efc3b6743453bf0ed2a954",
|
||||
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=b31e80c39b29106d02bf9b3a0a833343",
|
||||
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb",
|
||||
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=d419cb63a12dc175d71645c876bfc2ab",
|
||||
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=ec0a01609bec55e90f0692d86cb81625",
|
||||
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
|
||||
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da",
|
||||
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898",
|
||||
"/css/dist/all.css": "/css/dist/all.css?id=53cc9f0f60753c049f4e49617d21c0fe",
|
||||
"/css/dist/all.css": "/css/dist/all.css?id=0836d3b034a9b1de17d797ff35f1983e",
|
||||
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
|
||||
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
|
||||
"/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=69e5d8e4e818f05fd882cceb758d1eba",
|
||||
|
@ -35,18 +35,18 @@
|
|||
"/js/dist/all.js": "/js/dist/all.js?id=13bdb521e0c745d7f81dae3fb110b650",
|
||||
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=19ccc62a8f1ea103dede4808837384d4",
|
||||
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
|
||||
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=d419cb63a12dc175d71645c876bfc2ab",
|
||||
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=ec0a01609bec55e90f0692d86cb81625",
|
||||
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=76482123f6c70e866d6b971ba91de7bb",
|
||||
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=b061bb141af3bdb6280c6ee772cf8f4f",
|
||||
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=b31e80c39b29106d02bf9b3a0a833343",
|
||||
"/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=f677207c6cf9678eb539abecb408c374",
|
||||
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=07273f6ca3c698a39e8fc2075af4fa07",
|
||||
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=23f2e8df6b60e8d9816e645cc5000874",
|
||||
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
|
||||
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=c1f33574ecb9d3e69d9b8fe5bd68e101",
|
||||
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=2600828a93288ba791de9bf711171796",
|
||||
"/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=44bf834f2110504a793dadec132a5898",
|
||||
"/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=8ca888bbc050d9680cbb65021382acba",
|
||||
"/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=821b68e0a3efc3b6743453bf0ed2a954",
|
||||
"/css/dist/skins/skin-purple.min.css": "/css/dist/skins/skin-purple.min.css?id=6fe68325d5356197672c27bc77cedcb4",
|
||||
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=7d92dea45d94be7e1d4e427c728d335d",
|
||||
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=e56aa0fcc7789f1efd1e14bc5e0b4159",
|
||||
"/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
|
||||
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=e6e53eef152bba01a4c666a4d8b01117",
|
||||
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=620b684d9dd9d3bb5fdda00a3a2467c3",
|
||||
"/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da"
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@
|
|||
//Hover and active states
|
||||
&:hover > a, &.active > a {
|
||||
color: @sidebar-dark-hover-color;
|
||||
background: @sidebar-dark-hover-bg;
|
||||
background: @link-hover-border-color;
|
||||
border-left-color: @link-hover-border-color;
|
||||
}
|
||||
//First Level Submenu
|
||||
|
|
|
@ -359,7 +359,7 @@ body {
|
|||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading {
|
||||
z-index: 840 !important;
|
||||
z-index: 0 !important;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
@ -688,6 +688,12 @@ th.css-accessory > .th-inner::before
|
|||
.sidebar-menu {
|
||||
margin-top:100px
|
||||
}
|
||||
.navbar-custom-menu > .navbar-nav > li.dropdown.user.user-menu {
|
||||
float:right;
|
||||
}
|
||||
.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {
|
||||
margin-right:-39px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1268px) and (min-width: 912px){
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
|
||||
&.btn-primary, .btn-primary:link {
|
||||
background-color: darken(@black, 10%);
|
||||
border-color: darken(@black, 20%);
|
||||
border-color: #FFF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
@ -375,7 +375,10 @@ input[type=text], input[type=search] {
|
|||
color: var(--text-main);
|
||||
}
|
||||
.skin-black-dark .main-header .navbar .dropdown-menu li a {
|
||||
color: var(--header);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.skin-black-dark .main-header .navbar .dropdown-menu li a:hover {
|
||||
background-color: #000000;
|
||||
}
|
||||
.fixed-table-body thead th .th-inner, .skin-black-dark .sidebar-menu>li.active>a, .skin-black .sidebar-menu>li:hover>a, .sidebar-toggle:hover {
|
||||
background-color: var(--header)!important;
|
||||
|
|
|
@ -245,7 +245,6 @@ body {
|
|||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--button-primary);
|
||||
color: var(--link)!important;
|
||||
}
|
||||
#componentsTable>tbody>tr>td>nobr>a>i.fa {
|
||||
color: var(--text-main);
|
||||
|
@ -361,7 +360,10 @@ input[type=text], input[type=search] {
|
|||
color: var(--text-main);
|
||||
}
|
||||
.skin-blue-dark .main-header .navbar .dropdown-menu li a {
|
||||
color: var(--header);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.skin-blue-dark .main-header .navbar .dropdown-menu li a:hover {
|
||||
background-color: #3c8dbc;
|
||||
}
|
||||
.fixed-table-body thead th .th-inner, .skin-blue-dark .sidebar-menu>li.active>a, .skin-blue .sidebar-menu>li:hover>a, .sidebar-toggle:hover {
|
||||
background-color: var(--header)!important;
|
||||
|
|
|
@ -236,7 +236,6 @@ body {
|
|||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--button-primary);
|
||||
color: var(--link)!important;
|
||||
}
|
||||
#componentsTable>tbody>tr>td>nobr>a>i.fa {
|
||||
color: var(--text-main);
|
||||
|
@ -348,7 +347,10 @@ input[type=text], input[type=search] {
|
|||
color: var(--text-main);
|
||||
}
|
||||
.skin-green-dark .main-header .navbar .dropdown-menu li a {
|
||||
color: var(--link);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.skin-green-dark .main-header .navbar .dropdown-menu li a:hover {
|
||||
background-color: #006300;
|
||||
}
|
||||
.fixed-table-body thead th .th-inner, .skin-green-dark .sidebar-menu>li.active>a, .skin-green .sidebar-menu>li:hover>a, .sidebar-toggle:hover {
|
||||
background-color: var(--header)!important;
|
||||
|
|
|
@ -243,7 +243,6 @@ a:link.btn-default{
|
|||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--button-primary);
|
||||
color: var(--link)!important;
|
||||
}
|
||||
#componentsTable>tbody>tr>td>nobr>a>i.fa {
|
||||
color: var(--text-main);
|
||||
|
@ -359,7 +358,10 @@ input[type=text], input[type=search] {
|
|||
color: var(--text-main);
|
||||
}
|
||||
.skin-orange-dark .main-header .navbar .dropdown-menu li a {
|
||||
color: var(--header);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.skin-orange-dark .main-header .navbar .dropdown-menu li a:hover {
|
||||
background-color: #ff8c00;
|
||||
}
|
||||
.fixed-table-body thead th .th-inner, .skin-orange-dark .sidebar-menu>li.active>a, .skin-orange .sidebar-menu>li:hover>a, .sidebar-toggle:hover {
|
||||
background-color: var(--header)!important;
|
||||
|
|
|
@ -246,7 +246,6 @@ body {
|
|||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--button-primary);
|
||||
color: var(--link)!important;
|
||||
}
|
||||
#componentsTable>tbody>tr>td>nobr>a>i.fa {
|
||||
color: var(--text-main);
|
||||
|
@ -362,7 +361,10 @@ input[type=text], input[type=search] {
|
|||
color: var(--text-main);
|
||||
}
|
||||
.skin-purple-dark .main-header .navbar .dropdown-menu li a {
|
||||
color: var(--header);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.skin-purple-dark .main-header .navbar .dropdown-menu li a:hover {
|
||||
background-color: #5f5ca8;
|
||||
}
|
||||
.fixed-table-body thead th .th-inner, .skin-purple-dark .sidebar-menu>li.active>a, .skin-purple .sidebar-menu>li:hover>a, .sidebar-toggle:hover {
|
||||
background-color: var(--header)!important;
|
||||
|
|
|
@ -247,7 +247,6 @@ body {
|
|||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--button-primary);
|
||||
color: var(--link)!important;
|
||||
}
|
||||
#componentsTable>tbody>tr>td>nobr>a>i.fa {
|
||||
color: var(--text-main);
|
||||
|
@ -363,7 +362,10 @@ input[type=text], input[type=search] {
|
|||
color: var(--text-main);
|
||||
}
|
||||
.skin-red-dark .main-header .navbar .dropdown-menu li a {
|
||||
color: var(--header);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.skin-red-dark .main-header .navbar .dropdown-menu li a:hover {
|
||||
background-color: #c23320;
|
||||
}
|
||||
.fixed-table-body thead th .th-inner, .skin-red-dark .sidebar-menu>li.active>a, .skin-red .sidebar-menu>li:hover>a, .sidebar-toggle:hover {
|
||||
background-color: var(--header)!important;
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
&.btn-primary, .btn-primary:link {
|
||||
background-color: var(--button-default);
|
||||
border-color: var(--button-default);
|
||||
border-color: #000000;
|
||||
color: #545454;
|
||||
}
|
||||
|
||||
|
@ -355,8 +355,11 @@ input[type=text], input[type=search] {
|
|||
.skin-yellow-dark .main-header .navbar .dropdown-menu li a {
|
||||
color: var(--header);
|
||||
}
|
||||
.skin-yellow-dark .main-header .navbar .dropdown-menu li a:hover {
|
||||
background-color: #000000;
|
||||
}
|
||||
tr th div.th-inner {
|
||||
color:var(--text-main);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.tab-content, .tab-pane {
|
||||
background-color: var(--back-main);
|
||||
|
|
|
@ -17,6 +17,7 @@ return [
|
|||
'update' => [
|
||||
'error' => 'Asset was not updated, please try again',
|
||||
'success' => 'Asset updated successfully.',
|
||||
'encrypted_warning' => 'Asset updated successfully, but encrypted custom fields were not due to permissions',
|
||||
'nothing_updated' => 'No fields were selected, so nothing was updated.',
|
||||
'no_assets_selected' => 'No assets were selected, so nothing was updated.',
|
||||
'assets_do_not_exist_or_are_invalid' => 'Selected assets cannot be updated.',
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
return array(
|
||||
|
||||
'does_not_exist' => 'License does not exist or you do not have permission to view it.',
|
||||
'user_does_not_exist' => 'User does not exist.',
|
||||
'user_does_not_exist' => 'User does not exist or you do not have permission to view them.',
|
||||
'asset_does_not_exist' => 'The asset you are trying to associate with this license does not exist.',
|
||||
'owner_doesnt_match_asset' => 'The asset you are trying to associate with this license is owned by somene other than the person selected in the assigned to dropdown.',
|
||||
'assoc_users' => 'This license is currently checked out to a user and cannot be deleted. Please check the license in first, and then try deleting again. ',
|
||||
|
|
|
@ -245,6 +245,7 @@ return [
|
|||
'select_all' => 'Select All',
|
||||
'search' => 'Search',
|
||||
'select_category' => 'Select a Category',
|
||||
'select_datasource' => 'Select a Datasource',
|
||||
'select_department' => 'Select a Department',
|
||||
'select_depreciation' => 'Select a Depreciation Type',
|
||||
'select_location' => 'Select a Location',
|
||||
|
@ -507,6 +508,9 @@ return [
|
|||
'or' => 'or',
|
||||
'url' => 'URL',
|
||||
'edit_fieldset' => 'Edit fieldset fields and options',
|
||||
'permission_denied_superuser_demo' => 'Permission denied. You cannot update user information for superadmins on the demo.',
|
||||
'pwd_reset_not_sent' => 'User is not activated, is LDAP synced, or does not have an email address',
|
||||
'error_sending_email' => 'Error sending email',
|
||||
'bulk' => [
|
||||
'delete' =>
|
||||
[
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<h3 style="padding-top: 20px">{{trans('general.sign_tos')}}</h3>
|
||||
<div id="signature-pad" class="m-signature-pad">
|
||||
<div class="m-signature-pad--body col-md-12 col-sm-12 col-lg-12 col-xs-12">
|
||||
<canvas></canvas>
|
||||
<canvas style="width:100%;"></canvas>
|
||||
<input type="hidden" name="signature_output" id="signature_output">
|
||||
</div>
|
||||
<div class="col-md-12 col-sm-12 col-lg-12 col-xs-12 text-center">
|
||||
|
@ -94,6 +94,7 @@
|
|||
@section('moar_scripts')
|
||||
|
||||
<script nonce="{{ csrf_token() }}">
|
||||
|
||||
var wrapper = document.getElementById("signature-pad"),
|
||||
clearButton = wrapper.querySelector("[data-action=clear]"),
|
||||
saveButton = wrapper.querySelector("[data-action=save]"),
|
||||
|
@ -103,19 +104,20 @@
|
|||
// Adjust canvas coordinate space taking into account pixel ratio,
|
||||
// to make it look crisp on mobile devices.
|
||||
// This also causes canvas to be cleared.
|
||||
function resizeCanvas() {
|
||||
// When zoomed out to less than 100%, for some very strange reason,
|
||||
// some browsers report devicePixelRatio as less than 1
|
||||
// and only part of the canvas is cleared then.
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
canvas.width = canvas.offsetWidth * ratio;
|
||||
canvas.height = canvas.offsetHeight * ratio;
|
||||
canvas.getContext("2d").scale(ratio, ratio);
|
||||
if (window.matchMedia("(min-width: 768px)").matches) {
|
||||
function resizeCanvas() {
|
||||
// When zoomed out to less than 100%, for some very strange reason,
|
||||
// some browsers report devicePixelRatio as less than 1
|
||||
// and only part of the canvas is cleared then.
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
canvas.width = canvas.offsetWidth * ratio;
|
||||
canvas.height = canvas.offsetHeight * ratio;
|
||||
canvas.getContext("2d").scale(ratio, ratio);
|
||||
}
|
||||
window.onresize = resizeCanvas;
|
||||
resizeCanvas();
|
||||
}
|
||||
|
||||
window.onresize = resizeCanvas;
|
||||
resizeCanvas();
|
||||
|
||||
signaturePad = new SignaturePad(canvas);
|
||||
|
||||
$('#clear_button').on("click", function (event) {
|
||||
|
|
|
@ -414,19 +414,24 @@
|
|||
|
||||
@if ($field->isFieldDecryptable($asset->{$field->db_column_name()} ))
|
||||
@can('assets.view.encrypted_custom_fields')
|
||||
<span id="text-{{ $field->id }}-to-hide">********</span>
|
||||
<span class="js-copy-{{ $field->id }}" id="text-{{ $field->id }}-to-show" style="font-size: 0px;">
|
||||
@if (($field->format=='URL') && ($asset->{$field->db_column_name()}!=''))
|
||||
<a href="{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}" target="_new">{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}</a>
|
||||
@elseif (($field->format=='DATE') && ($asset->{$field->db_column_name()}!=''))
|
||||
{{ \App\Helpers\Helper::gracefulDecrypt($field, \App\Helpers\Helper::getFormattedDateObject($asset->{$field->db_column_name()}, 'date', false)) }}
|
||||
@else
|
||||
{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}
|
||||
@endif
|
||||
</span>
|
||||
<i class="fa-regular fa-clipboard js-copy-link" data-clipboard-target=".js-copy-{{ $field->id }}" aria-hidden="true" data-tooltip="true" data-placement="top" title="{{ trans('general.copy_to_clipboard') }}">
|
||||
<span class="sr-only">{{ trans('general.copy_to_clipboard') }}</span>
|
||||
</i>
|
||||
@php
|
||||
$fieldSize=strlen(Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}))
|
||||
@endphp
|
||||
@if ($fieldSize>0)
|
||||
<span id="text-{{ $field->id }}-to-hide">{{ str_repeat('*', $fieldSize) }}</span>
|
||||
<span class="js-copy-{{ $field->id }}" id="text-{{ $field->id }}-to-show" style="font-size: 0px;">
|
||||
@if (($field->format=='URL') && ($asset->{$field->db_column_name()}!=''))
|
||||
<a href="{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}" target="_new">{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}</a>
|
||||
@elseif (($field->format=='DATE') && ($asset->{$field->db_column_name()}!=''))
|
||||
{{ \App\Helpers\Helper::gracefulDecrypt($field, \App\Helpers\Helper::getFormattedDateObject($asset->{$field->db_column_name()}, 'date', false)) }}
|
||||
@else
|
||||
{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}
|
||||
@endif
|
||||
</span>
|
||||
<i class="fa-regular fa-clipboard js-copy-link" data-clipboard-target=".js-copy-{{ $field->id }}" aria-hidden="true" data-tooltip="true" data-placement="top" title="{{ trans('general.copy_to_clipboard') }}">
|
||||
<span class="sr-only">{{ trans('general.copy_to_clipboard') }}</span>
|
||||
</i>
|
||||
@endif
|
||||
@else
|
||||
{{ strtoupper(trans('admin/custom_fields/general.encrypted')) }}
|
||||
@endcan
|
||||
|
|
|
@ -374,7 +374,7 @@
|
|||
</a>
|
||||
</li>
|
||||
@endcan
|
||||
<li class="divider"></li>
|
||||
<li class="divider" style="margin-top: -1px; margin-bottom: -1px"></li>
|
||||
<li>
|
||||
|
||||
<a href="{{ route('logout.get') }}"
|
||||
|
@ -1041,6 +1041,64 @@
|
|||
});
|
||||
});
|
||||
|
||||
// Select encrypted custom fields to hide them in the asset list
|
||||
$(document).ready(function() {
|
||||
// Selector for elements with css-padlock class
|
||||
var selector = 'td.css-padlock';
|
||||
|
||||
// Function to add original value to elements
|
||||
function addValue($element) {
|
||||
// Get original value of the element
|
||||
var originalValue = $element.text().trim();
|
||||
|
||||
// Show asterisks only for not empty values
|
||||
if (originalValue !== '') {
|
||||
// This is necessary to avoid loop because value is generated dynamically
|
||||
if (originalValue !== '' && originalValue !== asterisks) $element.attr('value', originalValue);
|
||||
|
||||
// Hide the original value and show asterisks of the same length
|
||||
var asterisks = '*'.repeat(originalValue.length);
|
||||
$element.text(asterisks);
|
||||
|
||||
// Add click event to show original text
|
||||
$element.click(function() {
|
||||
var $this = $(this);
|
||||
if ($this.text().trim() === asterisks) {
|
||||
$this.text($this.attr('value'));
|
||||
} else {
|
||||
$this.text(asterisks);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Add value to existing elements
|
||||
$(selector).each(function() {
|
||||
addValue($(this));
|
||||
});
|
||||
|
||||
// Function to handle mutations in the DOM because content is generated dynamically
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
// Check if new nodes have been inserted
|
||||
if (mutation.type === 'childList') {
|
||||
mutation.addedNodes.forEach(function(node) {
|
||||
if ($(node).is(selector)) {
|
||||
addValue($(node));
|
||||
} else {
|
||||
$(node).find(selector).each(function() {
|
||||
addValue($(this));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Configure the observer to observe changes in the DOM
|
||||
var config = { childList: true, subtree: true };
|
||||
observer.observe(document.body, config);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-4 col-xs-12"><label for="modal-city">{{ trans('general.city') }}:</label></div>
|
||||
<div class="col-md-8 col-xs-12 required"><input type='text' name="city" id='modal-city' class="form-control"></div>
|
||||
<div class="col-md-8 col-xs-12"><input type='text' name="city" id='modal-city' class="form-control"></div>
|
||||
</div>
|
||||
|
||||
<div class="dynamic-form-row">
|
||||
|
|
|
@ -306,6 +306,7 @@
|
|||
<label style="grid-area: source-title">DataSource</label>
|
||||
<select style="grid-area: source-field" x-model="option.datasource">
|
||||
<optgroup label="Asset">
|
||||
<option value="" disabled>{{ trans('general.select_datasource') }}</option>
|
||||
<option value="asset_tag">{{trans('admin/hardware/table.asset_tag')}}</option>
|
||||
<option value="name">{{trans('admin/hardware/form.name')}}</option>
|
||||
<option value="serial">{{trans('admin/hardware/table.serial')}}</option>
|
||||
|
@ -313,6 +314,8 @@
|
|||
<option value="order_number">{{trans('admin/hardware/form.order')}}</option>
|
||||
<option value="purchase_date">{{trans('admin/hardware/table.purchase_date')}}</option>
|
||||
<option value="assignedTo">{{trans('admin/hardware/table.assigned_to')}}</option>
|
||||
<option value="last_audit_date">{{trans('general.last_audit')}}</option>
|
||||
<option value="next_audit_date">{{trans('general.next_audit_date')}}</option>
|
||||
</optgroup>
|
||||
<optgroup label="Asset Model">
|
||||
<option value="model.name">{{trans('admin/models/table.name')}}</option>
|
||||
|
@ -348,6 +351,7 @@
|
|||
</optgroup>
|
||||
<optgroup label="Custom Fields">
|
||||
@foreach($customFields as $customField)
|
||||
|
||||
<option value="{{ $customField->db_column }}">{{ $customField->name }}</option>
|
||||
@endforeach
|
||||
</optgroup>
|
||||
|
|
|
@ -5,10 +5,12 @@ namespace Tests\Feature\Api\Assets;
|
|||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Company;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Location;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Testing\Fluent\AssertableJson;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
@ -479,4 +481,55 @@ class AssetStoreTest extends TestCase
|
|||
$json->has('messages.company_id')->etc();
|
||||
});
|
||||
}
|
||||
|
||||
public function testEncryptedCustomFieldCanBeStored()
|
||||
{
|
||||
$this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL');
|
||||
|
||||
$status = Statuslabel::factory()->create();
|
||||
$field = CustomField::factory()->testEncrypted()->create();
|
||||
$superuser = User::factory()->superuser()->create();
|
||||
$assetData = Asset::factory()->hasEncryptedCustomField($field)->make();
|
||||
|
||||
$response = $this->actingAsForApi($superuser)
|
||||
->postJson(route('api.assets.store'), [
|
||||
$field->db_column_name() => 'This is encrypted field',
|
||||
'model_id' => $assetData->model->id,
|
||||
'status_id' => $status->id,
|
||||
'asset_tag' => '1234',
|
||||
])
|
||||
->assertStatusMessageIs('success')
|
||||
->assertOk()
|
||||
->json();
|
||||
|
||||
$asset = Asset::findOrFail($response['payload']['id']);
|
||||
$this->assertEquals('This is encrypted field', Crypt::decrypt($asset->{$field->db_column_name()}));
|
||||
}
|
||||
|
||||
public function testPermissionNeededToStoreEncryptedField()
|
||||
{
|
||||
// @todo:
|
||||
$this->markTestIncomplete();
|
||||
|
||||
$status = Statuslabel::factory()->create();
|
||||
$field = CustomField::factory()->testEncrypted()->create();
|
||||
$normal_user = User::factory()->editAssets()->create();
|
||||
$assetData = Asset::factory()->hasEncryptedCustomField($field)->make();
|
||||
|
||||
$response = $this->actingAsForApi($normal_user)
|
||||
->postJson(route('api.assets.store'), [
|
||||
$field->db_column_name() => 'Some Other Value Entirely!',
|
||||
'model_id' => $assetData->model->id,
|
||||
'status_id' => $status->id,
|
||||
'asset_tag' => '1234',
|
||||
])
|
||||
// @todo: this is 403 unauthorized
|
||||
->assertStatusMessageIs('success')
|
||||
->assertOk()
|
||||
->assertMessagesAre('Asset updated successfully, but encrypted custom fields were not due to permissions')
|
||||
->json();
|
||||
|
||||
$asset = Asset::findOrFail($response['payload']['id']);
|
||||
$this->assertEquals('This is encrypted field', Crypt::decrypt($asset->{$field->db_column_name()}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,12 @@ use App\Models\Location;
|
|||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AssetUpdateTest extends TestCase
|
||||
{
|
||||
|
||||
public function testThatANonExistentAssetIdReturnsError()
|
||||
{
|
||||
$this->actingAsForApi(User::factory()->editAssets()->createAssets()->create())
|
||||
|
@ -262,4 +263,47 @@ class AssetUpdateTest extends TestCase
|
|||
$this->assertTrue($asset->defaultLoc->is($rtdLocation));
|
||||
$this->assertTrue($asset->location->is($location));
|
||||
}
|
||||
}
|
||||
|
||||
public function testEncryptedCustomFieldCanBeUpdated()
|
||||
{
|
||||
$this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL');
|
||||
|
||||
$field = CustomField::factory()->testEncrypted()->create();
|
||||
$asset = Asset::factory()->hasEncryptedCustomField($field)->create();
|
||||
$superuser = User::factory()->superuser()->create();
|
||||
|
||||
$this->actingAsForApi($superuser)
|
||||
->patchJson(route('api.assets.update', $asset->id), [
|
||||
$field->db_column_name() => 'This is encrypted field'
|
||||
])
|
||||
->assertStatusMessageIs('success')
|
||||
->assertOk();
|
||||
|
||||
$asset->refresh();
|
||||
$this->assertEquals('This is encrypted field', Crypt::decrypt($asset->{$field->db_column_name()}));
|
||||
}
|
||||
|
||||
public function testPermissionNeededToUpdateEncryptedField()
|
||||
{
|
||||
$this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL');
|
||||
|
||||
$field = CustomField::factory()->testEncrypted()->create();
|
||||
$asset = Asset::factory()->hasEncryptedCustomField($field)->create();
|
||||
$normal_user = User::factory()->editAssets()->create();
|
||||
|
||||
$asset->{$field->db_column_name()} = Crypt::encrypt("encrypted value should not change");
|
||||
$asset->save();
|
||||
|
||||
// test that a 'normal' user *cannot* change the encrypted custom field
|
||||
$this->actingAsForApi($normal_user)
|
||||
->patchJson(route('api.assets.update', $asset->id), [
|
||||
$field->db_column_name() => 'Some Other Value Entirely!'
|
||||
])
|
||||
->assertStatusMessageIs('success')
|
||||
->assertOk()
|
||||
->assertMessagesAre('Asset updated successfully, but encrypted custom fields were not due to permissions');
|
||||
|
||||
$asset->refresh();
|
||||
$this->assertEquals("encrypted value should not change", Crypt::decrypt($asset->{$field->db_column_name()}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Tests\Feature\Api\Users;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Group;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
@ -58,4 +60,119 @@ class UpdateUserApiTest extends TestCase
|
|||
|
||||
$this->assertEquals(0, $user->refresh()->activated);
|
||||
}
|
||||
|
||||
public function testUsersScopedToCompanyDuringUpdateWhenMultipleFullCompanySupportEnabled()
|
||||
{
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
$companyA = Company::factory()->create(['name'=>'Company A']);
|
||||
$companyB = Company::factory()->create(['name'=>'Company B']);
|
||||
|
||||
$adminA = User::factory(['company_id' => $companyA->id])->admin()->create();
|
||||
$adminB = User::factory(['company_id' => $companyB->id])->admin()->create();
|
||||
$adminNoCompany = User::factory(['company_id' => null])->admin()->create();
|
||||
|
||||
// Create users that belongs to company A and B and one that is unscoped
|
||||
$scoped_user_in_companyA = User::factory()->create(['company_id' => $companyA->id]);
|
||||
$scoped_user_in_companyB = User::factory()->create(['company_id' => $companyB->id]);
|
||||
$scoped_user_in_no_company = User::factory()->create(['company_id' => null]);
|
||||
|
||||
// Admin for Company A should allow updating user from Company A
|
||||
$this->actingAsForApi($adminA)
|
||||
->patchJson(route('api.users.update', $scoped_user_in_companyA))
|
||||
->assertStatus(200);
|
||||
|
||||
// Admin for Company A should get denied updating user from Company B
|
||||
$this->actingAsForApi($adminA)
|
||||
->patchJson(route('api.users.update', $scoped_user_in_companyB))
|
||||
->assertStatus(403);
|
||||
|
||||
// Admin for Company A should get denied updating user without a company
|
||||
$this->actingAsForApi($adminA)
|
||||
->patchJson(route('api.users.update', $scoped_user_in_no_company))
|
||||
->assertStatus(403);
|
||||
|
||||
// Admin for Company B should allow updating user from Company B
|
||||
$this->actingAsForApi($adminB)
|
||||
->patchJson(route('api.users.update', $scoped_user_in_companyB))
|
||||
->assertStatus(200);
|
||||
|
||||
// Admin for Company B should get denied updating user from Company A
|
||||
$this->actingAsForApi($adminB)
|
||||
->patchJson(route('api.users.update', $scoped_user_in_companyA))
|
||||
->assertStatus(403);
|
||||
|
||||
// Admin for Company B should get denied updating user without a company
|
||||
$this->actingAsForApi($adminB)
|
||||
->patchJson(route('api.users.update', $scoped_user_in_no_company))
|
||||
->assertStatus(403);
|
||||
|
||||
// Admin without a company should allow updating user without a company
|
||||
$this->actingAsForApi($adminNoCompany)
|
||||
->patchJson(route('api.users.update', $scoped_user_in_no_company))
|
||||
->assertStatus(200);
|
||||
|
||||
// Admin without a company should get denied updating user from Company A
|
||||
$this->actingAsForApi($adminNoCompany)
|
||||
->patchJson(route('api.users.update', $scoped_user_in_companyA))
|
||||
->assertStatus(403);
|
||||
|
||||
// Admin without a company should get denied updating user from Company B
|
||||
$this->actingAsForApi($adminNoCompany)
|
||||
->patchJson(route('api.users.update', $scoped_user_in_companyB))
|
||||
->assertStatus(403);
|
||||
}
|
||||
|
||||
public function testUserGroupsAreOnlyUpdatedIfAuthenticatedUserIsSuperUser()
|
||||
{
|
||||
$groupToJoin = Group::factory()->create();
|
||||
|
||||
$normalUser = User::factory()->editUsers()->create();
|
||||
$superUser = User::factory()->superuser()->create();
|
||||
|
||||
$oneUserToUpdate = User::factory()->create();
|
||||
$anotherUserToUpdate = User::factory()->create();
|
||||
|
||||
$this->actingAsForApi($normalUser)
|
||||
->patchJson(route('api.users.update', $oneUserToUpdate), [
|
||||
'groups' => [$groupToJoin->id],
|
||||
]);
|
||||
|
||||
$this->actingAsForApi($superUser)
|
||||
->patchJson(route('api.users.update', $anotherUserToUpdate), [
|
||||
'groups' => [$groupToJoin->id],
|
||||
]);
|
||||
|
||||
$this->assertFalse(
|
||||
$oneUserToUpdate->refresh()->groups->contains($groupToJoin),
|
||||
'Non-super-user was able to modify user group'
|
||||
);
|
||||
$this->assertTrue($anotherUserToUpdate->refresh()->groups->contains($groupToJoin));
|
||||
}
|
||||
|
||||
public function testUserGroupsCanBeClearedBySuperUser()
|
||||
{
|
||||
$normalUser = User::factory()->editUsers()->create();
|
||||
$superUser = User::factory()->superuser()->create();
|
||||
|
||||
$oneUserToUpdate = User::factory()->create();
|
||||
$anotherUserToUpdate = User::factory()->create();
|
||||
|
||||
$joinedGroup = Group::factory()->create();
|
||||
$oneUserToUpdate->groups()->sync([$joinedGroup->id]);
|
||||
$anotherUserToUpdate->groups()->sync([$joinedGroup->id]);
|
||||
|
||||
$this->actingAsForApi($normalUser)
|
||||
->patchJson(route('api.users.update', $oneUserToUpdate), [
|
||||
'groups' => null,
|
||||
]);
|
||||
|
||||
$this->actingAsForApi($superUser)
|
||||
->patchJson(route('api.users.update', $anotherUserToUpdate), [
|
||||
'groups' => null,
|
||||
]);
|
||||
|
||||
$this->assertTrue($oneUserToUpdate->refresh()->groups->contains($joinedGroup));
|
||||
$this->assertFalse($anotherUserToUpdate->refresh()->groups->contains($joinedGroup));
|
||||
}
|
||||
}
|
||||
|
|
13
tests/Support/CanSkipTests.php
Normal file
13
tests/Support/CanSkipTests.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Support;
|
||||
|
||||
trait CanSkipTests
|
||||
{
|
||||
public function markIncompleteIfMySQL($message = 'Test skipped due to database driver being MySQL.')
|
||||
{
|
||||
if (config('database.default') === 'mysql') {
|
||||
$this->markTestIncomplete($message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -87,5 +87,18 @@ trait CustomTestMacros
|
|||
return $this;
|
||||
}
|
||||
);
|
||||
|
||||
TestResponse::macro(
|
||||
'assertMessagesAre',
|
||||
function (string $message) {
|
||||
Assert::assertEquals(
|
||||
$message,
|
||||
$this['messages'],
|
||||
"Response messages was not {$message}"
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
|||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
use RuntimeException;
|
||||
use Tests\Support\AssertsAgainstSlackNotifications;
|
||||
use Tests\Support\CanSkipTests;
|
||||
use Tests\Support\CustomTestMacros;
|
||||
use Tests\Support\InteractsWithAuthentication;
|
||||
use Tests\Support\InitializesSettings;
|
||||
|
@ -14,6 +15,7 @@ use Tests\Support\InitializesSettings;
|
|||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
use AssertsAgainstSlackNotifications;
|
||||
use CanSkipTests;
|
||||
use CreatesApplication;
|
||||
use CustomTestMacros;
|
||||
use InteractsWithAuthentication;
|
||||
|
|
26
tests/Unit/Models/Labels/FieldOptionTest.php
Normal file
26
tests/Unit/Models/Labels/FieldOptionTest.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Models\Labels;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\Labels\FieldOption;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
class FieldOptionTest extends TestCase
|
||||
{
|
||||
public function testItDisplaysAssignedToProperly()
|
||||
{
|
||||
// "assignedTo" is a "special" value that can be used in the new label engine
|
||||
$fieldOption = FieldOption::fromString('Assigned To=assignedTo');
|
||||
|
||||
$asset = Asset::factory()
|
||||
->assignedToUser(User::factory()->create(['first_name' => 'Luke', 'last_name' => 'Skywalker']))
|
||||
->create();
|
||||
|
||||
$this->assertEquals('Luke Skywalker', $fieldOption->getValue($asset));
|
||||
// If the "assignedTo" relationship was eager loaded then the way to get the
|
||||
// relationship changes from $asset->assignedTo to $asset->assigned.
|
||||
$this->assertEquals('Luke Skywalker', $fieldOption->getValue(Asset::with('assignedTo')->find($asset->id)));
|
||||
}
|
||||
}
|
12
upgrade.php
12
upgrade.php
|
@ -19,6 +19,7 @@ $app_environment = 'develop';
|
|||
$skip_php_checks = false;
|
||||
$branch = 'master';
|
||||
$branch_override = false;
|
||||
$no_interactive = false;
|
||||
|
||||
// Check for branch or other overrides
|
||||
if ($argc > 1){
|
||||
|
@ -32,6 +33,9 @@ if ($argc > 1){
|
|||
$branch = $argv[$arg];
|
||||
$branch_override = true;
|
||||
break;
|
||||
case '--no-interactive':
|
||||
$no_interactive = true;
|
||||
break;
|
||||
default: // for legacy support from before we started using --branch
|
||||
$branch = $argv[$arg];
|
||||
$branch_override = true;
|
||||
|
@ -81,7 +85,13 @@ if($upgrade_requirements){
|
|||
echo "Found PHP requirements, will check for PHP > $php_min_works and < $php_max_wontwork\n";
|
||||
}
|
||||
// done fetching requirements
|
||||
$yesno = readline("\nProceed with upgrade? [Y/n]: ");
|
||||
|
||||
if (!$no_interactive) {
|
||||
$yesno = readline("\nProceed with upgrade? [Y/n]: ");
|
||||
} else {
|
||||
$yesno = "yes";
|
||||
}
|
||||
|
||||
if ($yesno == "yes" || $yesno == "YES" ||$yesno == "y" ||$yesno == "Y"){
|
||||
# don't do anything
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue