Compare commits
334 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcd805638e | ||
|
|
63629abb93 | ||
|
|
6373ef3283 | ||
|
|
00a7c1e9e2 | ||
|
|
f37d5d3d03 | ||
|
|
57e52f0ba4 | ||
|
|
dc8e06fc65 | ||
|
|
db3f80bb9b | ||
|
|
f5dda06c55 | ||
|
|
c3166d491a | ||
|
|
763e17f491 | ||
|
|
47b2fe571e | ||
|
|
c8f6318aba | ||
|
|
adbb3a8f31 | ||
|
|
1f142fde8a | ||
|
|
44eee019d9 | ||
|
|
f636aac2dd | ||
|
|
54fd1b993b | ||
|
|
a5731a3088 | ||
|
|
6449d0aaf9 | ||
|
|
931e2df3bd | ||
|
|
7f8eddede6 | ||
|
|
4ddab03792 | ||
|
|
ff341caf34 | ||
|
|
5754f0aa3f | ||
|
|
785bc40d9d | ||
|
|
9d50e0e8d0 | ||
|
|
9deb4204c8 | ||
|
|
1ab349a63d | ||
|
|
2e2d087639 | ||
|
|
bfadb2cea6 | ||
|
|
44eb67440a | ||
|
|
8fb97da314 | ||
|
|
12ff465cdb | ||
|
|
f89d789832 | ||
|
|
4c3b46ea88 | ||
|
|
834e0a9dd5 | ||
|
|
681c41bd18 | ||
|
|
74488ddceb | ||
|
|
19820f1b42 | ||
|
|
2a88781cd5 | ||
|
|
f96c867bd3 | ||
|
|
06f8e8620a | ||
|
|
95d907c9e9 | ||
|
|
d990152856 | ||
|
|
05609230b2 | ||
|
|
220c254093 | ||
|
|
02313ce361 | ||
|
|
e70f7c610a | ||
|
|
32f77c3285 | ||
|
|
0a639f4fcc | ||
|
|
f2b55fb641 | ||
|
|
0e443356f0 | ||
|
|
76f7f01398 | ||
|
|
992de0156b | ||
|
|
c96b5f5a85 | ||
|
|
8d2685f0f0 | ||
|
|
eb14cc7f43 | ||
|
|
3dc67cdba6 | ||
|
|
0bbe0c85d7 | ||
|
|
49415806e1 | ||
|
|
5edbb4b229 | ||
|
|
c40b8334fc | ||
|
|
0a37c9564a | ||
|
|
985193ffff | ||
|
|
721add5bc1 | ||
|
|
ff8fa6ec77 | ||
|
|
e0a6f22489 | ||
|
|
030fdd60ff | ||
|
|
fdde844ce5 | ||
|
|
d263990401 | ||
|
|
bf7a856fa6 | ||
|
|
1e062d4fc8 | ||
|
|
ca37de5e45 | ||
|
|
9ba2fd93c1 | ||
|
|
a2e177e754 | ||
|
|
5e6db0b219 | ||
|
|
b09ded2a3b | ||
|
|
124343911f | ||
|
|
462f8c791f | ||
|
|
9a224a07ba | ||
|
|
df4686bc96 | ||
|
|
b6c432a596 | ||
|
|
de9f487664 | ||
|
|
ef668317a9 | ||
|
|
cf368a4577 | ||
|
|
2e71968c04 | ||
|
|
fdb5b3baf1 | ||
|
|
c745fa095b | ||
|
|
70e6a6ced6 | ||
|
|
6772835efc | ||
|
|
fb482b0dd6 | ||
|
|
9f43d3345f | ||
|
|
6e83679528 | ||
|
|
a050aba72f | ||
|
|
0031fab0fe | ||
|
|
585bdff364 | ||
|
|
1d9741a49e | ||
|
|
9f7f1460e9 | ||
|
|
f871759753 | ||
|
|
8e17818f1e | ||
|
|
d19c6ab8e7 | ||
|
|
d14b1e3825 | ||
|
|
ba12ee9954 | ||
|
|
d8bb69533c | ||
|
|
01d3606c42 | ||
|
|
208f1db3b2 | ||
|
|
e5b02da54b | ||
|
|
d6ead5ae17 | ||
|
|
1d7d31b9ae | ||
|
|
2a817c2123 | ||
|
|
f3a7467235 | ||
|
|
2da6f9136f | ||
|
|
79549dbfb9 | ||
|
|
a48d09f37e | ||
|
|
0dc78fdea6 | ||
|
|
75a8639a20 | ||
|
|
380c6171b7 | ||
|
|
d36d6b8e07 | ||
|
|
c00a1fa21b | ||
|
|
bbcd215ea4 | ||
|
|
444f9a81da | ||
|
|
b4eee5a9b7 | ||
|
|
72f9fe444d | ||
|
|
eb423c252a | ||
|
|
382fb31670 | ||
|
|
e6ba4a423d | ||
|
|
13ed6cde67 | ||
|
|
cac78cdbf3 | ||
|
|
8b67326e95 | ||
|
|
f65bc5caee | ||
|
|
0329028e2c | ||
|
|
d3d96c8285 | ||
|
|
5909860c5a | ||
|
|
1023fa3edd | ||
|
|
65e6d56f1f | ||
|
|
bf34385c3e | ||
|
|
55a526a6b3 | ||
|
|
bbf7fbcff4 | ||
|
|
4a5cb94d94 | ||
|
|
cb184a9687 | ||
|
|
fb37dbed92 | ||
|
|
e410696a36 | ||
|
|
45bfec5cd3 | ||
|
|
055522510b | ||
|
|
f1d0d1bfe7 | ||
|
|
c0aa6c153e | ||
|
|
da3451bf0d | ||
|
|
5f76e03616 | ||
|
|
84710eac98 | ||
|
|
81bf41a091 | ||
|
|
134acf3b87 | ||
|
|
82d8b2ab82 | ||
|
|
adc0d3a6ac | ||
|
|
8b15841c4b | ||
|
|
fd4ee60276 | ||
|
|
93358b5872 | ||
|
|
1c4e20c712 | ||
|
|
0e1f6a3fd1 | ||
|
|
71d0e6369e | ||
|
|
b27aeb1952 | ||
|
|
bec2b170ec | ||
|
|
269d3fe509 | ||
|
|
34d5473553 | ||
|
|
4ac15daee7 | ||
|
|
5f3a1f6287 | ||
|
|
bfc60864dd | ||
|
|
ffa2701f89 | ||
|
|
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 |
@@ -135,7 +135,8 @@
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/10137?v=3",
|
||||
"profile": "https://github.com/ghost",
|
||||
"contributions": [
|
||||
"translation"
|
||||
"translation",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1938,6 +1939,195 @@
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "giannello",
|
||||
"name": "Giuseppe Iannello",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/551789?v=4",
|
||||
"profile": "https://github.com/giannello",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PeterDaveHello",
|
||||
"name": "Peter Dave Hello",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3691490?v=4",
|
||||
"profile": "https://www.peterdavehello.org/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sigmoidal",
|
||||
"name": "sigmoidal",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6106332?v=4",
|
||||
"profile": "https://github.com/sigmoidal",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "phenixdotnet",
|
||||
"name": "Vincent Lainé",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2082554?v=4",
|
||||
"profile": "https://github.com/phenixdotnet",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "derlucas",
|
||||
"name": "Lucas Pleß",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1943040?v=4",
|
||||
"profile": "http://www.lucas-pless.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "iansltx",
|
||||
"name": "Ian Littman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/472804?v=4",
|
||||
"profile": "http://twitter.com/iansltx",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PauloLuna",
|
||||
"name": "João Paulo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3519029?v=4",
|
||||
"profile": "https://github.com/PauloLuna",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ThoBur",
|
||||
"name": "ThoBur",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/70443365?v=4",
|
||||
"profile": "https://github.com/ThoBur",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "alek13",
|
||||
"name": "Alexander Chibrikin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1972329?v=4",
|
||||
"profile": "http://phpprofi.ru/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "winstan",
|
||||
"name": "Anthony Winstanley",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/438332?v=4",
|
||||
"profile": "https://github.com/winstan",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fashberg",
|
||||
"name": "Folke",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3075214?v=4",
|
||||
"profile": "https://github.com/fashberg",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "benwa",
|
||||
"name": "Bennett Blodinger",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1351571?v=4",
|
||||
"profile": "https://github.com/benwa",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ncareau",
|
||||
"name": "NMC",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2974631?v=4",
|
||||
"profile": "https://nmc.dev",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "andres-baller",
|
||||
"name": "andres-baller",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/52182449?v=4",
|
||||
"profile": "https://github.com/andres-baller",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sean-borg",
|
||||
"name": "sean-borg",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/67109348?v=4",
|
||||
"profile": "https://github.com/sean-borg",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "EDVLeer",
|
||||
"name": "EDVLeer",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/32170051?v=4",
|
||||
"profile": "https://github.com/EDVLeer",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Kurokat",
|
||||
"name": "Kurokat",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/23075196?v=4",
|
||||
"profile": "https://github.com/Kurokat",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "koelle25",
|
||||
"name": "Kevin Köllmann",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/915514?v=4",
|
||||
"profile": "https://www.kevinkoellmann.de",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sw-mreyes",
|
||||
"name": "sw-mreyes",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/49025941?v=4",
|
||||
"profile": "https://github.com/sw-mreyes",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -51,6 +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_AUTO_EMBED_METHOD='attachment'
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: IMAGE LIBRARY
|
||||
@@ -76,6 +77,7 @@ ENCRYPT=false
|
||||
COOKIE_NAME=snipeit_session
|
||||
COOKIE_DOMAIN=null
|
||||
SECURE_COOKIES=false
|
||||
API_TOKEN_EXPIRATION_YEARS=40
|
||||
|
||||
# --------------------------------------------
|
||||
# OPTIONAL: SECURITY HEADER SETTINGS
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -60,3 +60,4 @@ phpmd\.xml
|
||||
_ide_helper.php
|
||||
.phpstorm.meta.php
|
||||
_ide_helper_models.php
|
||||
/.phplint-cache
|
||||
|
||||
10
.htaccess
10
.htaccess
@@ -5,7 +5,15 @@
|
||||
|
||||
# Make sure .env files not not browseable if in a sub-directory.
|
||||
<FilesMatch "\.env$">
|
||||
Deny from all
|
||||
# Apache 2.2
|
||||
<IfModule !authz_core_module>
|
||||
Deny from all
|
||||
</IfModule>
|
||||
|
||||
# Apache 2.4+
|
||||
<IfModule authz_core_module>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
</IfModule>
|
||||
|
||||
80
.travis.yml
80
.travis.yml
@@ -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,6 +1,6 @@
|
||||
FROM alpine:3.8
|
||||
FROM alpine:3.12
|
||||
# Apache + PHP
|
||||
RUN apk add --update --no-cache \
|
||||
RUN apk add --no-cache \
|
||||
apache2 \
|
||||
php7 \
|
||||
php7-common \
|
||||
@@ -23,6 +23,8 @@ RUN apk add --update --no-cache \
|
||||
php7-fileinfo \
|
||||
php7-simplexml \
|
||||
php7-session \
|
||||
php7-dom \
|
||||
php7-xmlwriter \
|
||||
curl \
|
||||
wget \
|
||||
vim \
|
||||
|
||||
20
README.md
20
README.md
@@ -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. :)
|
||||
|
||||
-----
|
||||
@@ -91,7 +90,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
| [<img src="https://avatars3.githubusercontent.com/u/197404?v=3" width="110px;"/><br /><sub>snipe</sub>](http://www.snipe.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/snipe/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/snipe/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/36335?v=3" width="110px;"/><br /><sub>Brady Wetherington</sub>](http://www.uberbrady.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/snipe/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/3803132?v=3" width="110px;"/><br /><sub>Daniel Meltzer</sub>](https://github.com/dmeltzer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1609106?v=3" width="110px;"/><br /><sub>Michael T</sub>](http://www.tuckertechonline.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [<img src="https://avatars2.githubusercontent.com/u/3274937?v=3" width="110px;"/><br /><sub>madd15</sub>](https://github.com/madd15)<br />[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [<img src="https://avatars2.githubusercontent.com/u/894126?v=3" width="110px;"/><br /><sub>Vincent Sposato</sub>](https://github.com/vsposato)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [<img src="https://avatars0.githubusercontent.com/u/1639757?v=3" width="110px;"/><br /><sub>Andrea Bergamasco</sub>](https://github.com/vjandrea)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") |
|
||||
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/10640152?v=3" width="110px;"/><br /><sub>Karol</sub>](https://github.com/kpawelski)<br />[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [<img src="https://avatars3.githubusercontent.com/u/600106?v=3" width="110px;"/><br /><sub>morph027</sub>](http://blog.morph027.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [<img src="https://avatars3.githubusercontent.com/u/22935755?v=3" width="110px;"/><br /><sub>fvleminckx</sub>](https://github.com/fvleminckx)<br />[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars2.githubusercontent.com/u/15633547?v=3" width="110px;"/><br /><sub>itsupportcmsukorg</sub>](https://github.com/itsupportcmsukorg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/12373799?v=3" width="110px;"/><br /><sub>Frank</sub>](https://override.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [<img src="https://avatars0.githubusercontent.com/u/10137?v=3" width="110px;"/><br /><sub>Deleted user</sub>](https://github.com/ghost)<br />[🌍](#translation-ghost "Translation") | [<img src="https://avatars1.githubusercontent.com/u/10802313?v=3" width="110px;"/><br /><sub>tiagom62</sub>](https://github.com/tiagom62)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/10640152?v=3" width="110px;"/><br /><sub>Karol</sub>](https://github.com/kpawelski)<br />[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [<img src="https://avatars3.githubusercontent.com/u/600106?v=3" width="110px;"/><br /><sub>morph027</sub>](http://blog.morph027.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [<img src="https://avatars3.githubusercontent.com/u/22935755?v=3" width="110px;"/><br /><sub>fvleminckx</sub>](https://github.com/fvleminckx)<br />[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars2.githubusercontent.com/u/15633547?v=3" width="110px;"/><br /><sub>itsupportcmsukorg</sub>](https://github.com/itsupportcmsukorg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/12373799?v=3" width="110px;"/><br /><sub>Frank</sub>](https://override.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [<img src="https://avatars0.githubusercontent.com/u/10137?v=3" width="110px;"/><br /><sub>Deleted user</sub>](https://github.com/ghost)<br />[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [<img src="https://avatars1.githubusercontent.com/u/10802313?v=3" width="110px;"/><br /><sub>tiagom62</sub>](https://github.com/tiagom62)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
|
||||
| [<img src="https://avatars3.githubusercontent.com/u/2389047?v=3" width="110px;"/><br /><sub>Ryan Stafford</sub>](https://github.com/rystaf)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [<img src="https://avatars2.githubusercontent.com/u/10345935?v=3" width="110px;"/><br /><sub>Eammon Hanlon</sub>](https://github.com/ehanlon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [<img src="https://avatars0.githubusercontent.com/u/441924?v=3" width="110px;"/><br /><sub>zjean</sub>](https://github.com/zjean)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [<img src="https://avatars0.githubusercontent.com/u/12660103?v=3" width="110px;"/><br /><sub>Matthias Frei</sub>](http://www.frei.media)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [<img src="https://avatars0.githubusercontent.com/u/3767518?v=3" width="110px;"/><br /><sub>opsydev</sub>](https://github.com/opsydev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [<img src="https://avatars1.githubusercontent.com/u/82290?v=3" width="110px;"/><br /><sub>Daniel Dreier</sub>](http://www.ddreier.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [<img src="https://avatars0.githubusercontent.com/u/23448?v=3" width="110px;"/><br /><sub>Nikolai Prokoschenko</sub>](http://rassie.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/13452757?v=3" width="110px;"/><br /><sub>Drew</sub>](https://github.com/YetAnotherCodeMonkey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [<img src="https://avatars0.githubusercontent.com/u/1342320?v=3" width="110px;"/><br /><sub>Walter</sub>](https://github.com/merid14)<br />[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [<img src="https://avatars3.githubusercontent.com/u/11254614?v=3" width="110px;"/><br /><sub>Petr Baloun</sub>](https://github.com/balous)<br />[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [<img src="https://avatars0.githubusercontent.com/u/6117660?v=3" width="110px;"/><br /><sub>reidblomquist</sub>](https://github.com/reidblomquist)<br />[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/539914?v=3" width="110px;"/><br /><sub>Mathieu Kooiman</sub>](https://github.com/mathieuk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [<img src="https://avatars3.githubusercontent.com/u/6606421?v=3" width="110px;"/><br /><sub>csayre</sub>](https://github.com/csayre)<br />[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/768488?v=3" width="110px;"/><br /><sub>Adam Dunson</sub>](https://github.com/adamdunson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/5547470?v=3" width="110px;"/><br /><sub>Hereward</sub>](https://github.com/thehereward)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [<img src="https://avatars0.githubusercontent.com/u/5802977?v=3" width="110px;"/><br /><sub>swoopdk</sub>](https://github.com/swoopdk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [<img src="https://avatars1.githubusercontent.com/u/3470403?v=3" width="110px;"/><br /><sub>Abdullah Alansari</sub>](https://linkedin.com/in/ahimta)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [<img src="https://avatars0.githubusercontent.com/u/796443?v=3" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [<img src="https://avatars0.githubusercontent.com/u/614564?v=3" width="110px;"/><br /><sub>Patrick Gallagher</sub>](http://macadmincorner.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/7165922?v=3" width="110px;"/><br /><sub>Miliamber</sub>](https://github.com/Miliamber)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [<img src="https://avatars3.githubusercontent.com/u/861766?v=3" width="110px;"/><br /><sub>hawk554</sub>](https://github.com/hawk554)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") |
|
||||
@@ -115,14 +114,15 @@ 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") | [<img src="https://avatars.githubusercontent.com/u/551789?v=4" width="110px;"/><br /><sub>Giuseppe Iannello</sub>](https://github.com/giannello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [<img src="https://avatars.githubusercontent.com/u/3691490?v=4" width="110px;"/><br /><sub>Peter Dave Hello</sub>](https://www.peterdavehello.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [<img src="https://avatars.githubusercontent.com/u/6106332?v=4" width="110px;"/><br /><sub>sigmoidal</sub>](https://github.com/sigmoidal)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/2082554?v=4" width="110px;"/><br /><sub>Vincent Lainé</sub>](https://github.com/phenixdotnet)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [<img src="https://avatars.githubusercontent.com/u/1943040?v=4" width="110px;"/><br /><sub>Lucas Pleß</sub>](http://www.lucas-pless.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [<img src="https://avatars.githubusercontent.com/u/472804?v=4" width="110px;"/><br /><sub>Ian Littman</sub>](http://twitter.com/iansltx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [<img src="https://avatars.githubusercontent.com/u/3519029?v=4" width="110px;"/><br /><sub>João Paulo</sub>](https://github.com/PauloLuna)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [<img src="https://avatars.githubusercontent.com/u/70443365?v=4" width="110px;"/><br /><sub>ThoBur</sub>](https://github.com/ThoBur)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [<img src="https://avatars.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://avatars.githubusercontent.com/u/438332?v=4" width="110px;"/><br /><sub>Anthony Winstanley</sub>](https://github.com/winstan)<br />[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/3075214?v=4" width="110px;"/><br /><sub>Folke</sub>](https://github.com/fashberg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [<img src="https://avatars.githubusercontent.com/u/1351571?v=4" width="110px;"/><br /><sub>Bennett Blodinger</sub>](https://github.com/benwa)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [<img src="https://avatars.githubusercontent.com/u/2974631?v=4" width="110px;"/><br /><sub>NMC</sub>](https://nmc.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [<img src="https://avatars.githubusercontent.com/u/52182449?v=4" width="110px;"/><br /><sub>andres-baller</sub>](https://github.com/andres-baller)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [<img src="https://avatars.githubusercontent.com/u/67109348?v=4" width="110px;"/><br /><sub>sean-borg</sub>](https://github.com/sean-borg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [<img src="https://avatars.githubusercontent.com/u/32170051?v=4" width="110px;"/><br /><sub>EDVLeer</sub>](https://github.com/EDVLeer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [<img src="https://avatars.githubusercontent.com/u/23075196?v=4" width="110px;"/><br /><sub>Kurokat</sub>](https://github.com/Kurokat)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/915514?v=4" width="110px;"/><br /><sub>Kevin Köllmann</sub>](https://www.kevinkoellmann.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [<img src="https://avatars.githubusercontent.com/u/49025941?v=4" width="110px;"/><br /><sub>sw-mreyes</sub>](https://github.com/sw-mreyes)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
105
app/Console/Commands/FixMismatchedAssetsAndLogs.php
Normal file
105
app/Console/Commands/FixMismatchedAssetsAndLogs.php
Normal file
@@ -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 {
|
||||
|
||||
550
app/Console/Commands/LdapSync.php
Normal file → Executable file
550
app/Console/Commands/LdapSync.php
Normal file → Executable file
@@ -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,275 +42,241 @@ class LdapSync extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
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)
|
||||
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->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());
|
||||
$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; // FIXME - this would be better called "$default_location", which is more explicit about its purpose
|
||||
|
||||
/**
|
||||
* 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 $ou_loc) {
|
||||
$ldap_ou_lengths[] = strlen($ou_loc["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
|
||||
*
|
||||
*/
|
||||
private function processLdapUsers(): void
|
||||
{
|
||||
try {
|
||||
$ldapUsers = $this->ldap->getLdapUsers();
|
||||
} 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);
|
||||
// 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 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' => [],
|
||||
];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
399
app/Console/Commands/LdapSyncNg.php
Normal file
399
app/Console/Commands/LdapSyncNg.php
Normal file
@@ -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();
|
||||
|
||||
|
||||
44
app/Console/Commands/PurgeLoginAttempts.php
Normal file
44
app/Console/Commands/PurgeLoginAttempts.php
Normal file
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,9 +66,6 @@ class ResetDemoSettings extends Command
|
||||
$settings->version_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
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ class Handler extends ExceptionHandler
|
||||
\Illuminate\Session\TokenMismatchException::class,
|
||||
\Illuminate\Validation\ValidationException::class,
|
||||
\Intervention\Image\Exception\NotSupportedException::class,
|
||||
\League\OAuth2\Server\Exception\OAuthServerException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -105,7 +106,7 @@ class Handler extends ExceptionHandler
|
||||
protected function unauthenticated($request, AuthenticationException $exception)
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => 'Unauthorized.'], 401);
|
||||
return response()->json(['error' => 'Unauthorized or unauthenticated.'], 401);
|
||||
}
|
||||
|
||||
return redirect()->guest('login');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +428,26 @@ class Helper
|
||||
return $statuslabel_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of deployable status labels in an array to make a dropdown menu
|
||||
*
|
||||
* @todo This should probably be a selectlist, same as the other endpoints
|
||||
* and we should probably add to the API controllers to make sure that
|
||||
* the status_id submitted is actually really deployable.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.1.0]
|
||||
* @return Array
|
||||
*/
|
||||
public static function deployableStatusLabelList()
|
||||
{
|
||||
$statuslabel_list = Statuslabel::where('deployable', '=', '1')->orderBy('default_label', 'desc')
|
||||
->orderBy('name','asc')
|
||||
->orderBy('deployable','desc')
|
||||
->pluck('name', 'id')->toArray();
|
||||
return $statuslabel_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of status label types in an array to make a dropdown menu
|
||||
*
|
||||
|
||||
@@ -30,7 +30,20 @@ class AssetModelsController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', AssetModel::class);
|
||||
$allowed_columns = ['id','image','name','model_number','eol','notes','created_at','manufacturer','requestable', 'assets_count'];
|
||||
$allowed_columns =
|
||||
[
|
||||
'id',
|
||||
'image',
|
||||
'name',
|
||||
'model_number',
|
||||
'eol',
|
||||
'notes',
|
||||
'created_at',
|
||||
'manufacturer',
|
||||
'requestable',
|
||||
'assets_count',
|
||||
'category'
|
||||
];
|
||||
|
||||
$assetmodels = AssetModel::select([
|
||||
'models.id',
|
||||
@@ -75,13 +88,14 @@ class AssetModelsController extends Controller
|
||||
case 'manufacturer':
|
||||
$assetmodels->OrderManufacturer($order);
|
||||
break;
|
||||
case 'category':
|
||||
$assetmodels->OrderCategory($order);
|
||||
break;
|
||||
default:
|
||||
$assetmodels->orderBy($sort, $order);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$total = $assetmodels->count();
|
||||
$assetmodels = $assetmodels->skip($offset)->take($limit)->get();
|
||||
return (new AssetModelsTransformer)->transformAssetModels($assetmodels, $total);
|
||||
@@ -239,17 +253,17 @@ class AssetModelsController extends Controller
|
||||
$assetmodel->use_text = '';
|
||||
|
||||
if ($settings->modellistCheckedValue('category')) {
|
||||
$assetmodel->use_text .= (($assetmodel->category) ? e($assetmodel->category->name).' - ' : '');
|
||||
$assetmodel->use_text .= (($assetmodel->category) ? $assetmodel->category->name.' - ' : '');
|
||||
}
|
||||
|
||||
if ($settings->modellistCheckedValue('manufacturer')) {
|
||||
$assetmodel->use_text .= (($assetmodel->manufacturer) ? e($assetmodel->manufacturer->name).' ' : '');
|
||||
$assetmodel->use_text .= (($assetmodel->manufacturer) ? $assetmodel->manufacturer->name.' ' : '');
|
||||
}
|
||||
|
||||
$assetmodel->use_text .= e($assetmodel->name);
|
||||
$assetmodel->use_text .= $assetmodel->name;
|
||||
|
||||
if (($settings->modellistCheckedValue('model_number')) && ($assetmodel->model_number!='')) {
|
||||
$assetmodel->use_text .= ' (#'.e($assetmodel->model_number).')';
|
||||
$assetmodel->use_text .= ' (#'.$assetmodel->model_number.')';
|
||||
}
|
||||
|
||||
$assetmodel->use_image = ($settings->modellistCheckedValue('image') && ($assetmodel->image)) ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null;
|
||||
|
||||
@@ -141,8 +141,6 @@ class AssetsController extends Controller
|
||||
}
|
||||
|
||||
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
|
||||
$offset = (($assets) && (request('offset') > $assets->count())) ? 0 : request('offset', 0);
|
||||
|
||||
|
||||
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
|
||||
// case we override with the actual count, so we should return 0 items.
|
||||
@@ -812,7 +810,7 @@ class AssetsController extends Controller
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
$asset->last_audit_date = date('Y-m-d h:i:s');
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
if ($asset->save()) {
|
||||
$log = $asset->logAudit(request('note'),request('location_id'));
|
||||
|
||||
@@ -20,7 +20,7 @@ class DepreciationsController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Depreciation::class);
|
||||
$allowed_columns = ['id','name','created_at'];
|
||||
$allowed_columns = ['id','name','months','created_at'];
|
||||
|
||||
$depreciations = Depreciation::select('id','name','months','user_id','created_at','updated_at');
|
||||
|
||||
|
||||
@@ -103,11 +103,33 @@ class LicensesController extends Controller
|
||||
case 'category':
|
||||
$licenses = $licenses->leftJoin('categories', 'licenses.category_id', '=', 'categories.id')->orderBy('categories.name', $order);
|
||||
break;
|
||||
case 'depreciation':
|
||||
$licenses = $licenses->leftJoin('depreciations', 'licenses.depreciation_id', '=', 'depreciations.id')->orderBy('depreciations.name', $order);
|
||||
break;
|
||||
case 'company':
|
||||
$licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
|
||||
break;
|
||||
default:
|
||||
$allowed_columns = ['id','name','purchase_cost','expiration_date','purchase_order','order_number','notes','purchase_date','serial','company','category','license_name','license_email','free_seats_count','seats'];
|
||||
$allowed_columns =
|
||||
[
|
||||
'id',
|
||||
'name',
|
||||
'purchase_cost',
|
||||
'expiration_date',
|
||||
'purchase_order',
|
||||
'order_number',
|
||||
'notes',
|
||||
'purchase_date',
|
||||
'serial',
|
||||
'company',
|
||||
'category',
|
||||
'license_name',
|
||||
'license_email',
|
||||
'free_seats_count',
|
||||
'seats',
|
||||
'termination_date',
|
||||
'depreciation_id'
|
||||
];
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
|
||||
$licenses = $licenses->orderBy($sort, $order);
|
||||
break;
|
||||
|
||||
@@ -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" => [ [
|
||||
|
||||
@@ -60,6 +60,7 @@ class UsersController extends Controller
|
||||
'users.updated_at',
|
||||
'users.username',
|
||||
'users.zip',
|
||||
'users.ldap_import',
|
||||
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department','assets','licenses','accessories','consumables')
|
||||
->withCount('assets as assets_count','licenses as licenses_count','accessories as accessories_count','consumables as consumables_count');
|
||||
@@ -68,7 +69,7 @@ class UsersController extends Controller
|
||||
|
||||
if (($request->filled('deleted')) && ($request->input('deleted')=='true')) {
|
||||
$users = $users->onlyTrashed();
|
||||
} elseif (($request->filled('all')) && ($request->input('deleted')=='true')) {
|
||||
} elseif (($request->filled('all')) && ($request->input('all')=='true')) {
|
||||
$users = $users->withTrashed();
|
||||
}
|
||||
|
||||
@@ -131,7 +132,7 @@ class UsersController extends Controller
|
||||
'assets','accessories', 'consumables','licenses','groups','activated','created_at',
|
||||
'two_factor_enrolled','two_factor_optin','last_login', 'assets_count', 'licenses_count',
|
||||
'consumables_count', 'accessories_count', 'phone', 'address', 'city', 'state',
|
||||
'country', 'zip', 'id'
|
||||
'country', 'zip', 'id', 'ldap_import'
|
||||
];
|
||||
|
||||
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
|
||||
@@ -184,16 +185,16 @@ class UsersController extends Controller
|
||||
foreach ($users as $user) {
|
||||
$name_str = '';
|
||||
if ($user->last_name!='') {
|
||||
$name_str .= e($user->last_name).', ';
|
||||
$name_str .= $user->last_name.', ';
|
||||
}
|
||||
$name_str .= e($user->first_name);
|
||||
$name_str .= $user->first_name;
|
||||
|
||||
if ($user->username!='') {
|
||||
$name_str .= ' ('.e($user->username).')';
|
||||
$name_str .= ' ('.$user->username.')';
|
||||
}
|
||||
|
||||
if ($user->employee_num!='') {
|
||||
$name_str .= ' - #'.e($user->employee_num);
|
||||
$name_str .= ' - #'.$user->employee_num;
|
||||
}
|
||||
|
||||
$user->use_text = $name_str;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Assets;
|
||||
|
||||
|
||||
use App\Exceptions\CheckoutNotAllowed;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\CheckInOutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetCheckoutRequest;
|
||||
@@ -33,7 +34,8 @@ class AssetCheckoutController extends Controller
|
||||
$this->authorize('checkout', $asset);
|
||||
|
||||
if ($asset->availableForCheckout()) {
|
||||
return view('hardware/checkout', compact('asset'));
|
||||
return view('hardware/checkout', compact('asset'))
|
||||
->with('statusLabel_list', Helper::deployableStatusLabelList());
|
||||
}
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
|
||||
|
||||
@@ -75,6 +77,10 @@ class AssetCheckoutController extends Controller
|
||||
$expected_checkin = $request->get('expected_checkin');
|
||||
}
|
||||
|
||||
if ($request->filled('status_id')) {
|
||||
$asset->status_id = $request->get('status_id');
|
||||
}
|
||||
|
||||
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $request->get('name'))) {
|
||||
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.checkout.success'));
|
||||
}
|
||||
|
||||
@@ -165,10 +165,17 @@ class AssetsController extends Controller
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($field->field_encrypted=='1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
|
||||
}
|
||||
if(is_array($request->input($field->convertUnicodeDbSlug()))){
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e(implode(', ', $request->input($field->convertUnicodeDbSlug()))));
|
||||
}else{
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e($request->input($field->convertUnicodeDbSlug())));
|
||||
} }
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
if(is_array($request->input($field->convertUnicodeDbSlug()))){
|
||||
$asset->{$field->convertUnicodeDbSlug()} = implode(', ', $request->input($field->convertUnicodeDbSlug()));
|
||||
}else{
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,10 +349,18 @@ class AssetsController extends Controller
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
if ($field->field_encrypted=='1') {
|
||||
if (Gate::allows('admin')) {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e($request->input($field->convertUnicodeDbSlug())));
|
||||
if(is_array($request->input($field->convertUnicodeDbSlug()))){
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e(implode(', ', $request->input($field->convertUnicodeDbSlug()))));
|
||||
}else{
|
||||
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt(e($request->input($field->convertUnicodeDbSlug())));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
if(is_array($request->input($field->convertUnicodeDbSlug()))){
|
||||
$asset->{$field->convertUnicodeDbSlug()} = implode(', ', $request->input($field->convertUnicodeDbSlug()));
|
||||
}else{
|
||||
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,13 +105,13 @@ class LoginController extends Controller
|
||||
$samlData = $request->session()->get('saml_login');
|
||||
if ($saml->isEnabled() && !empty($samlData)) {
|
||||
try {
|
||||
LOG::debug("Attempting to log user in by SAML authentication.");
|
||||
Log::debug("Attempting to log user in by SAML authentication.");
|
||||
$user = $saml->samlLogin($samlData);
|
||||
if(!is_null($user)) {
|
||||
Auth::login($user, true);
|
||||
} else {
|
||||
$username = $saml->getUsername();
|
||||
LOG::debug("SAML user '$username' could not be found in database.");
|
||||
Log::error("SAML user '$username' could not be found in database.");
|
||||
$request->session()->flash('error', trans('auth/message.signin.error'));
|
||||
$saml->clearData();
|
||||
}
|
||||
@@ -121,7 +121,7 @@ class LoginController extends Controller
|
||||
$user->save();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
LOG::debug("There was an error authenticating the SAML user: " . $e->getMessage());
|
||||
Log::error("There was an error authenticating the SAML user: " . $e->getMessage());
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -157,9 +157,26 @@ class LoginController extends Controller
|
||||
if (Setting::getSettings()->login_remote_user_enabled == "1" && isset($remote_user) && !empty($remote_user)) {
|
||||
Log::debug("Authenticating via HTTP header $header_name.");
|
||||
|
||||
$pos = strpos($remote_user, '\\');
|
||||
$strip_prefixes = [
|
||||
// IIS/AD
|
||||
// https://github.com/snipe/snipe-it/pull/5862
|
||||
'\\',
|
||||
|
||||
// Google Cloud IAP
|
||||
// https://cloud.google.com/iap/docs/identity-howto#getting_the_users_identity_with_signed_headers
|
||||
'accounts.google.com:',
|
||||
];
|
||||
|
||||
$pos = 0;
|
||||
foreach ($strip_prefixes as $needle) {
|
||||
if (($pos = strpos($remote_user, $needle)) !== FALSE) {
|
||||
$pos += strlen($needle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($pos > 0) {
|
||||
$remote_user = substr($remote_user, $pos + 1);
|
||||
$remote_user = substr($remote_user, $pos);
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -189,8 +206,8 @@ class LoginController extends Controller
|
||||
return redirect()->back()->withInput()->withErrors($validator);
|
||||
}
|
||||
|
||||
$this->maxLoginAttempts = config('auth.throttle.max_attempts');
|
||||
$this->lockoutTime = config('auth.throttle.lockout_duration');
|
||||
$this->maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
|
||||
$this->lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
|
||||
|
||||
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
|
||||
$this->fireLockoutEvent($request);
|
||||
@@ -234,6 +251,7 @@ class LoginController extends Controller
|
||||
|
||||
if ($user = Auth::user()) {
|
||||
$user->last_login = \Carbon::now();
|
||||
$user->activated = 1;
|
||||
$user->save();
|
||||
}
|
||||
// Redirect to the users page
|
||||
@@ -451,8 +469,8 @@ class LoginController extends Controller
|
||||
*/
|
||||
protected function hasTooManyLoginAttempts(Request $request)
|
||||
{
|
||||
$lockoutTime = config('auth.throttle.lockout_duration');
|
||||
$maxLoginAttempts = config('auth.throttle.max_attempts');
|
||||
$lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
|
||||
$maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
|
||||
|
||||
return $this->limiter()->tooManyAttempts(
|
||||
$this->throttleKey($request),
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,8 +101,8 @@ class SamlController extends Controller
|
||||
$errors = $auth->getErrors();
|
||||
|
||||
if (!empty($errors)) {
|
||||
Log::debug("There was an error with SAML ACS: " . implode(', ', $errors));
|
||||
Log::debug("Reason: " . $auth->getLastErrorReason());
|
||||
Log::error("There was an error with SAML ACS: " . implode(', ', $errors));
|
||||
Log::error("Reason: " . $auth->getLastErrorReason());
|
||||
return redirect()->route('login')->with('error', trans('auth/message.signin.error'));
|
||||
}
|
||||
|
||||
@@ -113,7 +115,7 @@ class SamlController extends Controller
|
||||
* Receives LogoutRequest/LogoutResponse from IdP and flashes
|
||||
* back to the LoginController for logging out.
|
||||
*
|
||||
* /saml/slo
|
||||
* /saml/sls
|
||||
*
|
||||
* @author Johnson Yi <jyi.dev@outlook.com>
|
||||
*
|
||||
@@ -126,12 +128,13 @@ class SamlController extends Controller
|
||||
public function sls(Request $request)
|
||||
{
|
||||
$auth = $this->saml->getAuth();
|
||||
$sloUrl = $auth->processSLO(true, null, null, null, true);
|
||||
$retrieveParametersFromServer = $this->saml->getSetting('retrieveParametersFromServer', false);
|
||||
$sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
|
||||
$errors = $auth->getErrors();
|
||||
|
||||
if (!empty($errors)) {
|
||||
Log::debug("There was an error with SAML SLS: " . implode(', ', $errors));
|
||||
Log::debug("Reason: " . $auth->getLastErrorReason());
|
||||
Log::error("There was an error with SAML SLS: " . implode(', ', $errors));
|
||||
Log::error("Reason: " . $auth->getLastErrorReason());
|
||||
return view('errors.403');
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
23
app/Http/Controllers/HealthController.php
Normal file
23
app/Http/Controllers/HealthController.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
|
||||
/**
|
||||
* This controller provide the healthz route for
|
||||
* the Snipe-IT Asset Management application.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class HealthController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Returns a fixed JSON content ({ "status": "ok"}) which indicate the app is up and running
|
||||
*/
|
||||
public function get() {
|
||||
return response()->json([
|
||||
"status" => "ok"
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -67,7 +67,6 @@ class LocationsController extends Controller
|
||||
{
|
||||
$this->authorize('create', Location::class);
|
||||
$location = new Location();
|
||||
$location->id = null; // This is required to make Laravels different validation work, it errors if the parameter doesn't exist (maybe a bug)?
|
||||
$location->name = $request->input('name');
|
||||
$location->parent_id = $request->input('parent_id', null);
|
||||
$location->currency = $request->input('currency', '$');
|
||||
@@ -132,7 +131,6 @@ class LocationsController extends Controller
|
||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
}
|
||||
|
||||
|
||||
// Update the location data
|
||||
$location->name = $request->input('name');
|
||||
$location->parent_id = $request->input('parent_id', null);
|
||||
|
||||
@@ -158,7 +158,7 @@ class ManufacturersController extends Controller
|
||||
public function destroy($manufacturerId)
|
||||
{
|
||||
$this->authorize('delete', Manufacturer::class);
|
||||
if (is_null($manufacturer = Manufacturer::withCount('models as models_count')->find($manufacturerId))) {
|
||||
if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) {
|
||||
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.not_found'));
|
||||
}
|
||||
|
||||
@@ -174,8 +174,12 @@ class ManufacturersController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the manufacturer
|
||||
$manufacturer->delete();
|
||||
// Soft delete the manufacturer if active, permanent delete if is already deleted
|
||||
if($manufacturer->deleted_at === NULL) {
|
||||
$manufacturer->delete();
|
||||
} else {
|
||||
$manufacturer->forceDelete();
|
||||
}
|
||||
// Redirect to the manufacturers management page
|
||||
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.success'));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
@@ -539,6 +632,14 @@ class ReportsController extends Controller
|
||||
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
|
||||
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
|
||||
}
|
||||
|
||||
if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) {
|
||||
$assets->whereBetween('assets.last_audit_date', [$request->input('last_audit_start'), $request->input('last_audit_end')]);
|
||||
}
|
||||
|
||||
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
|
||||
$assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]);
|
||||
}
|
||||
|
||||
$assets->orderBy('assets.created_at', 'ASC')->chunk(20, function($assets) use($handle, $customfields, $request) {
|
||||
|
||||
|
||||
@@ -577,6 +577,7 @@ class SettingsController extends Controller
|
||||
$setting->default_currency = $request->input('default_currency', '$');
|
||||
$setting->date_display_format = $request->input('date_display_format');
|
||||
$setting->time_display_format = $request->input('time_display_format');
|
||||
$setting->digit_separator = $request->input('digit_separator');
|
||||
|
||||
if ($setting->save()) {
|
||||
return redirect()->route('settings.index')
|
||||
@@ -1015,7 +1016,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 +1024,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 +1036,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'));
|
||||
}
|
||||
|
||||
@@ -1138,6 +1142,7 @@ class SettingsController extends Controller
|
||||
*/
|
||||
public function getPurge()
|
||||
{
|
||||
\Log::warning('User ID '.Auth::user()->id.' is attempting a PURGE');
|
||||
return view('settings.purge-form');
|
||||
}
|
||||
|
||||
@@ -1154,6 +1159,8 @@ class SettingsController extends Controller
|
||||
{
|
||||
if (! config('app.lock_passwords')) {
|
||||
if ('DELETE' == $request->input('confirm_purge')) {
|
||||
|
||||
\Log::warning('User ID '.Auth::user()->id.' initiated a PURGE!');
|
||||
// Run a backup immediately before processing
|
||||
Artisan::call('backup:run');
|
||||
Artisan::call('snipeit:purge', ['--force' => 'true', '--no-interaction' => true]);
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Services\LdapAd;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use App\Models\User; // Note that this is awful close to 'Users' the namespace above; be careful
|
||||
|
||||
class LDAPImportController extends Controller
|
||||
{
|
||||
@@ -65,6 +66,7 @@ class LDAPImportController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('update', User::class);
|
||||
// Call Artisan LDAP import command.
|
||||
$location_id = $request->input('location_id');
|
||||
Artisan::call('snipeit:ldap-sync', ['--location_id' => $location_id, '--json_summary' => true]);
|
||||
|
||||
@@ -38,7 +38,7 @@ class UserFilesController extends Controller
|
||||
$filename = 'user-' . $user->id . '-' . str_random(8);
|
||||
$filename .= '-' . str_slug($file->getClientOriginalName()) . '.' . $extension;
|
||||
if (!$file->move($destinationPath, $filename)) {
|
||||
return JsonResponse::create(["error" => "Unabled to move file"], 500);
|
||||
return redirect()->back()->with('error', trans('admin/users/message.upload.invalidfiles'));
|
||||
}
|
||||
//Log the uploaded file to the log
|
||||
$logAction = new Actionlog();
|
||||
@@ -57,10 +57,10 @@ class UserFilesController extends Controller
|
||||
}
|
||||
$logActions[] = $logAction;
|
||||
}
|
||||
// dd($logActions);
|
||||
return JsonResponse::create($logActions);
|
||||
// dd($logActions);
|
||||
return redirect()->back()->with('success', trans('admin/users/message.upload.success'));
|
||||
}
|
||||
return JsonResponse::create(["error" => "No User associated with this request"], 500);
|
||||
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class Kernel extends HttpKernel
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
\App\Http\Middleware\NoSessionStore::class,
|
||||
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
@@ -38,6 +39,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' => [
|
||||
|
||||
58
app/Http/Middleware/AssetCountForSidebar.php
Normal file
58
app/Http/Middleware/AssetCountForSidebar.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class CheckForSetup
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!($request->is('setup*')) && !($request->is('.env'))) {
|
||||
if (!($request->is('setup*')) && !($request->is('.env')) && !($request->is('health'))) {
|
||||
return redirect(url('/').'/setup');
|
||||
}
|
||||
|
||||
|
||||
29
app/Http/Middleware/NoSessionStore.php
Normal file
29
app/Http/Middleware/NoSessionStore.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
|
||||
class NoSessionStore
|
||||
{
|
||||
protected $except = [
|
||||
'health'
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
foreach ($this->except as $except) {
|
||||
if ($request->is($except)) {
|
||||
config()->set('session.driver', 'array');
|
||||
}
|
||||
}
|
||||
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: ".config('app.url')." https://secure.gravatar.com http://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);
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@ class VerifyCsrfToken extends BaseVerifier
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
'health'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class ImageUploadRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'image' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml',
|
||||
'image' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp',
|
||||
'avatar' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml',
|
||||
];
|
||||
}
|
||||
@@ -91,8 +91,8 @@ class ImageUploadRequest extends Request
|
||||
\Log::info('File name will be: '.$file_name);
|
||||
\Log::debug('File extension is: '. $ext);
|
||||
|
||||
if ($image->getClientOriginalExtension()!=='svg') {
|
||||
\Log::debug('Not an SVG - resize');
|
||||
if (($image->getClientOriginalExtension()!=='webp') && ($image->getClientOriginalExtension()!=='svg')) {
|
||||
\Log::debug('Not an SVG or webp - resize');
|
||||
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
|
||||
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
@@ -102,20 +102,27 @@ class ImageUploadRequest extends Request
|
||||
// This requires a string instead of an object, so we use ($string)
|
||||
Storage::disk('public')->put($path.'/'.$file_name, (string)$upload->encode());
|
||||
|
||||
|
||||
// If the file is an SVG, we need to clean it and NOT encode it
|
||||
} else {
|
||||
\Log::debug('This is an SVG');
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($image->getRealPath());
|
||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||
// If the file is a webp, we need to just move it since webp support
|
||||
// needs to be compiled into gd for resizing to be available
|
||||
if ($image->getClientOriginalExtension()=='webp') {
|
||||
\Log::debug('This is a webp, just move it');
|
||||
Storage::disk('public')->put($path.'/'.$file_name, file_get_contents($image));
|
||||
// If the file is an SVG, we need to clean it and NOT encode it
|
||||
} else {
|
||||
|
||||
try {
|
||||
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
|
||||
Storage::disk('public')->put($path.'/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
\Log::debug('This is an SVG');
|
||||
$sanitizer = new Sanitizer();
|
||||
$dirtySVG = file_get_contents($image->getRealPath());
|
||||
$cleanSVG = $sanitizer->sanitize($dirtySVG);
|
||||
|
||||
try {
|
||||
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
|
||||
Storage::disk('public')->put($path.'/'.$file_name, $cleanSVG);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Upload no workie :( ');
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class SetupUserRequest extends Request
|
||||
'last_name' => 'required|string|min:1',
|
||||
'username' => 'required|string|min:2|unique:users,username,NULL,deleted_at',
|
||||
'email' => 'email|unique:users,email',
|
||||
'password' => 'required|min:6|confirmed',
|
||||
'password' => 'required|min:8|confirmed',
|
||||
'email_domain' => 'required|min:4',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ class LicensesTransformer
|
||||
'order_number' => e($license->order_number),
|
||||
'purchase_order' => e($license->purchase_order),
|
||||
'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'),
|
||||
'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'),
|
||||
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
|
||||
'purchase_cost' => e($license->purchase_cost),
|
||||
'notes' => e($license->notes),
|
||||
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
|
||||
|
||||
@@ -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) ? [
|
||||
|
||||
@@ -25,15 +25,15 @@ class SelectlistTransformer
|
||||
foreach ($select_items as $select_item) {
|
||||
$items_array[]= [
|
||||
'id' => (int) $select_item->id,
|
||||
'text' => ($select_item->use_text) ? e($select_item->use_text) : e($select_item->name),
|
||||
'image' => ($select_item->use_image) ? e($select_item->use_image) : null,
|
||||
'text' => ($select_item->use_text) ? $select_item->use_text : $select_item->name,
|
||||
'image' => ($select_item->use_image) ? $select_item->use_image : null,
|
||||
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
$results = [
|
||||
'items' => $items_array,
|
||||
'results' => $items_array,
|
||||
'pagination' =>
|
||||
[
|
||||
'more' => ($select_items->currentPage() >= $select_items->lastPage()) ? false : true,
|
||||
|
||||
@@ -52,6 +52,7 @@ class UsersTransformer
|
||||
'notes'=> e($user->notes),
|
||||
'permissions' => $user->decodePermissions(),
|
||||
'activated' => ($user->activated =='1') ? true : false,
|
||||
'ldap_import' => ($user->ldap_import =='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,
|
||||
|
||||
@@ -125,7 +125,10 @@ class UserImporter extends ItemImporter
|
||||
if ($department) {
|
||||
$this->log('A matching department ' . $department_name . ' already exists');
|
||||
return $department->id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$department = new department();
|
||||
$department->name = $department_name;
|
||||
$department->user_id = $this->user_id;
|
||||
@@ -134,7 +137,8 @@ class UserImporter extends ItemImporter
|
||||
$this->log('department ' . $department_name . ' was created');
|
||||
return $department->id;
|
||||
}
|
||||
$this->logError($department, 'Company');
|
||||
|
||||
$this->logError($department, 'Department');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
@@ -240,12 +246,18 @@ class Asset extends Depreciable
|
||||
*/
|
||||
public function availableForCheckout()
|
||||
{
|
||||
if (
|
||||
(empty($this->assigned_to)) &&
|
||||
(empty($this->deleted_at)) &&
|
||||
(($this->assetstatus) && ($this->assetstatus->deployable == 1)))
|
||||
{
|
||||
return true;
|
||||
|
||||
// This asset is not currently assigned to anyone and is not deleted...
|
||||
if ((!$this->assigned_to) && (!$this->deleted_at)) {
|
||||
|
||||
// The asset status is not archived and is deployable
|
||||
if (($this->assetstatus) && ($this->assetstatus->archived == '0')
|
||||
&& ($this->assetstatus->deployable == '1'))
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -424,7 +436,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';
|
||||
}
|
||||
|
||||
@@ -285,9 +285,13 @@ class CustomField extends Model
|
||||
*/
|
||||
public function formatFieldValuesAsArray()
|
||||
{
|
||||
$result = [];
|
||||
$arr = preg_split("/\\r\\n|\\r|\\n/", $this->field_values);
|
||||
|
||||
$result[''] = 'Select '.strtolower($this->format);
|
||||
if (($this->element!='checkbox') && ($this->element!='radio')) {
|
||||
$result[''] = 'Select '.strtolower($this->format);
|
||||
}
|
||||
|
||||
|
||||
for ($x = 0; $x < count($arr); $x++) {
|
||||
$arr_parts = explode('|', $arr[$x]);
|
||||
|
||||
297
app/Models/Ldap.php
Normal file
297
app/Models/Ldap.php
Normal file
@@ -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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,7 @@ class License extends Depreciable
|
||||
'manufacturer' => ['name'],
|
||||
'company' => ['name'],
|
||||
'category' => ['name'],
|
||||
'depreciation' => ['name'],
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,7 @@ class Location extends SnipeModel
|
||||
'address2' => 'max:80|nullable',
|
||||
'zip' => 'min:3|max:10|nullable',
|
||||
'manager_id' => 'exists:users,id|nullable',
|
||||
'parent_id' => 'nullable|exists:locations,id|different:id',
|
||||
'parent_id' => 'non_circular:locations,id'
|
||||
);
|
||||
|
||||
protected $casts = [
|
||||
@@ -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.
|
||||
|
||||
@@ -17,7 +17,7 @@ class Manufacturer extends SnipeModel
|
||||
|
||||
// Declare the rules for the form validation
|
||||
protected $rules = array(
|
||||
'name' => 'required|min:2|max:255|unique:manufacturers,name,NULL,deleted_at',
|
||||
'name' => 'required|min:2|max:255|unique:manufacturers,name,NULL,id,deleted_at,NULL',
|
||||
'url' => 'url|nullable',
|
||||
'support_url' => 'url|nullable',
|
||||
'support_email' => 'email|nullable'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -70,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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
11
app/Policies/PredefinedKitPolicy.php
Normal file
11
app/Policies/PredefinedKitPolicy.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
class PredefinedKitPolicy extends SnipePermissionsPolicy
|
||||
{
|
||||
protected function columnName()
|
||||
{
|
||||
return 'kits';
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,129 @@ namespace App\Presenters;
|
||||
*/
|
||||
class AssetModelPresenter extends Presenter
|
||||
{
|
||||
|
||||
public static function dataTableLayout() {
|
||||
|
||||
$layout = [
|
||||
[
|
||||
"field" => "checkbox",
|
||||
"checkbox" => true
|
||||
],
|
||||
[
|
||||
"field" => "id",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('general.id'),
|
||||
"visible" => false
|
||||
], [
|
||||
"field" => "company",
|
||||
"searchable" => true,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('admin/companies/table.title'),
|
||||
"visible" => false,
|
||||
"formatter" => "companiesLinkObjFormatter"
|
||||
], [
|
||||
"field" => "name",
|
||||
"searchable" => true,
|
||||
"sortable" => true,
|
||||
"visible" => true,
|
||||
"title" => trans('general.name'),
|
||||
"formatter" => "modelsLinkFormatter"
|
||||
],
|
||||
[
|
||||
"field" => "image",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('general.image'),
|
||||
"visible" => true,
|
||||
"formatter" => 'imageFormatter',
|
||||
],
|
||||
[
|
||||
"field" => "manufacturer",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('general.manufacturer'),
|
||||
"visible" => false,
|
||||
"formatter" => 'manufacturersLinkObjFormatter',
|
||||
],
|
||||
[
|
||||
"field" => "model_number",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('admin/models/table.modelnumber'),
|
||||
"visible" => true,
|
||||
],
|
||||
[
|
||||
"field" => "assets_count",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('admin/models/table.numassets'),
|
||||
"visible" => true,
|
||||
],
|
||||
[
|
||||
"field" => "depreciation",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('general.depreciation'),
|
||||
"visible" => false,
|
||||
"formatter" => "depreciationsLinkObjFormatter",
|
||||
],
|
||||
[
|
||||
"field" => "category",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('general.category'),
|
||||
"visible" => false,
|
||||
"formatter" => "categoriesLinkObjFormatter",
|
||||
],
|
||||
[
|
||||
"field" => "eol",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('general.eol'),
|
||||
"visible" => true,
|
||||
],
|
||||
[
|
||||
"field" => "fieldset",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('admin/models/general.fieldset'),
|
||||
"visible" => true,
|
||||
"formatter" => "fieldsetsLinkObjFormatter",
|
||||
],
|
||||
[
|
||||
"field" => "notes",
|
||||
"searchable" => true,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('general.notes'),
|
||||
"visible" => false,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
$layout[] = [
|
||||
"field" => "actions",
|
||||
"searchable" => false,
|
||||
"sortable" => false,
|
||||
"switchable" => false,
|
||||
"title" => trans('table.actions'),
|
||||
"formatter" => "licensesActionsFormatter",
|
||||
];
|
||||
|
||||
|
||||
return json_encode($layout);
|
||||
|
||||
}
|
||||
/**
|
||||
* Formatted note for this model
|
||||
* @return string
|
||||
|
||||
@@ -146,6 +146,7 @@ class AssetPresenter extends Presenter
|
||||
"searchable" => true,
|
||||
"sortable" => true,
|
||||
"title" => trans('general.purchase_cost'),
|
||||
"formatter" => 'numberWithCommas',
|
||||
"footerFormatter" => 'sumFormatter',
|
||||
], [
|
||||
"field" => "order_number",
|
||||
@@ -360,18 +361,13 @@ class AssetPresenter extends Presenter
|
||||
/**
|
||||
* Get Displayable Name
|
||||
* @return string
|
||||
*
|
||||
* @todo this should be factored out - it should be subsumed by fullName (below)
|
||||
*
|
||||
**/
|
||||
public function name()
|
||||
{
|
||||
|
||||
if (empty($this->model->name)) {
|
||||
if (isset($this->model->model)) {
|
||||
return $this->model->model->name.' ('.$this->model->asset_tag.')';
|
||||
}
|
||||
return $this->model->asset_tag;
|
||||
}
|
||||
return $this->model->name . ' (' . $this->model->asset_tag . ')';
|
||||
|
||||
return $this->fullName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,13 +377,18 @@ class AssetPresenter extends Presenter
|
||||
public function fullName()
|
||||
{
|
||||
$str = '';
|
||||
|
||||
// Asset name
|
||||
if ($this->model->name) {
|
||||
$str .= $this->name;
|
||||
$str .= $this->model->name;
|
||||
}
|
||||
|
||||
// Asset tag
|
||||
if ($this->asset_tag) {
|
||||
$str .= ' ('.$this->model->asset_tag.')';
|
||||
}
|
||||
|
||||
// Asset Model name
|
||||
if ($this->model->model) {
|
||||
$str .= ' - '.$this->model->model->name;
|
||||
}
|
||||
|
||||
@@ -8,5 +8,49 @@ namespace App\Presenters;
|
||||
*/
|
||||
class DepreciationPresenter extends Presenter
|
||||
{
|
||||
/**
|
||||
* Json Column Layout for bootstrap table
|
||||
* @return string
|
||||
*/
|
||||
public static function dataTableLayout()
|
||||
{
|
||||
$layout = [
|
||||
[
|
||||
"field" => "id",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('general.id'),
|
||||
"visible" => false
|
||||
], [
|
||||
"field" => "name",
|
||||
"searchable" => true,
|
||||
"sortable" => true,
|
||||
"title" => trans('general.name'),
|
||||
"visible" => true,
|
||||
"formatter" => 'depreciationsLinkFormatter',
|
||||
],
|
||||
|
||||
[
|
||||
"field" => "months",
|
||||
"searchable" => true,
|
||||
"sortable" => true,
|
||||
"title" => trans('admin/depreciations/table.term'),
|
||||
"visible" => true,
|
||||
],
|
||||
|
||||
[
|
||||
"field" => "actions",
|
||||
"searchable" => false,
|
||||
"sortable" => false,
|
||||
"switchable" => false,
|
||||
"title" => trans('table.actions'),
|
||||
"visible" => true,
|
||||
"formatter" => "depreciationsActionsFormatter",
|
||||
]
|
||||
];
|
||||
|
||||
return json_encode($layout);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -97,7 +97,26 @@ class LicensePresenter extends Presenter
|
||||
"visible" => false,
|
||||
"title" => trans('general.purchase_date'),
|
||||
'formatter' => 'dateDisplayFormatter'
|
||||
], [
|
||||
],
|
||||
[
|
||||
"field" => "termination_date",
|
||||
"searchable" => true,
|
||||
"sortable" => true,
|
||||
"visible" => false,
|
||||
"title" => trans('admin/licenses/form.termination_date'),
|
||||
'formatter' => 'dateDisplayFormatter'
|
||||
],
|
||||
[
|
||||
"field" => "depreciation",
|
||||
"searchable" => true,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('admin/hardware/form.depreciation'),
|
||||
"visible" => false,
|
||||
"formatter" => "depreciationsLinkObjFormatter",
|
||||
],
|
||||
|
||||
[
|
||||
"field" => "maintained",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -225,6 +225,15 @@ class UserPresenter extends Presenter
|
||||
"visible" => true,
|
||||
'formatter' => 'groupsFormatter'
|
||||
],
|
||||
[
|
||||
"field" => "ldap_import",
|
||||
"searchable" => false,
|
||||
"sortable" => true,
|
||||
"switchable" => true,
|
||||
"title" => trans('admin/settings/general.ldap_enabled'),
|
||||
"visible" => false,
|
||||
'formatter' => 'trueFalseFormatter'
|
||||
],
|
||||
[
|
||||
"field" => "two_factor_enrolled",
|
||||
"searchable" => false,
|
||||
@@ -301,7 +310,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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ use App\Models\Depreciation;
|
||||
use App\Models\License;
|
||||
use App\Models\Location;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\PredefinedKit;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
@@ -33,6 +34,7 @@ use App\Policies\DepreciationPolicy;
|
||||
use App\Policies\LicensePolicy;
|
||||
use App\Policies\LocationPolicy;
|
||||
use App\Policies\ManufacturerPolicy;
|
||||
use App\Policies\PredefinedKitPolicy;
|
||||
use App\Policies\StatuslabelPolicy;
|
||||
use App\Policies\SupplierPolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
@@ -63,6 +65,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
Depreciation::class => DepreciationPolicy::class,
|
||||
License::class => LicensePolicy::class,
|
||||
Location::class => LocationPolicy::class,
|
||||
PredefinedKit::class => PredefinedKitPolicy::class,
|
||||
Statuslabel::class => StatuslabelPolicy::class,
|
||||
Supplier::class => SupplierPolicy::class,
|
||||
User::class => UserPolicy::class,
|
||||
@@ -87,8 +90,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' ]);
|
||||
|
||||
@@ -58,6 +58,52 @@ class ValidationServiceProvider extends ServiceProvider
|
||||
});
|
||||
|
||||
|
||||
// Prevent circular references
|
||||
//
|
||||
// Example usage in Location model where parent_id references another Location:
|
||||
//
|
||||
// protected $rules = array(
|
||||
// 'parent_id' => 'non_circular:locations,id,10'
|
||||
// );
|
||||
//
|
||||
Validator::extend('non_circular', function ($attribute, $value, $parameters, $validator) {
|
||||
if (count($parameters) < 2) {
|
||||
throw new \Exception('Required validator parameters: <table>,<primary key>[,depth]');
|
||||
}
|
||||
|
||||
// Parameters from the rule implementation ($pk will likely be 'id')
|
||||
$table = array_get($parameters, 0);
|
||||
$pk = array_get($parameters, 1);
|
||||
$depth = (int) array_get($parameters, 2, 50);
|
||||
|
||||
// Data from the edited model
|
||||
$data = $validator->getData();
|
||||
|
||||
// The primary key value from the edited model
|
||||
$data_pk = array_get($data, $pk);
|
||||
$value_pk = $value;
|
||||
|
||||
// If we’re editing an existing model and there is a parent value set…
|
||||
while ($data_pk && $value_pk) {
|
||||
|
||||
// It’s not valid for any parent id to be equal to the existing model’s id
|
||||
if ($data_pk == $value_pk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid accidental infinite loops
|
||||
if (--$depth < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the next parent id
|
||||
$value_pk = DB::table($table)->select($attribute)->where($pk, '=', $value_pk)->value($attribute);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
// Yo dawg. I heard you like validators.
|
||||
// This validates the custom validator regex in custom fields.
|
||||
// We're just checking that the regex won't throw an exception, not
|
||||
|
||||
@@ -49,6 +49,7 @@ class LdapAd extends LdapAdConfiguration
|
||||
'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
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -145,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,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;
|
||||
}
|
||||
@@ -177,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);
|
||||
}
|
||||
@@ -208,9 +235,20 @@ 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'];
|
||||
$user->notes = 'Imported from LDAP';
|
||||
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'];
|
||||
}
|
||||
|
||||
// this is a new user
|
||||
if (!isset($user->id)) {
|
||||
$user->notes = 'Imported from LDAP';
|
||||
}
|
||||
|
||||
$user->ldap_import = 1;
|
||||
|
||||
return $user;
|
||||
@@ -275,6 +313,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>
|
||||
*
|
||||
@@ -282,22 +322,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;
|
||||
@@ -398,7 +462,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'],
|
||||
@@ -409,7 +473,7 @@ class LdapAd extends LdapAdConfiguration
|
||||
$schema->userAccountControl(),
|
||||
$schema->title(),
|
||||
$schema->telephone(),
|
||||
];
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,7 +503,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!');
|
||||
@@ -490,6 +554,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'));
|
||||
@@ -320,6 +324,20 @@ class Saml
|
||||
return $this->_auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a setting.
|
||||
*
|
||||
* @author Johnson Yi <jyi.dev@outlook.com>
|
||||
*
|
||||
* @param string|array|int $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getSetting($key, $default = null) {
|
||||
return data_get($this->_settings, $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SP metadata. The XML representation.
|
||||
*
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.2",
|
||||
"php": "^7.2.5",
|
||||
"ext-curl": "*",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-pdo": "*",
|
||||
"adldap2/adldap2": "^10.2",
|
||||
"alek13/slack": "^1.12",
|
||||
"bacon/bacon-qr-code": "^1.0",
|
||||
"barryvdh/laravel-cors": "^0.11.3",
|
||||
"barryvdh/laravel-debugbar": "^3.2",
|
||||
@@ -33,16 +34,15 @@
|
||||
"guzzlehttp/guzzle": "^6.5",
|
||||
"intervention/image": "^2.5",
|
||||
"javiereguiluz/easyslugger": "^1.0",
|
||||
"laravel/framework": "^6.0",
|
||||
"laravel/framework": "^6.20",
|
||||
"laravel/helpers": "^1.2",
|
||||
"laravel/passport": "^8.4",
|
||||
"laravel/passport": "^9.3.2",
|
||||
"laravel/slack-notification-channel": "^2.0",
|
||||
"laravel/tinker": "^2.4",
|
||||
"laravelcollective/html": "^6.0",
|
||||
"league/csv": "^9.5",
|
||||
"league/flysystem-aws-s3-v3": "^1.0",
|
||||
"league/flysystem-cached-adapter": "^1.0",
|
||||
"maknz/slack": "^1.7",
|
||||
"neitanod/forceutf8": "^2.0",
|
||||
"nesbot/carbon": "^2.32",
|
||||
"onelogin/php-saml": "^3.4",
|
||||
@@ -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",
|
||||
@@ -109,7 +110,7 @@
|
||||
"optimize-autoloader": true,
|
||||
"process-timeout": 3000,
|
||||
"platform": {
|
||||
"php": "7.2"
|
||||
"php": "7.2.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4243
composer.lock
generated
4243
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -279,7 +279,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'min_php' => '7.1.3',
|
||||
'min_php' => '7.2.5',
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@@ -103,7 +103,10 @@ return [
|
||||
'email' => 'auth.emails.password',
|
||||
'table' => 'password_resets',
|
||||
'expire' => env('RESET_PASSWORD_LINK_EXPIRES', 900),
|
||||
'throttle' => env('LOGIN_MAX_ATTEMPTS', 60),
|
||||
'throttle' => [
|
||||
'max_attempts' => env('LOGIN_MAX_ATTEMPTS', 5),
|
||||
'lockout_duration' => env('LOGIN_LOCKOUT_DURATION', 60)
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -67,6 +67,12 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
'stdout' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => StreamHandler::class,
|
||||
'with' => [ 'stream' => 'php://stdout', ],
|
||||
],
|
||||
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => env('APP_LOG_LEVEL', 'error'),
|
||||
|
||||
@@ -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),
|
||||
];
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
return array (
|
||||
'app_version' => 'v5.0.5',
|
||||
'full_app_version' => 'v5.0.5-52-g63a8535de',
|
||||
'build_version' => '5509',
|
||||
'app_version' => 'v5.1.2',
|
||||
'full_app_version' => 'v5.1.2 - build 5847-g00a7c1e9e',
|
||||
'build_version' => '5847',
|
||||
'prerelease_version' => '',
|
||||
'hash_version' => 'g63a8535de',
|
||||
'full_hash' => 'v5.0.5-52-g63a8535de',
|
||||
'hash_version' => 'g00a7c1e9e',
|
||||
'full_hash' => 'v5.1.2-33-g00a7c1e9e',
|
||||
'branch' => 'master',
|
||||
);
|
||||
@@ -17,7 +17,7 @@ class CreateCheckoutAcceptancesTable extends Migration
|
||||
$table->increments('id');
|
||||
|
||||
$table->morphs('checkoutable');
|
||||
$table->integer('assigned_to_id')->unsigned();
|
||||
$table->integer('assigned_to_id')->nullable();
|
||||
|
||||
$table->string('signature_filename')->nullable();
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddDigitSeparatorToSettings extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('settings', function (Blueprint $table) {
|
||||
$table->char('digit_separator')->nullable()->default('1234.56');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('settings', function (Blueprint $table) {
|
||||
$table->dropColumn('digit_separator');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class SwapTargetTypeIndexOrder extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('action_logs', function (Blueprint $table) {
|
||||
$table->dropIndex(['target_id', 'target_type']);
|
||||
});
|
||||
|
||||
Schema::table('action_logs', function (Blueprint $table) {
|
||||
$table->index(['target_type', 'target_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('action_logs', function (Blueprint $table) {
|
||||
$table->dropIndex(['target_type', 'target_id']);
|
||||
});
|
||||
|
||||
Schema::table('action_logs', function (Blueprint $table) {
|
||||
$table->index(['target_id', 'target_type']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateMinPassword extends Migration
|
||||
{
|
||||
/**
|
||||
* This migration solves the issue of settings with a minimum password requirement
|
||||
* that is below the actual Snipe-IT minimum requirement in v5 (min 5 became min 8).
|
||||
*
|
||||
* Even though we documented the change in all of the v5 releases, we were still
|
||||
* running into issues where admins did not update their password minimum length
|
||||
* and could not save settings elsewhere, and would not see a warning.
|
||||
*
|
||||
* @todo Loosen up the model level validation for the Settings model and rely on
|
||||
* FormRequests where it makes more sense. Having a form that returns no useful
|
||||
* errors is a bad design pattern.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
App\Models\Setting::where('pwd_secure_min', '<', '8')
|
||||
->update(['pwd_secure_min' => '8']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class FixBadLdapServerUrlForV5 extends Migration
|
||||
{
|
||||
/**
|
||||
* Under v4 and previous versions of Snipe-IT, we permitted users to incorrectly specify LDAP URL's in their settings, and Snipe-IT
|
||||
* would silently permit that.
|
||||
*
|
||||
* v5's LDAP system is not so lenient, and requires either ldap:// or ldaps:// in front of the server's URL. This migration tries
|
||||
* to find misconfigured LDAP URL's and prepend 'ldap://' to them. (That's what we assumed if we *didn't* see ldaps://)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// UPDATE settings SET ldap_server = CONCAT('ldap://',ldap_server) WHERE ldap_server NOT LIKE 'ldap://%' AND ldap_server NOT LIKE 'ldaps://%'
|
||||
$settings = App\Models\Setting::where("ldap_server","not like","ldap://%")->where("ldap_server","not like","ldaps://%");
|
||||
foreach($settings->get() AS $setting) { // we don't formally support having multiple settings records, but just in case they come up...
|
||||
$setting->ldap_server = "ldap://".$setting->ldap_server;
|
||||
$setting->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
// Since previous versions supported ldap:// URL's just fine, we don't need to migrate these changes back out on rollback.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddProviderToOauthTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
* Sigh. https://github.com/laravel/passport/blob/master/UPGRADE.md#upgrading-to-90-from-8x
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// Add a 'provider' column if not existing or else modify it
|
||||
if (!Schema::hasColumn('oauth_clients', 'provider')) {
|
||||
Schema::table('oauth_clients', function (Blueprint $table) {
|
||||
$table->string('provider')->after('secret')->nullable();
|
||||
});
|
||||
} else {
|
||||
Schema::table('oauth_clients', function (Blueprint $table) {
|
||||
$table->string('provider')->after('secret')->nullable()->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
if (Schema::hasColumn('oauth_clients', 'provider')) {
|
||||
Schema::table('oauth_clients', function (Blueprint $table) {
|
||||
$table->dropColumn('provider');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class SettingsSeeder extends Seeder
|
||||
$settings->per_page = 20;
|
||||
$settings->site_name = 'Snipe-IT Demo';
|
||||
$settings->auto_increment_assets = 1;
|
||||
$settings->logo = 'logo.png';
|
||||
$settings->logo = 'snipe-logo.png';
|
||||
$settings->alert_email = 'service@snipe-it.io';
|
||||
$settings->header_color = null;
|
||||
$settings->barcode_type = 'QRCODE';
|
||||
|
||||
@@ -49,5 +49,7 @@ php artisan migrate --force
|
||||
php artisan config:clear
|
||||
php artisan config:cache
|
||||
|
||||
chown -R apache:root /var/www/html/storage/logs/laravel.log
|
||||
|
||||
export APACHE_LOG_DIR=/var/log/apache2
|
||||
exec httpd -DNO_DETACH < /dev/null
|
||||
|
||||
1322
package-lock.json
generated
1322
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user