Merge branch 'hotfixes/2fa_qr' into develop

# Conflicts:
#	.all-contributorsrc
#	Dockerfile
#	README.md
#	app/Console/Commands/LdapSync.php
#	app/Http/Controllers/Api/ImportController.php
#	app/Http/Controllers/AssetModelsController.php
#	app/Http/Controllers/Assets/AssetsController.php
#	app/Http/Controllers/Auth/LoginController.php
#	app/Http/Controllers/CategoriesController.php
#	app/Http/Controllers/CompaniesController.php
#	app/Http/Controllers/DepartmentsController.php
#	app/Http/Controllers/ImportsController.php
#	app/Http/Controllers/LocationsController.php
#	app/Http/Controllers/ManufacturersController.php
#	app/Http/Controllers/SuppliersController.php
#	app/Http/Requests/ItemImportRequest.php
#	app/Http/Transformers/ActionlogsTransformer.php
#	composer.json
#	composer.lock
#	config/app.php
#	config/version.php
#	docker/startup.sh
#	public/css/build/all.css
#	public/css/dist/all.css
#	public/js/build/all.js
#	public/js/build/vue.js
#	public/js/build/vue.js.map
#	public/js/dist/all.js
#	public/mix-manifest.json
This commit is contained in:
snipe 2019-03-20 02:17:02 -07:00
commit bca82684a1
34 changed files with 1074 additions and 72 deletions

View file

@ -26,6 +26,7 @@ vim \
git \
cron \
mysql-client \
supervisor \
cron \
gcc \
make \
@ -92,7 +93,9 @@ RUN \
&& rm -r "/var/www/html/storage/app/backups" && ln -fs "/var/lib/snipeit/dumps" "/var/www/html/storage/app/backups" \
&& mkdir -p "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \
&& ln -fs "/var/lib/snipeit/keys/oauth-public.key" "/var/www/html/storage/oauth-public.key" \
&& chown docker "/var/lib/snipeit/keys/"
&& chown docker "/var/lib/snipeit/keys/" \
&& chmod +x /var/www/html/artisan \
&& echo "Finished setting up application in /var/www/html"
############## DEPENDENCIES via COMPOSER ###################
@ -119,16 +122,11 @@ VOLUME ["/var/lib/snipeit"]
##### START SERVER
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
COPY docker/startup.sh docker/supervisord.conf /
COPY docker/supervisor-exit-event-listener /usr/bin/supervisor-exit-event-listener
RUN chmod +x /startup.sh /usr/bin/supervisor-exit-event-listener
# Add Tini
ENV TINI_VERSION v0.14.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]
CMD ["/entrypoint.sh"]
CMD ["/startup.sh"]
EXPOSE 80
EXPOSE 443

View file

@ -1,5 +1,5 @@
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=master)](https://travis-ci.org/snipe/snipe-it) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-201-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
[![All Contributors](https://img.shields.io/badge/all_contributors-180-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
## Snipe-IT - Open Source Asset Management System

View file

@ -179,7 +179,7 @@ class AssetModelsController extends Controller
try {
Storage::disk('public')->delete('assetmodels/'.$assetmodel->image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}

View file

@ -25,7 +25,7 @@ class ImportController extends Controller
*/
public function index()
{
//
$this->authorize('import');
$imports = Import::latest()->get();
return (new ImportsTransformer)->transformImports($imports);
@ -39,10 +39,8 @@ class ImportController extends Controller
*/
public function store()
{
//
if (!Company::isCurrentUserAuthorized()) {
return redirect()->route('hardware.index')->with('error', trans('general.insufficient_permissions'));
} elseif (!config('app.lock_passwords')) {
$this->authorize('import');
if (!config('app.lock_passwords')) {
$files = Input::file('files');
$path = config('app.private_uploads').'/imports';
$results = [];
@ -119,7 +117,7 @@ class ImportController extends Controller
*/
public function process(ItemImportRequest $request, $import_id)
{
$this->authorize('create', Asset::class);
$this->authorize('import');
// Run a backup immediately before processing
Artisan::call('backup:run');
$errors = $request->import(Import::find($import_id));

View file

@ -54,6 +54,7 @@ class UsersController extends Controller
'users.phone',
'users.state',
'users.two_factor_enrolled',
'users.two_factor_optin',
'users.updated_at',
'users.username',
'users.zip',

View file

@ -195,7 +195,7 @@ class AssetModelsController extends Controller
try {
Storage::disk('public')->delete('models/'.$model->image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}

View file

@ -308,7 +308,7 @@ class AssetsController extends Controller
unlink(public_path().'/uploads/assets/'.$asset->image);
$asset->image = '';
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}

View file

@ -15,6 +15,11 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Redirect;
use Log;
use View;
use Otp\Otp;
use Otp\GoogleAuthenticator;
use ParagonIE\ConstantTime\Encoding;
/**
* This controller handles authentication for the user, including local
@ -198,22 +203,24 @@ class LoginController extends Controller
return redirect()->route('login')->with('error', 'You must be logged in.');
}
$user = Auth::user();
$google2fa = app()->make('pragmarx.google2fa');
if ($user->two_factor_secret=='') {
$user->two_factor_secret = $google2fa->generateSecretKey(32);
$user->save();
$settings = Setting::getSettings();
$user = Auth::user();
if (($user->two_factor_secret!='') && ($user->two_factor_enrolled==1)) {
return redirect()->route('two-factor')->with('error', 'Your device is already enrolled.');
}
$google2fa_url = $google2fa->getQRCodeInline(
urlencode(Setting::getSettings()->site_name),
urlencode($user->username),
$user->two_factor_secret
);
return view('auth.two_factor_enroll')->with('google2fa_url', $google2fa_url);
new Otp();
$secret = GoogleAuthenticator::generateRandom();
$user->two_factor_secret = $secret;
$user->save();
$barcode = new \Com\Tecnick\Barcode\Barcode();
$barcode_obj = $barcode->getBarcodeObj('QRCODE', 'otpauth://totp/'.urlencode($settings->site_name).':'.urlencode($user->username).'?secret='.urlencode($secret).'&issuer=Snipe-IT&period=30', 300, 300, 'black', array(-2, -2, -2, -2));
return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj);
}
@ -242,18 +249,23 @@ class LoginController extends Controller
return redirect()->route('login')->with('error', 'You must be logged in.');
}
$user = Auth::user();
$secret = $request->get('two_factor_secret');
$google2fa = app()->make('pragmarx.google2fa');
$valid = $google2fa->verifyKey($user->two_factor_secret, $secret);
if (!$request->has('two_factor_secret')) {
return redirect()->route('two-factor')->with('error', 'Two-factor code is required.');
}
if ($valid) {
$user = Auth::user();
$otp = new Otp();
if ($otp->checkTotp(Encoding::base32DecodeUpper($user->two_factor_secret), $request->get('two_factor_secret'))) {
$user->two_factor_enrolled = 1;
$user->save();
$request->session()->put('2fa_authed', 'true');
return redirect()->route('home')->with('success', 'You are logged in!');
}
\Log::debug('Did not match');
return redirect()->route('two-factor')->with('error', 'Invalid two-factor code');

View file

@ -14,7 +14,7 @@ class ImportsController extends Controller
*/
public function index()
{
$this->authorize('create', Asset::class);
$this->authorize('import');
$imports = (new ImportsTransformer)->transformImports(Import::latest()->get());
return view('importer/import')->with('imports', $imports);
}

View file

@ -164,7 +164,7 @@ class ManufacturersController extends Controller
try {
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}

View file

@ -52,6 +52,8 @@ class UsersTransformer
'permissions' => $user->decodePermissions(),
'activated' => ($user->activated =='1') ? true : false,
'two_factor_activated' => ($user->two_factor_active()) ? true : false,
'two_factor_enrolled' => ($user->two_factor_active_and_enrolled()) ? true : false,
'assets_count' => (int) $user->assets_count,
'licenses_count' => (int) $user->licenses_count,
'accessories_count' => (int) $user->accessories_count,

View file

@ -525,7 +525,11 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
}
/**
* Check whether two-factor authorization is required and the user has activated it
* Check whether two-factor authorization is requiredfor this user
*
* 0 = 2FA disabled
* 1 = 2FA optional
* 2 = 2FA universally required
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
@ -534,10 +538,45 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/
public function two_factor_active () {
if (Setting::getSettings()->two_factor_enabled !='0') {
if (($this->two_factor_optin =='1') && ($this->two_factor_enrolled)) {
// If the 2FA is optional and the user has opted in
if ((Setting::getSettings()->two_factor_enabled =='1') && ($this->two_factor_optin =='1'))
{
return true;
}
// If the 2FA is required for everyone so is implicitly active
elseif (Setting::getSettings()->two_factor_enabled =='2')
{
return true;
}
return false;
}
/**
* Check whether two-factor authorization is required and the user has activated it
* and enrolled a device
*
* 0 = 2FA disabled
* 1 = 2FA optional
* 2 = 2FA universally required
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.6.14]
*
* @return bool
*/
public function two_factor_active_and_enrolled () {
// If the 2FA is optional and the user has opted in and is enrolled
if ((Setting::getSettings()->two_factor_enabled =='1') && ($this->two_factor_optin =='1') && ($this->two_factor_enrolled =='1'))
{
return true;
}
// If the 2FA is required for everyone and the user has enrolled
elseif ((Setting::getSettings()->two_factor_enabled =='2') && ($this->two_factor_enrolled))
{
return true;
}
return false;

View file

@ -53,7 +53,7 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can view the accessory.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function view(User $user, $item = null)
@ -64,7 +64,7 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can create accessories.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function create(User $user)
@ -75,7 +75,7 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can update the accessory.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function update(User $user, $item = null)
@ -86,7 +86,7 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can delete the accessory.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function delete(User $user, $item = null)
@ -97,11 +97,13 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can manage the accessory.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function manage(User $user, $item = null)
{
return $user->hasAccess($this->columnName().'.edit');
}
}

View file

@ -223,14 +223,14 @@ class UserPresenter extends Presenter
[
"field" => "two_factor_enrolled",
"searchable" => false,
"sortable" => false,
"sortable" => true,
"switchable" => true,
"title" => trans('admin/users/general.two_factor_enrolled'),
"visible" => false,
'formatter' => 'trueFalseFormatter'
],
[
"field" => "two_factor_active",
"field" => "two_factor_activated",
"searchable" => false,
"sortable" => false,
"switchable" => true,
@ -243,7 +243,7 @@ class UserPresenter extends Presenter
"searchable" => false,
"sortable" => true,
"switchable" => true,
"title" => trans('general.activated'),
"title" => trans('general.login_enabled'),
"visible" => true,
'formatter' => 'trueFalseFormatter'
],

View file

@ -113,6 +113,14 @@ class AuthServiceProvider extends ServiceProvider
});
// Can the user import CSVs?
Gate::define('import', function ($user) {
if ($user->hasAccess('import') ) {
return true;
}
});
# -----------------------------------------
# Reports
# -----------------------------------------

View file

@ -34,6 +34,7 @@
"league/flysystem-sftp": "~1.0",
"maknz/slack": "^1.7",
"neitanod/forceutf8": "^2.0",
"paragonie/constant_time_encoding": "^1.0",
"patchwork/utf8": "~1.2",
"phpdocumentor/reflection-docblock": "3.2.2",
"phpspec/prophecy": "1.7.5",

View file

@ -27,6 +27,15 @@ return array(
)
),
'CSV Import' => array(
array(
'permission' => 'import',
'label' => '',
'note' => 'This will allow users to import even if access to users, assets, etc is denied elsewhere.',
'display' => true,
)
),
'Reports' => array(
array(
'permission' => 'reports.view',

View file

@ -5,7 +5,7 @@ if [ -z "$APP_KEY" ]
then
echo "Please re-run this container with an environment variable \$APP_KEY"
echo "An example APP_KEY you could use is: "
php artisan key:generate --show
/var/www/html/artisan key:generate --show
exit
fi
@ -47,6 +47,8 @@ then
cp -ax /var/www/html/vendor/laravel/passport/database/migrations/* /var/www/html/database/migrations/
fi
exec supervisord -c /supervisord.conf
php artisan migrate --force
php artisan config:clear
php artisan config:cache

View file

@ -0,0 +1,19 @@
#!/usr/bin/env python
# A supervisor event listener which terminates supervisord if any of its child
# processes enter the FATAL state.
# https://stackoverflow.com/a/37527488/119527
import os
import signal
from supervisor import childutils
def main():
while True:
headers, payload = childutils.listener.wait()
childutils.listener.ok()
if headers['eventname'] != 'PROCESS_STATE_FATAL':
continue
os.kill(os.getppid(), signal.SIGTERM)
if __name__ == "__main__":
main()

27
docker/supervisord.conf Normal file
View file

@ -0,0 +1,27 @@
[supervisord]
nodaemon=true
[program:apache]
; https://advancedweb.hu/2018/07/03/supervisor_docker/
command=apache2ctl -DFOREGROUND
killasgroup=true
stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:run_schedule]
; Simply run the Laravel command scheduler every minute
command=/bin/bash -c "while true; do /var/www/html/artisan schedule:run; sleep 1m; done"
user=docker
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
; https://stackoverflow.com/a/37527488/119527
[eventlistener:exit_on_any_fatal]
command=supervisor-exit-event-listener
events=PROCESS_STATE_FATAL

617
public/css/build/all.css Normal file

File diff suppressed because one or more lines are too long

22
public/css/dist/all.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

29
public/js/dist/all.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,5 @@
{
<<<<<<< HEAD
"/js/app.js": "/js/app.js?id=be43e3109667a86c9a02",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=f7a5d783fef321018f4c",
"/css/build/app.css": "/css/build/app.css?id=0dfc05b0fe1dcc9b6e3d",
@ -14,4 +15,18 @@
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=bc5e33610f678021cc48",
"/js/dist/bootstrap-table-simple-view.js": "/js/dist/bootstrap-table-simple-view.js?id=3926b8f4aaad6ca20d31",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=6b4ccfd094c065f065ae"
=======
"/js/build/vue.js": "/js/build/vue.js?id=96f90510b797ac27a94b",
"/css/AdminLTE.css": "/css/AdminLTE.css?id=5e72463a66acbcc740d5",
"/css/app.css": "/css/app.css?id=407edb63cc6b6dc62405",
"/css/overrides.css": "/css/overrides.css?id=2d81c3704393bac77011",
"/js/build/vue.js.map": "/js/build/vue.js.map?id=423f16f63b86abd6b196",
"/css/AdminLTE.css.map": "/css/AdminLTE.css.map?id=0be7790b84909dca6a0a",
"/css/app.css.map": "/css/app.css.map?id=96b5c985e860716e6a16",
"/css/overrides.css.map": "/css/overrides.css.map?id=f7ce9ca49027594ac402",
"/css/dist/all.css": "/css/dist/all.css?id=98db4e9b7650453c8b00",
"/js/dist/all.js": "/js/dist/all.js?id=114f1025a1b3e8975476",
"/css/build/all.css": "/css/build/all.css?id=98db4e9b7650453c8b00",
"/js/build/all.js": "/js/build/all.js?id=114f1025a1b3e8975476"
>>>>>>> hotfixes/2fa_qr
}

View file

@ -40,9 +40,8 @@
</div>
</div>
</div>
<div class="alert col-md-12"
<div class="alert col-md-12" style="text-align:left"
:class="alertClass"
style="text-align:left"
v-if="statusText">
{{ this.statusText }}
</div>

View file

@ -346,7 +346,18 @@ $(document).ready(function () {
}
function formatDataSelection (datalist) {
return datalist.text;
// This a heinous workaround for a known bug in Select2.
// Without this, the rich selectlists are vulnerable to XSS.
// Many thanks to @uberbrady for this fix. It ain't pretty,
// but it resolves the issue until Select2 addresses it on their end.
//
// Bug was reported in 2016 :{
// https://github.com/select2/select2/issues/4587
return datalist.text.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
// This handles the radio button selectors for the checkout-to-foo options

View file

@ -226,4 +226,5 @@
'zip' => 'Zip',
'noimage' => 'No image uploaded or image not found.',
'token_expired' => 'Your form session has expired. Please try again.',
'login_enabled' => 'Login Enabled',
];

View file

@ -28,7 +28,7 @@
</div>
<div class="col-md-12 text-center">
<img src="{{ $google2fa_url }}" style="padding: 15px 0px 15px 0px">
{!! $barcode_obj->getHtmlDiv() !!}
</div>
</div>

View file

@ -509,7 +509,7 @@
</a>
</li>
@endcan
@can('create', \App\Models\Asset::class)
@can('import')
<li{!! (Request::is('import/*') ? ' class="active"' : '') !!}>
<a href="{{ route('imports.index') }}">
<i class="fa fa-cloud-download"></i>

View file

@ -61,7 +61,7 @@
<!-- Require signature for acceptance -->
<div class="form-group {{ $errors->has('require_accept_signature') ? 'error' : '' }}">
<div class="col-md-3">
{{ Form::label('full_multiple_companies_support',
{{ Form::label('require_accept_signature',
trans('admin/settings/general.require_accept_signature')) }}
</div>
<div class="col-md-9">
@ -166,7 +166,7 @@
<!-- Default EULA -->
<div class="form-group {{ $errors->has('default_eula_text') ? 'error' : '' }}">
<div class="col-md-3">
{{ Form::label('per_page', trans('admin/settings/general.default_eula_text')) }}
{{ Form::label('default_eula_text', trans('admin/settings/general.default_eula_text')) }}
</div>
<div class="col-md-9">
{{ Form::textarea('default_eula_text', Input::old('default_eula_text', $setting->default_eula_text), array('class' => 'form-control','placeholder' => 'Add your default EULA text')) }}

View file

@ -123,22 +123,22 @@
<table class="table table-striped">
@if (!is_null($user->company))
<tr>
<td>{{ trans('general.company') }}</td>
<td class="text-nowrap">{{ trans('general.company') }}</td>
<td>{{ $user->company->name }}</td>
</tr>
@endif
<tr>
<td>{{ trans('admin/users/table.name') }}</td>
<td class="text-nowrap">{{ trans('admin/users/table.name') }}</td>
<td>{{ $user->present()->fullName() }}</td>
</tr>
<tr>
<td>{{ trans('admin/users/table.username') }}</td>
<td class="text-nowrap">{{ trans('admin/users/table.username') }}</td>
<td>{{ $user->username }}</td>
</tr>
<tr>
<td>{{ trans('general.groups') }}</td>
<td class="text-nowrap">{{ trans('general.groups') }}</td>
<td>
@if ($user->groups->count() > 0)
@foreach ($user->groups as $group)
@ -160,21 +160,21 @@
@if ($user->jobtitle)
<tr>
<td>{{ trans('admin/users/table.job') }}</td>
<td class="text-nowrap">{{ trans('admin/users/table.job') }}</td>
<td>{{ $user->jobtitle }}</td>
</tr>
@endif
@if ($user->employee_num)
<tr>
<td>{{ trans('admin/users/table.employee_num') }}</td>
<td class="text-nowrap">{{ trans('admin/users/table.employee_num') }}</td>
<td>{{ $user->employee_num }}</td>
</tr>
@endif
@if ($user->manager)
<tr>
<td>{{ trans('admin/users/table.manager') }}</td>
<td class="text-nowrap">{{ trans('admin/users/table.manager') }}</td>
<td>
<a href="{{ route('users.show', $user->manager->id) }}">{{ $user->manager->getFullNameAttribute() }}</a>
@ -184,21 +184,21 @@
@if ($user->email)
<tr>
<td>{{ trans('admin/users/table.email') }}</td>
<td class="text-nowrap">{{ trans('admin/users/table.email') }}</td>
<td><a href="mailto:{{ $user->email }}">{{ $user->email }}</a></td>
</tr>
@endif
@if ($user->phone)
<tr>
<td>{{ trans('admin/users/table.phone') }}</td>
<td class="text-nowrap">{{ trans('admin/users/table.phone') }}</td>
<td><a href="tel:{{ $user->phone }}">{{ $user->phone }}</a></td>
</tr>
@endif
@if ($user->userloc)
<tr>
<td>{{ trans('admin/users/table.location') }}</td>
<td class="text-nowrap">{{ trans('admin/users/table.location') }}</td>
<td>{{ link_to_route('locations.show', $user->userloc->name, [$user->userloc->id]) }}</td>
@ -206,14 +206,14 @@
@endif
@if ($user->last_login)
<tr>
<td>{{ trans('general.last_login') }}</td>
<td class="text-nowrap">{{ trans('general.last_login') }}</td>
<td>{{ \App\Helpers\Helper::getFormattedDateObject($user->last_login, 'datetime', false) }}</td>
</tr>
@endif
@if (!is_null($user->department))
<tr>
<td>{{ trans('general.department') }}</td>
<td class="text-nowrap">{{ trans('general.department') }}</td>
<td><a href="{{ route('departments.show', $user->department) }}">{{ $user->department->name }}</a></td>
</tr>
@endif
@ -223,6 +223,45 @@
<td>{{ $user->created_at->format('F j, Y h:iA') }}</td>
</tr>
@endif
<tr>
<td class="text-nowrap">{{ trans('general.login_enabled') }}</td>
<td>{{ ($user->activated=='1') ? trans('general.yes') : trans('general.no') }}</td>
</tr>
@if ($user->activated=='1')
<tr>
<td class="text-nowrap">{{ trans('admin/users/general.two_factor_active') }}</td>
<td>{{ ($user->two_factor_active()) ? trans('general.yes') : trans('general.no') }}</td>
</tr>
<tr>
<td class="text-nowrap">{{ trans('admin/users/general.two_factor_enrolled') }}</td>
<td class="two_factor_resetrow">
<div class="row">
<div class="col-md-1" id="two_factor_reset_toggle">
{{ ($user->two_factor_active_and_enrolled()) ? trans('general.yes') : trans('general.no') }}
</div>
@if ((Auth::user()->isSuperUser()) && ($snipeSettings->two_factor_enabled!='0'))
<div class="col-md-11">
<a class="btn btn-default btn-sm pull-left" id="two_factor_reset" style="margin-right: 10px;"> {{ trans('admin/settings/general.two_factor_reset') }}</a>
<span id="two_factor_reseticon">
</span>
<span id="two_factor_resetresult">
</span>
<span id="two_factor_resetstatus">
</span>
<br><br><p class="help-block">{{ trans('admin/settings/general.two_factor_reset_help') }}</p>
</div>
</div>
@endif
</td>
</tr>
@endif
</table>
</div>
</div> <!--/col-md-8-->
@ -532,6 +571,40 @@
@include ('partials.bootstrap-table', ['simple_view' => true])
<script nonce="{{ csrf_token() }}">
$(function () {
$("#two_factor_reset").click(function(){
$("#two_factor_resetrow").removeClass('success');
$("#two_factor_resetrow").removeClass('danger');
$("#two_factor_resetstatus").html('');
$("#two_factor_reseticon").html('<i class="fa fa-spinner spin"></i>');
$.ajax({
url: '{{ route('api.users.two_factor_reset', ['id'=> $user->id]) }}',
type: 'POST',
data: {},
headers: {
"X-Requested-With": 'XMLHttpRequest',
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
},
dataType: 'json',
success: function (data) {
$("#two_factor_reset_toggle").html('').html('{{ trans('general.no') }}');
$("#two_factor_reseticon").html('');
$("#two_factor_resetstatus").html('<i class="fa fa-check text-success"></i>' + data.message);
},
error: function (data) {
$("#two_factor_reseticon").html('');
$("#two_factor_reseticon").html('<i class="fa fa-exclamation-triangle text-danger"></i>');
$('#two_factor_resetstatus').text(data.message);
}
});
});
//binds to onchange event of your input field
var uploadedFileSize = 0;
$('#fileupload').bind('change', function() {