Compare commits
266 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60d269afb5 | |||
| e2cb7a0242 | |||
| f9b1fdc36b | |||
| 93cf8d4e0a | |||
| e83bc03d97 | |||
| b0d493ee51 | |||
| 4882b01787 | |||
| f9dcf0783a | |||
| 985f3658be | |||
| 705dd34f3e | |||
| 6cf5426540 | |||
| 2105a1ec1d | |||
| f475bdbb2d | |||
| 96eb623229 | |||
| 820a39cc90 | |||
| 615051cf66 | |||
| bef42eb43c | |||
| 6f99ce2b07 | |||
| 76ee5a679b | |||
| 26e4354433 | |||
| 72fc03aa50 | |||
| bf1f8659cb | |||
| c0d7564658 | |||
| 74b26a349c | |||
| e9bfb157bb | |||
| ef957399aa | |||
| 973eacf6c3 | |||
| 02fef7049f | |||
| b2660002b9 | |||
| 2c0b9f959b | |||
| 51286d2244 | |||
| 712363f861 | |||
| 0cdd83aabf | |||
| 98dfb9d1b5 | |||
| ae5635ff97 | |||
| 8a38b9d018 | |||
| 734cb941dd | |||
| c66f8c04c8 | |||
| fa24799d2b | |||
| 57d6a7d35e | |||
| 9e2d402c3a | |||
| 551e28eec9 | |||
| 9e30be8a04 | |||
| 8457207c8f | |||
| cc4f1a1485 | |||
| 2ea805b7ed | |||
| 20ef74db42 | |||
| 3dda4c9116 | |||
| e805feff9e | |||
| 0286cf6d46 | |||
| 6ebdfdbabb | |||
| 68487e1200 | |||
| f19b9a44fc | |||
| 124af6ac6b | |||
| 7f126969d0 | |||
| e4b0e9673d | |||
| 7ab44ca963 | |||
| 4898b58bdb | |||
| 9b1e7ba8a6 | |||
| e3d2369151 | |||
| e5f0a12c25 | |||
| de6f3f866f | |||
| 8444a60bc9 | |||
| 3237411b5d | |||
| 5ba1659563 | |||
| 6e068ec339 | |||
| 4fd666716f | |||
| 6eb860ca24 | |||
| 903698a7b0 | |||
| 513faf2db5 | |||
| 9e46bc6c28 | |||
| 402fecd408 | |||
| a97b15ec96 | |||
| 8504c9e8b9 | |||
| 4ebfd6624c | |||
| fbaf6e2494 | |||
| 612d3f9b2a | |||
| 6e4ab5cd96 | |||
| 849966d2c5 | |||
| 1f5bcf2475 | |||
| 58c476195b | |||
| dcd98a7bf1 | |||
| 686ab681e5 | |||
| 052f8e2c42 | |||
| e9578ba8a1 | |||
| 0c0de5e351 | |||
| 10cadecd14 | |||
| 644084658a | |||
| 07936ea901 | |||
| 08784f9cc5 | |||
| a87e615e7f | |||
| df5cc7525e | |||
| 75b8c3455c | |||
| 81d38a0ded | |||
| b2a8af2fa9 | |||
| 9d2363741e | |||
| fc6a33ad38 | |||
| 896ce3456e | |||
| 9db191f0b2 | |||
| c7d752fb65 | |||
| 5026177161 | |||
| d2805442ad | |||
| 7765c87387 | |||
| 6dccf399a5 | |||
| 7432e3fb2d | |||
| caeea9f530 | |||
| 0fdfd013e7 | |||
| d537fc5c32 | |||
| 9164dda64f | |||
| 5ea9c31eab | |||
| c8572deb5c | |||
| 57d25ebb20 | |||
| be114176a2 | |||
| 1f9b04405c | |||
| 4ef11c463c | |||
| 5fb31a5a3f | |||
| f0e04ab9e4 | |||
| 8a65081768 | |||
| c451fde466 | |||
| 5098d69c05 | |||
| b12de13041 | |||
| 72126f7d44 | |||
| df000ce32f | |||
| 6a2e21f502 | |||
| 5ad6234584 | |||
| cc79bb1449 | |||
| c8d588871c | |||
| 8890372a69 | |||
| 80f2d749a2 | |||
| 7f7064c835 | |||
| 31d0b4a46c | |||
| 5759d4819e | |||
| c49788dd9f | |||
| 3641a6d451 | |||
| 8c79070cd9 | |||
| f8563bec94 | |||
| fd7c0bc5fb | |||
| 7d708572fc | |||
| ea68ff1284 | |||
| 66ccf4da03 | |||
| 0b4a13156f | |||
| a8a3962008 | |||
| e110a7b15e | |||
| 30d68309a9 | |||
| 547d1a5a93 | |||
| adf64361e8 | |||
| a43fb060f4 | |||
| c607d89817 | |||
| 8c19b11e73 | |||
| 178ed82dc4 | |||
| 6757df5a2d | |||
| 044dfe2620 | |||
| 9e319e91d6 | |||
| 895a544d4c | |||
| 2aa9412565 | |||
| 35ab5c7df0 | |||
| 8a7cd87644 | |||
| 1cdf6f8263 | |||
| b8ad930690 | |||
| ec14a117b7 | |||
| b7cc12a466 | |||
| f4080a7aa9 | |||
| 5abfbdd1d2 | |||
| 31bbb2d035 | |||
| 33dca84ec7 | |||
| 460485d843 | |||
| b59269d5e2 | |||
| 118931b6fb | |||
| 8bbf8409b1 | |||
| b2101a5188 | |||
| ede20523f2 | |||
| 89d9b830a0 | |||
| d76eea53c4 | |||
| 63a8535de1 | |||
| ef7434b7ac | |||
| 148b375ec2 | |||
| d49bc33b54 | |||
| 5bff5cb8c0 | |||
| c8634d85f5 | |||
| a5423649f4 | |||
| b026953190 | |||
| 3c209b29b1 | |||
| c0fb87c7f9 | |||
| 48c3d0fd19 | |||
| 67f60127be | |||
| 4a8a98a7d3 | |||
| 4b8e9a962b | |||
| 966179290b | |||
| 6618e88a9a | |||
| ab6d2987a4 | |||
| 45c140814f | |||
| 467ff045aa | |||
| e0103a7fbd | |||
| 432263f09f | |||
| 911841d188 | |||
| e8fd2a7fe8 | |||
| df5972c4fa | |||
| 4d003ac97a | |||
| 9f4a212b44 | |||
| 12667f41b9 | |||
| a8390ce4bd | |||
| cfc1e1366c | |||
| 44e5fec707 | |||
| f138d3b781 | |||
| bbd7a752a0 | |||
| a66a22ffb2 | |||
| b29f794b35 | |||
| 410c99da17 | |||
| c800662f0c | |||
| b828985151 | |||
| e6f53a53bc | |||
| 0c2800c7dd | |||
| a8f9f5239c | |||
| 63bb2de4d4 | |||
| c4b2ef5660 | |||
| 296655542d | |||
| a55694da2f | |||
| 33bda9b6d1 | |||
| 42347d0f0c | |||
| 6554903aeb | |||
| 502322be4b | |||
| 72facffcbe | |||
| 001bb3cbe2 | |||
| f42bd3a144 | |||
| b9330cf6b1 | |||
| 2844800caf | |||
| b7e8b9bad7 | |||
| a7ecaa6ebc | |||
| 7937258f6e | |||
| cce0739bb7 | |||
| c93f4ef0d5 | |||
| 89e36dbc42 | |||
| d317052ede | |||
| 8ee9e5e059 | |||
| 2602e4d602 | |||
| f357d9fc90 | |||
| 0eda53c484 | |||
| 79a4acae1a | |||
| 91e0b26bbb | |||
| 5f82214703 | |||
| d517e2fd61 | |||
| 28612d8b61 | |||
| 6ee3d0eb97 | |||
| 7987a4eca4 | |||
| 2aa8e1e76b | |||
| be0e327221 | |||
| 9a1acced58 | |||
| d74df93c48 | |||
| b9a9949570 | |||
| 4ccba5337a | |||
| 8aae2b46cd | |||
| d03d4deef9 | |||
| 21ceea0aed | |||
| 2219c9ccb5 | |||
| bf9e53fbe7 | |||
| f68580b482 | |||
| aec2f2a249 | |||
| 831da2d6d1 | |||
| f2aebe5f9a | |||
| 40c0ba9a95 | |||
| f5a0726f98 | |||
| 2263dae8f4 | |||
| 88f03e6b55 | |||
| 8827d33a43 | |||
| 66ac5d05ad | |||
| 18012279f9 |
@@ -1938,6 +1938,24 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dampfklon",
|
||||
"name": "Dampfklon",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1733625?v=4",
|
||||
"profile": "https://github.com/dampfklon",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chamilton-ccn",
|
||||
"name": "Charles Hamilton",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/52973156?v=4",
|
||||
"profile": "https://communityclosing.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+12
-2
@@ -51,7 +51,7 @@ MAIL_FROM_ADDR=you@example.com
|
||||
MAIL_FROM_NAME='Snipe-IT'
|
||||
MAIL_REPLYTO_ADDR=you@example.com
|
||||
MAIL_REPLYTO_NAME='Snipe-IT'
|
||||
MAIL_BACKUP_NOTIFICATION_ADDRESS=you@example.com
|
||||
MAIL_AUTO_EMBED_METHOD='attachment'
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: IMAGE LIBRARY
|
||||
@@ -59,6 +59,15 @@ MAIL_BACKUP_NOTIFICATION_ADDRESS=you@example.com
|
||||
# --------------------------------------------
|
||||
IMAGE_LIB=gd
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: BACKUP SETTINGS
|
||||
# --------------------------------------------
|
||||
MAIL_BACKUP_NOTIFICATION_DRIVER=null
|
||||
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
|
||||
BACKUP_ENV=true
|
||||
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SESSION SETTINGS
|
||||
# --------------------------------------------
|
||||
@@ -68,6 +77,7 @@ ENCRYPT=false
|
||||
COOKIE_NAME=snipeit_session
|
||||
COOKIE_DOMAIN=null
|
||||
SECURE_COOKIES=false
|
||||
API_TOKEN_EXPIRATION_YEARS=40
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SECURITY HEADER SETTINGS
|
||||
@@ -125,6 +135,7 @@ PRIVATE_AWS_BUCKET_ROOT=null
|
||||
# --------------------------------------------
|
||||
LOGIN_MAX_ATTEMPTS=5
|
||||
LOGIN_LOCKOUT_DURATION=60
|
||||
RESET_PASSWORD_LINK_EXPIRES=900
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: MISC
|
||||
@@ -134,6 +145,5 @@ APP_LOG_MAX_FILES=10
|
||||
APP_LOCKED=false
|
||||
APP_CIPHER=AES-256-CBC
|
||||
GOOGLE_MAPS_API=
|
||||
BACKUP_ENV=true
|
||||
LDAP_MEM_LIM=500M
|
||||
LDAP_TIME_LIM=600
|
||||
|
||||
@@ -48,6 +48,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Any errors that appear in your browser's error console.
|
||||
- Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo.
|
||||
- Include any additional information you can find in `storage/logs` and your webserver's logs.
|
||||
- Include the output from `php -m` (this should display what modules you have enabled.)
|
||||
|
||||
**Additional context**
|
||||
- Is this a fresh install or an upgrade?
|
||||
|
||||
@@ -60,3 +60,4 @@ phpmd\.xml
|
||||
_ide_helper.php
|
||||
.phpstorm.meta.php
|
||||
_ide_helper_models.php
|
||||
/.phplint-cache
|
||||
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
addons:
|
||||
code_climate:
|
||||
repo_token:
|
||||
secure: "C/bUAEpwfZB82dkzI2Nxx3PW5w/BzbKkSyCkp6YjT046jD2/QKvz6ngCFlt3tAWV11TXWFI6D8DzkMmdWOrQl3SGlPZXRD8QOvCiz0HiGMDvlxjAaPaQecGaQZdx/H4m6xTUXRNUVaYmxlMgkkFCWhAp+HZDs0iyOEVamp0Jszg="
|
||||
hosts:
|
||||
- localhost
|
||||
sudo: false
|
||||
|
||||
# see http://about.travis-ci.org/docs/user/languages/php/ for more hints
|
||||
language: php
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
||||
# list any PHP version you want to test against
|
||||
php:
|
||||
- 7.2
|
||||
- 7.3.0
|
||||
- 7.4
|
||||
|
||||
|
||||
# execute any number of scripts before the test run, custom env's are available as variables
|
||||
before_script:
|
||||
- phpenv config-add .github/travis-memory.ini
|
||||
- phantomjs --webdriver=4444 &
|
||||
- sleep 4
|
||||
- mysql -e 'CREATE DATABASE snipeit_unit;'
|
||||
- mysql -e 'CREATE USER "travis'@'localhost";'
|
||||
- mysql -e 'GRANT ALL PRIVILEGES ON * . * TO "travis'@'localhost";'
|
||||
- mysql -e 'FLUSH PRIVILEGES;'
|
||||
- cp .env.testing-ci .env
|
||||
- composer self-update
|
||||
- composer install -n --prefer-source
|
||||
- chmod -R 777 storage
|
||||
- php artisan migrate --env=testing-ci --database=mysql --force
|
||||
- ./vendor/bin/codecept build
|
||||
- php artisan --env=testing-ci key:generate
|
||||
- php artisan --env=testing-ci snipeit:travisci-install
|
||||
- php artisan --env=testing-ci db:seed --database=mysql --force
|
||||
- php artisan --env=testing-ci snipeit:create-admin --first_name=Alison --last_name=Foobar --email=me@example.com --username=snipe --password=password
|
||||
- php artisan --env=testing-ci passport:install
|
||||
- php artisan serve --env=testing-ci --port=8000 --host=localhost &
|
||||
- sleep 5
|
||||
- pip install --user codecov
|
||||
- sleep 5
|
||||
|
||||
|
||||
|
||||
# omitting "script:" will default to phpunit
|
||||
# use the $DB env variable to determine the phpunit.xml to use
|
||||
# script: ./vendor/bin/codecept run --env testing-ci
|
||||
script:
|
||||
- ./vendor/bin/codecept run unit
|
||||
# - ./vendor/bin/codecept run acceptance --env=testing-ci
|
||||
- ./vendor/bin/codecept run functional --env=functional-travis -g func1
|
||||
- ./vendor/bin/codecept run functional --env=functional-travis -g func2
|
||||
- ./vendor/bin/codecept run api --env=functional-travis
|
||||
|
||||
after_script:
|
||||
- vendor/bin/test-reporter
|
||||
|
||||
after_success:
|
||||
- codecov
|
||||
|
||||
after_failure:
|
||||
- cat tests/_output/*.fail.html
|
||||
- curl http://localhost:8000/login
|
||||
- cat storage/logs/laravel.log
|
||||
|
||||
# configure notifications (email, IRC, campfire etc)
|
||||
notifications:
|
||||
email: false
|
||||
slack:
|
||||
secure: vv9we1RxB9RsrMbomSdq6D7vz/okobw87pEkgIZjB+hj1QpQ2by90gsPsOa+NgsJEFaEP7e4KlT6SH8kK+zhbmuKaUd3d1//XdcancE22LZXi6tkiB5yuR/Jhhb1LLDqyGJTB4D92hMnnCPiUjpxNA3r437ttNeYRdYIEEP3drA=
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/5e136eb0c1965f3918d0
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: change # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
@@ -1,13 +1,11 @@
|
||||
[](https://travis-ci.org/snipe/snipe-it) [](https://crowdin.com/project/snipe-it) [](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
|
||||
[](#contributors) [](https://www.codetriage.com/snipe/snipe-it)
|
||||
[](#contributors) [](https://www.codetriage.com/snipe/snipe-it)
|
||||
|
||||
 [](https://crowdin.com/project/snipe-it) [](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
|
||||
[](#contributors)
|
||||
|
||||
## Snipe-IT - Open Source Asset Management System
|
||||
|
||||
This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc.
|
||||
|
||||
It is built on [Laravel 5.5](http://laravel.com).
|
||||
It is built on [Laravel 6](http://laravel.com).
|
||||
|
||||
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
|
||||
|
||||
@@ -64,7 +62,8 @@ Since the release of the JSON REST API, several third-party developers have been
|
||||
- [Snipe-IT plugin for Jira Service Desk (beta)](https://marketplace.atlassian.com/apps/1220379/snipe-it-for-jira-service-desk-beta?hosting=cloud&tab=overview) - for the upcoming Snipe-IT v5 only
|
||||
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
|
||||
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
|
||||
|
||||
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
|
||||
|
||||
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
|
||||
|
||||
-----
|
||||
@@ -115,14 +114,12 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/982885?v=4" width="110px;"/><br /><sub>Martin Stub</sub>](http://martinstub.dk)<br />[🌍](#translation-stubben "Translation") | [<img src="https://avatars2.githubusercontent.com/u/28959963?v=4" width="110px;"/><br /><sub>Meyer Flavio</sub>](https://github.com/meyerf99)<br />[🌍](#translation-meyerf99 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/796443?v=4" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[🌍](#translation-MicaelRodrigues "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10481331?v=4" width="110px;"/><br /><sub>Mikael Rasmussen</sub>](http://rubixy.com/)<br />[🌍](#translation-mikaelssen "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1544552?v=4" width="110px;"/><br /><sub>IxFail</sub>](https://github.com/IxFail)<br />[🌍](#translation-IxFail "Translation") | [<img src="https://avatars3.githubusercontent.com/u/18483118?v=4" width="110px;"/><br /><sub>Mohammed Fota</sub>](http://www.mohammedfota.com)<br />[🌍](#translation-MohammedFota "Translation") | [<img src="https://avatars0.githubusercontent.com/u/227080?v=4" width="110px;"/><br /><sub>Moayad Alserihi</sub>](https://github.com/omego)<br />[🌍](#translation-omego "Translation") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/1680266?v=4" width="110px;"/><br /><sub>saymd</sub>](https://github.com/saymd)<br />[🌍](#translation-saymd "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1826808?v=4" width="110px;"/><br /><sub>Patrik Larsson</sub>](https://nordsken.se)<br />[🌍](#translation-pooot "Translation") | [<img src="https://avatars1.githubusercontent.com/u/20584746?v=4" width="110px;"/><br /><sub>drcryo</sub>](https://github.com/drcryo)<br />[🌍](#translation-drcryo "Translation") | [<img src="https://avatars1.githubusercontent.com/u/19408004?v=4" width="110px;"/><br /><sub>pawel1615</sub>](https://github.com/pawel1615)<br />[🌍](#translation-pawel1615 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/23340468?v=4" width="110px;"/><br /><sub>bodrovics</sub>](https://github.com/bodrovics)<br />[🌍](#translation-bodrovics "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3257654?v=4" width="110px;"/><br /><sub>priatna</sub>](https://github.com/priatna)<br />[🌍](#translation-priatna "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5358374?v=4" width="110px;"/><br /><sub>Fan Jiang</sub>](https://amayume.net)<br />[🌍](#translation-ProfFan "Translation") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/22555451?v=4" width="110px;"/><br /><sub>ragnarcx</sub>](https://github.com/ragnarcx)<br />[🌍](#translation-ragnarcx "Translation") | [<img src="https://avatars2.githubusercontent.com/u/18654582?v=4" width="110px;"/><br /><sub>Rein van Haaren</sub>](http://www.reinvanhaaren.nl/)<br />[🌍](#translation-reinvanhaaren "Translation") | [<img src="https://avatars1.githubusercontent.com/u/386672?v=4" width="110px;"/><br /><sub>Teguh Dwicaksana</sub>](http://dheche.songolimo.net)<br />[🌍](#translation-dheche "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2572552?v=4" width="110px;"/><br /><sub>fraccie</sub>](https://github.com/FRaccie)<br />[🌍](#translation-FRaccie "Translation") | [<img src="https://avatars0.githubusercontent.com/u/35182720?v=4" width="110px;"/><br /><sub>vinzruzell</sub>](https://github.com/vinzruzell)<br />[🌍](#translation-vinzruzell "Translation") | [<img src="https://avatars1.githubusercontent.com/u/7883603?v=4" width="110px;"/><br /><sub>Kevin Austin</sub>](http://kevinaustin.com)<br />[🌍](#translation-vipsystem "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3861828?v=4" width="110px;"/><br /><sub>Wira Sandy</sub>](http://azuraweb.xyz)<br />[🌍](#translation-wira-sandy "Translation") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [<img src="https://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/47435081?v=4" width="110px;"/><br /><sub>Godfrey Martinez</sub>](https://github.com/Godmartinz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") | [<img src="https://avatars1.githubusercontent.com/u/2075128?v=4" width="110px;"/><br /><sub>bigtreeEdo</sub>](https://github.com/bigtreeEdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [<img src="https://avatars0.githubusercontent.com/u/5000430?v=4" width="110px;"/><br /><sub>Colin McNeil</sub>](https://colinmcneil.me/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [<img src="https://avatars0.githubusercontent.com/u/421625?v=4" width="110px;"/><br /><sub>JoKneeMo</sub>](https://github.com/JoKneeMo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [<img src="https://avatars0.githubusercontent.com/u/54849013?v=4" width="110px;"/><br /><sub>Joshi</sub>](http://www.redbridge.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [<img src="https://avatars2.githubusercontent.com/u/15731458?v=4" width="110px;"/><br /><sub>Anthony Burns</sub>](https://github.com/anthonypburns)<br />[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [<img src="https://avatars2.githubusercontent.com/u/1972329?v=4" width="110px;"/><br /><sub>Alexander Chibrikin</sub>](http://phpprofi.ru/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/4930051?v=4" width="110px;"/><br /><sub>Wes Hulette</sub>](http://macfoo.wordpress.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [<img src="https://avatars0.githubusercontent.com/u/8134591?v=4" width="110px;"/><br /><sub>patrict</sub>](https://github.com/patrict)<br />[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") |
|
||||
| [<img src="https://avatars3.githubusercontent.com/u/2611616?v=4" width="110px;"/><br /><sub>Dmitriy Minaev</sub>](https://github.com/VELIKII-DIVAN)<br />[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [<img src="https://avatars0.githubusercontent.com/u/5132245?v=4" width="110px;"/><br /><sub>liquidhorse</sub>](https://github.com/liquidhorse)<br />[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [<img src="https://avatars1.githubusercontent.com/u/183678?v=4" width="110px;"/><br /><sub>Jordi Boggiano</sub>](https://seld.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [<img src="https://avatars0.githubusercontent.com/u/653557?v=4" width="110px;"/><br /><sub>Ivan Nieto</sub>](https://github.com/inietov)<br />[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [<img src="https://avatars2.githubusercontent.com/u/6764151?v=4" width="110px;"/><br /><sub>Ben RUBSON</sub>](https://github.com/benrubson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [<img src="https://avatars2.githubusercontent.com/u/8554558?v=4" width="110px;"/><br /><sub>NMathar</sub>](https://github.com/NMathar)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [<img src="https://avatars1.githubusercontent.com/u/139566?v=4" width="110px;"/><br /><sub>Steffen</sub>](https://github.com/smb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/6609453?v=4" width="110px;"/><br /><sub>Sxderp</sub>](https://github.com/Sxderp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [<img src="https://avatars1.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>fanta8897</sub>](https://github.com/fanta8897)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [<img src="https://avatars2.githubusercontent.com/u/2576509?v=4" width="110px;"/><br /><sub>Andrey Bolonin</sub>](https://andreybolonin.com/phpconsulting/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [<img src="https://avatars3.githubusercontent.com/u/2173307?v=4" width="110px;"/><br /><sub>shinayoshi</sub>](http://www.shinayoshi.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [<img src="https://avatars3.githubusercontent.com/u/2130159?v=4" width="110px;"/><br /><sub>Hubert</sub>](https://github.com/reuser)<br />[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [<img src="https://avatars0.githubusercontent.com/u/6865789?v=4" width="110px;"/><br /><sub>KeenRivals</sub>](https://brashear.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [<img src="https://avatars3.githubusercontent.com/u/2902513?v=4" width="110px;"/><br /><sub>omyno</sub>](https://github.com/omyno)<br />[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/6271335?v=4" width="110px;"/><br /><sub>Evgeny</sub>](https://github.com/jackka)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [<img src="https://avatars2.githubusercontent.com/u/1169963?v=4" width="110px;"/><br /><sub>Colin Campbell</sub>](https://digitalist.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [<img src="https://avatars3.githubusercontent.com/u/2872098?v=4" width="110px;"/><br /><sub>Ľubomír Kučera</sub>](https://github.com/lubo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [<img src="https://avatars3.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://www.sourceguru.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [<img src="https://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [<img src="https://avatars2.githubusercontent.com/u/47435081?v=4" width="110px;"/><br /><sub>Godfrey Martinez</sub>](https://github.com/Godmartinz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") |
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/2075128?v=4" width="110px;"/><br /><sub>bigtreeEdo</sub>](https://github.com/bigtreeEdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [<img src="https://avatars0.githubusercontent.com/u/5000430?v=4" width="110px;"/><br /><sub>Colin McNeil</sub>](https://colinmcneil.me/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [<img src="https://avatars0.githubusercontent.com/u/421625?v=4" width="110px;"/><br /><sub>JoKneeMo</sub>](https://github.com/JoKneeMo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [<img src="https://avatars0.githubusercontent.com/u/54849013?v=4" width="110px;"/><br /><sub>Joshi</sub>](http://www.redbridge.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [<img src="https://avatars2.githubusercontent.com/u/15731458?v=4" width="110px;"/><br /><sub>Anthony Burns</sub>](https://github.com/anthonypburns)<br />[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [<img src="https://avatars1.githubusercontent.com/u/63399474?v=4" width="110px;"/><br /><sub>johnson-yi</sub>](https://github.com/johnson-yi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [<img src="https://avatars1.githubusercontent.com/u/1862720?v=4" width="110px;"/><br /><sub>Sanjay Govind</sub>](https://tangentmc.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/1255375?v=4" width="110px;"/><br /><sub>Peter Upfold</sub>](https://peter.upfold.org.uk/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [<img src="https://avatars2.githubusercontent.com/u/961717?v=4" width="110px;"/><br /><sub>Jared Biel</sub>](https://github.com/jbiel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/1255375?v=4" width="110px;"/><br /><sub>Peter Upfold</sub>](https://peter.upfold.org.uk/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [<img src="https://avatars2.githubusercontent.com/u/961717?v=4" width="110px;"/><br /><sub>Jared Biel</sub>](https://github.com/jbiel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [<img src="https://avatars1.githubusercontent.com/u/1733625?v=4" width="110px;"/><br /><sub>Dampfklon</sub>](https://github.com/dampfklon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [<img src="https://avatars2.githubusercontent.com/u/52973156?v=4" width="110px;"/><br /><sub>Charles Hamilton</sub>](https://communityclosing.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class FixMismatchedAssetsAndLogs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:fix-assets-and-logs {--dryrun : Run the sync process but don\'t update the database}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This script attempts to check the log table and check that the assets.assigned_to matches the last checkout.';
|
||||
|
||||
/**
|
||||
* Is dry-run?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $dryrun = false;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
if ($this->option('dryrun')) {
|
||||
$this->dryrun = true;
|
||||
}
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->info('This is a DRY RUN - no changes will be saved.' );
|
||||
}
|
||||
|
||||
$mismatch_count = 0;
|
||||
$assets = Asset::whereNotNull('assigned_to')
|
||||
->where('assigned_type', '=', 'App\\Models\\User')
|
||||
->orderBy('id', 'ASC')->get();
|
||||
foreach ($assets as $asset) {
|
||||
|
||||
// get the last checkout of the asset
|
||||
if ($checkout_log = Actionlog::where('target_type', '=', 'App\\Models\\User')
|
||||
->where('action_type', '=', 'checkout')
|
||||
->where('item_id', '=', $asset->id)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first()) {
|
||||
|
||||
// Now check for a subsequent checkin log - we want to ignore those
|
||||
if (!$checkin_log = Actionlog::where('target_type', '=', 'App\\Models\\User')
|
||||
->where('action_type', '=', 'checkin from')
|
||||
->where('item_id', '=', $asset->id)
|
||||
->whereDate('created_at', '>', $checkout_log->created_at)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first()) {
|
||||
|
||||
//print_r($asset);
|
||||
if ($checkout_log->target_id != $asset->assigned_to) {
|
||||
$this->error('Log ID: '.$checkout_log->id.' -- Asset ID '. $checkout_log->item_id.' SHOULD BE checked out to User '.$checkout_log->target_id.' but its assigned_to is '.$asset->assigned_to );
|
||||
|
||||
if (!$this->dryrun) {
|
||||
$asset->assigned_to = $checkout_log->target_id;
|
||||
if ($asset->save()) {
|
||||
$this->info('Asset record updated.');
|
||||
} else {
|
||||
$this->error('Error updating asset: '.$asset->getErrors());
|
||||
}
|
||||
}
|
||||
$mismatch_count++;
|
||||
}
|
||||
} else {
|
||||
//$this->info('Asset ID '.$asset->id.': There is a checkin '.$checkin_log->created_at.' after this checkout '.$checkout_log->created_at);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
$this->info($mismatch_count.' mismatched assets.');
|
||||
|
||||
}
|
||||
}
|
||||
@@ -88,11 +88,13 @@ class ImportLocations extends Command
|
||||
|
||||
if (array_key_exists('Parent Name', $row)) {
|
||||
$parent_name = trim($row['Parent Name']);
|
||||
} else {
|
||||
$parent_name = null;
|
||||
}
|
||||
|
||||
// Set the location attributes to save
|
||||
if (array_key_exists('Name', $row)) {
|
||||
$location = Location::firstOrNew(array('name' => trim($row['Name'])));
|
||||
$location = Location::firstOrCreate(array('name' => trim($row['Name'])));
|
||||
$location->name = trim($row['Name']);
|
||||
$this->info('Checking location: '.$location->name);
|
||||
} else {
|
||||
|
||||
Regular → Executable
+225
-330
@@ -1,24 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Log;
|
||||
use Exception;
|
||||
use App\Models\User;
|
||||
use App\Services\LdapAd;
|
||||
use App\Models\Location;
|
||||
use Illuminate\Console\Command;
|
||||
use Adldap\Models\User as AdldapUser;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Ldap;
|
||||
use App\Models\User;
|
||||
use App\Models\Location;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* LDAP / AD sync command.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
class LdapSync extends Command
|
||||
{
|
||||
/**
|
||||
@@ -26,79 +16,23 @@ class LdapSync extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:ldap-sync
|
||||
{--location= : A location name }
|
||||
{--location_id= : A location id}
|
||||
{--base_dn= : A diffrent base DN to use }
|
||||
{--summary : Print summary }
|
||||
{--json_summary : Print summary in json format }
|
||||
{--dryrun : Run the sync process but don\'t update the database}';
|
||||
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--summary} {--json_summary}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command line LDAP/AD sync';
|
||||
|
||||
/**
|
||||
* An LdapAd instance.
|
||||
*
|
||||
* @var \App\Models\LdapAd
|
||||
*/
|
||||
private $ldap;
|
||||
|
||||
/**
|
||||
* LDAP settings collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $settings = null;
|
||||
|
||||
/**
|
||||
* A default location collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $defaultLocation = null;
|
||||
|
||||
/**
|
||||
* Mapped locations collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $mappedLocations = null;
|
||||
|
||||
/**
|
||||
* The summary collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $summary;
|
||||
|
||||
/**
|
||||
* Is dry-run?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $dryrun = false;
|
||||
|
||||
/**
|
||||
* Show users to be imported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $userlist = [];
|
||||
protected $description = 'Command line LDAP sync';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(LdapAd $ldap)
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->ldap = $ldap;
|
||||
$this->settings = $this->ldap->ldapSettings;
|
||||
$this->summary = collect();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,280 +42,241 @@ class LdapSync extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
ini_set('max_execution_time', '600'); //600 seconds = 10 minutes
|
||||
ini_set('memory_limit', '500M');
|
||||
$old_error_reporting = error_reporting(); // grab old error_reporting .ini setting, for later re-enablement
|
||||
error_reporting($old_error_reporting & ~E_DEPRECATED); // disable deprecation warnings, for LDAP in PHP 7.4 (and greater)
|
||||
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
|
||||
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
|
||||
$ldap_result_username = Setting::getSettings()->ldap_username_field;
|
||||
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
|
||||
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
|
||||
|
||||
if ($this->option('dryrun')) {
|
||||
$this->dryrun = true;
|
||||
}
|
||||
$this->checkIfLdapIsEnabled();
|
||||
$this->checkLdapConnetion();
|
||||
$this->setBaseDn();
|
||||
$this->getUserDefaultLocation();
|
||||
/*
|
||||
* Use the default location if set, this is needed for the LDAP users sync page
|
||||
*/
|
||||
if (!$this->option('base_dn') && null == $this->defaultLocation) {
|
||||
$this->getMappedLocations();
|
||||
}
|
||||
$this->processLdapUsers();
|
||||
// Print table of users
|
||||
if ($this->dryrun) {
|
||||
$this->info('The following users will be synced!');
|
||||
$headers = ['First Name', 'Last Name', 'Username', 'Email', 'Employee #', 'Location Id', 'Status'];
|
||||
$this->table($headers, $this->summary->toArray());
|
||||
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag_field;
|
||||
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
|
||||
$ldap_result_email = Setting::getSettings()->ldap_email;
|
||||
|
||||
try {
|
||||
$ldapconn = Ldap::connectToLdap();
|
||||
Ldap::bindAdminToLdap($ldapconn);
|
||||
} catch (\Exception $e) {
|
||||
if ($this->option('json_summary')) {
|
||||
$json_summary = [ "error" => true, "error_message" => $e->getMessage(), "summary" => [] ];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
LOG::info($e);
|
||||
return [];
|
||||
}
|
||||
|
||||
error_reporting($old_error_reporting); // re-enable deprecation warnings.
|
||||
return $this->getSummary();
|
||||
}
|
||||
$summary = array();
|
||||
|
||||
/**
|
||||
* Generate the LDAP sync summary.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getSummary(): string
|
||||
{
|
||||
if ($this->option('summary') && null === $this->dryrun) {
|
||||
$this->summary->each(function ($item) {
|
||||
$this->info('USER: '.$item['note']);
|
||||
|
||||
if ('ERROR' === $item['status']) {
|
||||
$this->error('ERROR: '.$item['note']);
|
||||
}
|
||||
});
|
||||
} elseif ($this->option('json_summary')) {
|
||||
$json_summary = [
|
||||
'error' => false,
|
||||
'error_message' => '',
|
||||
'summary' => $this->summary->toArray(),
|
||||
];
|
||||
$this->info(json_encode($json_summary));
|
||||
try {
|
||||
if ($this->option('base_dn') != '') {
|
||||
$search_base = $this->option('base_dn');
|
||||
LOG::debug('Importing users from specified base DN: \"'.$search_base.'\".');
|
||||
} else {
|
||||
$search_base = null;
|
||||
}
|
||||
$results = Ldap::findLdapUsers($search_base);
|
||||
} catch (\Exception $e) {
|
||||
if ($this->option('json_summary')) {
|
||||
$json_summary = [ "error" => true, "error_message" => $e->getMessage(), "summary" => [] ];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
LOG::info($e);
|
||||
return [];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
/* Determine which location to assign users to by default. */
|
||||
$location = NULL;
|
||||
|
||||
/**
|
||||
* Create a new user or update an existing user.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param \Adldap\Models\User $snipeUser
|
||||
*/
|
||||
private function updateCreateUser(AdldapUser $snipeUser): void
|
||||
{
|
||||
$user = $this->ldap->processUser($snipeUser, $this->defaultLocation, $this->mappedLocations);
|
||||
$summary = [
|
||||
'firstname' => $user->first_name,
|
||||
'lastname' => $user->last_name,
|
||||
'username' => $user->username,
|
||||
'employee_number' => $user->employee_num,
|
||||
'email' => $user->email,
|
||||
'location_id' => $user->location_id,
|
||||
];
|
||||
// Only update the database if is not a dry run
|
||||
if (!$this->dryrun) {
|
||||
if ($user->isDirty()) { //if nothing on the user changed, don't bother trying to save anything nor put anything in the summary
|
||||
if ($user->save()) {
|
||||
$summary['note'] = ($user->wasRecentlyCreated ? 'CREATED' : 'UPDATED');
|
||||
$summary['status'] = 'SUCCESS';
|
||||
} else {
|
||||
$errors = '';
|
||||
foreach ($user->getErrors()->getMessages() as $error) {
|
||||
$errors .= implode(", ",$error);
|
||||
if ($this->option('location')!='') {
|
||||
$location = Location::where('name', '=', $this->option('location'))->first();
|
||||
LOG::debug('Location name '.$this->option('location').' passed');
|
||||
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
|
||||
} elseif ($this->option('location_id')!='') {
|
||||
$location = Location::where('id', '=', $this->option('location_id'))->first();
|
||||
LOG::debug('Location ID '.$this->option('location_id').' passed');
|
||||
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
|
||||
}
|
||||
|
||||
if (!isset($location)) {
|
||||
LOG::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
|
||||
}
|
||||
|
||||
/* Process locations with explicitly defined OUs, if doing a full import. */
|
||||
if ($this->option('base_dn')=='') {
|
||||
// Retrieve locations with a mapped OU, and sort them from the shallowest to deepest OU (see #3993)
|
||||
$ldap_ou_locations = Location::where('ldap_ou', '!=', '')->get()->toArray();
|
||||
$ldap_ou_lengths = array();
|
||||
|
||||
foreach ($ldap_ou_locations as $location) {
|
||||
$ldap_ou_lengths[] = strlen($location["ldap_ou"]);
|
||||
}
|
||||
|
||||
array_multisort($ldap_ou_lengths, SORT_ASC, $ldap_ou_locations);
|
||||
|
||||
if (sizeof($ldap_ou_locations) > 0) {
|
||||
LOG::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.');
|
||||
}
|
||||
|
||||
// Inject location information fields
|
||||
for ($i = 0; $i < $results["count"]; $i++) {
|
||||
$results[$i]["ldap_location_override"] = false;
|
||||
$results[$i]["location_id"] = 0;
|
||||
}
|
||||
|
||||
// Grab subsets based on location-specific DNs, and overwrite location for these users.
|
||||
foreach ($ldap_ou_locations as $ldap_loc) {
|
||||
try {
|
||||
$location_users = Ldap::findLdapUsers($ldap_loc["ldap_ou"]);
|
||||
} catch (\Exception $e) { // FIXME: this is stolen from line 77 or so above
|
||||
if ($this->option('json_summary')) {
|
||||
$json_summary = [ "error" => true, "error_message" => trans('admin/users/message.error.ldap_could_not_search')." Location: ".$ldap_loc['name']." (ID: ".$ldap_loc['id'].") cannot connect to \"".$ldap_loc["ldap_ou"]."\" - ".$e->getMessage(), "summary" => [] ];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
$summary['note'] = $snipeUser->getDN().' was not imported. REASON: '.$errors;
|
||||
$summary['status'] = 'ERROR';
|
||||
LOG::info($e);
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
$summary = null;
|
||||
}
|
||||
}
|
||||
$usernames = array();
|
||||
for ($i = 0; $i < $location_users["count"]; $i++) {
|
||||
|
||||
// $summary['note'] = ($user->getOriginal('username') ? 'UPDATED' : 'CREATED'); // this seems, kinda, like, superfluous, relative to the $summary['note'] thing above, yeah?
|
||||
if($summary) { //if the $user wasn't dirty, $summary was set to null so that we will skip the following push()
|
||||
$this->summary->push($summary);
|
||||
}
|
||||
}
|
||||
if (array_key_exists($ldap_result_username, $location_users[$i])) {
|
||||
$location_users[$i]["ldap_location_override"] = true;
|
||||
$location_users[$i]["location_id"] = $ldap_loc["id"];
|
||||
$usernames[] = $location_users[$i][$ldap_result_username][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the users to update / create.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param int $page The page to get the result set
|
||||
*/
|
||||
private function processLdapUsers(int $page=0): void
|
||||
{
|
||||
try {
|
||||
$ldapUsers = $this->ldap->getLdapUsers($page);
|
||||
} catch (Exception $e) {
|
||||
$this->outputError($e);
|
||||
exit($e->getMessage());
|
||||
}
|
||||
|
||||
if (0 == $ldapUsers->count()) {
|
||||
$msg = 'ERROR: No users found!';
|
||||
Log::error($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->error($msg);
|
||||
}
|
||||
exit($msg);
|
||||
}
|
||||
|
||||
// Process each individual users
|
||||
foreach ($ldapUsers as $user) {
|
||||
$this->updateCreateUser($user);
|
||||
}
|
||||
|
||||
if ($ldapUsers->getCurrentPage() < $ldapUsers->getPages()-1) {
|
||||
$this->processLdapUsers($ldapUsers->getCurrentPage() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mapped locations if a base_dn is provided.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function getMappedLocations()
|
||||
{
|
||||
$ldapOuLocation = Location::where('ldap_ou', '!=', '')->select(['id', 'ldap_ou'])->get();
|
||||
$locations = $ldapOuLocation->sortBy(function ($ou, $key) {
|
||||
return strlen($ou->ldap_ou);
|
||||
});
|
||||
if ($locations->count() > 0) {
|
||||
$msg = 'Some locations have special OUs set. Locations will be automatically set for users in those OUs.';
|
||||
LOG::debug($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->info($msg);
|
||||
}
|
||||
|
||||
$this->mappedLocations = $locations->pluck('ldap_ou', 'id'); // TODO: this seems ok-ish, but the key-> value is going location_id -> OU name, and the primary action here is the opposite of that - going from OU's to location ID's.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base dn if supplied.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function setBaseDn(): void
|
||||
{
|
||||
if ($this->option('base_dn')) {
|
||||
$this->ldap->baseDn = $this->option('base_dn');
|
||||
$msg = sprintf('Importing users from specified base DN: "%s"', $this->ldap->baseDn);
|
||||
LOG::debug($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->info($msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default location id for imported users.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function getUserDefaultLocation(): void
|
||||
{
|
||||
$location = $this->option('location_id') ?? $this->option('location');
|
||||
if ($location) {
|
||||
$userLocation = Location::where('name', '=', $location)
|
||||
->orWhere('id', '=', intval($location))
|
||||
->select(['name', 'id'])
|
||||
->first();
|
||||
if ($userLocation) {
|
||||
$msg = 'Importing users with default location: '.$userLocation->name.' ('.$userLocation->id.')';
|
||||
LOG::debug($msg);
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->info($msg);
|
||||
}
|
||||
|
||||
$this->defaultLocation = collect([
|
||||
$userLocation->id => $userLocation->name,
|
||||
]);
|
||||
} else {
|
||||
$msg = 'The supplied location is invalid!';
|
||||
LOG::error($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->error($msg);
|
||||
// Delete located users from the general group.
|
||||
foreach ($results as $key => $generic_entry) {
|
||||
if ((is_array($generic_entry)) && (array_key_exists($ldap_result_username, $generic_entry))) {
|
||||
if (in_array($generic_entry[$ldap_result_username][0], $usernames)) {
|
||||
unset($results[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
|
||||
$global_count = $results['count'];
|
||||
$results = array_merge($location_users, $results);
|
||||
$results['count'] = $global_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if LDAP intergration is enabled.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function checkIfLdapIsEnabled(): void
|
||||
{
|
||||
if (false === $this->settings['ldap_enabled']) {
|
||||
$msg = 'LDAP intergration is not enabled. Exiting sync process.';
|
||||
$this->info($msg);
|
||||
Log::info($msg);
|
||||
exit(0);
|
||||
/* Create user account entries in Snipe-IT */
|
||||
$tmp_pass = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
|
||||
$pass = bcrypt($tmp_pass);
|
||||
|
||||
for ($i = 0; $i < $results["count"]; $i++) {
|
||||
if (empty($ldap_result_active_flag) || $results[$i][$ldap_result_active_flag][0] == "TRUE") {
|
||||
|
||||
$item = array();
|
||||
$item["username"] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : "";
|
||||
$item["employee_number"] = isset($results[$i][$ldap_result_emp_num][0]) ? $results[$i][$ldap_result_emp_num][0] : "";
|
||||
$item["lastname"] = isset($results[$i][$ldap_result_last_name][0]) ? $results[$i][$ldap_result_last_name][0] : "";
|
||||
$item["firstname"] = isset($results[$i][$ldap_result_first_name][0]) ? $results[$i][$ldap_result_first_name][0] : "";
|
||||
$item["email"] = isset($results[$i][$ldap_result_email][0]) ? $results[$i][$ldap_result_email][0] : "" ;
|
||||
$item["ldap_location_override"] = isset($results[$i]["ldap_location_override"]) ? $results[$i]["ldap_location_override"]:"";
|
||||
$item["location_id"] = isset($results[$i]["location_id"]) ? $results[$i]["location_id"]:"";
|
||||
|
||||
$user = User::where('username', $item["username"])->first();
|
||||
if ($user) {
|
||||
// Updating an existing user.
|
||||
$item["createorupdate"] = 'updated';
|
||||
} else {
|
||||
// Creating a new user.
|
||||
$user = new User;
|
||||
$user->password = $pass;
|
||||
$user->activated = 0;
|
||||
$item["createorupdate"] = 'created';
|
||||
}
|
||||
|
||||
$user->first_name = $item["firstname"];
|
||||
$user->last_name = $item["lastname"];
|
||||
$user->username = $item["username"];
|
||||
$user->email = $item["email"];
|
||||
$user->employee_num = e($item["employee_number"]);
|
||||
|
||||
// Sync activated state for Active Directory.
|
||||
if ( array_key_exists('useraccountcontrol', $results[$i]) ) {
|
||||
/* The following is _probably_ the correct logic, but we can't use it because
|
||||
some users may have been dependent upon the previous behavior, and this
|
||||
could cause additional access to be available to users they don't want
|
||||
to allow to log in.
|
||||
|
||||
$useraccountcontrol = $results[$i]['useraccountcontrol'][0];
|
||||
if(
|
||||
// based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties
|
||||
($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT
|
||||
!($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE
|
||||
!($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT
|
||||
) {
|
||||
$user->activated = 1;
|
||||
} else {
|
||||
$user->activated = 0;
|
||||
} */
|
||||
$enabled_accounts = [
|
||||
'512', // 0x200 NORMAL_ACCOUNT
|
||||
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
|
||||
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
|
||||
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
|
||||
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
|
||||
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
|
||||
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'4260352',// 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
|
||||
'1049088',// 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
|
||||
];
|
||||
$user->activated = ( in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts) ) ? 1 : 0;
|
||||
}
|
||||
|
||||
// If we're not using AD, and there isn't an activated flag set, activate all users
|
||||
elseif (empty($ldap_result_active_flag)) {
|
||||
$user->activated = 1;
|
||||
}
|
||||
|
||||
if ($item['ldap_location_override'] == true) {
|
||||
$user->location_id = $item['location_id'];
|
||||
} elseif ((isset($location)) && (!empty($location))) {
|
||||
|
||||
if ((is_array($location)) && (array_key_exists('id', $location))) {
|
||||
$user->location_id = $location['id'];
|
||||
} elseif (is_object($location)) {
|
||||
$user->location_id = $location->id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$user->ldap_import = 1;
|
||||
|
||||
$errors = '';
|
||||
|
||||
if ($user->save()) {
|
||||
$item["note"] = $item["createorupdate"];
|
||||
$item["status"]='success';
|
||||
} else {
|
||||
foreach ($user->getErrors()->getMessages() as $key => $err) {
|
||||
$errors .= $err[0];
|
||||
}
|
||||
$item["note"] = $errors;
|
||||
$item["status"]='error';
|
||||
}
|
||||
|
||||
array_push($summary, $item);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to make sure we can access the server.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function checkLdapConnetion(): void
|
||||
{
|
||||
try {
|
||||
$this->ldap->testLdapAdUserConnection();
|
||||
$this->ldap->testLdapAdBindConnection();
|
||||
} catch (Exception $e) {
|
||||
$this->outputError($e);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the json summary to the screen if enabled.
|
||||
*
|
||||
* @param Exception $error
|
||||
*/
|
||||
private function outputError($error): void
|
||||
{
|
||||
if ($this->option('json_summary')) {
|
||||
$json_summary = [
|
||||
'error' => true,
|
||||
'error_message' => $error->getMessage(),
|
||||
'summary' => [],
|
||||
];
|
||||
if ($this->option('summary')) {
|
||||
for ($x = 0; $x < count($summary); $x++) {
|
||||
if ($summary[$x]['status']=='error') {
|
||||
$this->error('ERROR: '.$summary[$x]['firstname'].' '.$summary[$x]['lastname'].' (username: '.$summary[$x]['username'].') was not imported: '.$summary[$x]['note']);
|
||||
} else {
|
||||
$this->info('User '.$summary[$x]['firstname'].' '.$summary[$x]['lastname'].' (username: '.$summary[$x]['username'].') was '.strtoupper($summary[$x]['createorupdate']).'.');
|
||||
}
|
||||
}
|
||||
} else if ($this->option('json_summary')) {
|
||||
$json_summary = [ "error" => false, "error_message" => "", "summary" => $summary ]; // hardcoding the error to false and the error_message to blank seems a bit weird
|
||||
$this->info(json_encode($json_summary));
|
||||
} else {
|
||||
return $summary;
|
||||
}
|
||||
$this->error($error->getMessage());
|
||||
LOG::error($error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Log;
|
||||
use Exception;
|
||||
use App\Models\User;
|
||||
use App\Services\LdapAd;
|
||||
use App\Models\Location;
|
||||
use Illuminate\Console\Command;
|
||||
use Adldap\Models\User as AdldapUser;
|
||||
|
||||
/**
|
||||
* LDAP / AD sync command.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
class LdapSyncNg extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:ldap-sync-ng
|
||||
{--location= : A location name }
|
||||
{--location_id= : A location id}
|
||||
{--base_dn= : A diffrent base DN to use }
|
||||
{--summary : Print summary }
|
||||
{--json_summary : Print summary in json format }
|
||||
{--dryrun : Run the sync process but don\'t update the database}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command line LDAP/AD sync';
|
||||
|
||||
/**
|
||||
* An LdapAd instance.
|
||||
*
|
||||
* @var \App\Models\LdapAd
|
||||
*/
|
||||
private $ldap;
|
||||
|
||||
/**
|
||||
* LDAP settings collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $settings = null;
|
||||
|
||||
/**
|
||||
* A default location collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $defaultLocation = null;
|
||||
|
||||
/**
|
||||
* Mapped locations collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $mappedLocations = null;
|
||||
|
||||
/**
|
||||
* The summary collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
private $summary;
|
||||
|
||||
/**
|
||||
* Is dry-run?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $dryrun = false;
|
||||
|
||||
/**
|
||||
* Show users to be imported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $userlist = [];
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(LdapAd $ldap)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->ldap = $ldap;
|
||||
$this->settings = $this->ldap->ldapSettings;
|
||||
$this->summary = collect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$dispatcher = \Adldap\Adldap::getEventDispatcher();
|
||||
|
||||
// Listen for all model events.
|
||||
$dispatcher->listen('Adldap\Models\Events\*', function ($eventName, array $data) {
|
||||
echo $eventName; // Returns 'Adldap\Models\Events\Updating'
|
||||
var_dump($data); // Returns [0] => (object) Adldap\Models\Events\Updating;
|
||||
\Log::debug("Event: ".$eventName." data - ".print_r($data, true));
|
||||
});
|
||||
$dispatcher->listen('Adldap\Auth\Events\*', function ($eventName, array $data) {
|
||||
echo $eventName; // Returns 'Adldap\Models\Events\Updating'
|
||||
var_dump($data); // Returns [0] => (object) Adldap\Models\Events\Updating;
|
||||
\Log::debug("Event: ".$eventName." data - ".print_r($data, true));
|
||||
});
|
||||
|
||||
ini_set('max_execution_time', env('LDAP_TIME_LIM', "600")); //600 seconds = 10 minutes
|
||||
ini_set('memory_limit', '500M');
|
||||
$old_error_reporting = error_reporting(); // grab old error_reporting .ini setting, for later re-enablement
|
||||
error_reporting($old_error_reporting & ~E_DEPRECATED); // disable deprecation warnings, for LDAP in PHP 7.4 (and greater)
|
||||
|
||||
if ($this->option('dryrun')) {
|
||||
$this->dryrun = true;
|
||||
}
|
||||
$this->checkIfLdapIsEnabled();
|
||||
$this->checkLdapConnection();
|
||||
$this->setBaseDn();
|
||||
$this->getUserDefaultLocation();
|
||||
/*
|
||||
* Use the default location if set, this is needed for the LDAP users sync page
|
||||
*/
|
||||
if (!$this->option('base_dn') && null == $this->defaultLocation) {
|
||||
$this->getMappedLocations();
|
||||
}
|
||||
$this->processLdapUsers();
|
||||
// Print table of users
|
||||
if ($this->dryrun) {
|
||||
$this->info('The following users will be synced!');
|
||||
$headers = ['First Name', 'Last Name', 'Username', 'Email', 'Employee #', 'Location Id', 'Status'];
|
||||
$this->table($headers, $this->summary->toArray());
|
||||
}
|
||||
|
||||
error_reporting($old_error_reporting); // re-enable deprecation warnings.
|
||||
return $this->getSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the LDAP sync summary.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getSummary(): string
|
||||
{
|
||||
if ($this->option('summary') && null === $this->dryrun) {
|
||||
$this->summary->each(function ($item) {
|
||||
$this->info('USER: '.$item['note']);
|
||||
|
||||
if ('ERROR' === $item['status']) {
|
||||
$this->error('ERROR: '.$item['note']);
|
||||
}
|
||||
});
|
||||
} elseif ($this->option('json_summary')) {
|
||||
$json_summary = [
|
||||
'error' => false,
|
||||
'error_message' => '',
|
||||
'summary' => $this->summary->toArray(),
|
||||
];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user or update an existing user.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param \Adldap\Models\User $snipeUser
|
||||
*/
|
||||
private function updateCreateUser(AdldapUser $snipeUser): void
|
||||
{
|
||||
$user = $this->ldap->processUser($snipeUser, $this->defaultLocation, $this->mappedLocations);
|
||||
$summary = [
|
||||
'firstname' => $user->first_name,
|
||||
'lastname' => $user->last_name,
|
||||
'username' => $user->username,
|
||||
'employee_number' => $user->employee_num,
|
||||
'email' => $user->email,
|
||||
'location_id' => $user->location_id,
|
||||
];
|
||||
// Only update the database if is not a dry run
|
||||
if (!$this->dryrun) {
|
||||
if ($user->isDirty()) { //if nothing on the user changed, don't bother trying to save anything nor put anything in the summary
|
||||
if ($user->save()) {
|
||||
$summary['note'] = ($user->wasRecentlyCreated ? 'CREATED' : 'UPDATED');
|
||||
$summary['status'] = 'SUCCESS';
|
||||
} else {
|
||||
$errors = '';
|
||||
foreach ($user->getErrors()->getMessages() as $error) {
|
||||
$errors .= implode(", ",$error);
|
||||
}
|
||||
$summary['note'] = $snipeUser->getDN().' was not imported. REASON: '.$errors;
|
||||
$summary['status'] = 'ERROR';
|
||||
}
|
||||
} else {
|
||||
$summary = null;
|
||||
}
|
||||
}
|
||||
|
||||
// $summary['note'] = ($user->getOriginal('username') ? 'UPDATED' : 'CREATED'); // this seems, kinda, like, superfluous, relative to the $summary['note'] thing above, yeah?
|
||||
if($summary) { //if the $user wasn't dirty, $summary was set to null so that we will skip the following push()
|
||||
$this->summary->push($summary);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the users to update / create.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
*/
|
||||
private function processLdapUsers(): void
|
||||
{
|
||||
try {
|
||||
\Log::debug("CAL:LING GET LDAP SUSERS");
|
||||
$ldapUsers = $this->ldap->getLdapUsers();
|
||||
\Log::debug("END CALLING GET LDAP USERS");
|
||||
} catch (Exception $e) {
|
||||
$this->outputError($e);
|
||||
exit($e->getMessage());
|
||||
}
|
||||
|
||||
if (0 == $ldapUsers->count()) {
|
||||
$msg = 'ERROR: No users found!';
|
||||
Log::error($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->error($msg);
|
||||
}
|
||||
exit($msg);
|
||||
}
|
||||
|
||||
// Process each individual users
|
||||
foreach ($ldapUsers->getResults() as $user) { // AdLdap2's paginate() method is weird, it gets *everything* and ->getResults() returns *everything*
|
||||
$this->updateCreateUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mapped locations if a base_dn is provided.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function getMappedLocations()
|
||||
{
|
||||
$ldapOuLocation = Location::where('ldap_ou', '!=', '')->select(['id', 'ldap_ou'])->get();
|
||||
$locations = $ldapOuLocation->sortBy(function ($ou, $key) {
|
||||
return strlen($ou->ldap_ou);
|
||||
});
|
||||
if ($locations->count() > 0) {
|
||||
$msg = 'Some locations have special OUs set. Locations will be automatically set for users in those OUs.';
|
||||
LOG::debug($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->info($msg);
|
||||
}
|
||||
|
||||
$this->mappedLocations = $locations->pluck('ldap_ou', 'id'); // TODO: this seems ok-ish, but the key-> value is going location_id -> OU name, and the primary action here is the opposite of that - going from OU's to location ID's.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base dn if supplied.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function setBaseDn(): void
|
||||
{
|
||||
if ($this->option('base_dn')) {
|
||||
$this->ldap->baseDn = $this->option('base_dn');
|
||||
$msg = sprintf('Importing users from specified base DN: "%s"', $this->ldap->baseDn);
|
||||
LOG::debug($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->info($msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default location id for imported users.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function getUserDefaultLocation(): void
|
||||
{
|
||||
$location = $this->option('location_id') ?? $this->option('location');
|
||||
if ($location) {
|
||||
$userLocation = Location::where('name', '=', $location)
|
||||
->orWhere('id', '=', intval($location))
|
||||
->select(['name', 'id'])
|
||||
->first();
|
||||
if ($userLocation) {
|
||||
$msg = 'Importing users with default location: '.$userLocation->name.' ('.$userLocation->id.')';
|
||||
LOG::debug($msg);
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->info($msg);
|
||||
}
|
||||
|
||||
$this->defaultLocation = collect([
|
||||
$userLocation->id => $userLocation->name,
|
||||
]);
|
||||
} else {
|
||||
$msg = 'The supplied location is invalid!';
|
||||
LOG::error($msg);
|
||||
if ($this->dryrun) {
|
||||
$this->error($msg);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if LDAP intergration is enabled.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function checkIfLdapIsEnabled(): void
|
||||
{
|
||||
if (false === $this->settings['ldap_enabled']) {
|
||||
$msg = 'LDAP intergration is not enabled. Exiting sync process.';
|
||||
$this->info($msg);
|
||||
Log::info($msg);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to make sure we can access the server.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function checkLdapConnection(): void
|
||||
{
|
||||
try {
|
||||
$this->ldap->testLdapAdUserConnection();
|
||||
$this->ldap->testLdapAdBindConnection();
|
||||
} catch (Exception $e) {
|
||||
$this->outputError($e);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the json summary to the screen if enabled.
|
||||
*
|
||||
* @param Exception $error
|
||||
*/
|
||||
private function outputError($error): void
|
||||
{
|
||||
if ($this->option('json_summary')) {
|
||||
$json_summary = [
|
||||
'error' => true,
|
||||
'error_message' => $error->getMessage(),
|
||||
'summary' => [],
|
||||
];
|
||||
$this->info(json_encode($json_summary));
|
||||
}
|
||||
$this->error($error->getMessage());
|
||||
LOG::error($error);
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,10 @@ class MergeUsersByUsername extends Command
|
||||
foreach ($bad_user->assets as $asset) {
|
||||
$this->info( 'Updating asset '.$asset->asset_tag.' '.$asset->id.' to user '.$user->id);
|
||||
$asset->assigned_to = $user->id;
|
||||
$asset->save();
|
||||
if (!$asset->save()) {
|
||||
$this->error( 'Could not update assigned_to field on asset '.$asset->asset_tag.' '.$asset->id.' to user '.$user->id);
|
||||
$this->error( 'Error saving: '.$asset->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
// Walk the list of licenses
|
||||
|
||||
@@ -47,33 +47,33 @@ class MoveUploadsToNewDisk extends Command
|
||||
}
|
||||
$delete_local = $this->argument('delete_local');
|
||||
|
||||
$public_uploads['accessories'] = glob('storage/app/public/accessories'."/*.*");
|
||||
$public_uploads['assets'] = glob('storage/app/public/assets'."/*.*");
|
||||
$public_uploads['avatars'] = glob('storage/app/public/avatars'."/*.*");
|
||||
$public_uploads['categories'] = glob('storage/app/public/categories'."/*.*");
|
||||
$public_uploads['companies'] = glob('storage/app/public/companies'."/*.*");
|
||||
$public_uploads['components'] = glob('storage/app/public/components'."/*.*");
|
||||
$public_uploads['consumables'] = glob('storage/app/public/consumables'."/*.*");
|
||||
$public_uploads['departments'] = glob('storage/app/public/departments'."/*.*");
|
||||
$public_uploads['locations'] = glob('storage/app/public/locations'."/*.*");
|
||||
$public_uploads['manufacturers'] = glob('storage/app/public/manufacturers'."/*.*");
|
||||
$public_uploads['suppliers'] = glob('storage/app/public/suppliers'."/*.*");
|
||||
$public_uploads['assetmodels'] = glob('storage/app/public/models'."/*.*");
|
||||
$public_uploads['accessories'] = glob('public/accessories'."/*.*");
|
||||
$public_uploads['assets'] = glob('public/assets'."/*.*");
|
||||
$public_uploads['avatars'] = glob('public/avatars'."/*.*");
|
||||
$public_uploads['categories'] = glob('public/categories'."/*.*");
|
||||
$public_uploads['companies'] = glob('public/companies'."/*.*");
|
||||
$public_uploads['components'] = glob('public/components'."/*.*");
|
||||
$public_uploads['consumables'] = glob('public/consumables'."/*.*");
|
||||
$public_uploads['departments'] = glob('public/departments'."/*.*");
|
||||
$public_uploads['locations'] = glob('public/locations'."/*.*");
|
||||
$public_uploads['manufacturers'] = glob('public/manufacturers'."/*.*");
|
||||
$public_uploads['suppliers'] = glob('public/suppliers'."/*.*");
|
||||
$public_uploads['assetmodels'] = glob('public/models'."/*.*");
|
||||
|
||||
|
||||
// iterate files
|
||||
foreach($public_uploads as $public_type => $public_upload)
|
||||
{
|
||||
$type_count = 0;
|
||||
$this->info("\nThere are ".count($public_upload).' PUBLIC '.$public_type.' files.');
|
||||
$this->info("- There are ".count($public_upload).' PUBLIC '.$public_type.' files.');
|
||||
|
||||
for ($i = 0; $i < count($public_upload); $i++) {
|
||||
$type_count++;
|
||||
$filename = basename($public_upload[$i]);
|
||||
|
||||
try {
|
||||
Storage::disk('public')->put($public_type.'/'.$filename, file_get_contents($public_upload[$i]));
|
||||
$new_url = Storage::disk('public')->url($public_type.'/'.$filename, $filename);
|
||||
Storage::disk('public')->put('uploads/'.public_type.'/'.$filename, file_get_contents($public_upload[$i]));
|
||||
$new_url = Storage::disk('public')->url('uploads/'.$public_type.'/'.$filename, $filename);
|
||||
$this->info($type_count.'. PUBLIC: '.$filename.' was copied to '.$new_url);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
@@ -84,15 +84,16 @@ class MoveUploadsToNewDisk extends Command
|
||||
|
||||
}
|
||||
|
||||
$logos = glob('public/uploads'."/setting*.*");
|
||||
$this->info("\nThere are ".count($logos).' files that might be logos.');
|
||||
$logos = glob("public/uploads/setting*.*");
|
||||
$this->info("- There are ".count($logos).' files that might be logos.');
|
||||
$type_count = 0;
|
||||
|
||||
for ($l = 0; $l < count($logos); $l++) {
|
||||
foreach ($logos as $logo) {
|
||||
$this->info($logo);
|
||||
$type_count++;
|
||||
$filename = basename($logos[$l]);
|
||||
$new_url = Storage::disk('public')->url($logos[$l], file_get_contents($public_upload[$i]));
|
||||
$this->info($type_count.'. LOGO: '.$filename.' was copied to '.$new_url);
|
||||
$filename = basename($logo);
|
||||
Storage::disk('public')->put('uploads/'.$filename, file_get_contents($logo));
|
||||
$this->info($type_count.'. LOGO: '.$filename.' was copied to '.env('PUBLIC_AWS_URL').'/uploads/'.$filename);
|
||||
}
|
||||
|
||||
$private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*");
|
||||
@@ -107,8 +108,7 @@ class MoveUploadsToNewDisk extends Command
|
||||
|
||||
foreach($private_uploads as $private_type => $private_upload)
|
||||
{
|
||||
$this->info("\nThere are ".count($private_upload).' PRIVATE '.$private_type.' files.');
|
||||
// $this->info(print_r($private_upload, true));
|
||||
$this->info("- There are ".count($private_upload).' PRIVATE '.$private_type.' files.');
|
||||
|
||||
$type_count = 0;
|
||||
for ($x = 0; $x < count($private_upload); $x++) {
|
||||
@@ -116,7 +116,7 @@ class MoveUploadsToNewDisk extends Command
|
||||
$filename = basename($private_upload[$x]);
|
||||
|
||||
try {
|
||||
Storage::disk('private_uploads')->put($private_type.'/'.$filename, file_get_contents($public_upload[$i]));
|
||||
Storage::put($private_type.'/'.$filename, file_get_contents($private_upload[$i]));
|
||||
$new_url = Storage::url($private_type.'/'.$filename, $filename);
|
||||
$this->info($type_count.'. PRIVATE: '.$filename.' was copied to '.$new_url);
|
||||
|
||||
|
||||
@@ -55,8 +55,10 @@ class ObjectImportCommand extends Command
|
||||
->setShouldNotify($this->option('send-welcome'))
|
||||
->setUsernameFormat($this->option('username_format'));
|
||||
|
||||
$logFile = $this->option('logfile');
|
||||
\Log::useFiles($logFile);
|
||||
|
||||
// This $logFile/useFiles() bit is currently broken, so commenting it out for now
|
||||
// $logFile = $this->option('logfile');
|
||||
// \Log::useFiles($logFile);
|
||||
$this->comment('======= Importing Items from '.$filename.' =========');
|
||||
$importer->import();
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class PurgeLoginAttempts extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:purge-logins';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clears the login_attempts table';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ($this->confirm("\n****************************************************\nTHIS WILL DELETE ALL OF THE YOUR LOGIN ATTEMPT RECORDS. \nThere is NO undo! \n****************************************************\n\nDo you wish to continue? No backsies! [y|N]")) {
|
||||
\DB::statement('delete from login_attempts');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,11 +64,8 @@ class ResetDemoSettings extends Command
|
||||
$settings->thumbnail_max_h = '30';
|
||||
$settings->locale = 'en';
|
||||
$settings->version_footer = 'on';
|
||||
$settings->support_footer = 'on';
|
||||
$settings->support_footer = null;
|
||||
$settings->saml_enabled = '0';
|
||||
$settings->saml_sp_entitiyid = '0';
|
||||
$settings->saml_sp_acs_url = null;
|
||||
$settings->saml_sp_sls_url = null;
|
||||
$settings->saml_sp_x509cert = null;
|
||||
$settings->saml_idp_metadata = null;
|
||||
$settings->saml_attr_mapping_username = null;
|
||||
@@ -84,7 +81,7 @@ class ResetDemoSettings extends Command
|
||||
$user->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Notifications\ExpectedCheckinAdminNotification;
|
||||
use App\Notifications\ExpectedCheckinNotification;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Recipients\AlertRecipient;
|
||||
|
||||
class SendExpectedCheckinAlerts extends Command
|
||||
{
|
||||
@@ -58,7 +59,7 @@ class SendExpectedCheckinAlerts extends Command
|
||||
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
|
||||
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +55,14 @@ class SendExpirationAlerts extends Command
|
||||
$assets = Asset::getExpiringWarrantee($threshold);
|
||||
if ($assets->count() > 0) {
|
||||
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold]));
|
||||
Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold));
|
||||
\Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold));
|
||||
}
|
||||
|
||||
// Expiring licenses
|
||||
$licenses = License::getExpiringLicenses($threshold);
|
||||
if ($licenses->count() > 0) {
|
||||
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold]));
|
||||
Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold));
|
||||
\Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold));
|
||||
}
|
||||
} else {
|
||||
if ($settings->alert_email == '') {
|
||||
|
||||
@@ -52,7 +52,7 @@ class SendInventoryAlerts extends Command
|
||||
return new AlertRecipient($item);
|
||||
});
|
||||
|
||||
Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
|
||||
\Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
|
||||
}
|
||||
} else {
|
||||
if ($settings->alert_email == '') {
|
||||
|
||||
+299
-13
@@ -55,27 +55,313 @@ class Helper
|
||||
|
||||
/**
|
||||
* Static colors for pie charts.
|
||||
* This is inelegant, and could be refactored later.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.3]
|
||||
* @return Array
|
||||
*/
|
||||
public static function chartColors()
|
||||
public static function defaultChartColors($index = 0)
|
||||
{
|
||||
$colors = [
|
||||
'#f56954',
|
||||
'#00a65a',
|
||||
'#f39c12',
|
||||
'#00c0ef',
|
||||
'#3c8dbc',
|
||||
'#d2d6de',
|
||||
'#3c8dbc',
|
||||
'#3c8dbc',
|
||||
'#3c8dbc',
|
||||
|
||||
"#008941",
|
||||
"#FF4A46",
|
||||
"#006FA6",
|
||||
"#A30059",
|
||||
"#1CE6FF",
|
||||
"#FFDBE5",
|
||||
"#7A4900",
|
||||
"#0000A6",
|
||||
"#63FFAC",
|
||||
"#B79762",
|
||||
"#004D43",
|
||||
"#8FB0FF",
|
||||
"#997D87",
|
||||
"#5A0007",
|
||||
"#809693",
|
||||
"#FEFFE6",
|
||||
"#1B4400",
|
||||
"#4FC601",
|
||||
"#3B5DFF",
|
||||
"#4A3B53",
|
||||
"#FF2F80",
|
||||
"#61615A",
|
||||
"#BA0900",
|
||||
"#6B7900",
|
||||
"#00C2A0",
|
||||
"#FFAA92",
|
||||
"#FF90C9",
|
||||
"#B903AA",
|
||||
"#D16100",
|
||||
"#DDEFFF",
|
||||
"#000035",
|
||||
"#7B4F4B",
|
||||
"#A1C299",
|
||||
"#300018",
|
||||
"#0AA6D8",
|
||||
"#013349",
|
||||
"#00846F",
|
||||
"#372101",
|
||||
"#FFB500",
|
||||
"#C2FFED",
|
||||
"#A079BF",
|
||||
"#CC0744",
|
||||
"#C0B9B2",
|
||||
"#C2FF99",
|
||||
"#001E09",
|
||||
"#00489C",
|
||||
"#6F0062",
|
||||
"#0CBD66",
|
||||
"#EEC3FF",
|
||||
"#456D75",
|
||||
"#B77B68",
|
||||
"#7A87A1",
|
||||
"#788D66",
|
||||
"#885578",
|
||||
"#FAD09F",
|
||||
"#FF8A9A",
|
||||
"#D157A0",
|
||||
"#BEC459",
|
||||
"#456648",
|
||||
"#0086ED",
|
||||
"#886F4C",
|
||||
"#34362D",
|
||||
"#B4A8BD",
|
||||
"#00A6AA",
|
||||
"#452C2C",
|
||||
"#636375",
|
||||
"#A3C8C9",
|
||||
"#FF913F",
|
||||
"#938A81",
|
||||
"#575329",
|
||||
"#00FECF",
|
||||
"#B05B6F",
|
||||
"#8CD0FF",
|
||||
"#3B9700",
|
||||
"#04F757",
|
||||
"#C8A1A1",
|
||||
"#1E6E00",
|
||||
"#7900D7",
|
||||
"#A77500",
|
||||
"#6367A9",
|
||||
"#A05837",
|
||||
"#6B002C",
|
||||
"#772600",
|
||||
"#D790FF",
|
||||
"#9B9700",
|
||||
"#549E79",
|
||||
"#FFF69F",
|
||||
"#201625",
|
||||
"#72418F",
|
||||
"#BC23FF",
|
||||
"#99ADC0",
|
||||
"#3A2465",
|
||||
"#922329",
|
||||
"#5B4534",
|
||||
"#FDE8DC",
|
||||
"#404E55",
|
||||
"#0089A3",
|
||||
"#CB7E98",
|
||||
"#A4E804",
|
||||
"#324E72",
|
||||
"#6A3A4C",
|
||||
"#83AB58",
|
||||
"#001C1E",
|
||||
"#D1F7CE",
|
||||
"#004B28",
|
||||
"#C8D0F6",
|
||||
"#A3A489",
|
||||
"#806C66",
|
||||
"#222800",
|
||||
"#BF5650",
|
||||
"#E83000",
|
||||
"#66796D",
|
||||
"#DA007C",
|
||||
"#FF1A59",
|
||||
"#8ADBB4",
|
||||
"#1E0200",
|
||||
"#5B4E51",
|
||||
"#C895C5",
|
||||
"#320033",
|
||||
"#FF6832",
|
||||
"#66E1D3",
|
||||
"#CFCDAC",
|
||||
"#D0AC94",
|
||||
"#7ED379",
|
||||
"#012C58",
|
||||
"#7A7BFF",
|
||||
"#D68E01",
|
||||
"#353339",
|
||||
"#78AFA1",
|
||||
"#FEB2C6",
|
||||
"#75797C",
|
||||
"#837393",
|
||||
"#943A4D",
|
||||
"#B5F4FF",
|
||||
"#D2DCD5",
|
||||
"#9556BD",
|
||||
"#6A714A",
|
||||
"#001325",
|
||||
"#02525F",
|
||||
"#0AA3F7",
|
||||
"#E98176",
|
||||
"#DBD5DD",
|
||||
"#5EBCD1",
|
||||
"#3D4F44",
|
||||
"#7E6405",
|
||||
"#02684E",
|
||||
"#962B75",
|
||||
"#8D8546",
|
||||
"#9695C5",
|
||||
"#E773CE",
|
||||
"#D86A78",
|
||||
"#3E89BE",
|
||||
"#CA834E",
|
||||
"#518A87",
|
||||
"#5B113C",
|
||||
"#55813B",
|
||||
"#E704C4",
|
||||
"#00005F",
|
||||
"#A97399",
|
||||
"#4B8160",
|
||||
"#59738A",
|
||||
"#FF5DA7",
|
||||
"#F7C9BF",
|
||||
"#643127",
|
||||
"#513A01",
|
||||
"#6B94AA",
|
||||
"#51A058",
|
||||
"#A45B02",
|
||||
"#1D1702",
|
||||
"#E20027",
|
||||
"#E7AB63",
|
||||
"#4C6001",
|
||||
"#9C6966",
|
||||
"#64547B",
|
||||
"#97979E",
|
||||
"#006A66",
|
||||
"#391406",
|
||||
"#F4D749",
|
||||
"#0045D2",
|
||||
"#006C31",
|
||||
"#DDB6D0",
|
||||
"#7C6571",
|
||||
"#9FB2A4",
|
||||
"#00D891",
|
||||
"#15A08A",
|
||||
"#BC65E9",
|
||||
"#FFFFFE",
|
||||
"#C6DC99",
|
||||
"#203B3C",
|
||||
"#671190",
|
||||
"#6B3A64",
|
||||
"#F5E1FF",
|
||||
"#FFA0F2",
|
||||
"#CCAA35",
|
||||
"#374527",
|
||||
"#8BB400",
|
||||
"#797868",
|
||||
"#C6005A",
|
||||
"#3B000A",
|
||||
"#C86240",
|
||||
"#29607C",
|
||||
"#402334",
|
||||
"#7D5A44",
|
||||
"#CCB87C",
|
||||
"#B88183",
|
||||
"#AA5199",
|
||||
"#B5D6C3",
|
||||
"#A38469",
|
||||
"#9F94F0",
|
||||
"#A74571",
|
||||
"#B894A6",
|
||||
"#71BB8C",
|
||||
"#00B433",
|
||||
"#789EC9",
|
||||
"#6D80BA",
|
||||
"#953F00",
|
||||
"#5EFF03",
|
||||
"#E4FFFC",
|
||||
"#1BE177",
|
||||
"#BCB1E5",
|
||||
"#76912F",
|
||||
"#003109",
|
||||
"#0060CD",
|
||||
"#D20096",
|
||||
"#895563",
|
||||
"#29201D",
|
||||
"#5B3213",
|
||||
"#A76F42",
|
||||
"#89412E",
|
||||
"#1A3A2A",
|
||||
"#494B5A",
|
||||
"#A88C85",
|
||||
"#F4ABAA",
|
||||
"#A3F3AB",
|
||||
"#00C6C8",
|
||||
"#EA8B66",
|
||||
"#958A9F",
|
||||
"#BDC9D2",
|
||||
"#9FA064",
|
||||
"#BE4700",
|
||||
"#658188",
|
||||
"#83A485",
|
||||
"#453C23",
|
||||
"#47675D",
|
||||
"#3A3F00",
|
||||
"#061203",
|
||||
"#DFFB71",
|
||||
"#868E7E",
|
||||
"#98D058",
|
||||
"#6C8F7D",
|
||||
"#D7BFC2",
|
||||
"#3C3E6E",
|
||||
"#D83D66",
|
||||
"#2F5D9B",
|
||||
"#6C5E46",
|
||||
"#D25B88",
|
||||
"#5B656C",
|
||||
"#00B57F",
|
||||
"#545C46",
|
||||
"#866097",
|
||||
"#365D25",
|
||||
"#252F99",
|
||||
"#00CCFF",
|
||||
"#674E60",
|
||||
"#FC009C",
|
||||
"#92896B",
|
||||
];
|
||||
return $colors;
|
||||
|
||||
|
||||
|
||||
return $colors[$index];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
||||
*
|
||||
* @param string $hexCode Supported formats: `#FFF`, `#FFFFFF`, `FFF`, `FFFFFF`
|
||||
* @param float $adjustPercent A number between -1 and 1. E.g. 0.3 = 30% lighter; -0.4 = 40% darker.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function adjustBrightness($hexCode, $adjustPercent) {
|
||||
$hexCode = ltrim($hexCode, '#');
|
||||
|
||||
if (strlen($hexCode) == 3) {
|
||||
$hexCode = $hexCode[0] . $hexCode[0] . $hexCode[1] . $hexCode[1] . $hexCode[2] . $hexCode[2];
|
||||
}
|
||||
|
||||
$hexCode = array_map('hexdec', str_split($hexCode, 2));
|
||||
|
||||
foreach ($hexCode as & $color) {
|
||||
$adjustableLimit = $adjustPercent < 0 ? $color : 255 - $color;
|
||||
$adjustAmount = ceil($adjustableLimit * $adjustPercent);
|
||||
|
||||
$color = str_pad(dechex($color + $adjustAmount), 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return '#' . implode($hexCode);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -75,7 +75,8 @@ class AccessoryCheckoutController extends Controller
|
||||
'accessory_id' => $accessory->id,
|
||||
'created_at' => Carbon::now(),
|
||||
'user_id' => Auth::id(),
|
||||
'assigned_to' => $request->get('assigned_to')
|
||||
'assigned_to' => $request->get('assigned_to'),
|
||||
'note' => $request->input('note')
|
||||
]);
|
||||
|
||||
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
|
||||
|
||||
@@ -94,7 +94,7 @@ class AcceptanceController extends Controller {
|
||||
if (!Storage::exists('private_uploads/signatures')) Storage::makeDirectory('private_uploads/signatures', 775);
|
||||
|
||||
|
||||
|
||||
$sig_filename = '';
|
||||
if ($request->filled('signature_output')) {
|
||||
$sig_filename = "siglog-" .Str::uuid() . '-'.date('Y-m-d-his').".png";
|
||||
$data_uri = e($request->input('signature_output'));
|
||||
|
||||
@@ -154,7 +154,6 @@ class AccessoriesController extends Controller
|
||||
$offset = request('offset', 0);
|
||||
$limit = request('limit', 50);
|
||||
|
||||
$accessory->lastCheckoutArray = $accessory->lastCheckout->toArray();
|
||||
$accessory_users = $accessory->users;
|
||||
$total = $accessory_users->count();
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class LocationsController extends Controller
|
||||
$allowed_columns = [
|
||||
'id','name','address','address2','city','state','country','zip','created_at',
|
||||
'updated_at','manager_id','image',
|
||||
'assigned_assets_count','users_count','assets_count','currency'];
|
||||
'assigned_assets_count','users_count','assets_count','currency','ldap_ou'];
|
||||
|
||||
$locations = Location::with('parent', 'manager', 'children')->select([
|
||||
'locations.id',
|
||||
@@ -42,6 +42,7 @@ class LocationsController extends Controller
|
||||
'locations.created_at',
|
||||
'locations.updated_at',
|
||||
'locations.image',
|
||||
'locations.ldap_ou',
|
||||
'locations.currency'
|
||||
])->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
|
||||
@@ -15,6 +15,9 @@ use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Models\Ldap; // forward-port of v4 LDAP model for Sync
|
||||
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
@@ -75,10 +78,22 @@ class SettingsController extends Controller
|
||||
|
||||
Log::info('Preparing to get sample user set from LDAP directory');
|
||||
// Get a sample of 10 users so user can verify the data is correct
|
||||
$settings = Setting::getSettings();
|
||||
try {
|
||||
Log::info('Testing LDAP sync');
|
||||
error_reporting(E_ALL & ~E_DEPRECATED); // workaround for php7.4, which deprecates ldap_control_paged_result
|
||||
$users = $ldap->testUserImportSync();
|
||||
// $users = $ldap->testUserImportSync(); // from AdLdap2 from v5, disabling and falling back to v4's sync code
|
||||
$users = collect(Ldap::findLdapUsers())->slice(0, 11)->filter(function ($value, $key) { //choosing ELEVEN because one is going to be the count, which we're about to filter out in the next line
|
||||
return is_int($key);
|
||||
})->map(function ($item) use ($settings) {
|
||||
return (object) [
|
||||
'username' => $item[$settings['ldap_username_field']][0] ?? null,
|
||||
'employee_number' => $item[$settings['ldap_emp_num']][0] ?? null,
|
||||
'lastname' => $item[$settings['ldap_lname_field']][0] ?? null,
|
||||
'firstname' => $item[$settings['ldap_fname_field']][0] ?? null,
|
||||
'email' => $item[$settings['ldap_email']][0] ?? null,
|
||||
];
|
||||
});
|
||||
$message['user_sync'] = [
|
||||
'users' => $users
|
||||
];
|
||||
@@ -93,6 +108,51 @@ class SettingsController extends Controller
|
||||
return response()->json($message, 200);
|
||||
}
|
||||
|
||||
public function ldaptestlogin(Request $request, LdapAd $ldap)
|
||||
{
|
||||
|
||||
if (Setting::getSettings()->ldap_enabled!='1') {
|
||||
\Log::debug('LDAP is not enabled. Cannot test.');
|
||||
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
|
||||
}
|
||||
|
||||
|
||||
$rules = array(
|
||||
'ldaptest_user' => 'required',
|
||||
'ldaptest_password' => 'required'
|
||||
);
|
||||
|
||||
$validator = Validator::make($request->all(), $rules);
|
||||
if ($validator->fails()) {
|
||||
\Log::debug('LDAP Validation test failed.');
|
||||
$validation_errors = implode(' ',$validator->errors()->all());
|
||||
return response()->json(['message' => $validator->errors()->all()], 400);
|
||||
}
|
||||
|
||||
|
||||
\Log::debug('Preparing to test LDAP login');
|
||||
try {
|
||||
DB::beginTransaction(); //this was the easiest way to invoke a full test of an LDAP login without adding new users to the DB (which may not be desired)
|
||||
|
||||
// $results = $ldap->ldap->auth()->attempt($request->input('ldaptest_username'), $request->input('ldaptest_password'), true);
|
||||
// can't do this because that's a protected property.
|
||||
|
||||
$results = $ldap->ldapLogin($request->input('ldaptest_user'), $request->input('ldaptest_password')); // this would normally create a user on success (if they didn't already exist), but for the transaction
|
||||
if($results) {
|
||||
return response()->json(['message' => 'It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.'], 200);
|
||||
} else {
|
||||
return response()->json(['message' => 'Login Failed. '. $request->input('ldaptest_user').' did not successfully bind to LDAP.'], 400);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Connection failed');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
} finally {
|
||||
DB::rollBack(); // ALWAYS rollback, whether success or failure
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function slacktest(Request $request)
|
||||
{
|
||||
|
||||
@@ -137,7 +197,7 @@ class SettingsController extends Controller
|
||||
try {
|
||||
Notification::send(Setting::first(), new MailTest());
|
||||
return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,25 +167,30 @@ class StatuslabelsController extends Controller
|
||||
{
|
||||
$this->authorize('view', Statuslabel::class);
|
||||
|
||||
$statuslabels = Statuslabel::with('assets')->groupBy('id')->withCount('assets as assets_count')->get();
|
||||
$statuslabels = Statuslabel::with('assets')
|
||||
->groupBy('id')
|
||||
->withCount('assets as assets_count')
|
||||
->get();
|
||||
|
||||
$labels=[];
|
||||
$points=[];
|
||||
$colors=[];
|
||||
$default_color_count = 0;
|
||||
|
||||
foreach ($statuslabels as $statuslabel) {
|
||||
if ($statuslabel->assets_count > 0) {
|
||||
|
||||
$labels[]=$statuslabel->name. ' ('.number_format($statuslabel->assets_count).')';
|
||||
$points[]=$statuslabel->assets_count;
|
||||
|
||||
if ($statuslabel->color!='') {
|
||||
$colors[]=$statuslabel->color;
|
||||
$colors_array[] = $statuslabel->color;
|
||||
} else {
|
||||
$colors_array[] = Helper::defaultChartColors($default_color_count);
|
||||
$default_color_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$colors_array = array_merge($colors, Helper::chartColors());
|
||||
|
||||
$result= [
|
||||
"labels" => $labels,
|
||||
"datasets" => [ [
|
||||
|
||||
@@ -67,7 +67,9 @@ class UsersController extends Controller
|
||||
|
||||
|
||||
if (($request->filled('deleted')) && ($request->input('deleted')=='true')) {
|
||||
$users = $users->GetDeleted();
|
||||
$users = $users->onlyTrashed();
|
||||
} elseif (($request->filled('all')) && ($request->input('all')=='true')) {
|
||||
$users = $users->withTrashed();
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
|
||||
@@ -100,9 +100,9 @@ class AssetMaintenancesController extends Controller
|
||||
$assetMaintenance = new AssetMaintenance();
|
||||
$assetMaintenance->supplier_id = $request->input('supplier_id');
|
||||
$assetMaintenance->is_warranty = $request->input('is_warranty');
|
||||
$assetMaintenance->cost = e($request->input('cost'));
|
||||
$assetMaintenance->notes = e($request->input('notes'));
|
||||
$asset = Asset::find(e($request->input('asset_id')));
|
||||
$assetMaintenance->cost = $request->input('cost');
|
||||
$assetMaintenance->notes = $request->input('notes');
|
||||
$asset = Asset::find($request->input('asset_id'));
|
||||
|
||||
if ((!Company::isCurrentUserHasAccess($asset)) && ($asset!=null)) {
|
||||
return static::getInsufficientPermissionsRedirect();
|
||||
|
||||
@@ -76,9 +76,32 @@ class AssetCheckinController extends Controller
|
||||
$asset->status_id = e($request->get('status_id'));
|
||||
}
|
||||
|
||||
// This is just meant to correct legacy issues where some user data would have 0
|
||||
// as a location ID, which isn't valid. Later versions of Snipe-IT have stricter validation
|
||||
// rules, so it's necessary to fix this for long-time users. It's kinda gross, but will help
|
||||
// people (and their data) in the long run
|
||||
|
||||
if ($asset->rtd_location_id=='0') {
|
||||
\Log::debug('Manually override the RTD location IDs');
|
||||
\Log::debug('Original RTD Location ID: '.$asset->rtd_location_id);
|
||||
$asset->rtd_location_id = '';
|
||||
\Log::debug('New RTD Location ID: '.$asset->rtd_location_id);
|
||||
}
|
||||
|
||||
if ($asset->location_id=='0') {
|
||||
\Log::debug('Manually override the location IDs');
|
||||
\Log::debug('Original Location ID: '.$asset->location_id);
|
||||
$asset->location_id = '';
|
||||
\Log::debug('New RTD Location ID: '.$asset->location_id);
|
||||
}
|
||||
|
||||
$asset->location_id = $asset->rtd_location_id;
|
||||
\Log::debug('After Location ID: '.$asset->location_id);
|
||||
\Log::debug('After RTD Location ID: '.$asset->rtd_location_id);
|
||||
|
||||
|
||||
if ($request->filled('location_id')) {
|
||||
\Log::debug('NEW Location ID: '.$request->get('location_id'));
|
||||
$asset->location_id = e($request->get('location_id'));
|
||||
}
|
||||
|
||||
@@ -97,6 +120,6 @@ class AssetCheckinController extends Controller
|
||||
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.checkin.success'));
|
||||
}
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->route("hardware.index")->with('error', trans('admin/hardware/message.checkin.error'));
|
||||
return redirect()->route("hardware.index")->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class AssetCheckoutController extends Controller
|
||||
}
|
||||
|
||||
// Redirect to the asset management page with error
|
||||
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
|
||||
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
|
||||
} catch (CheckoutNotAllowed $e) {
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Http\Controllers\Auth;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ForgotPasswordController extends Controller
|
||||
{
|
||||
@@ -60,7 +59,7 @@ class ForgotPasswordController extends Controller
|
||||
*/
|
||||
|
||||
$request->validate([
|
||||
'email' => ['required', 'email', 'max:255'],
|
||||
'username' => ['required', 'max:255'],
|
||||
]);
|
||||
|
||||
|
||||
@@ -74,16 +73,16 @@ class ForgotPasswordController extends Controller
|
||||
*/
|
||||
$response = $this->broker()->sendResetLink(
|
||||
array_merge(
|
||||
$request->only('email'),
|
||||
$request->only('username'),
|
||||
['activated' => '1'],
|
||||
['ldap_import' => '0']
|
||||
)
|
||||
);
|
||||
|
||||
if ($response === \Password::RESET_LINK_SENT) {
|
||||
\Log::info('Password reset attempt: User '.$request->input('email').' found, password reset sent');
|
||||
\Log::info('Password reset attempt: User '.$request->input('username').' WAS found, password reset sent');
|
||||
} else {
|
||||
\Log::info('Password reset attempt: User '.$request->input('email').' not found or user is inactive');
|
||||
\Log::info('Password reset attempt: User matching username '.$request->input('username').' NOT FOUND or user is inactive');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@ class LoginController extends Controller
|
||||
|
||||
if ($user = Auth::user()) {
|
||||
$user->last_login = \Carbon::now();
|
||||
$user->activated = 1;
|
||||
$user->save();
|
||||
}
|
||||
// Redirect to the users page
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\SaveUserRequest;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class ResetPasswordController extends Controller
|
||||
{
|
||||
@@ -29,6 +33,8 @@ class ResetPasswordController extends Controller
|
||||
*/
|
||||
protected $redirectTo = '/';
|
||||
|
||||
protected $username = 'username';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
@@ -44,7 +50,7 @@ class ResetPasswordController extends Controller
|
||||
return [
|
||||
'token' => 'required',
|
||||
'username' => 'required',
|
||||
'password' => 'required|confirmed|'.Setting::passwordComplexityRulesSaving('update'),
|
||||
'password' => 'confirmed|'.Setting::passwordComplexityRulesSaving('store'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -55,7 +61,7 @@ class ResetPasswordController extends Controller
|
||||
'username', 'password', 'password_confirmation', 'token'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function showResetForm(Request $request, $token = null)
|
||||
{
|
||||
@@ -67,11 +73,48 @@ class ResetPasswordController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function reset(Request $request)
|
||||
{
|
||||
|
||||
$messages = [
|
||||
'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'),
|
||||
];
|
||||
|
||||
$request->validate($this->rules(), $request->all(), $this->validationErrorMessages());
|
||||
|
||||
// Check to see if the user even exists
|
||||
$user = User::where('username', '=', $request->input('username'))->first();
|
||||
|
||||
$broker = $this->broker();
|
||||
if (strpos(Setting::passwordComplexityRulesSaving('store'), 'disallow_same_pwd_as_user_fields') !== FALSE) {
|
||||
$request->validate(
|
||||
[
|
||||
'password' => 'required|notIn:["'.$user->email.'","'.$user->username.'","'.$user->first_name.'","'.$user->last_name.'"'
|
||||
], $messages);
|
||||
|
||||
}
|
||||
|
||||
|
||||
$response = $broker->reset(
|
||||
$this->credentials($request), function ($user, $password) {
|
||||
$this->resetPassword($user, $password);
|
||||
}
|
||||
);
|
||||
|
||||
return $response == \Password::PASSWORD_RESET
|
||||
? $this->sendResetResponse($request, $response)
|
||||
: $this->sendResetFailedResponse($request, $response);
|
||||
}
|
||||
|
||||
|
||||
protected function sendResetFailedResponse(Request $request, $response)
|
||||
{
|
||||
return redirect()->back()
|
||||
->withInput(['username'=> $request->input('username')])
|
||||
->withErrors(['username' => trans($response)]);
|
||||
->withErrors(['username' => trans($response), 'password' => trans($response)]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -53,8 +53,10 @@ class SamlController extends Controller
|
||||
if (empty($metadata)) {
|
||||
return response()->view('errors.403', [], 403);
|
||||
}
|
||||
|
||||
return response($metadata)->header('Content-Type', 'text/xml');
|
||||
|
||||
return response()->streamDownload(function () use ($metadata) {
|
||||
echo $metadata;
|
||||
}, 'snipe-it-metadata.xml', ['Content-Type' => 'text/xml']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -124,7 +124,7 @@ class ComponentsController extends Controller
|
||||
}
|
||||
$min = $component->numCHeckedOut();
|
||||
$validator = Validator::make($request->all(), [
|
||||
"qty" => "required|numeric|gt:$min"
|
||||
"qty" => "required|numeric|min:$min"
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
|
||||
@@ -39,7 +39,8 @@ class LicenseFilesController extends Controller
|
||||
$upload_success = false;
|
||||
foreach ($request->file('file') as $file) {
|
||||
|
||||
$file_name = 'license-'.date('Y-m-d-His').'-'.$file->getBasename().'.'.$file->getClientOriginalExtension();
|
||||
|
||||
$file_name = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$file->getClientOriginalExtension())).'.'.$file->getClientOriginalExtension();
|
||||
|
||||
|
||||
$upload_success = $file->storeAs('private_uploads/licenses', $file_name);
|
||||
|
||||
@@ -156,6 +156,28 @@ class ProfileController extends Controller
|
||||
if (!Hash::check($request->input('current_password'), $user->password)) {
|
||||
$validator->errors()->add('current_password', trans('validation.hashed_pass'));
|
||||
}
|
||||
|
||||
// This checks to make sure that the user's password isn't the same as their username,
|
||||
// email address, first name or last name (see https://github.com/snipe/snipe-it/issues/8661)
|
||||
// While this is handled via SaveUserRequest form request in other places, we have to do this manually
|
||||
// here because we don't have the username, etc form fields available in the profile password change
|
||||
// form.
|
||||
|
||||
// There may be a more elegant way to do this in the future.
|
||||
|
||||
// First let's see if that option is enabled in the settings
|
||||
if (strpos(Setting::passwordComplexityRulesSaving('store'), 'disallow_same_pwd_as_user_fields') !== FALSE) {
|
||||
if (($request->input('password') == $user->username) ||
|
||||
($request->input('password') == $user->email) ||
|
||||
($request->input('password') == $user->first_name) ||
|
||||
($request->input('password') == $user->last_name))
|
||||
{
|
||||
$validator->errors()->add('password', trans('validation.disallow_same_pwd_as_user_fields'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -217,6 +217,99 @@ class ReportsController extends Controller
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exports the activity report to CSV
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0.7]
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function postActivityReport(Request $request)
|
||||
{
|
||||
ini_set('max_execution_time', 12000);
|
||||
$this->authorize('reports.view');
|
||||
|
||||
\Debugbar::disable();
|
||||
$response = new StreamedResponse(function () {
|
||||
|
||||
\Log::debug('Starting streamed response');
|
||||
|
||||
// Open output stream
|
||||
$handle = fopen('php://output', 'w');
|
||||
stream_set_timeout($handle, 2000);
|
||||
|
||||
$header = [
|
||||
trans('general.date'),
|
||||
trans('general.admin'),
|
||||
trans('general.action'),
|
||||
trans('general.type'),
|
||||
trans('general.item'),
|
||||
'To',
|
||||
trans('general.notes'),
|
||||
'Changed',
|
||||
|
||||
];
|
||||
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
|
||||
\Log::debug('Starting headers: '.$executionTime);
|
||||
fputcsv($handle, $header);
|
||||
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
|
||||
\Log::debug('Added headers: '.$executionTime);
|
||||
|
||||
$actionlogs = Actionlog::with('item', 'user', 'target','location')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->chunk(20, function($actionlogs) use($handle) {
|
||||
|
||||
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
|
||||
\Log::debug('Walking results: '.$executionTime);
|
||||
$count = 0;
|
||||
|
||||
foreach ($actionlogs as $actionlog) {
|
||||
|
||||
$count++;
|
||||
$target_name = '';
|
||||
|
||||
if ($actionlog->target) {
|
||||
if ($actionlog->targetType()=='user') {
|
||||
$target_name = $actionlog->target->getFullNameAttribute();
|
||||
} else {
|
||||
$target_name = $actionlog->target->getDisplayNameAttribute();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$row = [
|
||||
$actionlog->created_at,
|
||||
($actionlog->user) ? e($actionlog->user->getFullNameAttribute()) : '',
|
||||
$actionlog->present()->actionType(),
|
||||
e($actionlog->itemType()),
|
||||
($actionlog->itemType()=='user') ? $actionlog->filename : e($actionlog->item->getDisplayNameAttribute()),
|
||||
$target_name,
|
||||
($actionlog->note) ? e($actionlog->note): '',
|
||||
$actionlog->log_meta,
|
||||
];
|
||||
fputcsv($handle, $row);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Close the output stream
|
||||
fclose($handle);
|
||||
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
|
||||
\Log::debug('-- SCRIPT COMPLETED IN '. $executionTime);
|
||||
|
||||
}, 200, [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition'
|
||||
=> 'attachment; filename="activity-report-'.date('Y-m-d-his').'.csv"',
|
||||
]);
|
||||
|
||||
|
||||
return $response;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Displays license report
|
||||
*
|
||||
|
||||
@@ -1015,7 +1015,7 @@ class SettingsController extends Controller
|
||||
|
||||
$path = 'app/backups';
|
||||
$backup_files = Storage::files($path);
|
||||
$files = [];
|
||||
$files_raw = [];
|
||||
|
||||
if (count($backup_files) > 0) {
|
||||
for ($f = 0; $f < count($backup_files); ++$f) {
|
||||
@@ -1023,7 +1023,7 @@ class SettingsController extends Controller
|
||||
// Skip dotfiles like .gitignore and .DS_STORE
|
||||
if ((substr(basename($backup_files[$f]), 0, 1) != '.')) {
|
||||
|
||||
$files[] = [
|
||||
$files_raw[] = [
|
||||
'filename' => basename($backup_files[$f]),
|
||||
'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])),
|
||||
'modified' => Storage::lastModified($backup_files[$f]),
|
||||
@@ -1035,6 +1035,9 @@ class SettingsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse the array so it lists oldest first
|
||||
$files = array_reverse($files_raw);
|
||||
|
||||
return view('settings/backups', compact('path', 'files'));
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ use App\Models\User;
|
||||
use App\Notifications\RequestAssetCancelation;
|
||||
use App\Notifications\RequestAssetNotification;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Redirect;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* This controller handles all actions related to the ability for users
|
||||
|
||||
@@ -38,6 +38,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\CheckLocale::class,
|
||||
\App\Http\Middleware\CheckForTwoFactor::class,
|
||||
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
|
||||
\App\Http\Middleware\AssetCountForSidebar::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Auth;
|
||||
use App\Models\Asset;
|
||||
use Closure;
|
||||
|
||||
class AssetCountForSidebar
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
try
|
||||
{
|
||||
$total_rtd_sidebar = Asset::RTD()->count();
|
||||
view()->share('total_rtd_sidebar', $total_rtd_sidebar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_deployed_sidebar = Asset::Deployed()->count();
|
||||
view()->share('total_deployed_sidebar', $total_deployed_sidebar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_archived_sidebar = Asset::Archived()->count();
|
||||
view()->share('total_archived_sidebar', $total_archived_sidebar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_pending_sidebar = Asset::Pending()->count();
|
||||
view()->share('total_pending_sidebar', $total_pending_sidebar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$total_undeployable_sidebar = Asset::Undeployable()->count();
|
||||
view()->share('total_undeployable_sidebar', $total_undeployable_sidebar);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ class SecurityHeaders
|
||||
$csp_policy[] = "connect-src 'self'";
|
||||
$csp_policy[] = "object-src 'none'";
|
||||
$csp_policy[] = "font-src 'self' data:";
|
||||
$csp_policy[] = "img-src 'self' data: gravatar.com maps.google.com maps.gstatic.com *.googleapis.com";
|
||||
$csp_policy[] = "img-src 'self' data: ".config('app.url')." ".env('PUBLIC_AWS_URL')." https://secure.gravatar.com http://gravatar.com maps.google.com maps.gstatic.com *.googleapis.com";
|
||||
$csp_policy = join(';', $csp_policy);
|
||||
$response->headers->set('Content-Security-Policy', $csp_policy);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class AssetFileRequest extends Request
|
||||
{
|
||||
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
|
||||
return [
|
||||
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,lic,xml,rtf|max:'.$max_file_size,
|
||||
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf|max:'.$max_file_size,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class SaveUserRequest extends FormRequest
|
||||
{
|
||||
|
||||
$rules = [
|
||||
'manager_id' => "nullable|exists:users,id|different:users.id"
|
||||
'manager_id' => "nullable|exists:users,id"
|
||||
];
|
||||
|
||||
switch($this->method())
|
||||
|
||||
@@ -70,22 +70,27 @@ class SettingsSamlRequest extends FormRequest
|
||||
]);
|
||||
|
||||
$csr = openssl_csr_new($dn, $pkey, ['digest_alg' => 'sha256']);
|
||||
|
||||
$x509 = openssl_csr_sign($csr, null, $pkey, 3650, ['digest_alg' => 'sha256']);
|
||||
|
||||
openssl_x509_export($x509, $x509cert);
|
||||
openssl_pkey_export($pkey, $privateKey);
|
||||
if ($csr) {
|
||||
|
||||
$errors = [];
|
||||
while (($error = openssl_error_string() !== false)) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
|
||||
if (!(empty($x509cert) && empty($privateKey))) {
|
||||
$this->merge([
|
||||
'saml_sp_x509cert' => $x509cert,
|
||||
'saml_sp_privatekey' => $privateKey,
|
||||
]);
|
||||
$x509 = openssl_csr_sign($csr, null, $pkey, 3650, ['digest_alg' => 'sha256']);
|
||||
|
||||
openssl_x509_export($x509, $x509cert);
|
||||
openssl_pkey_export($pkey, $privateKey);
|
||||
|
||||
$errors = [];
|
||||
while (($error = openssl_error_string() !== false)) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
|
||||
if (!(empty($x509cert) && empty($privateKey))) {
|
||||
$this->merge([
|
||||
'saml_sp_x509cert' => $x509cert,
|
||||
'saml_sp_privatekey' => $privateKey,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$validator->errors()->add('saml_integration', 'openssl.cnf is missing/invalid');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,13 @@ class AccessoriesTransformer
|
||||
|
||||
|
||||
$array = array();
|
||||
|
||||
|
||||
foreach ($accessory_users as $user) {
|
||||
\Log::debug(print_r($user->pivot, true));
|
||||
\Log::debug(print_r($user->pivot, true));
|
||||
$array[] = [
|
||||
|
||||
'assigned_pivot_id' => $user->pivot->id,
|
||||
'id' => (int) $user->id,
|
||||
'username' => e($user->username),
|
||||
@@ -77,7 +82,8 @@ class AccessoriesTransformer
|
||||
'first_name'=> e($user->first_name),
|
||||
'last_name'=> e($user->last_name),
|
||||
'employee_number' => e($user->employee_num),
|
||||
'checkout_notes' => $accessory->lastCheckoutArray[0]['note'],
|
||||
'checkout_notes' => $user->pivot->note,
|
||||
'last_checkout' => Helper::getFormattedDateObject($user->pivot->created_at, 'datetime'),
|
||||
'type' => 'user',
|
||||
'available_actions' => ['checkin' => true]
|
||||
];
|
||||
|
||||
@@ -47,6 +47,7 @@ class LocationsTransformer
|
||||
'assets_count' => (int) $location->assets_count,
|
||||
'users_count' => (int) $location->users_count,
|
||||
'currency' => ($location->currency) ? e($location->currency) : null,
|
||||
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
|
||||
'created_at' => Helper::getFormattedDateObject($location->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($location->updated_at, 'datetime'),
|
||||
'parent' => ($location->parent) ? [
|
||||
|
||||
@@ -54,7 +54,6 @@ class UsersTransformer
|
||||
'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,
|
||||
@@ -63,6 +62,7 @@ class UsersTransformer
|
||||
'created_at' => Helper::getFormattedDateObject($user->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($user->updated_at, 'datetime'),
|
||||
'last_login' => Helper::getFormattedDateObject($user->last_login, 'datetime'),
|
||||
'deleted_at' => ($user->deleted_at) ? Helper::getFormattedDateObject($user->deleted_at, 'datetime') : null,
|
||||
];
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
|
||||
@@ -234,7 +234,7 @@ class Accessory extends SnipeModel
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany('\App\Models\User', 'accessories_users', 'accessory_id', 'assigned_to')->withPivot('id')->withTrashed();
|
||||
return $this->belongsToMany('\App\Models\User', 'accessories_users', 'accessory_id', 'assigned_to')->withPivot('id', 'created_at', 'note')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+11
-4
@@ -130,6 +130,8 @@ class Asset extends Depreciable
|
||||
'supplier_id',
|
||||
'warranty_months',
|
||||
'requestable',
|
||||
'last_checkout',
|
||||
'expected_checkin',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
@@ -164,6 +166,7 @@ class Asset extends Depreciable
|
||||
'supplier' => ['name'],
|
||||
'company' => ['name'],
|
||||
'defaultLoc' => ['name'],
|
||||
'location' => ['name'],
|
||||
'model' => ['name', 'model_number'],
|
||||
'model.category' => ['name'],
|
||||
'model.manufacturer' => ['name'],
|
||||
@@ -232,7 +235,10 @@ class Asset extends Depreciable
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an asset is available for checkout
|
||||
* Determines if an asset is available for checkout.
|
||||
* This checks to see if the it's checked out to an invalid (deleted) user
|
||||
* OR if the assigned_to and deleted_at fields on the asset are empty AND
|
||||
* that the status is deployable
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
@@ -241,9 +247,10 @@ class Asset extends Depreciable
|
||||
public function availableForCheckout()
|
||||
{
|
||||
if (
|
||||
(empty($this->assigned_to)) &&
|
||||
((!$this->assignedTo) && ($this->assetstatus->archived == 0)) ||
|
||||
((empty($this->assigned_to)) &&
|
||||
(empty($this->deleted_at)) &&
|
||||
(($this->assetstatus) && ($this->assetstatus->deployable == 1)))
|
||||
(($this->assetstatus) && ($this->assetstatus->deployable == 1))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -424,7 +431,7 @@ class Asset extends Depreciable
|
||||
*/
|
||||
public function assignedTo()
|
||||
{
|
||||
return $this->morphTo('assigned', 'assigned_type', 'assigned_to');
|
||||
return $this->morphTo('assigned', 'assigned_type', 'assigned_to')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -165,6 +165,8 @@ class Category extends SnipeModel
|
||||
return $this->components()->count();
|
||||
case 'consumable':
|
||||
return $this->consumables()->count();
|
||||
case 'license':
|
||||
return $this->licenses()->count();
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Models\User;
|
||||
use App\Models\Setting;
|
||||
use Exception;
|
||||
use Input;
|
||||
use Log;
|
||||
|
||||
class Ldap extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* Makes a connection to LDAP using the settings in Admin > Settings.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return connection
|
||||
*/
|
||||
|
||||
public static function connectToLdap()
|
||||
{
|
||||
|
||||
$ldap_host = Setting::getSettings()->ldap_server;
|
||||
$ldap_version = Setting::getSettings()->ldap_version;
|
||||
$ldap_server_cert_ignore = Setting::getSettings()->ldap_server_cert_ignore;
|
||||
$ldap_use_tls = Setting::getSettings()->ldap_tls;
|
||||
|
||||
|
||||
// If we are ignoring the SSL cert we need to setup the environment variable
|
||||
// before we create the connection
|
||||
if ($ldap_server_cert_ignore=='1') {
|
||||
putenv('LDAPTLS_REQCERT=never');
|
||||
}
|
||||
|
||||
// If the user specifies where CA Certs are, make sure to use them
|
||||
if (env("LDAPTLS_CACERT")) {
|
||||
putenv("LDAPTLS_CACERT=".env("LDAPTLS_CACERT"));
|
||||
}
|
||||
|
||||
$connection = @ldap_connect($ldap_host);
|
||||
|
||||
if (!$connection) {
|
||||
throw new Exception('Could not connect to LDAP server at '.$ldap_host.'. Please check your LDAP server name and port number in your settings.');
|
||||
}
|
||||
|
||||
// Needed for AD
|
||||
ldap_set_option($connection, LDAP_OPT_REFERRALS, 0);
|
||||
ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, $ldap_version);
|
||||
ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, 20);
|
||||
|
||||
if ($ldap_use_tls=='1') {
|
||||
ldap_start_tls($connection);
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Binds/authenticates the user to LDAP, and returns their attributes.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param $username
|
||||
* @param $password
|
||||
* @param bool|false $user
|
||||
* @return bool true if the username and/or password provided are valid
|
||||
* false if the username and/or password provided are invalid
|
||||
* array of ldap_attributes if $user is true
|
||||
*
|
||||
*/
|
||||
static function findAndBindUserLdap($username, $password)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$connection = Ldap::connectToLdap();
|
||||
$ldap_username_field = $settings->ldap_username_field;
|
||||
$baseDn = $settings->ldap_basedn;
|
||||
$userDn = $ldap_username_field.'='.$username.','.$settings->ldap_basedn;
|
||||
|
||||
if ($settings->is_ad =='1') {
|
||||
// Check if they are using the userprincipalname for the username field.
|
||||
// If they are, we can skip building the UPN to authenticate against AD
|
||||
if ($ldap_username_field=='userprincipalname') {
|
||||
$userDn = $username;
|
||||
} else {
|
||||
// In case they haven't added an AD domain
|
||||
$userDn = ($settings->ad_domain != '') ? $username.'@'.$settings->ad_domain : $username.'@'.$settings->email_domain;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
\Log::debug('Attempting to login using distinguished name:'.$userDn);
|
||||
|
||||
|
||||
$filterQuery = $settings->ldap_auth_filter_query . $username;
|
||||
|
||||
|
||||
if (!$ldapbind = @ldap_bind($connection, $userDn, $password)) {
|
||||
if(!$ldapbind = Ldap::bindAdminToLdap($connection)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$results = ldap_search($connection, $baseDn, $filterQuery)) {
|
||||
throw new Exception('Could not search LDAP: ');
|
||||
}
|
||||
|
||||
if (!$entry = ldap_first_entry($connection, $results)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$user = ldap_get_attributes($connection, $entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_change_key_case($user);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Binds/authenticates an admin to LDAP for LDAP searching/syncing.
|
||||
* Here we also return a better error if the app key is donked.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param bool|false $user
|
||||
* @return bool true if the username and/or password provided are valid
|
||||
* false if the username and/or password provided are invalid
|
||||
*
|
||||
*/
|
||||
static function bindAdminToLdap($connection)
|
||||
{
|
||||
|
||||
$ldap_username = Setting::getSettings()->ldap_uname;
|
||||
|
||||
// Lets return some nicer messages for users who donked their app key, and disable LDAP
|
||||
try {
|
||||
$ldap_pass = \Crypt::decrypt(Setting::getSettings()->ldap_pword);
|
||||
} catch (Exception $e) {
|
||||
|
||||
throw new Exception('Your app key has changed! Could not decrypt LDAP password using your current app key, so LDAP authentication has been disabled. Login with a local account, update the LDAP password and re-enable it in Admin > Settings.');
|
||||
}
|
||||
|
||||
|
||||
if (!$ldapbind = @ldap_bind($connection, $ldap_username, $ldap_pass)) {
|
||||
throw new Exception('Could not bind to LDAP: '.ldap_error($connection));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse and map LDAP attributes based on settings
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
*
|
||||
* @param $ldapatttibutes
|
||||
* @return array|bool
|
||||
*/
|
||||
static function parseAndMapLdapAttributes($ldapatttibutes)
|
||||
{
|
||||
//Get LDAP attribute config
|
||||
$ldap_result_username = Setting::getSettings()->ldap_username_field;
|
||||
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
|
||||
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
|
||||
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
|
||||
$ldap_result_email = Setting::getSettings()->ldap_email;
|
||||
|
||||
// Get LDAP user data
|
||||
$item = array();
|
||||
$item["username"] = isset($ldapatttibutes[$ldap_result_username][0]) ? $ldapatttibutes[$ldap_result_username][0] : "";
|
||||
$item["employee_number"] = isset($ldapatttibutes[$ldap_result_emp_num][0]) ? $ldapatttibutes[$ldap_result_emp_num][0] : "";
|
||||
$item["lastname"] = isset($ldapatttibutes[$ldap_result_last_name][0]) ? $ldapatttibutes[$ldap_result_last_name][0] : "";
|
||||
$item["firstname"] = isset($ldapatttibutes[$ldap_result_first_name][0]) ? $ldapatttibutes[$ldap_result_first_name][0] : "";
|
||||
$item["email"] = isset($ldapatttibutes[$ldap_result_email][0]) ? $ldapatttibutes[$ldap_result_email][0] : "" ;
|
||||
|
||||
return $item;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create user from LDAP attributes
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param $ldapatttibutes
|
||||
* @return array|bool
|
||||
*/
|
||||
static function createUserFromLdap($ldapatttibutes)
|
||||
{
|
||||
$item = Ldap::parseAndMapLdapAttributes($ldapatttibutes);
|
||||
|
||||
|
||||
// Create user from LDAP data
|
||||
if (!empty($item["username"])) {
|
||||
$user = new User;
|
||||
$user->first_name = $item["firstname"];
|
||||
$user->last_name = $item["lastname"];
|
||||
$user->username = $item["username"];
|
||||
$user->email = $item["email"];
|
||||
|
||||
if (Setting::getSettings()->ldap_pw_sync=='1') {
|
||||
$user->password = bcrypt(Input::get("password"));
|
||||
} else {
|
||||
$pass = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 25);
|
||||
$user->password = bcrypt($pass);
|
||||
}
|
||||
|
||||
$user->activated = 1;
|
||||
$user->ldap_import = 1;
|
||||
$user->notes = 'Imported on first login from LDAP';
|
||||
|
||||
if ($user->save()) {
|
||||
return $user;
|
||||
} else {
|
||||
LOG::debug('Could not create user.'.$user->getErrors());
|
||||
throw new Exception("Could not create user: ".$user->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches LDAP
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param $ldapatttibutes
|
||||
* @param $base_dn
|
||||
* @return array|bool
|
||||
*/
|
||||
static function findLdapUsers($base_dn = null)
|
||||
{
|
||||
|
||||
$ldapconn = Ldap::connectToLdap();
|
||||
$ldap_bind = Ldap::bindAdminToLdap($ldapconn);
|
||||
// Default to global base DN if nothing else is provided.
|
||||
if (is_null($base_dn)) {
|
||||
$base_dn = Setting::getSettings()->ldap_basedn;
|
||||
}
|
||||
$filter = Setting::getSettings()->ldap_filter;
|
||||
|
||||
// Set up LDAP pagination for very large databases
|
||||
$page_size = 500;
|
||||
$cookie = '';
|
||||
$result_set = array();
|
||||
$global_count = 0;
|
||||
|
||||
// Perform the search
|
||||
do {
|
||||
|
||||
// Paginate (non-critical, if not supported by server)
|
||||
if (!$ldap_paging = @ldap_control_paged_result($ldapconn, $page_size, false, $cookie)) {
|
||||
throw new Exception('Problem with your LDAP connection. Try checking the Use TLS setting in Admin > Settings. ');
|
||||
}
|
||||
|
||||
if ($filter != '' && substr($filter, 0, 1) != '(') { // wrap parens around NON-EMPTY filters that DON'T have them, for back-compatibility with AdLdap2-based filters
|
||||
$filter = "($filter)";
|
||||
}
|
||||
$search_results = ldap_search($ldapconn, $base_dn, $filter);
|
||||
|
||||
if (!$search_results) {
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_search').ldap_error($ldapconn)); // FIXME this is never called in any routed context - only from the Artisan command. So this redirect will never work.
|
||||
}
|
||||
|
||||
// Get results from page
|
||||
$results = ldap_get_entries($ldapconn, $search_results);
|
||||
if (!$results) {
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_get_entries').ldap_error($ldapconn)); // FIXME this is never called in any routed context - only from the Artisan command. So this redirect will never work.
|
||||
}
|
||||
|
||||
// Add results to result set
|
||||
$global_count += $results['count'];
|
||||
$result_set = array_merge($result_set, $results);
|
||||
|
||||
@ldap_control_paged_result_response($ldapconn, $search_results, $cookie);
|
||||
|
||||
} while ($cookie !== null && $cookie != '');
|
||||
|
||||
|
||||
// Clean up after search
|
||||
$result_set['count'] = $global_count;
|
||||
$results = $result_set;
|
||||
@ldap_control_paged_result($ldapconn, 0);
|
||||
|
||||
return $results;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class Location extends SnipeModel
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'address', 'city', 'state', 'zip', 'created_at'];
|
||||
protected $searchableAttributes = ['name', 'address', 'city', 'state', 'zip', 'created_at', 'ldap_ou'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
|
||||
@@ -48,7 +48,6 @@ class Setting extends Model
|
||||
protected $rules = [
|
||||
'brand' => 'required|min:1|numeric',
|
||||
'qr_text' => 'max:31|nullable',
|
||||
'logo_img' => 'mimes:jpeg,bmp,png,gif',
|
||||
'alert_email' => 'email_array|nullable',
|
||||
'admin_cc_email' => 'email|nullable',
|
||||
'default_currency' => 'required',
|
||||
@@ -120,9 +119,6 @@ class Setting extends Model
|
||||
try {
|
||||
$usercount = User::withTrashed()->count();
|
||||
$settingsCount = self::count();
|
||||
\Log::debug('User table and settings table exist and have records.');
|
||||
\Log::debug('Settings: '.$settingsCount );
|
||||
\Log::debug('Users: '.$usercount );
|
||||
return $usercount > 0 && $settingsCount > 0;
|
||||
} catch (\Throwable $th) {
|
||||
\Log::debug('User table and settings table DO NOT exist or DO NOT have records');
|
||||
|
||||
+9
-4
@@ -26,10 +26,13 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
use UniqueUndeletedTrait;
|
||||
use Notifiable;
|
||||
use Presentable;
|
||||
use Searchable;
|
||||
|
||||
protected $dates = ['deleted_at'];
|
||||
protected $hidden = ['password','remember_token','permissions','reset_password_code','persist_code'];
|
||||
protected $table = 'users';
|
||||
protected $injectUniqueIdentifier = true;
|
||||
|
||||
protected $fillable = [
|
||||
'activated',
|
||||
'address',
|
||||
@@ -67,6 +70,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
* @var array
|
||||
*/
|
||||
|
||||
// 'username' => 'required|string|min:1|unique:users,username,NULL,id,deleted_at,NULL',
|
||||
protected $rules = [
|
||||
'first_name' => 'required|string|min:1',
|
||||
'username' => 'required|string|min:1|unique_undeleted',
|
||||
@@ -74,11 +78,10 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
'password' => 'required|min:8',
|
||||
'locale' => 'max:10|nullable',
|
||||
'website' => 'url|nullable',
|
||||
'manager_id' => 'nullable|exists:users,id|different:users.id',
|
||||
'manager_id' => 'nullable|exists:users,id|cant_manage_self',
|
||||
'location_id' => 'exists:locations,id|nullable',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
@@ -107,7 +110,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
'groups' => ['name'],
|
||||
'company' => ['name'],
|
||||
'manager' => ['first_name', 'last_name', 'username']
|
||||
];
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Check user permissions
|
||||
@@ -295,7 +299,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->belongsToMany('\App\Models\Accessory', 'accessories_users', 'assigned_to', 'accessory_id')->withPivot('id')->withTrashed();
|
||||
return $this->belongsToMany('\App\Models\Accessory', 'accessories_users', 'assigned_to', 'accessory_id')
|
||||
->withPivot('id', 'created_at', 'note')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -111,7 +111,7 @@ class CheckinAccessoryNotification extends Notification
|
||||
];
|
||||
|
||||
return (new SlackMessage)
|
||||
->content(':arrow_down: :keyboard: Accessory Checked In')
|
||||
->content(':arrow_down: :keyboard: '.trans('mail.Accessory_Checkin_Notification'))
|
||||
->from($botname)
|
||||
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
|
||||
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
|
||||
@@ -135,7 +135,7 @@ class CheckinAccessoryNotification extends Notification
|
||||
'note' => $this->note,
|
||||
'target' => $this->target,
|
||||
])
|
||||
->subject('Accessory checked in');
|
||||
->subject(trans('mail.Accessory_Checkin_Notification'));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class CheckinAssetNotification extends Notification
|
||||
];
|
||||
|
||||
return (new SlackMessage)
|
||||
->content(':arrow_down: :computer: Asset Checked In')
|
||||
->content(':arrow_down: :computer: '.trans('mail.Asset_Checkin_Notification'))
|
||||
->from($botname)
|
||||
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
|
||||
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
|
||||
@@ -113,7 +113,7 @@ class CheckinAssetNotification extends Notification
|
||||
'fields' => $fields,
|
||||
'expected_checkin' => $this->expected_checkin,
|
||||
])
|
||||
->subject('Asset checked in');
|
||||
->subject(trans('mail.Asset_Checkin_Notification'));
|
||||
|
||||
|
||||
return $message;
|
||||
|
||||
@@ -83,7 +83,7 @@ class CheckinLicenseSeatNotification extends Notification
|
||||
|
||||
|
||||
return (new SlackMessage)
|
||||
->content(':arrow_down: :floppy_disk: License Checked In')
|
||||
->content(':arrow_down: :floppy_disk: '.trans('mail.License_Checkin_Notification'))
|
||||
->from($botname)
|
||||
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
|
||||
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
|
||||
@@ -106,7 +106,7 @@ class CheckinLicenseSeatNotification extends Notification
|
||||
'note' => $this->note,
|
||||
'target' => $this->target,
|
||||
])
|
||||
->subject('License checked in');
|
||||
->subject(trans('mail.License_Checkin_Notification'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class ExpectedCheckinAdminNotification extends Notification
|
||||
[
|
||||
'assets' => $this->assets,
|
||||
])
|
||||
->subject('Expected asset checkin report');
|
||||
->subject(trans('mail.Expected_Checkin_Report'));
|
||||
|
||||
return $message;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
@@ -46,16 +46,18 @@ class ExpectedCheckinNotification extends Notification
|
||||
*/
|
||||
public function toMail()
|
||||
{
|
||||
$formatted_due = Carbon::parse($this->params->expected_checkin)->format('D, M j, Y');
|
||||
return (new MailMessage)
|
||||
->error()
|
||||
->subject('Reminder: '.$this->params->present()->name().' checkin deadline approaching')
|
||||
->line('Hi, '.$this->params->assignedto->first_name.' '.$this->params->assignedto->last_name)
|
||||
->greeting('An asset checked out to you is due to be checked back in on '.$formatted_due.'.')
|
||||
->line('Asset: '.$this->params->present()->name())
|
||||
->line('Serial: '.$this->params->serial)
|
||||
->line('Asset Tag: '.$this->params->asset_tag)
|
||||
->action('View Your Assets', route('view-assets'));
|
||||
|
||||
$message = (new MailMessage)->markdown('notifications.markdown.expected-checkin',
|
||||
[
|
||||
'date' => Helper::getFormattedDateObject($this->params->expected_checkin, 'date', false),
|
||||
'asset' => $this->params->present()->name(),
|
||||
'serial' => $this->params->serial,
|
||||
'asset_tag' => $this->params->asset_tag
|
||||
])
|
||||
->subject(trans('mail.Expected_Checkin_Notification', ['name' => $this->params->present()->name()]));
|
||||
|
||||
return $message;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,11 +62,9 @@ class RequestAssetNotification extends Notification
|
||||
$notifyBy = [];
|
||||
|
||||
if (Setting::getSettings()->slack_endpoint!='') {
|
||||
\Log::debug('use slack');
|
||||
$notifyBy[] = 'slack';
|
||||
}
|
||||
|
||||
|
||||
$notifyBy[] = 'mail';
|
||||
|
||||
return $notifyBy;
|
||||
|
||||
@@ -123,7 +123,16 @@ class LocationPresenter extends Presenter
|
||||
"switchable" => true,
|
||||
"title" => trans('admin/locations/table.country'),
|
||||
"visible" => false,
|
||||
],[
|
||||
],
|
||||
[
|
||||
"field" => "ldap_ou",
|
||||
"searchable" => true,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('admin/locations/table.ldap_ou'),
|
||||
"visible" => false,
|
||||
],
|
||||
[
|
||||
"field" => "manager",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
|
||||
@@ -301,7 +301,7 @@ class UserPresenter extends Presenter
|
||||
*/
|
||||
public function fullName()
|
||||
{
|
||||
return "{$this->first_name} {$this->last_name}";
|
||||
return html_entity_decode($this->first_name.' '.$this->last_name, ENT_QUOTES | ENT_XML1, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,8 +87,9 @@ class AuthServiceProvider extends ServiceProvider
|
||||
|
||||
$this->registerPolicies();
|
||||
Passport::routes();
|
||||
Passport::tokensExpireIn(Carbon::now()->addYears(20));
|
||||
Passport::refreshTokensExpireIn(Carbon::now()->addYears(20));
|
||||
Passport::tokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
|
||||
Passport::refreshTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
|
||||
Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
|
||||
Passport::withCookieSerialization();
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class SamlServiceProvider extends ServiceProvider
|
||||
'uses' => 'Auth\SamlController@login' ]
|
||||
);
|
||||
|
||||
Route::group(['prefix' => 'admin','middleware' => ['auth', 'authorize:superuser']], function () {
|
||||
Route::group(['prefix' => 'admin','middleware' => ['web','auth', 'authorize:superuser']], function () {
|
||||
|
||||
Route::get('saml', ['as' => 'settings.saml.index','uses' => 'SettingsController@getSamlSettings' ]);
|
||||
Route::post('saml', ['as' => 'settings.saml.save','uses' => 'SettingsController@postSamlSettings' ]);
|
||||
|
||||
@@ -91,6 +91,42 @@ class ValidationServiceProvider extends ServiceProvider
|
||||
});
|
||||
|
||||
|
||||
// This ONLY works for create/update user forms, since the Update Profile Password form doesn't
|
||||
// include any of these additional validator fields
|
||||
Validator::extend('disallow_same_pwd_as_user_fields', function ($attribute, $value, $parameters, $validator) {
|
||||
|
||||
$data = $validator->getData();
|
||||
|
||||
if (array_key_exists("username", $data)) {
|
||||
if ($data['username'] == $data['password']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists("email", $data)) {
|
||||
if ($data['email'] == $data['password']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists("first_name", $data)) {
|
||||
if ($data['first_name'] == $data['password']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists("last_name", $data)) {
|
||||
if ($data['last_name'] == $data['password']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
});
|
||||
|
||||
Validator::extend('letters', function ($attribute, $value, $parameters) {
|
||||
return preg_match('/\pL/', $value);
|
||||
});
|
||||
@@ -107,6 +143,27 @@ class ValidationServiceProvider extends ServiceProvider
|
||||
return preg_match('/\p{Z}|\p{S}|\p{P}/', $value);
|
||||
});
|
||||
|
||||
Validator::extend('cant_manage_self', function ($attribute, $value, $parameters, $validator) {
|
||||
// $value is the actual *value* of the thing that's being validated
|
||||
// $attribute is the name of the field that the validation is running on - probably manager_id in our case
|
||||
// $parameters are the optional parameters - an array for everything, split on commas. But we don't take any params here.
|
||||
// $validator gives us proper access to the rest of the actual data
|
||||
$data = $validator->getData();
|
||||
|
||||
if(array_key_exists("id", $data)) {
|
||||
if ($value && $value == $data['id']) {
|
||||
// if you definitely have an ID - you're saving an existing user - and your ID matches your manager's ID - fail.
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// no 'id' key to compare against (probably because this is a new user)
|
||||
// so it automatically passes this validation
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
+102
-19
@@ -23,10 +23,34 @@ use Illuminate\Support\Facades\Log;
|
||||
*/
|
||||
class LdapAd extends LdapAdConfiguration
|
||||
{
|
||||
/**
|
||||
* @see https://wdmsb.wordpress.com/2014/12/03/descriptions-of-active-directory-useraccountcontrol-value/
|
||||
*/
|
||||
const AD_USER_ACCOUNT_CONTROL_FLAGS = ['512', '544', '66048', '66080', '262656', '262688', '328192', '328224'];
|
||||
/* The following is _probably_ the correct logic, but we can't use it because
|
||||
some users may have been dependent upon the previous behavior, and this
|
||||
could cause additional access to be available to users they don't want
|
||||
to allow to log in.
|
||||
$useraccountcontrol = $results[$i]['useraccountcontrol'][0];
|
||||
if(
|
||||
// based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties
|
||||
($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT
|
||||
!($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE
|
||||
!($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT
|
||||
) {
|
||||
$user->activated = 1;
|
||||
} else {
|
||||
$user->activated = 0;
|
||||
} */
|
||||
const AD_USER_ACCOUNT_CONTROL_FLAGS = [
|
||||
'512', // 0x200 NORMAL_ACCOUNT
|
||||
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
|
||||
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
|
||||
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
|
||||
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
|
||||
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
|
||||
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'4260352',// 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
|
||||
'1049088',// 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
|
||||
'1114624',// 0x110200 NORMAL_ACCOUNT, NOT_DELEGATED, DONT_EXPIRE_PASSWORD
|
||||
];
|
||||
|
||||
/**
|
||||
* The LDAP results per page.
|
||||
@@ -122,9 +146,17 @@ class LdapAd extends LdapAdConfiguration
|
||||
throw new Exception('Unable to find user in LDAP directory!');
|
||||
}
|
||||
|
||||
return User::where('username', $username)
|
||||
$user = User::where('username', $username)
|
||||
->whereNull('deleted_at')->where('ldap_import', '=', 1)
|
||||
->where('activated', '=', '1')->first();
|
||||
/* Above, I could've just done ->firstOrFail() which would've been cleaner, but it would've been miserable to
|
||||
troubleshoot if it ever came up (giving a really generic and untraceable error message)
|
||||
*/
|
||||
if (!$user) {
|
||||
throw new Exception("User is either deleted, not activated (can't log in), not from LDAP, or can't be found in database");
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +174,7 @@ class LdapAd extends LdapAdConfiguration
|
||||
*/
|
||||
public function processUser(AdldapUser $user, ?Collection $defaultLocation=null, ?Collection $mappedLocations=null): ?User
|
||||
{
|
||||
// Only sync active users
|
||||
// Only sync active users <- I think this actually means 'existing', not 'activated/deactivated'
|
||||
if(!$user) {
|
||||
return null;
|
||||
}
|
||||
@@ -154,8 +186,26 @@ class LdapAd extends LdapAdConfiguration
|
||||
$snipeUser['email'] = $user->{$this->ldapSettings['ldap_email']}[0] ?? '';
|
||||
$snipeUser['title'] = $user->getTitle() ?? '';
|
||||
$snipeUser['telephonenumber'] = $user->getTelephoneNumber() ?? '';
|
||||
$snipeUser['location_id'] = $this->getLocationId($user, $defaultLocation, $mappedLocations);
|
||||
$snipeUser['activated'] = $this->getActiveStatus($user);
|
||||
|
||||
/*
|
||||
* $locationId being 'null' means we have no per-OU location information,
|
||||
* but instead of explicitly setting it to null - which would override any admin-generated
|
||||
* location assignments - we just don't set it at all. For a brand new User, the 'default null'
|
||||
* on the column will cover us. For an already existing user, this will not override any
|
||||
* locations that were explicitly chosen by the administrators.
|
||||
*
|
||||
* When syncing with a particular 'default location' in mind, those should still be respected
|
||||
* and it *will* override the administrators previous choices. I think this is a fair compromise.
|
||||
*/
|
||||
$locationId = $this->getLocationId($user, $defaultLocation, $mappedLocations);
|
||||
if ($locationId !== null ) {
|
||||
$snipeUser['location_id'] = $locationId;
|
||||
}
|
||||
|
||||
$activeStatus = $this->getActiveStatus($user);
|
||||
if ($activeStatus !== null) {
|
||||
$snipeUser['activated'] = $activeStatus;
|
||||
}
|
||||
|
||||
return $this->setUserModel($snipeUser);
|
||||
}
|
||||
@@ -185,8 +235,14 @@ class LdapAd extends LdapAdConfiguration
|
||||
$user->employee_num = trim($userInfo['employee_number']);
|
||||
$user->jobtitle = trim($userInfo['title']);
|
||||
$user->phone = trim($userInfo['telephonenumber']);
|
||||
$user->activated = $userInfo['activated'];
|
||||
$user->location_id = $userInfo['location_id'];
|
||||
if(array_key_exists('activated',$userInfo)) {
|
||||
$user->activated = $userInfo['activated'];
|
||||
} else if ( !$user->exists ) { // no 'activated' flag was set or unset, *AND* this user is new - activate by default.
|
||||
$user->activated = 1;
|
||||
}
|
||||
if(array_key_exists('location_id',$userInfo)) {
|
||||
$user->location_id = $userInfo['location_id'];
|
||||
}
|
||||
$user->notes = 'Imported from LDAP';
|
||||
$user->ldap_import = 1;
|
||||
|
||||
@@ -252,6 +308,8 @@ class LdapAd extends LdapAdConfiguration
|
||||
|
||||
/**
|
||||
* Set the active status of the user.
|
||||
* Returns 0 or 1 if the user is deactivated or activated
|
||||
* or returns null if we just don't know
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
@@ -259,22 +317,46 @@ class LdapAd extends LdapAdConfiguration
|
||||
*
|
||||
* @param \Adldap\Models\User $user
|
||||
*
|
||||
* @return int
|
||||
* @return int (or null)
|
||||
*/
|
||||
private function getActiveStatus(AdldapUser $user): int
|
||||
private function getActiveStatus(AdldapUser $user): ?int
|
||||
{
|
||||
$activeStatus = 0;
|
||||
/*
|
||||
* Check to see if we are connected to an AD server
|
||||
* if so, check the Active Directory User Account Control Flags
|
||||
* If the admin has set their own 'active flag' - respect that instead
|
||||
* (this may work to allow AD users to ignore the built-in UAC stuff that AD does)
|
||||
*/
|
||||
if ($user->hasAttribute($user->getSchema()->userAccountControl())) {
|
||||
if ($user->hasAttribute($user->getSchema()->userAccountControl()) && !$this->ldapSettings['ldap_active_flag']) {
|
||||
\Log::debug('This is AD - userAccountControl is'. $user->getSchema()->userAccountControl());
|
||||
$activeStatus = (in_array($user->getUserAccountControl(), self::AD_USER_ACCOUNT_CONTROL_FLAGS)) ? 1 : 0;
|
||||
} else {
|
||||
// If there is no activated flag, assume this is handled via the OU and activate the users
|
||||
|
||||
//\Log::debug('This looks like LDAP (or an AD where the UAC is disabled)');
|
||||
// If there is no activated flag, then we can't make any determination about activated/deactivated
|
||||
if (false == $this->ldapSettings['ldap_active_flag']) {
|
||||
$activeStatus = 1;
|
||||
\Log::debug('ldap_active_flag is false - no ldap_active_flag is set');
|
||||
return null;
|
||||
}
|
||||
|
||||
// If there *is* an activated flag, then respect it *only* if it is actually present. If it's not there, ignore it.
|
||||
if (!$user->hasAttribute($this->ldapSettings['ldap_active_flag'])) {
|
||||
return null; // 'active' flag is defined, but does not exist on returned user record. So we don't know if they're active or not.
|
||||
}
|
||||
|
||||
// if $user has the flag *AND* that flag has exactly one value -
|
||||
if ( $user->{$this->ldapSettings['ldap_active_flag']} && count($user->{$this->ldapSettings['ldap_active_flag']}) == 1 ) {
|
||||
|
||||
$active_flag_value = $user->{$this->ldapSettings['ldap_active_flag']}[0];
|
||||
|
||||
// if the value of that flag is case-insensitively the string 'false' or boolean false
|
||||
if ( strcasecmp($active_flag_value, "false") == 0 || $active_flag_value === false ) {
|
||||
return 0; // then make them INACTIVE
|
||||
} else {
|
||||
return 1; // otherwise active
|
||||
}
|
||||
}
|
||||
return 1; // fail 'open' (active) if we have the attribute and it's multivalued or empty; that's weird
|
||||
}
|
||||
|
||||
return $activeStatus;
|
||||
@@ -375,7 +457,7 @@ class LdapAd extends LdapAdConfiguration
|
||||
{
|
||||
/** @var Schema $schema */
|
||||
$schema = new $this->ldapConfig['schema'];
|
||||
return [
|
||||
return array_values(array_filter([
|
||||
$this->ldapSettings['ldap_username_field'],
|
||||
$this->ldapSettings['ldap_fname_field'],
|
||||
$this->ldapSettings['ldap_lname_field'],
|
||||
@@ -386,7 +468,7 @@ class LdapAd extends LdapAdConfiguration
|
||||
$schema->userAccountControl(),
|
||||
$schema->title(),
|
||||
$schema->telephone(),
|
||||
];
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,7 +498,7 @@ class LdapAd extends LdapAdConfiguration
|
||||
public function testLdapAdUserConnection(): void
|
||||
{
|
||||
try {
|
||||
$this->ldap->connect(); //uh, this doesn't seem to exist :/
|
||||
$this->ldap->connect();
|
||||
} catch (\Adldap\Auth\BindException $e) {
|
||||
Log::error($e);
|
||||
throw new Exception('Unable to connect to LDAP directory!');
|
||||
@@ -467,6 +549,7 @@ class LdapAd extends LdapAdConfiguration
|
||||
if (!is_null($filter)) {
|
||||
$search = $search->rawFilter($filter);
|
||||
}
|
||||
//I think it might be possible to potentially do our own paging here?
|
||||
|
||||
return $search->select($this->getSelectedFields())
|
||||
->paginate(self::PAGE_SIZE);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Services;
|
||||
use OneLogin\Saml2\Auth as OneLogin_Saml2_Auth;
|
||||
use OneLogin\Saml2\IdPMetadataParser as OneLogin_Saml2_IdPMetadataParser;
|
||||
use OneLogin\Saml2\Settings as OneLogin_Saml2_Settings;
|
||||
use OneLogin\Saml2\Utils as OneLogin_Saml2_Utils;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Exception;
|
||||
@@ -153,6 +154,9 @@ class Saml
|
||||
$this->_enabled = $setting->saml_enabled == '1';
|
||||
|
||||
if ($this->isEnabled()) {
|
||||
//Let onelogin/php-saml know to use 'X-Forwarded-*' headers if it is from a trusted proxy
|
||||
OneLogin_Saml2_Utils::setProxyVars(request()->isFromTrustedProxy());
|
||||
|
||||
data_set($settings, 'sp.entityId', url('/'));
|
||||
data_set($settings, 'sp.assertionConsumerService.url', route('saml.acs'));
|
||||
data_set($settings, 'sp.singleLogoutService.url', route('saml.sls'));
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"codeception/module-rest": "^1.2",
|
||||
"codeception/module-webdriver": "^1.0",
|
||||
"fzaninotto/faker": "^1.9",
|
||||
"overtrue/phplint": "^2.2",
|
||||
"phpunit/php-token-stream": "^3.1",
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
|
||||
Generated
+112
-2
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3fe8a441e49d1299687346810b350e00",
|
||||
"content-hash": "68cf0fb2c06b12c9f8b58efbca2cd72b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adldap2/adldap2",
|
||||
@@ -8476,6 +8476,116 @@
|
||||
],
|
||||
"time": "2020-01-17T21:11:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "n98/junit-xml",
|
||||
"version": "1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cmuench/junit-xml.git",
|
||||
"reference": "7df0dbaf413fcaa1a63ffbcef18654e7a4cceb46"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/cmuench/junit-xml/zipball/7df0dbaf413fcaa1a63ffbcef18654e7a4cceb46",
|
||||
"reference": "7df0dbaf413fcaa1a63ffbcef18654e7a4cceb46",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "3.7.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"N98\\JUnitXml": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Münch",
|
||||
"email": "c.muench@netz98.de"
|
||||
}
|
||||
],
|
||||
"description": "JUnit XML Document generation library",
|
||||
"support": {
|
||||
"issues": "https://github.com/cmuench/junit-xml/issues",
|
||||
"source": "https://github.com/cmuench/junit-xml/tree/master"
|
||||
},
|
||||
"time": "2013-11-23T13:11:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "overtrue/phplint",
|
||||
"version": "2.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/overtrue/phplint.git",
|
||||
"reference": "dcbb1b9c728de2f05ce6208db7dacb8b3df1c446"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/overtrue/phplint/zipball/dcbb1b9c728de2f05ce6208db7dacb8b3df1c446",
|
||||
"reference": "dcbb1b9c728de2f05ce6208db7dacb8b3df1c446",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"n98/junit-xml": "1.0.0",
|
||||
"php": ">=5.5.9",
|
||||
"symfony/console": "^3.2|^4.0|^5.0",
|
||||
"symfony/finder": "^3.0|^4.0|^5.0",
|
||||
"symfony/process": "^3.3|^4.0|^5.0",
|
||||
"symfony/yaml": "^3.0|^4.0|^5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"brainmaestro/composer-git-hooks": "^2.7",
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
"jakub-onderka/php-console-highlighter": "^0.3.2 || ^0.4"
|
||||
},
|
||||
"bin": [
|
||||
"bin/phplint"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"hooks": {
|
||||
"pre-commit": [
|
||||
"composer fix-style"
|
||||
],
|
||||
"pre-push": [
|
||||
"composer check-style"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Overtrue\\PHPLint\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "overtrue",
|
||||
"email": "anzhengchao@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "`phplint` is a tool that can speed up linting of php files by running several lint processes at once.",
|
||||
"keywords": [
|
||||
"check",
|
||||
"lint",
|
||||
"phplint",
|
||||
"syntax"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/overtrue/phplint/issues",
|
||||
"source": "https://github.com/overtrue/phplint/tree/2.2.0"
|
||||
},
|
||||
"time": "2020-11-04T23:50:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
"version": "1.0.3",
|
||||
@@ -9618,5 +9728,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "7.2"
|
||||
},
|
||||
"plugin-api-version": "1.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
||||
+2
-1
@@ -102,7 +102,7 @@ return [
|
||||
'provider' => 'users',
|
||||
'email' => 'auth.emails.password',
|
||||
'table' => 'password_resets',
|
||||
'expire' => env('LOGIN_LOCKOUT_DURATION', 60),
|
||||
'expire' => env('RESET_PASSWORD_LINK_EXPIRES', 900),
|
||||
'throttle' => env('LOGIN_MAX_ATTEMPTS', 60),
|
||||
],
|
||||
],
|
||||
@@ -120,4 +120,5 @@ return [
|
||||
|
||||
'password_timeout' => 10800,
|
||||
|
||||
|
||||
];
|
||||
|
||||
+28
-46
@@ -48,34 +48,21 @@ return [
|
||||
*/
|
||||
'exclude' => [
|
||||
base_path('vendor'),
|
||||
base_path('config'),
|
||||
base_path('node_modules'),
|
||||
],
|
||||
|
||||
/*
|
||||
* Determines if symlinks should be followed.
|
||||
*/
|
||||
'followLinks' => false,
|
||||
'follow_links' => false,
|
||||
|
||||
/*
|
||||
* Determines if it should avoid unreadable folders.
|
||||
*/
|
||||
'ignore_unreadable_directories' => false,
|
||||
],
|
||||
|
||||
/*
|
||||
* The names of the connections to the databases that should be backed up
|
||||
* MySQL, PostgreSQL, SQLite and Mongo databases are supported.
|
||||
*
|
||||
* The content of the database dump may be customized for each connection
|
||||
* by adding a 'dump' key to the connection settings in config/database.php.
|
||||
* E.g.
|
||||
* 'mysql' => [
|
||||
* ...
|
||||
* 'dump' => [
|
||||
* 'excludeTables' => [
|
||||
* 'table_to_exclude_from_backup',
|
||||
* 'another_table_to_exclude'
|
||||
* ]
|
||||
* ]
|
||||
* ],
|
||||
*
|
||||
* For a complete list of available customization options, see https://github.com/spatie/db-dumper
|
||||
*/
|
||||
'databases' => [
|
||||
env('DB_CONNECTION', 'mysql'),
|
||||
],
|
||||
@@ -117,7 +104,7 @@ return [
|
||||
|
||||
/*
|
||||
* You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'.
|
||||
* For Slack you need to install guzzlehttp/guzzle.
|
||||
* For Slack you need to install guzzlehttp/guzzle and laravel/slack-notification-channel.
|
||||
*
|
||||
* You can also use your own notification classes, just make sure the class is named after one of
|
||||
* the `Spatie\Backup\Events` classes.
|
||||
@@ -125,12 +112,12 @@ return [
|
||||
'notifications' => [
|
||||
|
||||
'notifications' => [
|
||||
\Spatie\Backup\Notifications\Notifications\BackupHasFailed::class => ['mail'],
|
||||
\Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFound::class => ['mail'],
|
||||
\Spatie\Backup\Notifications\Notifications\CleanupHasFailed::class => ['mail'],
|
||||
\Spatie\Backup\Notifications\Notifications\BackupWasSuccessful::class => ['mail'],
|
||||
\Spatie\Backup\Notifications\Notifications\HealthyBackupWasFound::class => ['mail'],
|
||||
\Spatie\Backup\Notifications\Notifications\CleanupWasSuccessful::class => ['mail'],
|
||||
\Spatie\Backup\Notifications\Notifications\BackupHasFailed::class => [env('MAIL_BACKUP_NOTIFICATION_DRIVER', null)],
|
||||
\Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFound::class => [env('MAIL_BACKUP_NOTIFICATION_DRIVER', null)],
|
||||
\Spatie\Backup\Notifications\Notifications\CleanupHasFailed::class => [env('MAIL_BACKUP_NOTIFICATION_DRIVER', null)],
|
||||
\Spatie\Backup\Notifications\Notifications\BackupWasSuccessful::class => [env('MAIL_BACKUP_NOTIFICATION_DRIVER', null)],
|
||||
\Spatie\Backup\Notifications\Notifications\HealthyBackupWasFound::class => [env('MAIL_BACKUP_NOTIFICATION_DRIVER', null)],
|
||||
\Spatie\Backup\Notifications\Notifications\CleanupWasSuccessful::class => [env('MAIL_BACKUP_NOTIFICATION_DRIVER', null)],
|
||||
],
|
||||
|
||||
/*
|
||||
@@ -163,22 +150,16 @@ return [
|
||||
* If a backup does not meet the specified requirements the
|
||||
* UnHealthyBackupWasFound event will be fired.
|
||||
*/
|
||||
'monitorBackups' => [
|
||||
'monitor_backups' => [
|
||||
[
|
||||
'name' => config('app.name'),
|
||||
'disks' => [env('PRIVATE_FILESYSTEM_DISK', 'local')],
|
||||
'newestBackupsShouldNotBeOlderThanDays' => 1,
|
||||
'storageUsedMayNotBeHigherThanMegabytes' => 5000,
|
||||
'health_checks' => [
|
||||
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1,
|
||||
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
[
|
||||
'name' => 'name of the second app',
|
||||
'disks' => ['local', 's3'],
|
||||
'newestBackupsShouldNotBeOlderThanDays' => 1,
|
||||
'storageUsedMayNotBeHigherThanMegabytes' => 5000,
|
||||
],
|
||||
*/
|
||||
],
|
||||
|
||||
'cleanup' => [
|
||||
@@ -193,38 +174,39 @@ return [
|
||||
*/
|
||||
'strategy' => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,
|
||||
|
||||
'defaultStrategy' => [
|
||||
'default_strategy' => [
|
||||
|
||||
/*
|
||||
* The number of days for which backups must be kept.
|
||||
*/
|
||||
'keepAllBackupsForDays' => 7,
|
||||
'keep_all_backups_for_days' => 7,
|
||||
|
||||
/*
|
||||
* The number of days for which daily backups must be kept.
|
||||
*/
|
||||
'keepDailyBackupsForDays' => 16,
|
||||
'keep_daily_backups_for_days' => 16,
|
||||
|
||||
/*
|
||||
* The number of weeks for which one weekly backup must be kept.
|
||||
*/
|
||||
'keepWeeklyBackupsForWeeks' => 8,
|
||||
'keep_weekly_backups_for_weeks' => 8,
|
||||
|
||||
/*
|
||||
* The number of months for which one monthly backup must be kept.
|
||||
*/
|
||||
'keepMonthlyBackupsForMonths' => 3,
|
||||
'keep_monthly_backups_for_months' => 4,
|
||||
|
||||
/*
|
||||
* The number of years for which one yearly backup must be kept.
|
||||
*/
|
||||
'keepYearlyBackupsForYears' => 2,
|
||||
'keep_yearly_backups_for_years' => 2,
|
||||
|
||||
/*
|
||||
* After cleaning up the backups remove the oldest backup until
|
||||
* this amount of megabytes has been reached.
|
||||
*/
|
||||
'deleteOldestBackupsWhenUsingMoreMegabytesThan' => 5000,
|
||||
'delete_oldest_backups_when_using_more_megabytes_than' => 5000,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
];
|
||||
@@ -23,6 +23,6 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'method' => env('MAIL_AUTO_EMBED_METHOD', 'base64'),
|
||||
'method' => env('MAIL_AUTO_EMBED_METHOD', 'attachment'),
|
||||
|
||||
];
|
||||
|
||||
@@ -12,4 +12,5 @@ return [
|
||||
*/
|
||||
'private_key' => env('PASSPORT_PRIVATE_KEY'),
|
||||
'public_key' => env('PASSPORT_PUBLIC_KEY'),
|
||||
'expiration_years' => env('API_TOKEN_EXPIRATION_YEARS', 20),
|
||||
];
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
return array (
|
||||
'app_version' => 'v5.0.2',
|
||||
'full_app_version' => 'v5.0.2 - build 5414-gc3e8f6406',
|
||||
'build_version' => '5414',
|
||||
'app_version' => 'v5.0.10',
|
||||
'full_app_version' => 'v5.0.10 - build 5680-gf9b1fdc36',
|
||||
'build_version' => '5680',
|
||||
'prerelease_version' => '',
|
||||
'hash_version' => 'gc3e8f6406',
|
||||
'full_hash' => 'v5.0.1-19-gc3e8f6406',
|
||||
'hash_version' => 'gf9b1fdc36',
|
||||
'full_hash' => 'v5.0.10-21-gf9b1fdc36',
|
||||
'branch' => 'master',
|
||||
);
|
||||
+8
-3
@@ -1,3 +1,8 @@
|
||||
files:
|
||||
- source: /resources/lang/en/**/*.php
|
||||
translation: '**/%original_file_name%'
|
||||
"commit_message": "Fixed: New translations %original_file_name% from Crowdin"
|
||||
|
||||
"files": [
|
||||
{
|
||||
"source" : "/resources/lang/en/**/*.php",
|
||||
"translation" : "/resources/lang/%locale%/%original_file_name%"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Actionlog;
|
||||
|
||||
class MoveAccessoryCheckoutNoteToJoinTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('accessories_users', function (Blueprint $table) {
|
||||
$table->string('note')->nullable(true)->default(null);
|
||||
});
|
||||
|
||||
// Loop through the checked out accessories, find their related action_log entry, and copy over the note
|
||||
// to the newly created note field
|
||||
|
||||
$accessories = Accessory::get();
|
||||
$count = 0;
|
||||
\Log::debug('Accessory Count: '. $accessories->count());
|
||||
|
||||
|
||||
// Loop through all of the accessories
|
||||
foreach ($accessories as $accessory) {
|
||||
$count++;
|
||||
|
||||
\Log::debug('Querying join logs');
|
||||
$join_logs = DB::table('accessories_users')->get();
|
||||
|
||||
// Loop through the accessories_users records
|
||||
foreach ($join_logs as $join_log) {
|
||||
\Log::debug($join_logs->count().' join log records');
|
||||
\Log::debug('Looking for accessories_users that match '. $join_log->created_at);
|
||||
|
||||
// Get the records from action_logs so we can copy the notes over to the new notes field
|
||||
// on the accessories_users table
|
||||
$action_log_entries = Actionlog::where('created_at', '=',$join_log->created_at)
|
||||
->where('target_id', '=',$join_log->assigned_to)
|
||||
->where('item_id', '=',$accessory->id)
|
||||
->where('target_type', '=','App\\Models\\User')
|
||||
->where('action_type', '=', 'checkout')
|
||||
->orderBy('created_at', 'DESC')->get();
|
||||
|
||||
\Log::debug($action_log_entries->count().' matching entries in the action_logs table');
|
||||
\Log::debug('Looking for action_logs that match '. $join_log->created_at);
|
||||
|
||||
foreach ($action_log_entries as $action_log_entry) {
|
||||
|
||||
\Log::debug('Checkout date in asset log: '.$action_log_entry->created_at.' against accessories_users: '.$join_log->created_at);
|
||||
\Log::debug('Action log: '.$action_log_entry->created_at);
|
||||
\Log::debug('Join log: '.$join_log->created_at);
|
||||
|
||||
if ($action_log_entry->created_at == $join_log->created_at) {
|
||||
DB::table('accessories_users')
|
||||
->where('id', $join_log->id)
|
||||
->update(['note' => $action_log_entry->note]);
|
||||
} else {
|
||||
\Log::debug('No match');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accessories_users', function (Blueprint $table) {
|
||||
$table->dropColumn('note');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use App\Models\User;
|
||||
use App\Models\Asset;
|
||||
|
||||
class FixZeroValuesForLocations extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
App\Models\Asset::where('location_id', '=', '0')
|
||||
->update(['location_id' => null]);
|
||||
|
||||
App\Models\Asset::where('rtd_location_id', '=', '0')
|
||||
->update(['rtd_location_id' => null]);
|
||||
|
||||
App\Models\User::where('location_id', '=', '0')
|
||||
->update(['location_id' => null]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class WidenLicenseSerialField extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('licenses', function (Blueprint $table) {
|
||||
$table->text('serial')->nullable()->default(null)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('licenses', function (Blueprint $table) {
|
||||
$table->string('serial', 2048)->nullable()->default(null)->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
Generated
+431
-33
@@ -274,14 +274,12 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
|
||||
"dev": true
|
||||
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "2.0.0",
|
||||
@@ -463,7 +461,6 @@
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
@@ -1411,6 +1408,39 @@
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz",
|
||||
"integrity": "sha1-/FQhoo/UImA2w7OJGmaiW8ZNIm4=",
|
||||
"requires": {
|
||||
"readable-stream": "~2.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
|
||||
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~1.0.6",
|
||||
"string_decoder": "~0.10.x",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-stream": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
|
||||
@@ -1514,6 +1544,14 @@
|
||||
"multicast-dns-service-types": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"boom": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
|
||||
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
|
||||
"requires": {
|
||||
"hoek": "2.x.x"
|
||||
}
|
||||
},
|
||||
"bootstrap": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz",
|
||||
@@ -2008,7 +2046,6 @@
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^2.2.1",
|
||||
"escape-string-regexp": "^1.0.2",
|
||||
@@ -2639,6 +2676,14 @@
|
||||
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
|
||||
"dev": true
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
|
||||
"integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
|
||||
"requires": {
|
||||
"boom": "2.x.x"
|
||||
}
|
||||
},
|
||||
"crypto-browserify": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||
@@ -3494,8 +3539,7 @@
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"escodegen": {
|
||||
"version": "1.14.3",
|
||||
@@ -3873,6 +3917,70 @@
|
||||
"webpack-sources": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"extract-zip": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz",
|
||||
"integrity": "sha1-ksz22B73Cp+kwXRxFMzvbYaIpsQ=",
|
||||
"requires": {
|
||||
"concat-stream": "1.5.0",
|
||||
"debug": "0.7.4",
|
||||
"mkdirp": "0.5.0",
|
||||
"yauzl": "2.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"concat-stream": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
|
||||
"integrity": "sha1-U/fUPFHF5D+ByP3QMyHGMb5o1hE=",
|
||||
"requires": {
|
||||
"inherits": "~2.0.1",
|
||||
"readable-stream": "~2.0.0",
|
||||
"typedarray": "~0.0.5"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz",
|
||||
"integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz",
|
||||
"integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=",
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
|
||||
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~1.0.6",
|
||||
"string_decoder": "~0.10.x",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
@@ -3918,6 +4026,14 @@
|
||||
"websocket-driver": ">=0.5.1"
|
||||
}
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
|
||||
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
|
||||
"requires": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"file-loader": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-0.11.2.tgz",
|
||||
@@ -4170,7 +4286,8 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -4191,12 +4308,14 @@
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -4211,17 +4330,20 @@
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -4338,7 +4460,8 @@
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -4350,6 +4473,7 @@
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -4364,6 +4488,7 @@
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
@@ -4371,12 +4496,14 @@
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -4395,6 +4522,7 @@
|
||||
"version": "0.5.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
@@ -4456,7 +4584,8 @@
|
||||
"npm-normalize-package-bin": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"npm-packlist": {
|
||||
"version": "1.4.8",
|
||||
@@ -4484,7 +4613,8 @@
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -4496,6 +4626,7 @@
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -4573,7 +4704,8 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -4609,6 +4741,7 @@
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -4628,6 +4761,7 @@
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -4671,12 +4805,14 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4749,6 +4885,22 @@
|
||||
"globule": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||
"requires": {
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"generate-object-property": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
|
||||
"integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
|
||||
"requires": {
|
||||
"is-property": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"get-assigned-identifiers": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz",
|
||||
@@ -4931,7 +5083,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -5023,6 +5174,26 @@
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hasha": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz",
|
||||
"integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=",
|
||||
"requires": {
|
||||
"is-stream": "^1.0.1",
|
||||
"pinkie-promise": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"hawk": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
|
||||
"integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
|
||||
"requires": {
|
||||
"boom": "2.x.x",
|
||||
"cryptiles": "2.x.x",
|
||||
"hoek": "2.x.x",
|
||||
"sntp": "1.x.x"
|
||||
}
|
||||
},
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
@@ -5039,6 +5210,11 @@
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hoek": {
|
||||
"version": "2.16.3",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
|
||||
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
|
||||
},
|
||||
"home-or-tmp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
|
||||
@@ -5598,6 +5774,23 @@
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-my-ip-valid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz",
|
||||
"integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ=="
|
||||
},
|
||||
"is-my-json-valid": {
|
||||
"version": "2.20.5",
|
||||
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz",
|
||||
"integrity": "sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A==",
|
||||
"requires": {
|
||||
"generate-function": "^2.0.0",
|
||||
"generate-object-property": "^1.1.0",
|
||||
"is-my-ip-valid": "^1.0.0",
|
||||
"jsonpointer": "^4.0.0",
|
||||
"xtend": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||
@@ -5661,6 +5854,11 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ="
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
|
||||
@@ -5678,8 +5876,7 @@
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
||||
"dev": true
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||
},
|
||||
"is-string": {
|
||||
"version": "1.0.5",
|
||||
@@ -5736,8 +5933,7 @@
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
@@ -5826,7 +6022,8 @@
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
|
||||
"integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
@@ -5948,6 +6145,11 @@
|
||||
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
|
||||
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
|
||||
},
|
||||
"jsonpointer": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz",
|
||||
"integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg=="
|
||||
},
|
||||
"jspdf": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.1.1.tgz",
|
||||
@@ -6035,7 +6237,7 @@
|
||||
"integrity": "sha512-J9X76xnncMw+wIqb15HeWfPMqPwYxSpPY8yWPJ7rAZN/ZDzFkjCSZObryCyUe8zbrVRNiuCnIeQteCzMn7GnWw==",
|
||||
"requires": {
|
||||
"canvg": "1.5.3",
|
||||
"file-saver": "github:eligrey/FileSaver.js#e865e37af9f9947ddcced76b549e27dc45c1cb2e",
|
||||
"file-saver": "github:eligrey/FileSaver.js#1.3.8",
|
||||
"html2canvas": "1.0.0-alpha.12",
|
||||
"omggif": "1.0.7",
|
||||
"promise-polyfill": "8.1.0",
|
||||
@@ -6074,6 +6276,11 @@
|
||||
"jquery": ">=1.5"
|
||||
}
|
||||
},
|
||||
"kew": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
|
||||
"integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s="
|
||||
},
|
||||
"keyv": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz",
|
||||
@@ -6094,6 +6301,14 @@
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"dev": true
|
||||
},
|
||||
"klaw": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
|
||||
"integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.9"
|
||||
}
|
||||
},
|
||||
"labeled-stream-splicer": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz",
|
||||
@@ -6264,8 +6479,7 @@
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"lodash._baseassign": {
|
||||
"version": "3.2.0",
|
||||
@@ -7500,11 +7714,159 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||
},
|
||||
"phantomjs": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-2.1.7.tgz",
|
||||
"integrity": "sha1-xpEPZ5NcNyhbYRQyn8LyfV8+MTQ=",
|
||||
"requires": {
|
||||
"extract-zip": "~1.5.0",
|
||||
"fs-extra": "~0.26.4",
|
||||
"hasha": "^2.2.0",
|
||||
"kew": "~0.7.0",
|
||||
"progress": "~1.1.8",
|
||||
"request": "~2.67.0",
|
||||
"request-progress": "~2.0.1",
|
||||
"which": "~1.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
|
||||
"integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ="
|
||||
},
|
||||
"aws-sign2": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
|
||||
"integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8="
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
|
||||
"integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz",
|
||||
"integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=",
|
||||
"requires": {
|
||||
"async": "^2.0.1",
|
||||
"combined-stream": "^1.0.5",
|
||||
"mime-types": "^2.1.11"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "0.26.7",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
|
||||
"integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^2.1.0",
|
||||
"klaw": "^1.0.0",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"rimraf": "^2.2.8"
|
||||
}
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
|
||||
"integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
|
||||
"requires": {
|
||||
"chalk": "^1.1.1",
|
||||
"commander": "^2.9.0",
|
||||
"is-my-json-valid": "^2.12.4",
|
||||
"pinkie-promise": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
|
||||
"integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
|
||||
"requires": {
|
||||
"assert-plus": "^0.2.0",
|
||||
"jsprim": "^1.2.2",
|
||||
"sshpk": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
|
||||
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
|
||||
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc="
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
|
||||
},
|
||||
"qs": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-5.2.1.tgz",
|
||||
"integrity": "sha1-gB/uAw4LlFDWOFrcSKTMVbRK7fw="
|
||||
},
|
||||
"request": {
|
||||
"version": "2.67.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.67.0.tgz",
|
||||
"integrity": "sha1-ivdHgOK/EeoK6aqWXBHxGv0nJ0I=",
|
||||
"requires": {
|
||||
"aws-sign2": "~0.6.0",
|
||||
"bl": "~1.0.0",
|
||||
"caseless": "~0.11.0",
|
||||
"combined-stream": "~1.0.5",
|
||||
"extend": "~3.0.0",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~1.0.0-rc3",
|
||||
"har-validator": "~2.0.2",
|
||||
"hawk": "~3.1.0",
|
||||
"http-signature": "~1.1.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.7",
|
||||
"node-uuid": "~1.4.7",
|
||||
"oauth-sign": "~0.8.0",
|
||||
"qs": "~5.2.0",
|
||||
"stringstream": "~0.0.4",
|
||||
"tough-cookie": "~2.2.0",
|
||||
"tunnel-agent": "~0.4.1"
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz",
|
||||
"integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc="
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
|
||||
"integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us="
|
||||
},
|
||||
"which": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
|
||||
"integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
@@ -8833,6 +9195,11 @@
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"progress": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
|
||||
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74="
|
||||
},
|
||||
"promise": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
@@ -9312,6 +9679,14 @@
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"request-progress": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz",
|
||||
"integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=",
|
||||
"requires": {
|
||||
"throttleit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -9461,7 +9836,6 @@
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
@@ -10137,6 +10511,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sntp": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
|
||||
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
|
||||
"requires": {
|
||||
"hoek": "2.x.x"
|
||||
}
|
||||
},
|
||||
"sockjs": {
|
||||
"version": "0.3.19",
|
||||
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
|
||||
@@ -10581,11 +10963,15 @@
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"stringstream": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
|
||||
"integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA=="
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -10635,8 +11021,7 @@
|
||||
"supports-color": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
|
||||
"dev": true
|
||||
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
|
||||
},
|
||||
"svg-pathdata": {
|
||||
"version": "5.0.5",
|
||||
@@ -10720,6 +11105,11 @@
|
||||
"resolved": "https://registry.npmjs.org/tether/-/tether-1.4.7.tgz",
|
||||
"integrity": "sha512-Z0J1aExjoFU8pybVkQAo/vD2wfSO63r+XOPfWQMC5qtf1bI7IWqNk4MiyBcgvvnY8kqnY06dVdvwTK2S3PU/Fw=="
|
||||
},
|
||||
"throttleit": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
|
||||
"integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw="
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
@@ -11945,6 +12335,14 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"yauzl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
|
||||
"integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
|
||||
"requires": {
|
||||
"fd-slicer": "~1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"less-loader": "^4.1.0",
|
||||
"list.js": "^1.5.0",
|
||||
"papaparse": "^4.3.3",
|
||||
"phantomjs": "^2.1.7",
|
||||
"select2": "4.0.13",
|
||||
"tableexport.jquery.plugin": "^1.10.20",
|
||||
"tether": "^1.4.0",
|
||||
|
||||
Vendored
+5
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+135
@@ -0,0 +1,135 @@
|
||||
|
||||
#signature-pad {
|
||||
padding-top: 250px;
|
||||
margin: auto;
|
||||
}
|
||||
.m-signature-pad {
|
||||
|
||||
position: relative;
|
||||
font-size: 10px;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border: 1px solid #e8e8e8;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.08) inset;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.m-signature-pad:before, .m-signature-pad:after {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
content: "";
|
||||
width: 40%;
|
||||
height: 10px;
|
||||
left: 20px;
|
||||
bottom: 10px;
|
||||
background: transparent;
|
||||
-webkit-transform: skew(-3deg) rotate(-3deg);
|
||||
-moz-transform: skew(-3deg) rotate(-3deg);
|
||||
-ms-transform: skew(-3deg) rotate(-3deg);
|
||||
-o-transform: skew(-3deg) rotate(-3deg);
|
||||
transform: skew(-3deg) rotate(-3deg);
|
||||
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.m-signature-pad:after {
|
||||
left: auto;
|
||||
right: 20px;
|
||||
-webkit-transform: skew(3deg) rotate(3deg);
|
||||
-moz-transform: skew(3deg) rotate(3deg);
|
||||
-ms-transform: skew(3deg) rotate(3deg);
|
||||
-o-transform: skew(3deg) rotate(3deg);
|
||||
transform: skew(3deg) rotate(3deg);
|
||||
}
|
||||
|
||||
.m-signature-pad--body {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
bottom: 60px;
|
||||
border: 1px solid #f4f4f4;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.m-signature-pad--body
|
||||
canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
|
||||
}
|
||||
|
||||
.m-signature-pad--footer {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.m-signature-pad--footer
|
||||
.description {
|
||||
color: #C3C3C3;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
margin-top: 1.8em;
|
||||
}
|
||||
|
||||
.m-signature-pad--footer
|
||||
.button {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.m-signature-pad--footer
|
||||
.button.clear {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.m-signature-pad--footer
|
||||
.button.save {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.m-signature-pad {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-width: 250px;
|
||||
min-height: 140px;
|
||||
margin: 5%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
.m-signature-pad {
|
||||
margin: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 320px) {
|
||||
.m-signature-pad--body {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 32px;
|
||||
}
|
||||
.m-signature-pad--footer {
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
bottom: 4px;
|
||||
height: 28px;
|
||||
}
|
||||
.m-signature-pad--footer
|
||||
.description {
|
||||
font-size: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
File diff suppressed because one or more lines are too long
+20
-12
File diff suppressed because one or more lines are too long
Vendored
+34
-26
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user