mirror of
https://github.com/snipe/snipe-it.git
synced 2025-03-05 20:52:15 -08:00
Merge branch 'develop' into fixes/trying_to_get_property_id_of_nonobject
This commit is contained in:
commit
2b4ee4827f
|
@ -2478,6 +2478,51 @@
|
|||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "leitwerk-ag",
|
||||
"name": "LEITWERK AG",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/24418301?v=4",
|
||||
"profile": "https://www.leitwerk.de/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "adamboutcher",
|
||||
"name": "Adam",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1911435?v=4",
|
||||
"profile": "http://www.aboutcher.co.uk",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sneak-it",
|
||||
"name": "Ian",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/16104273?v=4",
|
||||
"profile": "https://snksrv.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bestlong",
|
||||
"name": "Shao Yu-Lung (Allen)",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4023909?v=4",
|
||||
"profile": "http://blog.bestlong.idv.tw/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Haxatron",
|
||||
"name": "Haxatron",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/76475453?v=4",
|
||||
"profile": "https://github.com/Haxatron",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PlaneNuts",
|
||||
"name": "PlaneNuts",
|
||||
|
@ -2486,6 +2531,60 @@
|
|||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "exula",
|
||||
"name": "Bradley Coudriet",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3842948?v=4",
|
||||
"profile": "http://bjcpgd.cias.rit.edu",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "UniversalSuperBox",
|
||||
"name": "Dalton Durst",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/21966173?v=4",
|
||||
"profile": "https://daltondur.st",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "adagioajanes",
|
||||
"name": "Alex Janes",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/38761237?v=4",
|
||||
"profile": "https://adagiohealth.org",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nuraeil",
|
||||
"name": "Nuraeil",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/32387849?v=4",
|
||||
"profile": "https://github.com/nuraeil",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "TenOfTens",
|
||||
"name": "TenOfTens",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/48162670?v=4",
|
||||
"profile": "https://github.com/TenOfTens",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "insert-waffle",
|
||||
"name": "waffle",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9415391?v=4",
|
||||
"profile": "https://ditisjens.be/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -149,10 +149,12 @@ APP_LOG_MAX_FILES=10
|
|||
APP_LOCKED=false
|
||||
APP_CIPHER=AES-256-CBC
|
||||
APP_FORCE_TLS=false
|
||||
APP_ALLOW_INSECURE_HOSTS=false
|
||||
GOOGLE_MAPS_API=
|
||||
LDAP_MEM_LIM=500M
|
||||
LDAP_TIME_LIM=600
|
||||
IMPORT_TIME_LIMIT=600
|
||||
IMPORT_MEMORY_LIMIT=500M
|
||||
REPORT_TIME_LIMIT=12000
|
||||
REQUIRE_SAML=false
|
||||
|
||||
|
|
82
.github/workflows/docker-alpine.yml
vendored
Normal file
82
.github/workflows/docker-alpine.yml
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
# Snipe-IT (Alpine) Docker image build for hub.docker.com
|
||||
name: Docker images (Alpine)
|
||||
|
||||
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
|
||||
# Also run for PRs to ensure PR doesn't break Docker build process
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
tags:
|
||||
- 'v**'
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
|
||||
if: github.repository == 'snipe/snipe-it'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
|
||||
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
|
||||
# For a new commit on other branches, use the branch name as the tag for Docker image.
|
||||
# For a new tag, copy that tag name as the tag for Docker image.
|
||||
IMAGE_TAGS: |
|
||||
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
|
||||
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
|
||||
type=ref,event=tag,suffix=-alpine
|
||||
# Define default tag "flavor" for docker/metadata-action per
|
||||
# https://github.com/docker/metadata-action#flavor-input
|
||||
# We turn off 'latest' tag by default.
|
||||
TAGS_FLAVOR: |
|
||||
latest=false
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
# https://github.com/docker/login-action
|
||||
- name: Login to DockerHub
|
||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
###############################################
|
||||
# Build/Push the 'snipe/snipe-it' image
|
||||
###############################################
|
||||
# https://github.com/docker/metadata-action
|
||||
# Get Metadata for docker_build step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
|
||||
id: meta_build
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: snipe/snipe-it
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push 'snipe-it' image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.alpine
|
||||
platforms: linux/amd64
|
||||
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
||||
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
# Use tags / labels provided by 'docker/metadata-action' above
|
||||
tags: ${{ steps.meta_build.outputs.tags }}
|
||||
labels: ${{ steps.meta_build.outputs.labels }}
|
82
.github/workflows/docker.yml
vendored
Normal file
82
.github/workflows/docker.yml
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
# Snipe-IT Docker image build for hub.docker.com
|
||||
name: Docker images
|
||||
|
||||
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
|
||||
# Also run for PRs to ensure PR doesn't break Docker build process
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
tags:
|
||||
- 'v**'
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
|
||||
if: github.repository == 'snipe/snipe-it'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
|
||||
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
|
||||
# For a new commit on other branches, use the branch name as the tag for Docker image.
|
||||
# For a new tag, copy that tag name as the tag for Docker image.
|
||||
IMAGE_TAGS: |
|
||||
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
|
||||
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
|
||||
type=ref,event=tag
|
||||
# Define default tag "flavor" for docker/metadata-action per
|
||||
# https://github.com/docker/metadata-action#flavor-input
|
||||
# We turn off 'latest' tag by default.
|
||||
TAGS_FLAVOR: |
|
||||
latest=false
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
# https://github.com/docker/login-action
|
||||
- name: Login to DockerHub
|
||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
###############################################
|
||||
# Build/Push the 'snipe/snipe-it' image
|
||||
###############################################
|
||||
# https://github.com/docker/metadata-action
|
||||
# Get Metadata for docker_build step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
|
||||
id: meta_build
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: snipe/snipe-it
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push 'snipe-it' image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
||||
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
# Use tags / labels provided by 'docker/metadata-action' above
|
||||
tags: ${{ steps.meta_build.outputs.tags }}
|
||||
labels: ${{ steps.meta_build.outputs.labels }}
|
|
@ -77,6 +77,8 @@ COPY . /var/www/html
|
|||
|
||||
RUN a2enmod rewrite
|
||||
|
||||
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
|
||||
|
||||
############ INITIAL APPLICATION SETUP #####################
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
|
|
@ -34,6 +34,8 @@ RUN apk add --no-cache \
|
|||
mysql-client \
|
||||
tini
|
||||
|
||||
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
|
||||
|
||||
# Where apache's PID lives
|
||||
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
|
||||
|
||||
|
|
|
@ -98,5 +98,6 @@ VOLUME [ "/var/lib/snipeit" ]
|
|||
|
||||
COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env
|
||||
COPY --chmod=655 docker/docker-entrypoint.sh /usr/local/bin/docker-snipeit-entrypoint
|
||||
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
|
||||
ENTRYPOINT [ "/usr/local/bin/docker-snipeit-entrypoint" ]
|
||||
CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
|
|
@ -1,5 +1,5 @@
|
|||
 [](https://crowdin.com/project/snipe-it) [](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://discord.gg/yZFtShAcKk) [](https://huntr.dev)
|
||||
[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
|
||||
|
||||
## Snipe-IT - Open Source Asset Management System
|
||||
|
||||
|
@ -129,7 +129,9 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
|||
| [<img src="https://avatars.githubusercontent.com/u/36065681?v=4" width="110px;"/><br /><sub>David Valin Alonso</sub>](https://github.com/deivishome)<br />[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [<img src="https://avatars.githubusercontent.com/u/8290389?v=4" width="110px;"/><br /><sub>andreaci</sub>](https://github.com/andreaci)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [<img src="https://avatars.githubusercontent.com/u/1828542?v=4" width="110px;"/><br /><sub>Jelle Sebreghts</sub>](http://www.jellesebreghts.be)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [<img src="https://avatars.githubusercontent.com/u/11180862?v=4" width="110px;"/><br /><sub>Michael Pietsch</sub>](https://github.com/Skywalker-11)<br /> | [<img src="https://avatars.githubusercontent.com/u/22068886?v=4" width="110px;"/><br /><sub>Masudul Haque Shihab</sub>](https://github.com/sh1hab)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [<img src="https://avatars.githubusercontent.com/u/16099942?v=4" width="110px;"/><br /><sub>Supapong Areeprasertkul</sub>](http://www.freedomdive.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [<img src="https://avatars.githubusercontent.com/u/207358?v=4" width="110px;"/><br /><sub>Peter Sarossy</sub>](https://github.com/psarossy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/11823649?v=4" width="110px;"/><br /><sub>Renee Margaret McConahy</sub>](https://github.com/nepella)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [<img src="https://avatars.githubusercontent.com/u/5553884?v=4" width="110px;"/><br /><sub>JohnnyPicnic</sub>](https://github.com/JohnnyPicnic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [<img src="https://avatars.githubusercontent.com/u/8799594?v=4" width="110px;"/><br /><sub>markbrule</sub>](https://github.com/markbrule)<br />[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [<img src="https://avatars.githubusercontent.com/u/1962801?v=4" width="110px;"/><br /><sub>Mike Campbell</sub>](https://github.com/mikecmpbll)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [<img src="https://avatars.githubusercontent.com/u/11973217?v=4" width="110px;"/><br /><sub>tbrconnect</sub>](https://github.com/tbrconnect)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [<img src="https://avatars.githubusercontent.com/u/12447225?v=4" width="110px;"/><br /><sub>kcoyo</sub>](https://github.com/kcoyo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [<img src="https://avatars.githubusercontent.com/u/494017?v=4" width="110px;"/><br /><sub>Travis Miller</sub>](https://travismiller.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1975640?v=4" width="110px;"/><br /><sub>Evan Taylor</sub>](https://github.com/Delta5)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [<img src="https://avatars.githubusercontent.com/u/8735148?v=4" width="110px;"/><br /><sub>Petri Asikainen</sub>](https://github.com/PetriAsi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [<img src="https://avatars.githubusercontent.com/u/11424540?v=4" width="110px;"/><br /><sub>derdeagle</sub>](https://github.com/derdeagle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [<img src="https://avatars.githubusercontent.com/u/176950?v=4" width="110px;"/><br /><sub>Mike Frysinger</sub>](https://wh0rd.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [<img src="https://avatars.githubusercontent.com/u/22044358?v=4" width="110px;"/><br /><sub>ALPHA</sub>](https://github.com/AL4AL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [<img src="https://avatars.githubusercontent.com/u/1042587?v=4" width="110px;"/><br /><sub>FliegenKLATSCH</sub>](https://www.ifern.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [<img src="https://avatars.githubusercontent.com/u/442138?v=4" width="110px;"/><br /><sub>Jeremy Price</sub>](https://github.com/jerm)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/24418301?v=4" width="110px;"/><br /><sub>LEITWERK AG</sub>](https://www.leitwerk.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/1911435?v=4" width="110px;"/><br /><sub>Adam</sub>](http://www.aboutcher.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [<img src="https://avatars.githubusercontent.com/u/16104273?v=4" width="110px;"/><br /><sub>Ian</sub>](https://snksrv.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [<img src="https://avatars.githubusercontent.com/u/4023909?v=4" width="110px;"/><br /><sub>Shao Yu-Lung (Allen)</sub>](http://blog.bestlong.idv.tw/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [<img src="https://avatars.githubusercontent.com/u/76475453?v=4" width="110px;"/><br /><sub>Haxatron</sub>](https://github.com/Haxatron)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [<img src="https://avatars.githubusercontent.com/u/3842948?v=4" width="110px;"/><br /><sub>Bradley Coudriet</sub>](http://bjcpgd.cias.rit.edu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [<img src="https://avatars.githubusercontent.com/u/21966173?v=4" width="110px;"/><br /><sub>Dalton Durst</sub>](https://daltondur.st)<br />[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Department;
|
||||
use App\Models\Ldap;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Ldap;
|
||||
use App\Models\User;
|
||||
use App\Models\Location;
|
||||
use Log;
|
||||
|
||||
class LdapSync extends Command
|
||||
|
@ -91,7 +91,7 @@ class LdapSync extends Command
|
|||
}
|
||||
|
||||
/* 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
|
||||
$location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose
|
||||
|
||||
if ($this->option('location') != '') {
|
||||
$location = Location::where('name', '=', $this->option('location'))->first();
|
||||
|
@ -133,7 +133,7 @@ class LdapSync extends Command
|
|||
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
|
||||
} catch (\Exception $e) { // TODO: 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));
|
||||
|
@ -189,6 +189,7 @@ class LdapSync extends Command
|
|||
'name' => $item['department'],
|
||||
]);
|
||||
|
||||
|
||||
$user = User::where('username', $item['username'])->first();
|
||||
if ($user) {
|
||||
// Updating an existing user.
|
||||
|
@ -238,6 +239,7 @@ class LdapSync extends Command
|
|||
'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
|
||||
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
|
||||
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
|
||||
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
|
||||
];
|
||||
|
|
|
@ -1,398 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Adldap\Models\User as AdldapUser;
|
||||
use App\Models\Location;
|
||||
use App\Models\User;
|
||||
use App\Services\LdapAd;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -5,8 +5,10 @@ namespace App\Exceptions;
|
|||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Log;
|
||||
use Throwable;
|
||||
use JsonException;
|
||||
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
|
@ -17,7 +19,15 @@ class Handler extends ExceptionHandler
|
|||
* @var array
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
\Illuminate\Auth\AuthenticationException::class,
|
||||
\Illuminate\Auth\Access\AuthorizationException::class,
|
||||
\Symfony\Component\HttpKernel\Exception\HttpException::class,
|
||||
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
|
||||
\Illuminate\Session\TokenMismatchException::class,
|
||||
\Illuminate\Validation\ValidationException::class,
|
||||
\Intervention\Image\Exception\NotSupportedException::class,
|
||||
\League\OAuth2\Server\Exception\OAuthServerException::class,
|
||||
JsonException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -39,7 +49,6 @@ class Handler extends ExceptionHandler
|
|||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Exception $e
|
||||
* @return \Illuminate\Http\Response
|
||||
|
@ -53,6 +62,12 @@ class Handler extends ExceptionHandler
|
|||
return redirect()->back()->with('error', trans('general.token_expired'));
|
||||
}
|
||||
|
||||
// Invalid JSON exception
|
||||
// TODO: don't understand why we have to do this when we have the invalidJson() method, below, but, well, whatever
|
||||
if ($e instanceof JsonException) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'invalid JSON'), 422);
|
||||
}
|
||||
|
||||
|
||||
// Handle Ajax requests that fail because the model doesn't exist
|
||||
if ($request->ajax() || $request->wantsJson()) {
|
||||
|
@ -89,6 +104,23 @@ class Handler extends ExceptionHandler
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an authentication exception into an unauthenticated response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Auth\AuthenticationException $exception
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
protected function unauthenticated($request, AuthenticationException $exception)
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => 'Unauthorized or unauthenticated.'], 401);
|
||||
}
|
||||
|
||||
return redirect()->guest('login');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A list of the inputs that are never flashed for validation exceptions.
|
||||
*
|
||||
|
@ -111,4 +143,4 @@ class Handler extends ExceptionHandler
|
|||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
$maintenances = AssetMaintenance::with('asset', 'asset.model', 'asset.location', 'supplier', 'asset.company', 'admin');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
|
@ -101,6 +102,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('edit', Asset::class);
|
||||
// create a new model instance
|
||||
$assetMaintenance = new AssetMaintenance();
|
||||
$assetMaintenance->supplier_id = $request->input('supplier_id');
|
||||
|
@ -152,6 +154,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function update(Request $request, $assetMaintenanceId = null)
|
||||
{
|
||||
$this->authorize('edit', Asset::class);
|
||||
// Check if the asset maintenance exists
|
||||
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
|
||||
|
||||
|
@ -215,6 +218,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function destroy($assetMaintenanceId)
|
||||
{
|
||||
$this->authorize('edit', Asset::class);
|
||||
// Check if the asset maintenance exists
|
||||
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
|
||||
|
||||
|
@ -240,6 +244,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function show($assetMaintenanceId)
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
|
||||
if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot view a maintenance for that asset'));
|
||||
|
|
|
@ -235,6 +235,8 @@ class AssetModelsController extends Controller
|
|||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
|
||||
$this->authorize('view.selectlists');
|
||||
$assetmodels = AssetModel::select([
|
||||
'models.id',
|
||||
'models.name',
|
||||
|
|
|
@ -52,9 +52,8 @@ class AssetsController extends Controller
|
|||
*/
|
||||
public function index(Request $request, $audit = null)
|
||||
{
|
||||
|
||||
\Log::debug(Route::currentRouteName());
|
||||
|
||||
$filter_non_deprecable_assets = false;
|
||||
|
||||
/**
|
||||
* This looks MAD janky (and it is), but the AssetsController@index does a LOT of heavy lifting throughout the
|
||||
|
@ -68,6 +67,7 @@ class AssetsController extends Controller
|
|||
* which would have been far worse of a mess. *sad face* - snipe (Sept 1, 2021)
|
||||
*/
|
||||
if (Route::currentRouteName()=='api.depreciation-report.index') {
|
||||
$filter_non_deprecable_assets = true;
|
||||
$transformer = 'App\Http\Transformers\DepreciationReportTransformer';
|
||||
$this->authorize('reports.view');
|
||||
} else {
|
||||
|
@ -115,11 +115,28 @@ class AssetsController extends Controller
|
|||
|
||||
$assets = Company::scopeCompanyables(Asset::select('assets.*'), 'company_id', 'assets')
|
||||
->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo',
|
||||
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier'); //it's tempting to add assetlogs here, but don't - it blows up update-heavy installations
|
||||
'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
|
||||
|
||||
|
||||
if ($filter_non_deprecable_assets) {
|
||||
$non_deprecable_models = AssetModel::select('id')->whereNotNull('depreciation_id')->get();
|
||||
|
||||
$assets->InModelList($non_deprecable_models->toArray());
|
||||
}
|
||||
|
||||
// These are used by the API to query against specific ID numbers.
|
||||
// They are also used by the individual searches on detail pages like
|
||||
// locations, etc.
|
||||
|
||||
|
||||
// Search custom fields by column name
|
||||
foreach ($all_custom_fields as $field) {
|
||||
if ($request->filled($field->db_column_name())) {
|
||||
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($request->filled('status_id')) {
|
||||
$assets->where('assets.status_id', '=', $request->input('status_id'));
|
||||
}
|
||||
|
@ -327,8 +344,6 @@ class AssetsController extends Controller
|
|||
}]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Here we're just determining which Transformer (via $transformer) to use based on the
|
||||
|
@ -355,7 +370,6 @@ class AssetsController extends Controller
|
|||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -716,7 +730,6 @@ class AssetsController extends Controller
|
|||
$logaction->logaction('restored');
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.restore.success')));
|
||||
|
||||
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||
|
@ -820,8 +833,8 @@ class AssetsController extends Controller
|
|||
$this->authorize('checkin', $asset);
|
||||
|
||||
|
||||
$user = $asset->assignedUser;
|
||||
if (is_null($target = $asset->assignedTo)) {
|
||||
$target = $asset->assignedTo;
|
||||
if (is_null($target)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.already_checked_in')));
|
||||
}
|
||||
|
||||
|
@ -834,7 +847,7 @@ class AssetsController extends Controller
|
|||
if ($request->filled('name')) {
|
||||
$asset->name = $request->input('name');
|
||||
}
|
||||
|
||||
|
||||
$asset->location_id = $asset->rtd_location_id;
|
||||
|
||||
if ($request->filled('location_id')) {
|
||||
|
@ -845,13 +858,39 @@ class AssetsController extends Controller
|
|||
$asset->status_id = $request->input('status_id');
|
||||
}
|
||||
|
||||
$checkin_at = null;
|
||||
if ($request->filled('checkin_at')) {
|
||||
$checkin_at = $request->input('checkin_at');
|
||||
}
|
||||
|
||||
if ($asset->save()) {
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note')));
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkin an asset by asset tag
|
||||
*
|
||||
* @author [A. Janes] [<ajanes@adagiohealth.org>]
|
||||
* @since [v6.0]
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function checkinByTag(Request $request)
|
||||
{
|
||||
$this->authorize('checkin', Asset::class);
|
||||
$asset = Asset::where('asset_tag', $request->input('asset_tag'))->first();
|
||||
|
||||
if($asset) {
|
||||
return $this->checkin($request, $asset->id);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
'asset'=> e($request->input('asset_tag'))
|
||||
], 'Asset with tag '.e($request->input('asset_tag')).' not found'));
|
||||
}
|
||||
|
||||
|
||||
|
@ -864,9 +903,8 @@ class AssetsController extends Controller
|
|||
* @return JsonResponse
|
||||
*/
|
||||
public function audit(Request $request)
|
||||
|
||||
{
|
||||
|
||||
|
||||
$this->authorize('audit', Asset::class);
|
||||
$rules = [
|
||||
'asset_tag' => 'required',
|
||||
|
@ -914,11 +952,6 @@ class AssetsController extends Controller
|
|||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.e($request->input('asset_tag')).' not found'));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -936,12 +969,14 @@ class AssetsController extends Controller
|
|||
|
||||
$assets = Company::scopeCompanyables(Asset::select('assets.*'), 'company_id', 'assets')
|
||||
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
|
||||
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')->where('assets.requestable', '=', '1');
|
||||
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')->requestableAssets();
|
||||
|
||||
$offset = request('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$assets->TextSearch($request->input('search'));
|
||||
if ($request->filled('search')) {
|
||||
$assets->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
switch ($request->input('sort')) {
|
||||
case 'model':
|
||||
|
|
|
@ -50,6 +50,7 @@ class CategoriesController extends Controller
|
|||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
|
@ -69,8 +70,8 @@ class CategoriesController extends Controller
|
|||
if ($category->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $category, trans('admin/categories/message.create.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $category->getErrors()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,10 +86,11 @@ class CategoriesController extends Controller
|
|||
{
|
||||
$this->authorize('view', Category::class);
|
||||
$category = Category::findOrFail($id);
|
||||
|
||||
return (new CategoriesTransformer)->transformCategory($category);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
|
@ -136,6 +138,7 @@ class CategoriesController extends Controller
|
|||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a paginated collection for the select2 menus
|
||||
*
|
||||
|
@ -145,6 +148,7 @@ class CategoriesController extends Controller
|
|||
*/
|
||||
public function selectlist(Request $request, $category_type = 'asset')
|
||||
{
|
||||
$this->authorize('view.selectlists');
|
||||
$categories = Category::select([
|
||||
'id',
|
||||
'name',
|
||||
|
|
|
@ -56,10 +56,11 @@ class CompaniesController extends Controller
|
|||
|
||||
$total = $companies->count();
|
||||
$companies = $companies->skip($offset)->take($limit)->get();
|
||||
|
||||
return (new CompaniesTransformer)->transformCompanies($companies, $total);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
|
@ -95,10 +96,11 @@ class CompaniesController extends Controller
|
|||
{
|
||||
$this->authorize('view', Company::class);
|
||||
$company = Company::findOrFail($id);
|
||||
|
||||
return (new CompaniesTransformer)->transformCompany($company);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
|
@ -157,6 +159,7 @@ class CompaniesController extends Controller
|
|||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
$this->authorize('view.selectlists');
|
||||
$companies = Company::select([
|
||||
'companies.id',
|
||||
'companies.name',
|
||||
|
|
|
@ -66,8 +66,8 @@ class DepartmentsController extends Controller
|
|||
|
||||
$total = $departments->count();
|
||||
$departments = $departments->skip($offset)->take($limit)->get();
|
||||
|
||||
return (new DepartmentsTransformer)->transformDepartments($departments, $total);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,8 +91,8 @@ class DepartmentsController extends Controller
|
|||
if ($department->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $department, trans('admin/departments/message.create.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,6 +134,7 @@ class DepartmentsController extends Controller
|
|||
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates and deletes selected department.
|
||||
*
|
||||
|
@ -153,8 +154,8 @@ class DepartmentsController extends Controller
|
|||
}
|
||||
|
||||
$department->delete();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/departments/message.delete.success')));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,6 +167,8 @@ class DepartmentsController extends Controller
|
|||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
|
||||
$this->authorize('view.selectlists');
|
||||
$departments = Department::select([
|
||||
'id',
|
||||
'name',
|
||||
|
|
|
@ -26,7 +26,7 @@ class LicensesController extends Controller
|
|||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('view', License::class);
|
||||
$licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'freeSeats', 'supplier', 'category')->withCount('freeSeats as free_seats_count'));
|
||||
$licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'supplier', 'category')->withCount('freeSeats as free_seats_count'));
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$licenses->where('company_id', '=', $request->input('company_id'));
|
||||
|
@ -92,6 +92,10 @@ class LicensesController extends Controller
|
|||
$licenses = $licenses->TextSearch($request->input('search'));
|
||||
}
|
||||
|
||||
if ($request->input('deleted')=='true') {
|
||||
$licenses->onlyTrashed();
|
||||
}
|
||||
|
||||
// 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.
|
||||
$offset = (($licenses) && ($request->get('offset') > $licenses->count())) ? $licenses->count() : $request->get('offset', 0);
|
||||
|
@ -144,7 +148,6 @@ class LicensesController extends Controller
|
|||
}
|
||||
|
||||
$total = $licenses->count();
|
||||
|
||||
$licenses = $licenses->skip($offset)->take($limit)->get();
|
||||
|
||||
return (new LicensesTransformer)->transformLicenses($licenses, $total);
|
||||
|
|
|
@ -73,6 +73,7 @@ class LocationsController extends Controller
|
|||
break;
|
||||
}
|
||||
|
||||
|
||||
$total = $locations->count();
|
||||
$locations = $locations->skip($offset)->take($limit)->get();
|
||||
|
||||
|
@ -137,6 +138,7 @@ class LocationsController extends Controller
|
|||
return (new LocationsTransformer)->transformLocation($location);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
|
@ -155,8 +157,8 @@ class LocationsController extends Controller
|
|||
$location = $request->handleImages($location);
|
||||
|
||||
if ($location->isValid()) {
|
||||
$location->save();
|
||||
|
||||
$location->save();
|
||||
return response()->json(
|
||||
Helper::formatStandardApiResponse(
|
||||
'success',
|
||||
|
@ -221,6 +223,9 @@ class LocationsController extends Controller
|
|||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
|
||||
$this->authorize('view.selectlists');
|
||||
|
||||
$locations = Location::select([
|
||||
'locations.id',
|
||||
'locations.name',
|
||||
|
|
|
@ -72,8 +72,8 @@ class ManufacturersController extends Controller
|
|||
if ($manufacturer->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $manufacturer, trans('admin/manufacturers/message.create.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $manufacturer->getErrors()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,11 +131,11 @@ class ManufacturersController extends Controller
|
|||
|
||||
if ($manufacturer->isDeletable()) {
|
||||
$manufacturer->delete();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.assoc_users')));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.assoc_users')));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -147,6 +147,8 @@ class ManufacturersController extends Controller
|
|||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
|
||||
$this->authorize('view.selectlists');
|
||||
$manufacturers = Manufacturer::select([
|
||||
'id',
|
||||
'name',
|
||||
|
|
|
@ -31,8 +31,8 @@ class PredefinedKitsController extends Controller
|
|||
|
||||
$offset = $request->input('offset', 0);
|
||||
$limit = $request->input('limit', 50);
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';
|
||||
$order = $request->input('order') === 'desc' ? 'desc' : 'asc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'name';
|
||||
$kits->orderBy($sort, $order);
|
||||
|
||||
$total = $kits->count();
|
||||
|
@ -88,7 +88,7 @@ class PredefinedKitsController extends Controller
|
|||
$kit->fill($request->all());
|
||||
|
||||
if ($kit->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.update_success'))); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.update_success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $kit->getErrors()));
|
||||
|
@ -113,7 +113,7 @@ class PredefinedKitsController extends Controller
|
|||
|
||||
$kit->delete();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/kits/general.delete_success'))); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/kits/general.delete_success')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,12 +171,12 @@ class PredefinedKitsController extends Controller
|
|||
$license_id = $request->get('license');
|
||||
$relation = $kit->licenses();
|
||||
if ($relation->find($license_id)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['license' => 'License already attached to kit']));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['license' => trans('admin/kits/general.license_error')]));
|
||||
}
|
||||
|
||||
$relation->attach($license_id, ['quantity' => $quantity]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'License added successfull')); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.license_added_success')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,7 +196,7 @@ class PredefinedKitsController extends Controller
|
|||
}
|
||||
$kit->licenses()->syncWithoutDetaching([$license_id => ['quantity' => $quantity]]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'License updated')); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.license_updated')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -274,7 +274,7 @@ class PredefinedKitsController extends Controller
|
|||
}
|
||||
$kit->models()->syncWithoutDetaching([$model_id => ['quantity' => $quantity]]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'License updated')); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.license_updated')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -327,12 +327,12 @@ class PredefinedKitsController extends Controller
|
|||
$consumable_id = $request->get('consumable');
|
||||
$relation = $kit->consumables();
|
||||
if ($relation->find($consumable_id)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['consumable' => 'Consumable already attached to kit']));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['consumable' => trans('admin/kits/general.consumable_error')]));
|
||||
}
|
||||
|
||||
$relation->attach($consumable_id, ['quantity' => $quantity]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Consumable added successfull')); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.consumable_added_success')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -352,7 +352,7 @@ class PredefinedKitsController extends Controller
|
|||
}
|
||||
$kit->consumables()->syncWithoutDetaching([$consumable_id => ['quantity' => $quantity]]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Consumable updated')); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.consumable_updated')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -368,7 +368,7 @@ class PredefinedKitsController extends Controller
|
|||
|
||||
$kit->consumables()->detach($consumable_id);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Delete was successfull')); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.consumable_deleted')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -405,12 +405,12 @@ class PredefinedKitsController extends Controller
|
|||
$accessory_id = $request->get('accessory');
|
||||
$relation = $kit->accessories();
|
||||
if ($relation->find($accessory_id)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['accessory' => 'Accessory already attached to kit']));
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, ['accessory' => trans('admin/kits/general.accessory_error')]));
|
||||
}
|
||||
|
||||
$relation->attach($accessory_id, ['quantity' => $quantity]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Accessory added successfull')); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.accessory_added_success')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -430,7 +430,7 @@ class PredefinedKitsController extends Controller
|
|||
}
|
||||
$kit->accessories()->syncWithoutDetaching([$accessory_id => ['quantity' => $quantity]]);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Accessory updated')); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.accessory_updated')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -446,6 +446,6 @@ class PredefinedKitsController extends Controller
|
|||
|
||||
$kit->accessories()->detach($accessory_id);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, 'Delete was successfull')); // TODO: trans
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $kit, trans('admin/kits/general.accessory_deleted')));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,172 +2,156 @@
|
|||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\LoginAttemptsTransformer;
|
||||
use App\Models\Ldap;
|
||||
use App\Models\Setting;
|
||||
use Mail;
|
||||
use App\Notifications\SlackTest;
|
||||
use App\Notifications\MailTest;
|
||||
use App\Services\LdapAd;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator; // forward-port of v4 LDAP model for Sync
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Http\Requests\SlackSettingsRequest;
|
||||
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Test the ldap settings
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param App\Models\LdapAd $ldap
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function ldapAdSettingsTest(LdapAd $ldap): JsonResponse
|
||||
{
|
||||
if (! $ldap->init()) {
|
||||
Log::info('LDAP is not enabled so we cannot test.');
|
||||
|
||||
|
||||
public function ldaptest()
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
if ($settings->ldap_enabled!='1') {
|
||||
\Log::debug('LDAP is not enabled cannot test.');
|
||||
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
|
||||
}
|
||||
|
||||
// The connect, bind and resulting users message
|
||||
$message = [];
|
||||
\Log::debug('Preparing to test LDAP connection');
|
||||
|
||||
// This is all kinda fucked right now. The connection test doesn't actually do what you think,
|
||||
// // and the way we parse the errors
|
||||
// on the JS side is horrible.
|
||||
Log::info('Preparing to test LDAP user login');
|
||||
// Test user can connect to the LDAP server
|
||||
$message = []; //where we collect together test messages
|
||||
try {
|
||||
$ldap->testLdapAdUserConnection();
|
||||
$message['login'] = [
|
||||
'message' => 'Successfully connected to LDAP server.',
|
||||
];
|
||||
} catch (\Exception $ex) {
|
||||
\Log::debug('Connection to LDAP server '.Setting::getSettings()->ldap_server.' failed. Please check your LDAP settings and try again. Server Responded with error: '.$ex->getMessage());
|
||||
|
||||
return response()->json(
|
||||
['message' => 'Connection to LDAP server '.Setting::getSettings()->ldap_server." failed. Verify that the LDAP hostname is entered correctly and that it can be reached from this web server. \n\nServer Responded with error: ".$ex->getMessage(),
|
||||
|
||||
], 400);
|
||||
}
|
||||
|
||||
Log::info('Preparing to test LDAP bind connection');
|
||||
// Test user can bind to the LDAP server
|
||||
try {
|
||||
Log::info('Testing Bind');
|
||||
$ldap->testLdapAdBindConnection();
|
||||
$message['bind'] = [
|
||||
'message' => 'Successfully bound to LDAP server.',
|
||||
];
|
||||
} catch (\Exception $ex) {
|
||||
Log::info('LDAP Bind failed');
|
||||
|
||||
return response()->json(['message' => 'Connection to LDAP successful, but we were unable to Bind the LDAP user '.Setting::getSettings()->ldap_uname.". Verify your that your LDAP Bind username and password are correct. \n\nServer Responded with error: ".$ex->getMessage(),
|
||||
], 400);
|
||||
}
|
||||
|
||||
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(); // 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,
|
||||
];
|
||||
});
|
||||
if ($users->count() > 0) {
|
||||
$message['user_sync'] = [
|
||||
'users' => $users,
|
||||
];
|
||||
} else {
|
||||
$message['user_sync'] = [
|
||||
'message' => 'Connection to LDAP was successful, however there were no users returned from your query. You should confirm the Base Bind DN above.',
|
||||
$connection = Ldap::connectToLdap();
|
||||
try {
|
||||
$message['bind'] = ['message' => 'Successfully bound to LDAP server.'];
|
||||
\Log::debug('attempting to bind to LDAP for LDAP test');
|
||||
Ldap::bindAdminToLdap($connection);
|
||||
$message['login'] = [
|
||||
'message' => 'Successfully connected to LDAP server.',
|
||||
];
|
||||
|
||||
return response()->json($message, 400);
|
||||
$users = collect(Ldap::findLdapUsers(null,10))->filter(function ($value, $key) {
|
||||
return is_int($key);
|
||||
})->slice(0, 10)->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,
|
||||
];
|
||||
});
|
||||
if ($users->count() > 0) {
|
||||
$message['user_sync'] = [
|
||||
'users' => $users,
|
||||
];
|
||||
} else {
|
||||
$message['user_sync'] = [
|
||||
'message' => 'Connection to LDAP was successful, however there were no users returned from your query. You should confirm the Base Bind DN above.',
|
||||
];
|
||||
|
||||
return response()->json($message, 400);
|
||||
}
|
||||
|
||||
return response()->json($message, 200);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Bind failed');
|
||||
\Log::debug("Exception was: ".$e->getMessage());
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
//return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
Log::info('LDAP sync failed');
|
||||
$message['user_sync'] = [
|
||||
'message' => 'Error getting users from LDAP directory, error: '.$ex->getMessage(),
|
||||
];
|
||||
|
||||
return response()->json($message, 400);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Connection failed but we cannot debug it any further on our end.');
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
|
||||
return response()->json($message, 200);
|
||||
|
||||
}
|
||||
|
||||
public function ldaptestlogin(Request $request, LdapAd $ldap)
|
||||
public function ldaptestlogin(Request $request)
|
||||
{
|
||||
|
||||
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 = [
|
||||
|
||||
$rules = array(
|
||||
'ldaptest_user' => 'required',
|
||||
'ldaptest_password' => '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());
|
||||
|
||||
$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)
|
||||
$connection = Ldap::connectToLdap();
|
||||
try {
|
||||
Ldap::bindAdminToLdap($connection);
|
||||
\Log::debug('Attempting to bind to LDAP for LDAP test');
|
||||
try {
|
||||
$ldap_user = Ldap::findAndBindUserLdap($request->input('ldaptest_user'), $request->input('ldaptest_password'));
|
||||
if ($ldap_user) {
|
||||
\Log::debug('It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.');
|
||||
return response()->json(['message' => 'It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.'], 200);
|
||||
}
|
||||
return response()->json(['message' => 'Login Failed. '. $request->input('ldaptest_user').' did not successfully bind to LDAP.'], 400);
|
||||
|
||||
// $results = $ldap->ldap->auth()->attempt($request->input('ldaptest_username'), $request->input('ldaptest_password'), true);
|
||||
// can't do this because that's a protected property.
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('LDAP login failed');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
}
|
||||
|
||||
$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('Bind failed');
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
//return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug('Connection failed');
|
||||
|
||||
return response()->json(['message' => $e->getMessage()], 400);
|
||||
} finally {
|
||||
DB::rollBack(); // ALWAYS rollback, whether success or failure
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function slacktest(Request $request)
|
||||
public function slacktest(SlackSettingsRequest $request)
|
||||
{
|
||||
|
||||
// Only attempt the slack request if the validation passes
|
||||
if ($request->validate([
|
||||
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:https://hooks.slack.com|nullable',
|
||||
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
|
||||
])) {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:https://hooks.slack.com/|nullable',
|
||||
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['message' => 'Validation failed', 'errors' => $validator->errors()], 422);
|
||||
}
|
||||
|
||||
// If validation passes, continue to the curl request
|
||||
$slack = new Client([
|
||||
'base_url' => e($request->input('slack_endpoint')),
|
||||
'defaults' => [
|
||||
|
@ -185,16 +169,17 @@ class SettingsController extends Controller
|
|||
|
||||
try {
|
||||
$slack->post($request->input('slack_endpoint'), ['body' => $payload]);
|
||||
|
||||
return response()->json(['message' => 'Success'], 200);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => 'Oops! Please check the channel name and webhook endpoint URL. Slack responded with: '.$e->getMessage()], 400);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => 'Please check the channel name and webhook endpoint URL ('.e($request->input('slack_endpoint')).'). Slack responded with: '.$e->getMessage()], 400);
|
||||
}
|
||||
|
||||
//}
|
||||
return response()->json(['message' => 'Something went wrong :( '], 400);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the email configuration
|
||||
*
|
||||
|
@ -204,19 +189,19 @@ class SettingsController extends Controller
|
|||
*/
|
||||
public function ajaxTestEmail()
|
||||
{
|
||||
if (! config('app.lock_passwords')) {
|
||||
if (!config('app.lock_passwords')) {
|
||||
try {
|
||||
Notification::send(Setting::first(), new MailTest());
|
||||
|
||||
return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Mail would have been sent, but this application is in demo mode! '], 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete server-cached barcodes
|
||||
*
|
||||
|
@ -239,6 +224,7 @@ class SettingsController extends Controller
|
|||
if ($extension == 'png') {
|
||||
\Log::debug('Deleting: '.$file);
|
||||
|
||||
|
||||
try {
|
||||
Storage::disk('public')->delete($file);
|
||||
\Log::debug('Deleting: '.$file);
|
||||
|
@ -252,6 +238,10 @@ class SettingsController extends Controller
|
|||
return response()->json(['message' => 'Deleted '.$file_count.' barcodes'], 200);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of login attempts
|
||||
*
|
||||
|
@ -274,4 +264,4 @@ class SettingsController extends Controller
|
|||
|
||||
return (new LoginAttemptsTransformer)->transformLoginAttempts($login_attempt_results, $total);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -70,8 +70,8 @@ class SuppliersController extends Controller
|
|||
if ($supplier->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $supplier, trans('admin/suppliers/message.create.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $supplier->getErrors()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,6 +90,7 @@ class SuppliersController extends Controller
|
|||
return (new SuppliersTransformer)->transformSupplier($supplier);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
|
@ -127,6 +128,7 @@ class SuppliersController extends Controller
|
|||
$supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
|
||||
$this->authorize('delete', $supplier);
|
||||
|
||||
|
||||
if ($supplier->assets_count > 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count])));
|
||||
}
|
||||
|
@ -153,6 +155,9 @@ class SuppliersController extends Controller
|
|||
*/
|
||||
public function selectlist(Request $request)
|
||||
{
|
||||
|
||||
$this->authorize('view.selectlists');
|
||||
|
||||
$suppliers = Supplier::select([
|
||||
'id',
|
||||
'name',
|
||||
|
|
|
@ -50,6 +50,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
return view('asset_maintenances/index');
|
||||
}
|
||||
|
||||
|
@ -64,6 +65,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('edit', Asset::class);
|
||||
$asset = null;
|
||||
|
||||
if ($asset = Asset::find(request('asset_id'))) {
|
||||
|
@ -94,6 +96,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('edit', Asset::class);
|
||||
// create a new model instance
|
||||
$assetMaintenance = new AssetMaintenance();
|
||||
$assetMaintenance->supplier_id = $request->input('supplier_id');
|
||||
|
@ -145,6 +148,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function edit($assetMaintenanceId = null)
|
||||
{
|
||||
$this->authorize('edit', Asset::class);
|
||||
// Check if the asset maintenance exists
|
||||
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
|
||||
// Redirect to the improvement management page
|
||||
|
@ -195,6 +199,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function update(Request $request, $assetMaintenanceId = null)
|
||||
{
|
||||
$this->authorize('edit', Asset::class);
|
||||
// Check if the asset maintenance exists
|
||||
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
|
||||
// Redirect to the asset maintenance management page
|
||||
|
@ -262,6 +267,7 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function destroy($assetMaintenanceId)
|
||||
{
|
||||
$this->authorize('edit', Asset::class);
|
||||
// Check if the asset maintenance exists
|
||||
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
|
||||
// Redirect to the asset maintenance management page
|
||||
|
@ -290,6 +296,8 @@ class AssetMaintenancesController extends Controller
|
|||
*/
|
||||
public function show($assetMaintenanceId)
|
||||
{
|
||||
$this->authorize('view', Asset::class);
|
||||
|
||||
// Check if the asset maintenance exists
|
||||
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
|
||||
// Redirect to the asset maintenance management page
|
||||
|
|
|
@ -123,6 +123,7 @@ class AssetModelsController extends Controller
|
|||
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates and processes form data from the edit
|
||||
* Asset Model form based on the model ID passed.
|
||||
|
@ -231,10 +232,11 @@ class AssetModelsController extends Controller
|
|||
|
||||
return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', trans('admin/models/message.not_found'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the model information to present to the model view page
|
||||
*
|
||||
|
@ -266,6 +268,7 @@ class AssetModelsController extends Controller
|
|||
*/
|
||||
public function getClone($modelId = null)
|
||||
{
|
||||
$this->authorize('create', AssetModel::class);
|
||||
// Check if the model exists
|
||||
if (is_null($model_to_clone = AssetModel::find($modelId))) {
|
||||
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
|
||||
|
@ -281,6 +284,7 @@ class AssetModelsController extends Controller
|
|||
->with('clone_model', $model_to_clone);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the custom fields form
|
||||
*
|
||||
|
@ -294,6 +298,8 @@ class AssetModelsController extends Controller
|
|||
return view('models.custom_fields_form')->with('model', AssetModel::find($modelId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that allows the user to bulk edit model attrbutes
|
||||
*
|
||||
|
@ -336,6 +342,8 @@ class AssetModelsController extends Controller
|
|||
->with('error', 'You must select at least one model to edit.');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view that allows the user to bulk edit model attrbutes
|
||||
*
|
||||
|
@ -348,6 +356,7 @@ class AssetModelsController extends Controller
|
|||
$models_raw_array = $request->input('ids');
|
||||
$update_array = [];
|
||||
|
||||
|
||||
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id') != 'NC'))) {
|
||||
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
|
||||
}
|
||||
|
@ -361,6 +370,7 @@ class AssetModelsController extends Controller
|
|||
$update_array['depreciation_id'] = $request->input('depreciation_id');
|
||||
}
|
||||
|
||||
|
||||
if (count($update_array) > 0) {
|
||||
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
||||
|
||||
|
@ -442,7 +452,9 @@ class AssetModelsController extends Controller
|
|||
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues)
|
||||
{
|
||||
foreach ($defaultValues as $customFieldId => $defaultValue) {
|
||||
if ($defaultValue) {
|
||||
if(is_array($defaultValue)){
|
||||
$model->defaultValues()->attach($customFieldId, ['default_value' => implode(', ', $defaultValue)]);
|
||||
}elseif ($defaultValue) {
|
||||
$model->defaultValues()->attach($customFieldId, ['default_value' => $defaultValue]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -761,6 +761,13 @@ class AssetsController extends Controller
|
|||
return view('hardware/quickscan')->with('next_audit_date', $dt);
|
||||
}
|
||||
|
||||
public function quickScanCheckin()
|
||||
{
|
||||
$this->authorize('checkin', Asset::class);
|
||||
|
||||
return view('hardware/quickscan-checkin');
|
||||
}
|
||||
|
||||
public function audit($id)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
use App\Models\Actionlog;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\CheckInOutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
@ -123,6 +124,24 @@ class BulkAssetsController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$changed = [];
|
||||
$asset = Asset::where('id' ,$assetId)->get();
|
||||
|
||||
foreach ($this->update_array as $key => $value) {
|
||||
if ($this->update_array[$key] != $asset->toArray()[0][$key]) {
|
||||
$changed[$key]['old'] = $asset->toArray()[0][$key];
|
||||
$changed[$key]['new'] = $this->update_array[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = Asset::class;
|
||||
$logAction->item_id = $assetId;
|
||||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->log_meta = json_encode($changed);
|
||||
$logAction->logaction('update');
|
||||
|
||||
DB::table('assets')
|
||||
->where('id', $assetId)
|
||||
->update($this->update_array);
|
||||
|
|
|
@ -82,6 +82,8 @@ class ForgotPasswordController extends Controller
|
|||
\Log::info('Password reset attempt: User '.$request->input('username').'failed with exception: '.$e );
|
||||
}
|
||||
|
||||
// Prevent timing attack to enumerate users.
|
||||
usleep(500000 + random_int(0, 1500000));
|
||||
|
||||
if ($response === \Password::RESET_LINK_SENT) {
|
||||
\Log::info('Password reset attempt: User '.$request->input('username').' WAS found, password reset sent');
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace App\Http\Controllers\Auth;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Services\LdapAd;
|
||||
use App\Models\Ldap;
|
||||
use App\Services\Saml;
|
||||
use Com\Tecnick\Barcode\Barcode;
|
||||
use Google2FA;
|
||||
|
@ -39,11 +39,6 @@ class LoginController extends Controller
|
|||
*/
|
||||
protected $redirectTo = '/';
|
||||
|
||||
/**
|
||||
* @var LdapAd
|
||||
*/
|
||||
protected $ldap;
|
||||
|
||||
/**
|
||||
* @var Saml
|
||||
*/
|
||||
|
@ -52,12 +47,11 @@ class LoginController extends Controller
|
|||
/**
|
||||
* Create a new authentication controller instance.
|
||||
*
|
||||
* @param LdapAd $ldap
|
||||
* @param Saml $saml
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(/*LdapAd $ldap, */ Saml $saml)
|
||||
public function __construct(Saml $saml)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware('guest', ['except' => ['logout', 'postTwoFactorAuth', 'getTwoFactorAuth', 'getTwoFactorEnroll']]);
|
||||
|
@ -74,6 +68,12 @@ class LoginController extends Controller
|
|||
return redirect()->intended('/');
|
||||
}
|
||||
|
||||
//If the environment is set to ALWAYS require SAML, go straight to the SAML route.
|
||||
//We don't need to check other settings, as this should override those.
|
||||
if(config('app.require_saml')) {
|
||||
return redirect()->route('saml.login');
|
||||
}
|
||||
|
||||
if ($this->saml->isEnabled() && Setting::getSettings()->saml_forcelogin == '1' && ! ($request->has('nosaml') || $request->session()->has('error'))) {
|
||||
return redirect()->route('saml.login');
|
||||
}
|
||||
|
@ -141,13 +141,47 @@ class LoginController extends Controller
|
|||
*/
|
||||
private function loginViaLdap(Request $request): User
|
||||
{
|
||||
$ldap = \App::make(LdapAd::class);
|
||||
try {
|
||||
return $ldap->ldapLogin($request->input('username'), $request->input('password'));
|
||||
} catch (\Exception $ex) {
|
||||
LOG::debug('LDAP user login: '.$ex->getMessage());
|
||||
throw new \Exception($ex->getMessage());
|
||||
}
|
||||
Log::debug("Binding user to LDAP.");
|
||||
$ldap_user = Ldap::findAndBindUserLdap($request->input('username'), $request->input('password'));
|
||||
if (!$ldap_user) {
|
||||
Log::debug("LDAP user ".$request->input('username')." not found in LDAP or could not bind");
|
||||
throw new \Exception("Could not find user in LDAP directory");
|
||||
} else {
|
||||
Log::debug("LDAP user ".$request->input('username')." successfully bound to LDAP");
|
||||
}
|
||||
|
||||
// Check if the user already exists in the database and was imported via LDAP
|
||||
$user = User::where('username', '=', $request->input('username'))->whereNull('deleted_at')->where('ldap_import', '=', 1)->where('activated', '=', '1')->first(); // FIXME - if we get more than one we should fail. and we sure about this ldap_import thing?
|
||||
Log::debug("Local auth lookup complete");
|
||||
|
||||
// The user does not exist in the database. Try to get them from LDAP.
|
||||
// If user does not exist and authenticates successfully with LDAP we
|
||||
// will create it on the fly and sign in with default permissions
|
||||
if (!$user) {
|
||||
Log::debug("Local user ".$request->input('username')." does not exist");
|
||||
Log::debug("Creating local user ".$request->input('username'));
|
||||
|
||||
if ($user = Ldap::createUserFromLdap($ldap_user)) { //this handles passwords on its own
|
||||
Log::debug("Local user created.");
|
||||
} else {
|
||||
Log::debug("Could not create local user.");
|
||||
throw new \Exception("Could not create local user");
|
||||
}
|
||||
// If the user exists and they were imported from LDAP already
|
||||
} else {
|
||||
Log::debug("Local user ".$request->input('username')." exists in database. Updating existing user against LDAP.");
|
||||
|
||||
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
|
||||
|
||||
if (Setting::getSettings()->ldap_pw_sync=='1') {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
$user->email = $ldap_attr['email'];
|
||||
$user->first_name = $ldap_attr['firstname'];
|
||||
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
|
||||
$user->save();
|
||||
} // End if(!user)
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function loginViaRemoteUser(Request $request)
|
||||
|
@ -201,6 +235,11 @@ class LoginController extends Controller
|
|||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
//If the environment is set to ALWAYS require SAML, return access denied
|
||||
if(config('app.require_saml')) {
|
||||
return view('errors.403');
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->login_common_disabled == '1') {
|
||||
return view('errors.403');
|
||||
}
|
||||
|
@ -263,6 +302,7 @@ class LoginController extends Controller
|
|||
return redirect()->intended()->with('success', trans('auth/message.signin.success'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Two factor enrollment page
|
||||
*
|
||||
|
@ -363,7 +403,7 @@ class LoginController extends Controller
|
|||
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
|
||||
$user->two_factor_enrolled = 1;
|
||||
$user->save();
|
||||
$request->session()->put('2fa_authed', 'true');
|
||||
$request->session()->put('2fa_authed', $user->id);
|
||||
|
||||
return redirect()->route('home')->with('success', 'You are logged in!');
|
||||
}
|
||||
|
@ -371,6 +411,7 @@ class LoginController extends Controller
|
|||
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.invalid_code'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logout page.
|
||||
*
|
||||
|
@ -417,6 +458,7 @@ class LoginController extends Controller
|
|||
return redirect()->route('login')->with(['success' => trans('auth/message.logout.success'), 'loggedout' => true]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a validator for an incoming registration request.
|
||||
*
|
||||
|
|
|
@ -31,6 +31,7 @@ class BulkAssetModelsController extends Controller
|
|||
|
||||
// If deleting....
|
||||
if ($request->input('bulk_actions') == 'delete') {
|
||||
$this->authorize('delete', AssetModel::class);
|
||||
$valid_count = 0;
|
||||
foreach ($models as $model) {
|
||||
if ($model->assets_count == 0) {
|
||||
|
@ -42,7 +43,7 @@ class BulkAssetModelsController extends Controller
|
|||
|
||||
// Otherwise display the bulk edit screen
|
||||
}
|
||||
|
||||
$this->authorize('update', AssetModel::class);
|
||||
$nochange = ['NC' => 'No Change'];
|
||||
|
||||
return view('models/bulk-edit', compact('models'))
|
||||
|
@ -64,6 +65,8 @@ class BulkAssetModelsController extends Controller
|
|||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$this->authorize('update', AssetModel::class);
|
||||
|
||||
$models_raw_array = $request->input('ids');
|
||||
$update_array = [];
|
||||
|
||||
|
@ -106,6 +109,8 @@ class BulkAssetModelsController extends Controller
|
|||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$this->authorize('delete', AssetModel::class);
|
||||
|
||||
$models_raw_array = $request->input('ids');
|
||||
|
||||
if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) {
|
||||
|
|
|
@ -53,6 +53,7 @@ class CustomFieldsController extends Controller
|
|||
return redirect()->route('fields.index');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a view with a form to create a new custom field.
|
||||
*
|
||||
|
@ -86,15 +87,17 @@ class CustomFieldsController extends Controller
|
|||
$this->authorize('create', CustomField::class);
|
||||
|
||||
$field = new CustomField([
|
||||
'name' => $request->get('name'),
|
||||
'element' => $request->get('element'),
|
||||
'help_text' => $request->get('help_text'),
|
||||
'field_values' => $request->get('field_values'),
|
||||
'field_encrypted' => $request->get('field_encrypted', 0),
|
||||
'show_in_email' => $request->get('show_in_email', 0),
|
||||
'user_id' => Auth::id(),
|
||||
"name" => trim($request->get("name")),
|
||||
"element" => $request->get("element"),
|
||||
"help_text" => $request->get("help_text"),
|
||||
"field_values" => $request->get("field_values"),
|
||||
"field_encrypted" => $request->get("field_encrypted", 0),
|
||||
"show_in_email" => $request->get("show_in_email", 0),
|
||||
"is_unique" => $request->get("is_unique", 0),
|
||||
"user_id" => Auth::id()
|
||||
]);
|
||||
|
||||
|
||||
if ($request->filled('custom_format')) {
|
||||
$field->format = e($request->get('custom_format'));
|
||||
} else {
|
||||
|
@ -109,6 +112,7 @@ class CustomFieldsController extends Controller
|
|||
->with('error', trans('admin/custom_fields/message.field.create.error'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detach a custom field from a fieldset.
|
||||
*
|
||||
|
@ -123,12 +127,23 @@ class CustomFieldsController extends Controller
|
|||
|
||||
$this->authorize('update', $field);
|
||||
|
||||
// Check that the field exists - this is mostly related to the demo, where we
|
||||
// rewrite the data every x minutes, so it's possible someone might be disassociating
|
||||
// a field from a fieldset just as we're wiping the database
|
||||
if (($field) && ($fieldset_id)) {
|
||||
|
||||
if ($field->fieldset()->detach($fieldset_id)) {
|
||||
return redirect()->route('fieldsets.show', ['fieldset' => $fieldset_id])
|
||||
->with('success', trans('admin/custom_fields/message.field.delete.success'));
|
||||
} else {
|
||||
return redirect()->back()->withErrors(['message' => "Field is in use and cannot be deleted."]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return redirect()->back()->withErrors(['message' => 'Field is in-use']);
|
||||
return redirect()->back()->withErrors(['message' => "Error deleting field from fieldset"]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,14 +163,14 @@ class CustomFieldsController extends Controller
|
|||
return redirect()->back()->withErrors(['message' => 'Field is in-use']);
|
||||
}
|
||||
$field->delete();
|
||||
|
||||
return redirect()->route('fields.index')
|
||||
->with('success', trans('admin/custom_fields/message.field.delete.success'));
|
||||
return redirect()->route("fields.index")
|
||||
->with("success", trans('admin/custom_fields/message.field.delete.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withErrors(['message' => 'Field does not exist']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a view to edit a custom field
|
||||
*
|
||||
|
@ -167,7 +182,7 @@ class CustomFieldsController extends Controller
|
|||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
$field = CustomField::find($id);
|
||||
if ($field = CustomField::find($id)) {
|
||||
|
||||
$this->authorize('update', $field);
|
||||
|
||||
|
@ -181,8 +196,14 @@ class CustomFieldsController extends Controller
|
|||
'customFormat' => $customFormat,
|
||||
'predefinedFormats' => Helper::predefined_formats(),
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route("fields.index")
|
||||
->with("error", trans('admin/custom_fields/message.field.invalid'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store the updated field
|
||||
*
|
||||
|
@ -200,12 +221,13 @@ class CustomFieldsController extends Controller
|
|||
|
||||
$this->authorize('update', $field);
|
||||
|
||||
$field->name = e($request->get('name'));
|
||||
$field->element = e($request->get('element'));
|
||||
$field->field_values = e($request->get('field_values'));
|
||||
$field->user_id = Auth::id();
|
||||
$field->help_text = $request->get('help_text');
|
||||
$field->show_in_email = $request->get('show_in_email', 0);
|
||||
$field->name = trim(e($request->get("name")));
|
||||
$field->element = e($request->get("element"));
|
||||
$field->field_values = e($request->get("field_values"));
|
||||
$field->user_id = Auth::id();
|
||||
$field->help_text = $request->get("help_text");
|
||||
$field->show_in_email = $request->get("show_in_email", 0);
|
||||
$field->is_unique = $request->get("is_unique", 0);
|
||||
|
||||
if ($request->get('format') == 'CUSTOM REGEX') {
|
||||
$field->format = e($request->get('custom_format'));
|
||||
|
|
|
@ -22,6 +22,13 @@ use Redirect;
|
|||
*/
|
||||
class CustomFieldsetsController extends Controller
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
return redirect()->route("fields.index")
|
||||
->with("error", trans('admin/custom_fields/message.fieldset.does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and stores a new custom field.
|
||||
*
|
||||
|
|
|
@ -61,12 +61,12 @@ class CheckoutKitController extends Controller
|
|||
|
||||
$checkout_result = $this->kitService->checkout($request, $kit, $user);
|
||||
if (Arr::has($checkout_result, 'errors') && count($checkout_result['errors']) > 0) {
|
||||
return redirect()->back()->with('error', 'Checkout error')->with('error_messages', $checkout_result['errors']); // TODO: trans
|
||||
return redirect()->back()->with('error', trans('general.checkout_error'))->with('error_messages', $checkout_result['errors']);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'Checkout was successful')
|
||||
return redirect()->back()->with('success', trans('general.checkout_success'))
|
||||
->with('assets', Arr::get($checkout_result, 'assets', null))
|
||||
->with('accessories', Arr::get($checkout_result, 'accessories', null))
|
||||
->with('consumables', Arr::get($checkout_result, 'consumables', null)); // TODO: trans
|
||||
->with('consumables', Arr::get($checkout_result, 'consumables', null));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ class PredefinedKitsController extends Controller
|
|||
return redirect()->back()->withInput()->withErrors($kit->getErrors());
|
||||
}
|
||||
|
||||
return redirect()->route('kits.index')->with('success', 'Kit was successfully created.'); // TODO: trans()
|
||||
return redirect()->route('kits.index')->with('success', trans('admin/kits/general.kit_created'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,7 +85,7 @@ class PredefinedKitsController extends Controller
|
|||
->with('licenses', $kit->licenses);
|
||||
}
|
||||
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,13 +103,13 @@ class PredefinedKitsController extends Controller
|
|||
// Check if the kit exists
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
// Redirect to the kits management page
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
$kit->name = $request->input('name');
|
||||
|
||||
if ($kit->save()) {
|
||||
return redirect()->route('kits.index')->with('success', 'Kit was successfully updated'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('success', trans('admin/kits/general.kit_updated'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withInput()->withErrors($kit->getErrors());
|
||||
|
@ -129,7 +129,7 @@ class PredefinedKitsController extends Controller
|
|||
$this->authorize('delete', PredefinedKit::class);
|
||||
// Check if the kit exists
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
return redirect()->route('kits.index')->with('error', 'Kit not found'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_not_found'));
|
||||
}
|
||||
|
||||
// Delete childs
|
||||
|
@ -141,7 +141,7 @@ class PredefinedKitsController extends Controller
|
|||
$kit->delete();
|
||||
|
||||
// Redirect to the kit management page
|
||||
return redirect()->route('kits.index')->with('success', 'Kit was successfully deleted'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('success', trans('admin/kits/general.kit_deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,7 +176,7 @@ class PredefinedKitsController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,7 +191,7 @@ class PredefinedKitsController extends Controller
|
|||
$this->authorize('update', PredefinedKit::class);
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
// Redirect to the kits management page
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), $kit->makeModelRules($model_id));
|
||||
|
@ -206,7 +206,7 @@ class PredefinedKitsController extends Controller
|
|||
$pivot->quantity = $request->input('quantity');
|
||||
$pivot->save();
|
||||
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', 'Model updated successfully.'); // TODO: trans
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', trans('admin/kits/general.kit_model_updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,14 +221,14 @@ class PredefinedKitsController extends Controller
|
|||
$this->authorize('update', PredefinedKit::class);
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
// Redirect to the kits management page
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
// Delete childs
|
||||
$kit->models()->detach($model_id);
|
||||
|
||||
// Redirect to the kit management page
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', 'Model was successfully detached'); // TODO: trans
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', trans('admin/kits/general.kit_model_detached'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -243,10 +243,10 @@ class PredefinedKitsController extends Controller
|
|||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
if (! ($kit = PredefinedKit::find($kit_id))) {
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
if (! ($license = $kit->licenses()->find($license_id))) {
|
||||
return redirect()->route('kits.index')->with('error', 'License does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.license_none'));
|
||||
}
|
||||
|
||||
return view('kits/license-edit', [
|
||||
|
@ -269,7 +269,7 @@ class PredefinedKitsController extends Controller
|
|||
$this->authorize('update', PredefinedKit::class);
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
// Redirect to the kits management page
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), $kit->makeLicenseRules($license_id));
|
||||
|
@ -284,7 +284,7 @@ class PredefinedKitsController extends Controller
|
|||
$pivot->quantity = $request->input('quantity');
|
||||
$pivot->save();
|
||||
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', 'License updated successfully.'); // TODO: trans
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', trans('admin/kits/general.license_updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -300,14 +300,14 @@ class PredefinedKitsController extends Controller
|
|||
$this->authorize('update', PredefinedKit::class);
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
// Redirect to the kits management page
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
// Delete childs
|
||||
$kit->licenses()->detach($license_id);
|
||||
|
||||
// Redirect to the kit management page
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', 'License was successfully detached'); // TODO: trans
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', trans('admin/kits/general.license_detached'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -322,10 +322,10 @@ class PredefinedKitsController extends Controller
|
|||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
if (! ($kit = PredefinedKit::find($kit_id))) {
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
if (! ($accessory = $kit->accessories()->find($accessory_id))) {
|
||||
return redirect()->route('kits.index')->with('error', 'Accessory does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.accessory_none'));
|
||||
}
|
||||
|
||||
return view('kits/accessory-edit', [
|
||||
|
@ -348,7 +348,7 @@ class PredefinedKitsController extends Controller
|
|||
$this->authorize('update', PredefinedKit::class);
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
// Redirect to the kits management page
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), $kit->makeAccessoryRules($accessory_id));
|
||||
|
@ -363,7 +363,7 @@ class PredefinedKitsController extends Controller
|
|||
$pivot->quantity = $request->input('quantity');
|
||||
$pivot->save();
|
||||
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', 'Accessory updated successfully.'); // TODO: trans
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', trans('admin/kits/general.accessory_updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -378,14 +378,14 @@ class PredefinedKitsController extends Controller
|
|||
$this->authorize('update', PredefinedKit::class);
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
// Redirect to the kits management page
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
// Delete childs
|
||||
$kit->accessories()->detach($accessory_id);
|
||||
|
||||
// Redirect to the kit management page
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', 'Accessory was successfully detached'); // TODO: trans
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', trans('admin/kits/general.accessory_detached'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -400,10 +400,10 @@ class PredefinedKitsController extends Controller
|
|||
{
|
||||
$this->authorize('update', PredefinedKit::class);
|
||||
if (! ($kit = PredefinedKit::find($kit_id))) {
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
if (! ($consumable = $kit->consumables()->find($consumable_id))) {
|
||||
return redirect()->route('kits.index')->with('error', 'Consumable does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.consumable_none'));
|
||||
}
|
||||
|
||||
return view('kits/consumable-edit', [
|
||||
|
@ -426,7 +426,7 @@ class PredefinedKitsController extends Controller
|
|||
$this->authorize('update', PredefinedKit::class);
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
// Redirect to the kits management page
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), $kit->makeConsumableRules($consumable_id));
|
||||
|
@ -441,7 +441,7 @@ class PredefinedKitsController extends Controller
|
|||
$pivot->quantity = $request->input('quantity');
|
||||
$pivot->save();
|
||||
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', 'Consumable updated successfully.'); // TODO: trans
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', trans('admin/kits/general.consumable_updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -456,13 +456,13 @@ class PredefinedKitsController extends Controller
|
|||
$this->authorize('update', PredefinedKit::class);
|
||||
if (is_null($kit = PredefinedKit::find($kit_id))) {
|
||||
// Redirect to the kits management page
|
||||
return redirect()->route('kits.index')->with('error', 'Kit does not exist'); // TODO: trans
|
||||
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
|
||||
}
|
||||
|
||||
// Delete childs
|
||||
$kit->consumables()->detach($consumable_id);
|
||||
|
||||
// Redirect to the kit management page
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', 'Consumable was successfully detached'); // TODO: trans
|
||||
return redirect()->route('kits.edit', $kit_id)->with('success', trans('admin/kits/general.consumable_detached'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -230,7 +230,7 @@ class LicensesController extends Controller
|
|||
*/
|
||||
public function show($licenseId = null)
|
||||
{
|
||||
$license = License::with('assignedusers', 'licenseSeats.user', 'licenseSeats.asset')->find($licenseId);
|
||||
$license = License::with('assignedusers')->find($licenseId);
|
||||
|
||||
if ($license) {
|
||||
$this->authorize('view', $license);
|
||||
|
|
|
@ -6,17 +6,49 @@ use App\Helpers\Helper;
|
|||
|
||||
class ModalController extends Controller
|
||||
{
|
||||
public function show($type, $itemId = null)
|
||||
{
|
||||
|
||||
/**
|
||||
* Load the modal views after confirming they are in the allowed_types array.
|
||||
* The allowed types away just prevents shithead skiddies from fuzzing the urls
|
||||
* with automated scripts and junking up the logs. - snipe
|
||||
*
|
||||
* @version v5.3.7-pre
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @author [A. Gianotto] [<snipe@snipe.net]
|
||||
* @return View
|
||||
*/
|
||||
function show ($type, $itemId = null) {
|
||||
|
||||
// These values should correspond to a file in resources/views/modals/
|
||||
$allowed_types = [
|
||||
'category',
|
||||
'kit-model',
|
||||
'kit-license',
|
||||
'kit-consumable',
|
||||
'kit-accessory',
|
||||
'location',
|
||||
'manufacturer',
|
||||
'model',
|
||||
'statuslabel',
|
||||
'supplier',
|
||||
'upload-file',
|
||||
'user',
|
||||
];
|
||||
|
||||
|
||||
if (in_array($type, $allowed_types)) {
|
||||
$view = view("modals.${type}");
|
||||
|
||||
if ($type == 'statuslabel') {
|
||||
if ($type == "statuslabel") {
|
||||
$view->with('statuslabel_types', Helper::statusTypeList());
|
||||
}
|
||||
if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) {
|
||||
$view->with('kitId', $itemId);
|
||||
}
|
||||
return $view;
|
||||
}
|
||||
|
||||
return $view;
|
||||
abort(404,'Page not found');
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,10 +64,12 @@ class ProfileController extends Controller
|
|||
$user->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
|
||||
if ($request->input('avatar_delete') == 1) {
|
||||
$user->avatar = null;
|
||||
}
|
||||
|
||||
|
||||
if ($request->hasFile('avatar')) {
|
||||
$path = 'avatars';
|
||||
|
||||
|
@ -101,6 +103,7 @@ class ProfileController extends Controller
|
|||
return redirect()->back()->withInput()->withErrors($user->getErrors());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a page with the API token generation interface.
|
||||
*
|
||||
|
@ -113,6 +116,12 @@ class ProfileController extends Controller
|
|||
*/
|
||||
public function api()
|
||||
{
|
||||
|
||||
// Make sure the self.api permission has been granted
|
||||
if (!Gate::allows('self.api')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return view('account/api');
|
||||
}
|
||||
|
||||
|
@ -177,11 +186,12 @@ class ProfileController extends Controller
|
|||
if (! $validator->fails()) {
|
||||
$user->password = Hash::make($request->input('password'));
|
||||
$user->save();
|
||||
|
||||
return redirect()->route('account.password.index')->with('success', 'Password updated!');
|
||||
}
|
||||
|
||||
}
|
||||
return redirect()->back()->withInput()->withErrors($validator);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -513,6 +513,10 @@ class ReportsController extends Controller
|
|||
$header[] = trans('general.department');
|
||||
}
|
||||
|
||||
if ($request->filled('title')) {
|
||||
$header[] = trans('admin/users/table.title');
|
||||
}
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$header[] = trans('general.status');
|
||||
}
|
||||
|
@ -756,6 +760,14 @@ class ReportsController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
if ($request->filled('title')) {
|
||||
if ($asset->checkedOutToUser()) {
|
||||
$row[] = ($asset->assignedto) ? $asset->assignedto->jobtitle : '';
|
||||
} else {
|
||||
$row[] = ''; // Empty string if unassigned
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use App\Http\Requests\ImageUploadRequest;
|
|||
use App\Http\Requests\SettingsSamlRequest;
|
||||
use App\Http\Requests\SetupUserRequest;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use App\Notifications\FirstAdminNotification;
|
||||
use App\Notifications\MailTest;
|
||||
|
@ -21,6 +22,7 @@ use Image;
|
|||
use Input;
|
||||
use Redirect;
|
||||
use Response;
|
||||
use App\Http\Requests\SlackSettingsRequest;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
|
@ -418,6 +420,7 @@ class SettingsController extends Controller
|
|||
$setting->brand = 1;
|
||||
}
|
||||
|
||||
|
||||
$setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo');
|
||||
|
||||
|
||||
|
@ -427,8 +430,10 @@ class SettingsController extends Controller
|
|||
// If they are uploading an image, validate it and upload it
|
||||
}
|
||||
|
||||
|
||||
$setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo');
|
||||
|
||||
|
||||
if ('1' == $request->input('clear_label_logo')) {
|
||||
Storage::disk('public')->delete($setting->label_logo);
|
||||
$setting->label_logo = null;
|
||||
|
@ -473,6 +478,7 @@ class SettingsController extends Controller
|
|||
return redirect()->back()->withInput()->withErrors($setting->getErrors());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a form to allow a super admin to update settings.
|
||||
*
|
||||
|
@ -612,6 +618,26 @@ class SettingsController extends Controller
|
|||
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
|
||||
}
|
||||
|
||||
// Check if the audit interval has changed - if it has, we want to update ALL of the assets audit dates
|
||||
if ($request->input('audit_interval') != $setting->audit_interval) {
|
||||
|
||||
// Be careful - this could be a negative number
|
||||
$audit_diff_months = ((int)$request->input('audit_interval') - (int)($setting->audit_interval));
|
||||
|
||||
// Grab all of the assets that have an existing next_audit_date
|
||||
$assets = Asset::whereNotNull('next_audit_date')->get();
|
||||
|
||||
// Update all of the assets' next_audit_date values
|
||||
foreach ($assets as $asset) {
|
||||
|
||||
if ($asset->next_audit_date != '') {
|
||||
$old_next_audit = new \DateTime($asset->next_audit_date);
|
||||
$asset->next_audit_date = $old_next_audit->modify($audit_diff_months.' month')->format('Y-m-d');
|
||||
$asset->forceSave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$alert_email = rtrim($request->input('alert_email'), ',');
|
||||
$alert_email = trim($alert_email);
|
||||
$admin_cc_email = rtrim($request->input('admin_cc_email'), ',');
|
||||
|
@ -659,12 +685,16 @@ class SettingsController extends Controller
|
|||
*
|
||||
* @return View
|
||||
*/
|
||||
public function postSlack(Request $request)
|
||||
public function postSlack(SlackSettingsRequest $request)
|
||||
{
|
||||
if (is_null($setting = Setting::getSettings())) {
|
||||
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
|
||||
}
|
||||
|
||||
$setting->slack_endpoint = $request->input('slack_endpoint');
|
||||
$setting->slack_channel = $request->input('slack_channel');
|
||||
$setting->slack_botname = $request->input('slack_botname');
|
||||
|
||||
if ($setting->save()) {
|
||||
return redirect()->route('settings.index')
|
||||
->with('success', trans('admin/settings/message.update.success'));
|
||||
|
@ -827,6 +857,8 @@ class SettingsController extends Controller
|
|||
$setting->labels_display_company_name = $request->input('labels_display_company_name', '0');
|
||||
$setting->labels_display_company_name = $request->input('labels_display_company_name', '0');
|
||||
|
||||
|
||||
|
||||
if ($request->filled('labels_display_name')) {
|
||||
$setting->labels_display_name = 1;
|
||||
} else {
|
||||
|
@ -1005,11 +1037,11 @@ class SettingsController extends Controller
|
|||
*/
|
||||
public function getBackups()
|
||||
{
|
||||
|
||||
$path = 'app/backups';
|
||||
$backup_files = Storage::files($path);
|
||||
$files_raw = [];
|
||||
|
||||
|
||||
if (count($backup_files) > 0) {
|
||||
for ($f = 0; $f < count($backup_files); $f++) {
|
||||
|
||||
|
@ -1018,7 +1050,6 @@ class SettingsController extends Controller
|
|||
//$lastmodified = Carbon::parse(Storage::lastModified($backup_files[$f]))->toDatetimeString();
|
||||
$file_timestamp = Storage::lastModified($backup_files[$f]);
|
||||
|
||||
|
||||
$files_raw[] = [
|
||||
'filename' => basename($backup_files[$f]),
|
||||
'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])),
|
||||
|
@ -1027,6 +1058,7 @@ class SettingsController extends Controller
|
|||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1192,11 +1224,10 @@ class SettingsController extends Controller
|
|||
// grab the user's info so we can make sure they exist in the system
|
||||
$user = User::find(Auth::user()->id);
|
||||
|
||||
// TODO: run a backup
|
||||
|
||||
// TODO: run a backup
|
||||
|
||||
// TODO: add db:wipe
|
||||
|
||||
Artisan::call('db:wipe');
|
||||
|
||||
// run the restore command
|
||||
Artisan::call('snipeit:restore',
|
||||
|
@ -1206,27 +1237,29 @@ class SettingsController extends Controller
|
|||
'filename' => storage_path($path).'/'.$filename
|
||||
]);
|
||||
|
||||
$output = Artisan::output();
|
||||
|
||||
|
||||
// If it's greater than 300, it probably worked
|
||||
$output = Artisan::output();
|
||||
if (strlen($output) > 300) {
|
||||
$find_user = DB::table('users')->where('first_name', $user->first_name)->where('last_name', $user->last_name)->exists();
|
||||
if(!$find_user){
|
||||
\Log::warning('Attempting to restore user: ' . $user->first_name . ' ' . $user->last_name);
|
||||
$new_user = $user->replicate();
|
||||
$new_user->push();
|
||||
}
|
||||
|
||||
$session_files = glob(storage_path("framework/sessions/*"));
|
||||
foreach ($session_files as $file) {
|
||||
if (is_file($file))
|
||||
unlink($file);
|
||||
}
|
||||
DB::table('users')->update(['remember_token' => null]);
|
||||
\Auth::logout();
|
||||
|
||||
return redirect()->route('login')->with('success', 'Your system has been restored. Please login again.');
|
||||
} else {
|
||||
return redirect()->route('settings.backups.index')->with('error', $output);
|
||||
|
||||
}
|
||||
//dd($output);
|
||||
|
||||
// TODO: insert the user if they are not there in the old one
|
||||
|
||||
|
||||
|
||||
|
||||
// log the user out
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found'));
|
||||
|
|
|
@ -179,6 +179,7 @@ class SuppliersController extends Controller
|
|||
*/
|
||||
public function show($supplierId = null)
|
||||
{
|
||||
$this->authorize('view', Supplier::class);
|
||||
$supplier = Supplier::find($supplierId);
|
||||
|
||||
if (isset($supplier->id)) {
|
||||
|
|
|
@ -4,32 +4,12 @@ namespace App\Http\Controllers\Users;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Services\LdapAd;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan; // Note that this is awful close to 'Users' the namespace above; be careful
|
||||
|
||||
class LDAPImportController extends Controller
|
||||
{
|
||||
/**
|
||||
* An Ldap instance.
|
||||
*
|
||||
* @var LdapAd
|
||||
*/
|
||||
protected $ldap;
|
||||
|
||||
/**
|
||||
* __construct.
|
||||
*
|
||||
* @param LdapAd $ldap
|
||||
*/
|
||||
public function __construct(LdapAd $ldap)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->ldap = $ldap;
|
||||
$this->ldap->init();
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Return view for LDAP import.
|
||||
*
|
||||
* @author Aladin Alaily
|
||||
|
@ -43,6 +23,7 @@ class LDAPImportController extends Controller
|
|||
*/
|
||||
public function create()
|
||||
{
|
||||
// I guess this prolly oughtta... I dunno. Do something?
|
||||
$this->authorize('update', User::class);
|
||||
try {
|
||||
//$this->ldap->connect(); I don't think this actually exists in LdapAd.php, and we don't really 'persist' LDAP connections anyways...right?
|
||||
|
|
|
@ -479,7 +479,6 @@ class UsersController extends Controller
|
|||
$user->first_name = '';
|
||||
$user->last_name = '';
|
||||
$user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0);
|
||||
|
||||
$user->id = null;
|
||||
|
||||
// Get this user groups
|
||||
|
@ -529,7 +528,7 @@ class UsersController extends Controller
|
|||
strtolower(trans('general.id')),
|
||||
trans('admin/companies/table.title'),
|
||||
trans('admin/users/table.title'),
|
||||
trans('admin/users/table.employee_num'),
|
||||
trans('general.employee_number'),
|
||||
trans('admin/users/table.name'),
|
||||
trans('admin/users/table.username'),
|
||||
trans('admin/users/table.email'),
|
||||
|
|
|
@ -57,7 +57,7 @@ class ViewAssetsController extends Controller
|
|||
*/
|
||||
public function getRequestableIndex()
|
||||
{
|
||||
$assets = Asset::with('model', 'defaultLoc', 'location', 'assignedTo', 'requests')->Hardware()->RequestableAssets()->get();
|
||||
$assets = Asset::with('model', 'defaultLoc', 'location', 'assignedTo', 'requests')->Hardware()->RequestableAssets();
|
||||
$models = AssetModel::with('category', 'requests', 'assets')->RequestableModels()->get();
|
||||
|
||||
return view('account/requestable-assets', compact('assets', 'models'));
|
||||
|
@ -165,7 +165,7 @@ class ViewAssetsController extends Controller
|
|||
$settings->notify(new RequestAssetCancelation($data));
|
||||
|
||||
return redirect()->route('requestable-assets')
|
||||
->with('success')->with('success', trans('admin/hardware/message.requests.cancel-success'));
|
||||
->with('success')->with('success', trans('admin/hardware/message.requests.cancel'));
|
||||
}
|
||||
|
||||
$logaction->logaction('requested');
|
||||
|
@ -245,7 +245,9 @@ class ViewAssetsController extends Controller
|
|||
$data_uri = e($request->get('signature_output'));
|
||||
$encoded_image = explode(',', $data_uri);
|
||||
$decoded_image = base64_decode($encoded_image[1]);
|
||||
file_put_contents($path.'/'.$sig_filename, $decoded_image);
|
||||
|
||||
Storage::putFileAs($path, $decoded_image, $sig_filename);
|
||||
//file_put_contents($path.'/'.$sig_filename, $decoded_image);
|
||||
}
|
||||
|
||||
$logaction = new Actionlog();
|
||||
|
|
|
@ -24,6 +24,7 @@ class Kernel extends HttpKernel
|
|||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\SecurityHeaders::class,
|
||||
\App\Http\Middleware\PreventBackHistory::class,
|
||||
\Fruitcake\Cors\HandleCors::class,
|
||||
|
||||
];
|
||||
|
||||
|
@ -44,7 +45,6 @@ class Kernel extends HttpKernel
|
|||
],
|
||||
|
||||
'api' => [
|
||||
\Fruitcake\Cors\HandleCors::class,
|
||||
'throttle:120,1',
|
||||
'auth:api',
|
||||
],
|
||||
|
|
|
@ -17,6 +17,10 @@ class CustomFieldSetDefaultValuesForModel extends Component
|
|||
|
||||
public function mount()
|
||||
{
|
||||
if(is_null($this->model_id)){
|
||||
return;
|
||||
}
|
||||
|
||||
$this->model = AssetModel::find($this->model_id); // It's possible to do some clever route-model binding here, but let's keep it simple, shall we?
|
||||
$this->fieldset_id = $this->model->fieldset_id;
|
||||
|
||||
|
|
|
@ -38,11 +38,14 @@ class LoginForm extends Component
|
|||
$this->can_submit = false;
|
||||
}
|
||||
|
||||
$this->validateOnly($fields);
|
||||
|
||||
$this->can_submit = true;
|
||||
|
||||
$whatever = $this->validateOnly($fields);
|
||||
//\Log::info(print_r($whatever,true));
|
||||
|
||||
$errors = $this->getErrorBag();
|
||||
|
||||
$this->can_submit = $this->username !== "" && $this->password !== "" && !$errors->has('username') && !$errors->has('password') ; // wait, what?
|
||||
|
||||
\Log::info("Oy - can we submit yet?!".$this->can_submit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,7 +61,7 @@ class LoginForm extends Component
|
|||
public function submitForm()
|
||||
{
|
||||
|
||||
$this->can_submit = true;
|
||||
//$this->can_submit = true;
|
||||
|
||||
if (auth()->attempt($this->validate())) {
|
||||
return redirect()->intended('/');
|
||||
|
|
|
@ -32,7 +32,7 @@ class CheckForTwoFactor
|
|||
if ($settings = Setting::getSettings()) {
|
||||
if (Auth::check() && ($settings->two_factor_enabled != '')) {
|
||||
// This user is already 2fa-authed
|
||||
if ($request->session()->get('2fa_authed')) {
|
||||
if ($request->session()->get('2fa_authed')==Auth::user()->id) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,6 @@ class ImageUploadRequest extends Request
|
|||
|
||||
// If the user isn't uploading anything new but wants to delete their old image, do so
|
||||
} else {
|
||||
\Log::debug('No file passed for '.$form_fieldname);
|
||||
if ($this->input('image_delete') == '1') {
|
||||
\Log::debug('Deleting image');
|
||||
try {
|
||||
|
|
|
@ -57,7 +57,6 @@ class ItemImportRequest extends FormRequest
|
|||
}
|
||||
// We submit as csv field: column, but the importer is happier if we flip it here.
|
||||
$fieldMappings = array_change_key_case(array_flip($import->field_map), CASE_LOWER);
|
||||
// dd($fieldMappings);
|
||||
}
|
||||
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
|
||||
->setUserId(Auth::id())
|
||||
|
@ -65,8 +64,6 @@ class ItemImportRequest extends FormRequest
|
|||
->setShouldNotify($this->has('send-welcome'))
|
||||
->setUsernameFormat('firstname.lastname')
|
||||
->setFieldMappings($fieldMappings);
|
||||
// $logFile = storage_path('logs/importer.log');
|
||||
// \Log::useFiles($logFile);
|
||||
$importer->import();
|
||||
|
||||
return $this->errors;
|
||||
|
|
|
@ -9,6 +9,14 @@ abstract class Request extends FormRequest
|
|||
{
|
||||
protected $rules = [];
|
||||
|
||||
public function json($key = null, $default = null)
|
||||
{
|
||||
if ($this->ajax() || $this->wantsJson()) {
|
||||
json_decode($this->getContent(), false, 512, JSON_THROW_ON_ERROR); // ignore output, just throw
|
||||
}
|
||||
return parent::json($key, $default);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return $this->rules;
|
||||
|
|
33
app/Http/Requests/SlackSettingsRequest.php
Normal file
33
app/Http/Requests/SlackSettingsRequest.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
class SlackSettingsRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:"https://hooks.slack.com"|nullable',
|
||||
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
|
||||
'slack_botname' => 'string|nullable',
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -79,7 +79,7 @@ class AccessoriesTransformer
|
|||
'first_name'=> e($user->first_name),
|
||||
'last_name'=> e($user->last_name),
|
||||
'employee_number' => e($user->employee_num),
|
||||
'checkout_notes' => $user->pivot->note,
|
||||
'checkout_notes' => e($user->pivot->note),
|
||||
'last_checkout' => Helper::getFormattedDateObject($user->pivot->created_at, 'datetime'),
|
||||
'type' => 'user',
|
||||
'available_actions' => ['checkin' => true],
|
||||
|
|
|
@ -36,7 +36,11 @@ class ActionlogsTransformer
|
|||
foreach ($value as $meta_key => $meta_value) {
|
||||
if (is_array($meta_value)) {
|
||||
foreach ($meta_value as $meta_value_key => $meta_value_value) {
|
||||
$clean_meta[$key][$meta_value_key] = e($meta_value_value);
|
||||
if (is_scalar($meta_value_value)) {
|
||||
$clean_meta[$key][$meta_value_key] = e($meta_value_value);
|
||||
} else {
|
||||
$clean_meta[$key][$meta_value_key] = 'invalid scalar: '.print_r($meta_value_value, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ class AssetsTransformer
|
|||
|
||||
'id' => $component->id,
|
||||
'pivot_id' => $component->pivot->id,
|
||||
'name' => $component->name,
|
||||
'name' => e($component->name),
|
||||
'qty' => $component->pivot->assigned_qty,
|
||||
'price_cost' => $component->purchase_cost,
|
||||
'purchase_total' => $component->purchase_cost * $component->pivot->assigned_qty,
|
||||
|
@ -171,8 +171,8 @@ class AssetsTransformer
|
|||
|
||||
return $asset->assigned ? [
|
||||
'id' => $asset->assigned->id,
|
||||
'name' => $asset->assigned->display_name,
|
||||
'type' => $asset->assignedType(),
|
||||
'name' => e($asset->assigned->display_name),
|
||||
'type' => $asset->assignedType()
|
||||
] : null;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,14 +9,14 @@ use Illuminate\Database\Eloquent\Collection;
|
|||
|
||||
class GroupsTransformer
|
||||
{
|
||||
public function transformGroups(Collection $groups)
|
||||
public function transformGroups (Collection $groups, $total = null)
|
||||
{
|
||||
$array = [];
|
||||
foreach ($groups as $group) {
|
||||
$array[] = self::transformGroup($group);
|
||||
}
|
||||
|
||||
return (new DatatablesTransformer)->transformDatatables($array);
|
||||
return (new DatatablesTransformer)->transformDatatables($array, $total);
|
||||
}
|
||||
|
||||
public function transformGroup(Group $group)
|
||||
|
|
|
@ -46,7 +46,9 @@ class LicensesTransformer
|
|||
'category' => ($license->category) ? ['id' => (int) $license->category->id, 'name'=> e($license->category->name)] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
|
||||
'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'),
|
||||
'user_can_checkout' => (bool) ($license->free_seats_count > 0),
|
||||
|
||||
];
|
||||
|
||||
$permissions_array['available_actions'] = [
|
||||
|
|
|
@ -68,7 +68,7 @@ class AssetImporter extends ItemImporter
|
|||
$this->log('No Matching Asset, Creating a new one');
|
||||
$asset = new Asset;
|
||||
}
|
||||
|
||||
$this->item['notes'] = $this->findCsvMatch($row, 'asset_notes');
|
||||
$this->item['image'] = $this->findCsvMatch($row, 'image');
|
||||
$this->item['requestable'] = $this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'));
|
||||
$asset->requestable = $this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'));
|
||||
|
@ -96,6 +96,14 @@ class AssetImporter extends ItemImporter
|
|||
$item['rtd_location_id'] = $this->item['location_id'];
|
||||
}
|
||||
|
||||
if (isset($this->item['last_audit_date'])) {
|
||||
$item['last_audit_date'] = $this->item['last_audit_date'];
|
||||
}
|
||||
|
||||
if (isset($this->item['next_audit_date'])) {
|
||||
$item['next_audit_date'] = $this->item['next_audit_date'];
|
||||
}
|
||||
|
||||
if ($editingAsset) {
|
||||
$asset->update($item);
|
||||
} else {
|
||||
|
|
|
@ -76,6 +76,17 @@ class ItemImporter extends Importer
|
|||
if ($this->findCsvMatch($row, 'purchase_date') != '') {
|
||||
$this->item['purchase_date'] = date('Y-m-d 00:00:01', strtotime($this->findCsvMatch($row, 'purchase_date')));
|
||||
}
|
||||
|
||||
$this->item['last_audit_date'] = null;
|
||||
if ($this->findCsvMatch($row, 'last_audit_date') != '') {
|
||||
$this->item['last_audit_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'last_audit_date')));
|
||||
}
|
||||
|
||||
$this->item['next_audit_date'] = null;
|
||||
if ($this->findCsvMatch($row, 'next_audit_date') != '') {
|
||||
$this->item['next_audit_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'next_audit_date')));
|
||||
}
|
||||
|
||||
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
|
||||
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
|
||||
$this->item['user_id'] = $this->user_id;
|
||||
|
@ -178,6 +189,7 @@ class ItemImporter extends Importer
|
|||
*/
|
||||
public function createOrFetchAssetModel(array $row)
|
||||
{
|
||||
$condition = array();
|
||||
$asset_model_name = $this->findCsvMatch($row, 'asset_model');
|
||||
$asset_modelNumber = $this->findCsvMatch($row, 'model_number');
|
||||
// TODO: At the moment, this means we can't update the model number if the model name stays the same.
|
||||
|
@ -189,8 +201,16 @@ class ItemImporter extends Importer
|
|||
} elseif ((empty($asset_model_name)) && (empty($asset_modelNumber))) {
|
||||
$asset_model_name = 'Unknown';
|
||||
}
|
||||
|
||||
if ((!empty($asset_model_name)) && (empty($asset_modelNumber))) {
|
||||
$condition[] = ['name', '=', $asset_model_name];
|
||||
} elseif ((!empty($asset_model_name)) && (!empty($asset_modelNumber))) {
|
||||
$condition[] = ['name', '=', $asset_model_name];
|
||||
$condition[] = ['model_number', '=', $asset_modelNumber];
|
||||
}
|
||||
|
||||
$editingModel = $this->updating;
|
||||
$asset_model = AssetModel::where(['name' => $asset_model_name, 'model_number' => $asset_modelNumber])->first();
|
||||
$asset_model = AssetModel::where($condition)->first();
|
||||
|
||||
if ($asset_model) {
|
||||
if (! $this->updating) {
|
||||
|
@ -201,7 +221,12 @@ class ItemImporter extends Importer
|
|||
$this->log('Matching Model found, updating it.');
|
||||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||
$item['name'] = $asset_model_name;
|
||||
$item['model_number'] = $asset_modelNumber;
|
||||
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
|
||||
|
||||
if(!empty($asset_modelNumber)){
|
||||
$item['model_number'] = $asset_modelNumber;
|
||||
}
|
||||
|
||||
$asset_model->update($item);
|
||||
$asset_model->save();
|
||||
$this->log('Asset Model Updated');
|
||||
|
@ -214,6 +239,7 @@ class ItemImporter extends Importer
|
|||
$item = $this->sanitizeItemForStoring($asset_model, $editingModel);
|
||||
$item['name'] = $asset_model_name;
|
||||
$item['model_number'] = $asset_modelNumber;
|
||||
$item['notes'] = $this->findCsvMatch($row, 'model_notes');
|
||||
|
||||
$asset_model->fill($item);
|
||||
$item = null;
|
||||
|
@ -264,8 +290,7 @@ class ItemImporter extends Importer
|
|||
|
||||
return $category->id;
|
||||
}
|
||||
|
||||
$this->logError($category, 'Category "'.$asset_category.'"');
|
||||
$this->logError($category, 'Category "'. $asset_category. '"');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -356,7 +381,6 @@ class ItemImporter extends Importer
|
|||
}
|
||||
|
||||
$this->logError($status, 'Status "'.$asset_statuslabel_name.'"');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ class LicenseImporter extends ItemImporter
|
|||
{
|
||||
$editingLicense = false;
|
||||
$license = License::where('name', $this->item['name'])
|
||||
->where('serial', $this->item['serial'])
|
||||
->first();
|
||||
if ($license) {
|
||||
if (! $this->updating) {
|
||||
|
|
|
@ -50,6 +50,7 @@ class UserImporter extends ItemImporter
|
|||
$this->item['city'] = $this->findCsvMatch($row, 'city');
|
||||
$this->item['state'] = $this->findCsvMatch($row, 'state');
|
||||
$this->item['country'] = $this->findCsvMatch($row, 'country');
|
||||
$this->item['zip'] = $this->findCsvMatch($row, 'zip');
|
||||
$this->item['activated'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'activated')) == 1) ? '1' : 0;
|
||||
$this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num');
|
||||
$this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department'));
|
||||
|
@ -75,6 +76,7 @@ class UserImporter extends ItemImporter
|
|||
}
|
||||
|
||||
|
||||
|
||||
// This needs to be applied after the update logic, otherwise we'll overwrite user passwords
|
||||
// Issue #5408
|
||||
$this->item['password'] = bcrypt($this->tempPassword);
|
||||
|
@ -144,7 +146,6 @@ class UserImporter extends ItemImporter
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function sendWelcome($send = true)
|
||||
{
|
||||
$this->send_welcome = $send;
|
||||
|
|
|
@ -262,12 +262,13 @@ class Asset extends Depreciable
|
|||
&& ($this->assetstatus->deployable == '1'))
|
||||
{
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the asset out to the target
|
||||
*
|
||||
|
@ -301,6 +302,7 @@ class Asset extends Depreciable
|
|||
|
||||
$this->assignedTo()->associate($target);
|
||||
|
||||
|
||||
if ($name != null) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
@ -502,9 +504,10 @@ class Asset extends Depreciable
|
|||
}
|
||||
//this makes no sense
|
||||
return $this->defaultLoc;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return $this->defaultLoc;
|
||||
}
|
||||
|
||||
|
@ -675,13 +678,15 @@ class Asset extends Depreciable
|
|||
*/
|
||||
public static function getExpiringWarrantee($days = 30)
|
||||
{
|
||||
$days = (is_null($days)) ? 30 : $days;
|
||||
|
||||
return self::where('archived', '=', '0')
|
||||
->whereNotNull('warranty_months')
|
||||
->whereNotNull('purchase_date')
|
||||
->whereNull('deleted_at')
|
||||
->whereRaw(\DB::raw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH) <= DATE(NOW() + INTERVAL '
|
||||
.$days
|
||||
.' DAY) AND DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH) > NOW()'))
|
||||
. $days
|
||||
. ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()'))
|
||||
->orderBy('purchase_date', 'ASC')
|
||||
->get();
|
||||
}
|
||||
|
@ -825,7 +830,9 @@ class Asset extends Depreciable
|
|||
*/
|
||||
public function checkin_email()
|
||||
{
|
||||
return $this->model->category->checkin_email;
|
||||
if (($this->model) && ($this->model->category)) {
|
||||
return $this->model->category->checkin_email;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1167,9 +1174,10 @@ class Asset extends Depreciable
|
|||
{
|
||||
return Company::scopeCompanyables($query->where('requestable', '=', 1))
|
||||
->whereHas('assetstatus', function ($query) {
|
||||
$query->where('deployable', '=', 1)
|
||||
->where('pending', '=', 0)
|
||||
->where('archived', '=', 0);
|
||||
$query->where(function ($query) {
|
||||
$query->where('deployable', '=', 1)
|
||||
->where('archived', '=', 0); // you definitely can't request something that's archived
|
||||
})->orWhere('pending', '=', 1); // we've decided that even though an asset may be 'pending', you can still request it
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1315,6 +1323,7 @@ class Asset extends Depreciable
|
|||
{
|
||||
return $query->where(function ($query) use ($filter) {
|
||||
foreach ($filter as $key => $search_val) {
|
||||
|
||||
$fieldname = str_replace('custom_fields.', '', $key);
|
||||
|
||||
if ($fieldname == 'asset_tag') {
|
||||
|
@ -1452,6 +1461,7 @@ class Asset extends Depreciable
|
|||
$query->where('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ class CustomField extends Model
|
|||
'field_encrypted',
|
||||
'help_text',
|
||||
'show_in_email',
|
||||
'is_unique',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -83,7 +83,11 @@ class CustomFieldset extends Model
|
|||
|
||||
if (($field->field_encrypted != '1') ||
|
||||
(($field->field_encrypted == '1') && (Gate::allows('admin')))) {
|
||||
$rule[] = ($field->pivot->required == '1') ? 'required' : 'nullable';
|
||||
$rule[] = ($field->pivot->required == '1') ? 'required' : 'nullable';
|
||||
}
|
||||
|
||||
if ($field->is_unique == '1') {
|
||||
$rule[] = 'unique';
|
||||
}
|
||||
|
||||
array_push($rule, $field->attributes['format']);
|
||||
|
|
|
@ -68,13 +68,18 @@ class Depreciable extends SnipeModel
|
|||
*/
|
||||
public function getLinearDepreciatedValue()
|
||||
{
|
||||
// fraction of value left
|
||||
$numerator= (($this->purchase_cost-($this->purchase_cost*12/($this->get_depreciation()->months))));
|
||||
$denominator=$this->get_depreciation()->months/12;
|
||||
$deprecation_per_year= $numerator/$denominator;
|
||||
$deprecation_per_month= $deprecation_per_year/12;
|
||||
|
||||
$months_remaining = $this->time_until_depreciated()->m + 12 * $this->time_until_depreciated()->y; //UGlY
|
||||
$current_value = round(($months_remaining / $this->get_depreciation()->months) * $this->purchase_cost, 2);
|
||||
$months_depreciated=$this->get_depreciation()->months-$months_remaining;
|
||||
$current_value = $this->purchase_cost-($deprecation_per_month*$months_depreciated);
|
||||
|
||||
if($this->get_depreciation()->depreciation_min > $current_value) {
|
||||
|
||||
$current_value=$this->get_depreciation()->depreciation_min;
|
||||
$current_value=round($this->get_depreciation()->depreciation_min,2);
|
||||
}
|
||||
if ($current_value < 0) {
|
||||
$current_value = 0;
|
||||
|
|
|
@ -9,6 +9,22 @@ use Illuminate\Database\Eloquent\Model;
|
|||
use Input;
|
||||
use Log;
|
||||
|
||||
/***********************************************
|
||||
* TODOS:
|
||||
*
|
||||
* First off, we should probably make it so that the main LDAP thing we're using is an *instance* of this class,
|
||||
* rather than the static methods we use here. We should probably load up that class with its settings, so we
|
||||
* don't have to explicitly refer to them so often.
|
||||
*
|
||||
* Then, we should probably look at embedding some of the logic we use elsewhere into here - the various methods
|
||||
* should either return a User or false, or other things like that. Don't make the consumers of this class reach
|
||||
* into its guts. While that conflates this model with the User model, I think having the appropriate logic for
|
||||
* turning LDAP people into Users ought to belong here, so it's easier on the consumer of this class.
|
||||
*
|
||||
* We're probably going to have to eventually make it so that Snipe-IT users can define multiple LDAP servers,
|
||||
* and having this as a more instance-oriented class will be a step in the right direction.
|
||||
***********************************************/
|
||||
|
||||
class Ldap extends Model
|
||||
{
|
||||
/**
|
||||
|
@ -87,22 +103,40 @@ class Ldap extends Model
|
|||
if ($ldap_username_field == 'userprincipalname') {
|
||||
$userDn = $username;
|
||||
} else {
|
||||
// In case they haven't added an AD domain
|
||||
// TODO - we no longer respect the "add AD Domain to username" checkbox, but it still exists in settings.
|
||||
// We should probably just eliminate that checkbox to avoid confusion.
|
||||
// We let it sit in the DB, unused, to facilitate people downgrading (if they decide to).
|
||||
// Hopefully, in a later release, we can remove it from the settings.
|
||||
// This logic instead just means that if we're using UPN, we don't append ad_domain, if we aren't, then we do.
|
||||
// Hopefully that should handle all of our use cases, but if not we can backport our old logic.
|
||||
$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;
|
||||
$filter = Setting::getSettings()->ldap_filter;
|
||||
$filter = Setting::getSettings()->ldap_filter; //FIXME - this *does* respect the ldap filter, but I believe that AdLdap2 did *not*.
|
||||
$filterQuery = "({$filter}({$filterQuery}))";
|
||||
|
||||
\Log::debug('Filter query: '.$filterQuery);
|
||||
|
||||
if (! $ldapbind = @ldap_bind($connection, $userDn, $password)) {
|
||||
\Log::debug("Status of binding user: $userDn to directory: (directly!) ".($ldapbind ? "success" : "FAILURE"));
|
||||
if (! $ldapbind = self::bindAdminToLdap($connection)) {
|
||||
return false;
|
||||
/*
|
||||
* TODO PLEASE:
|
||||
*
|
||||
* this isn't very clear, so it's important to note: the $ldapbind value is never correctly returned - we never 'return true' from self::bindAdminToLdap() (the function
|
||||
* just "falls off the end" without ever explictly returning 'true')
|
||||
*
|
||||
* but it *does* have an interesting side-effect of checking for the LDAP password being incorrectly encrypted with the wrong APP_KEY, so I'm leaving it in for now.
|
||||
*
|
||||
* If it *did* correctly return 'true' on a succesful bind, it would _probably_ allow users to log in with an incorrect password. Which would be horrible!
|
||||
*
|
||||
* Let's definitely fix this at the next refactor!!!!
|
||||
*
|
||||
*/
|
||||
\Log::debug("Status of binding Admin user: $userDn to directory instead: ".($ldapbind ? "success" : "FAILURE"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,8 +169,6 @@ class Ldap extends Model
|
|||
{
|
||||
$ldap_username = Setting::getSettings()->ldap_uname;
|
||||
|
||||
$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);
|
||||
|
@ -147,6 +179,11 @@ class Ldap extends Model
|
|||
if (! $ldapbind = @ldap_bind($connection, $ldap_username, $ldap_pass)) {
|
||||
throw new Exception('Could not bind to LDAP: '.ldap_error($connection));
|
||||
}
|
||||
// TODO - this just "falls off the end" but the function states that it should return true or false
|
||||
// unfortunately, one of the use cases for this function is wrong and *needs* for that failure mode to fire
|
||||
// so I don't want to fix this right now.
|
||||
// this method MODIFIES STATE on the passed-in $connection and just returns true or false (or, in this case, undefined)
|
||||
// at the next refactor, this should be appropriately modified to be more consistent.
|
||||
}
|
||||
|
||||
|
||||
|
@ -233,14 +270,14 @@ class Ldap extends Model
|
|||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param $ldapatttibutes
|
||||
* @param $base_dn
|
||||
* @param $count
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function findLdapUsers($base_dn = null)
|
||||
public static function findLdapUsers($base_dn = null, $count = -1)
|
||||
{
|
||||
$ldapconn = self::connectToLdap();
|
||||
$ldap_bind = self::bindAdminToLdap($ldapconn);
|
||||
self::bindAdminToLdap($ldapconn);
|
||||
// Default to global base DN if nothing else is provided.
|
||||
if (is_null($base_dn)) {
|
||||
$base_dn = Setting::getSettings()->ldap_basedn;
|
||||
|
@ -256,40 +293,58 @@ class Ldap extends Model
|
|||
// 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)";
|
||||
} elseif ($filter == '') {
|
||||
$filter = '(cn=*)';
|
||||
}
|
||||
|
||||
$search_results = ldap_search($ldapconn, $base_dn, $filter);
|
||||
|
||||
// HUGE thanks to this article: https://stackoverflow.com/questions/68275972/how-to-get-paged-ldap-queries-in-php-8-and-read-more-than-1000-entries
|
||||
// which helped me wrap my head around paged results!
|
||||
\Log::info("ldap conn is: ".$ldapconn." basedn is: $base_dn, filter is: $filter - count is: $count. page size is: $page_size"); //FIXME - remove
|
||||
// if a $count is set and it's smaller than $page_size then use that as the page size
|
||||
$ldap_controls = [];
|
||||
//if($count == -1) { //count is -1 means we have to employ paging to query the entire directory
|
||||
$ldap_controls = [['oid' => LDAP_CONTROL_PAGEDRESULTS, 'iscritical' => false, 'value' => ['size'=> $count == -1||$count>$page_size ? $page_size : $count, 'cookie' => $cookie]]];
|
||||
//}
|
||||
$search_results = ldap_search($ldapconn, $base_dn, $filter, [], 0, /* $page_size */ -1, -1, LDAP_DEREF_NEVER, $ldap_controls); // TODO - I hate the @, and I hate that we get a full page even if we ask for 10 records. Can we use an ldap_control?
|
||||
\Log::info("did the search run? I guess so if you got here!");
|
||||
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.
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_search').ldap_error($ldapconn)); // TODO this is never called in any routed context - only from the Artisan command. So this redirect will never work.
|
||||
}
|
||||
|
||||
$errcode = null;
|
||||
$matcheddn = null;
|
||||
$errmsg = null;
|
||||
$referrals = null;
|
||||
$controls = [];
|
||||
ldap_parse_result($ldapconn, $search_results, $errcode , $matcheddn , $errmsg , $referrals, $controls);
|
||||
if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
|
||||
// You need to pass the cookie from the last call to the next one
|
||||
$cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
|
||||
\Log::debug("okay, at least one more page to go!!!");
|
||||
} else {
|
||||
\Log::debug("okay, we're out of pages - no cookie (or empty cookie) was passed");
|
||||
$cookie = '';
|
||||
}
|
||||
// Empty cookie means last page
|
||||
|
||||
// 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.
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_get_entries').ldap_error($ldapconn)); // TODO 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);
|
||||
\Log::debug("Total count is: $global_count");
|
||||
|
||||
@ldap_control_paged_result_response($ldapconn, $search_results, $cookie);
|
||||
} while ($cookie !== null && $cookie != '');
|
||||
} while ($cookie !== null && $cookie != '' && ($count == -1 || $global_count < $count)); // some servers don't even have pagination, and some will give you more results than you asked for, so just see if you have enough.
|
||||
|
||||
// Clean up after search
|
||||
$result_set['count'] = $global_count;
|
||||
$result_set['count'] = $global_count; // TODO: I would've figured you could just count the array instead?
|
||||
$results = $result_set;
|
||||
@ldap_control_paged_result($ldapconn, 0);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class License extends Depreciable
|
|||
|
||||
protected $rules = [
|
||||
'name' => 'required|string|min:3|max:255',
|
||||
'seats' => 'required|min:1|max:999|integer',
|
||||
'seats' => 'required|min:1|integer',
|
||||
'license_email' => 'email|nullable|max:120',
|
||||
'license_name' => 'string|nullable|max:100',
|
||||
'notes' => 'string|nullable',
|
||||
|
@ -175,12 +175,24 @@ class License extends Depreciable
|
|||
return true;
|
||||
}
|
||||
// Else we're adding seats.
|
||||
DB::transaction(function () use ($license, $oldSeats, $newSeats) {
|
||||
for ($i = $oldSeats; $i < $newSeats; $i++) {
|
||||
$license->licenseSeatsRelation()->save(new LicenseSeat, ['user_id' => Auth::id()]);
|
||||
}
|
||||
//Create enough seats for the change.
|
||||
$licenseInsert = [];
|
||||
for ($i = $oldSeats; $i < $newSeats; $i++) {
|
||||
$licenseInsert[] = [
|
||||
'user_id' => Auth::id(),
|
||||
'license_id' => $license->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
];
|
||||
}
|
||||
//Chunk and use DB transactions to prevent timeouts.
|
||||
collect($licenseInsert)->chunk(1000)->each(function ($chunk) {
|
||||
DB::transaction(function () use ($chunk) {
|
||||
LicenseSeat::insert($chunk->toArray());
|
||||
});
|
||||
});
|
||||
// On initail create, we shouldn't log the addition of seats.
|
||||
|
||||
// On initial create, we shouldn't log the addition of seats.
|
||||
if ($license->id) {
|
||||
//Log the addition of license to the log.
|
||||
$logAction = new Actionlog();
|
||||
|
@ -403,6 +415,7 @@ class License extends Depreciable
|
|||
->count();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the number of seats for this asset
|
||||
*
|
||||
|
@ -583,6 +596,7 @@ class License extends Depreciable
|
|||
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the next available free seat - used by
|
||||
* the API to populate next_seat
|
||||
|
@ -626,6 +640,8 @@ class License extends Depreciable
|
|||
*/
|
||||
public static function getExpiringLicenses($days = 60)
|
||||
{
|
||||
$days = (is_null($days)) ? 60 : $days;
|
||||
|
||||
return self::whereNotNull('expiration_date')
|
||||
->whereNull('deleted_at')
|
||||
->whereRaw(DB::raw('DATE_SUB(`expiration_date`,INTERVAL '.$days.' DAY) <= DATE(NOW()) '))
|
||||
|
|
|
@ -110,8 +110,10 @@ trait Loggable
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
$log->location_id = null;
|
||||
$log->note = $note;
|
||||
$log->action_date = $action_date;
|
||||
|
||||
if (Auth::user()) {
|
||||
$log->user_id = Auth::user()->id;
|
||||
|
|
|
@ -54,10 +54,7 @@ class Setting extends Model
|
|||
'admin_cc_email' => 'email|nullable',
|
||||
'default_currency' => 'required',
|
||||
'locale' => 'required',
|
||||
'slack_endpoint' => 'url|required_with:slack_channel|nullable|starts_with:https://hooks.slack.com',
|
||||
'labels_per_page' => 'numeric',
|
||||
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
|
||||
'slack_botname' => 'string|nullable',
|
||||
'labels_width' => 'numeric',
|
||||
'labels_height' => 'numeric',
|
||||
'labels_pmargin_left' => 'numeric|nullable',
|
||||
|
|
|
@ -17,8 +17,8 @@ class Supplier extends SnipeModel
|
|||
|
||||
protected $rules = [
|
||||
'name' => 'required|min:1|max:255|unique_undeleted',
|
||||
'address' => 'max:50|nullable',
|
||||
'address2' => 'max:50|nullable',
|
||||
'address' => 'max:250|nullable',
|
||||
'address2' => 'max:250|nullable',
|
||||
'city' => 'max:255|nullable',
|
||||
'state' => 'max:32|nullable',
|
||||
'country' => 'max:3|nullable',
|
||||
|
|
|
@ -102,7 +102,7 @@ class AssetPresenter extends Presenter
|
|||
'field' => 'employee_number',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'title' => trans('admin/users/table.employee_num'),
|
||||
'title' => trans('general.employee_number'),
|
||||
'visible' => false,
|
||||
'formatter' => 'employeeNumFormatter',
|
||||
], [
|
||||
|
|
|
@ -25,7 +25,7 @@ class PredefinedKitPresenter extends Presenter
|
|||
'field' => 'name',
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'title' => 'Name', // TODO: trans
|
||||
'title' => trans('general.name'),
|
||||
'formatter' => 'kitsLinkFormatter',
|
||||
],
|
||||
];
|
||||
|
@ -84,13 +84,13 @@ class PredefinedKitPresenter extends Presenter
|
|||
'field' => 'name',
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'title' => 'Name', // TODO: trans
|
||||
'title' => trans('general.name'),
|
||||
'formatter' => 'modelsLinkFormatter',
|
||||
], [
|
||||
'field' => 'quantity',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'title' => 'Quantity', // TODO: trans
|
||||
'title' => trans('general.quantity'),
|
||||
], [
|
||||
'field' => 'actions',
|
||||
'searchable' => false,
|
||||
|
@ -136,13 +136,13 @@ class PredefinedKitPresenter extends Presenter
|
|||
'field' => 'name',
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'title' => 'Name', // TODO: trans
|
||||
'title' => trans('general.name'),
|
||||
'formatter' => 'licensesLinkFormatter',
|
||||
], [
|
||||
'field' => 'quantity',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'title' => 'Quantity', // TODO: trans
|
||||
'title' => trans('general.quantity'),
|
||||
], [
|
||||
'field' => 'actions',
|
||||
'searchable' => false,
|
||||
|
@ -188,13 +188,13 @@ class PredefinedKitPresenter extends Presenter
|
|||
'field' => 'name',
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'title' => 'Name', // TODO: trans
|
||||
'title' => trans('general.name'),
|
||||
'formatter' => 'accessoriesLinkFormatter',
|
||||
], [
|
||||
'field' => 'quantity',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'title' => 'Quantity', // TODO: trans
|
||||
'title' => trans('general.quantity'),
|
||||
], [
|
||||
'field' => 'actions',
|
||||
'searchable' => false,
|
||||
|
@ -240,13 +240,13 @@ class PredefinedKitPresenter extends Presenter
|
|||
'field' => 'name',
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'title' => 'Name', // TODO: trans
|
||||
'title' => trans('general.name'),
|
||||
'formatter' => 'consumablesLinkFormatter',
|
||||
], [
|
||||
'field' => 'quantity',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'title' => 'Quantity', // TODO: trans
|
||||
'title' => trans('general.quantity'),
|
||||
], [
|
||||
'field' => 'actions',
|
||||
'searchable' => false,
|
||||
|
|
|
@ -157,7 +157,7 @@ class UserPresenter extends Presenter
|
|||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'switchable' => true,
|
||||
'title' => trans('admin/users/table.employee_num'),
|
||||
'title' => trans('general.employee_number'),
|
||||
'visible' => false,
|
||||
],
|
||||
[
|
||||
|
|
|
@ -43,6 +43,16 @@ class AppServiceProvider extends ServiceProvider
|
|||
}
|
||||
}
|
||||
|
||||
// TODO - isn't it somehow 'gauche' to check the environment directly; shouldn't we be using config() somehow?
|
||||
if ( ! env('APP_ALLOW_INSECURE_HOSTS')) { // unless you set APP_ALLOW_INSECURE_HOSTS, you should PROHIBIT forging domain parts of URL via Host: headers
|
||||
$url_parts = parse_url(config('app.url'));
|
||||
if ($url_parts && array_key_exists('scheme', $url_parts) && array_key_exists('host', $url_parts)) { // check for the *required* parts of a bare-minimum URL
|
||||
\URL::forceRootUrl(config('app.url'));
|
||||
} else {
|
||||
\Log::error("Your APP_URL in your .env is misconfigured - it is: ".config('app.url').". Many things will work strangely unless you fix it.");
|
||||
}
|
||||
}
|
||||
|
||||
\Illuminate\Pagination\Paginator::useBootstrap();
|
||||
|
||||
Schema::defaultStringLength(191);
|
||||
|
|
|
@ -151,6 +151,8 @@ class AuthServiceProvider extends ServiceProvider
|
|||
return $user->hasAccess('self.checkout_assets');
|
||||
});
|
||||
|
||||
// This is largely used to determine whether to display the gear icon sidenav
|
||||
// in the left-side navigation
|
||||
Gate::define('backend.interact', function ($user) {
|
||||
return $user->can('view', Statuslabel::class)
|
||||
|| $user->can('view', AssetModel::class)
|
||||
|
@ -165,5 +167,28 @@ class AuthServiceProvider extends ServiceProvider
|
|||
|| $user->can('view', CustomFieldset::class)
|
||||
|| $user->can('view', Depreciation::class);
|
||||
});
|
||||
|
||||
|
||||
// This determines whether or not an API user should be able to get the selectlists.
|
||||
// This can seem a little confusing, since view properties may not have been granted
|
||||
// to the logged in API user, but creating assets, licenses, etc won't work
|
||||
// if the user can't view and interact with the select lists.
|
||||
Gate::define('view.selectlists', function ($user) {
|
||||
return $user->can('update', Asset::class)
|
||||
|| $user->can('create', Asset::class)
|
||||
|| $user->can('checkout', Asset::class)
|
||||
|| $user->can('checkin', Asset::class)
|
||||
|| $user->can('audit', Asset::class)
|
||||
|| $user->can('update', License::class)
|
||||
|| $user->can('create', License::class)
|
||||
|| $user->can('update', Component::class)
|
||||
|| $user->can('create', Component::class)
|
||||
|| $user->can('update', Consumable::class)
|
||||
|| $user->can('create', Consumable::class)
|
||||
|| $user->can('update', Accessory::class)
|
||||
|| $user->can('create', Accessory::class)
|
||||
|| $user->can('update', User::class)
|
||||
|| $user->can('create', User::class);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\LdapAd;
|
||||
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class LdapServiceProvider extends ServiceProvider implements DeferrableProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton(LdapAd::class, LdapAd::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provides()
|
||||
{
|
||||
return [LdapAd::class];
|
||||
}
|
||||
}
|
|
@ -1,572 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Adldap\Adldap;
|
||||
use Adldap\Models\User as AdldapUser;
|
||||
use Adldap\Query\Paginator;
|
||||
use Adldap\Schemas\Schema;
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* LDAP queries.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
class LdapAd extends LdapAdConfiguration
|
||||
{
|
||||
/* The following is _probably_ the correct logic, but we can't use it because
|
||||
some users may have been dependent upon the previous behavior, and this
|
||||
could cause additional access to be available to users they don't want
|
||||
to allow to log in.
|
||||
$useraccountcontrol = $results[$i]['useraccountcontrol'][0];
|
||||
if(
|
||||
// based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties
|
||||
($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT
|
||||
!($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE
|
||||
!($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT
|
||||
) {
|
||||
$user->activated = 1;
|
||||
} else {
|
||||
$user->activated = 0;
|
||||
} */
|
||||
const AD_USER_ACCOUNT_CONTROL_FLAGS = [
|
||||
'512', // 0x200 NORMAL_ACCOUNT
|
||||
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
|
||||
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
|
||||
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
|
||||
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
|
||||
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
|
||||
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
|
||||
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
|
||||
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
|
||||
'1114624', // 0x110200 NORMAL_ACCOUNT, NOT_DELEGATED, DONT_EXPIRE_PASSWORD
|
||||
];
|
||||
|
||||
/**
|
||||
* The LDAP results per page.
|
||||
*/
|
||||
const PAGE_SIZE = 500;
|
||||
|
||||
/**
|
||||
* A base dn.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $baseDn = null;
|
||||
|
||||
/**
|
||||
* Adldap instance.
|
||||
*
|
||||
* @var \Adldap\Adldap
|
||||
*/
|
||||
protected $ldap;
|
||||
|
||||
/**
|
||||
* Initialize LDAP from user settings
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// Already initialized
|
||||
if ($this->ldap) {
|
||||
return true;
|
||||
}
|
||||
|
||||
parent::init();
|
||||
if ($this->isLdapEnabled()) {
|
||||
if ($this->ldapSettings['is_ad'] == 0) { //only for NON-AD setups!
|
||||
$this->ldapConfig['account_prefix'] = $this->ldapSettings['ldap_auth_filter_query'];
|
||||
$this->ldapConfig['account_suffix'] = ','.$this->ldapConfig['base_dn'];
|
||||
} /*
|
||||
To the point mentioned in ldapLogin(), we might want to add an 'else' clause here that
|
||||
sets up an 'account_suffix' of '@'.$this->ldapSettings['ad_domain'] *IF* the user has
|
||||
$this->ldapSettings['ad_append_domain'] enabled.
|
||||
That code in ldapLogin gets simplified, in exchange for putting all the weirdness here only.
|
||||
*/
|
||||
$this->ldap = new Adldap();
|
||||
$this->ldap->addProvider($this->ldapConfig);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user if they successfully login to the LDAP server.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*
|
||||
* @return \App\Models\User
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ldapLogin(string $username, string $password): User
|
||||
{
|
||||
if ($this->ldapSettings['ad_append_domain']) { //if you're using 'userprincipalname', don't check the ad_append_domain checkbox
|
||||
$login_username = $username.'@'.$this->ldapSettings['ad_domain']; // I feel like could can be solved with the 'suffix' feature? Then this would be easier.
|
||||
} else {
|
||||
$login_username = $username;
|
||||
}
|
||||
|
||||
if ($this->ldapConfig['username'] && $this->ldapConfig['password']) {
|
||||
$bind_as_user = false;
|
||||
} else {
|
||||
$bind_as_user = true;
|
||||
}
|
||||
|
||||
if (($this->ldap) && ($this->ldap->auth()->attempt($login_username, $password, $bind_as_user) === false)) {
|
||||
throw new Exception('Unable to validate user credentials!');
|
||||
}
|
||||
|
||||
// Should we sync the logged in user
|
||||
Log::debug('Attempting to find user in LDAP directory');
|
||||
$record = $this->ldap->search()->findBy($this->ldapSettings['ldap_username_field'], $username);
|
||||
|
||||
if ($record) {
|
||||
if ($this->isLdapSync($record)) {
|
||||
$this->syncUserLdapLogin($record, $password);
|
||||
}
|
||||
} else {
|
||||
throw new Exception('Unable to find user in LDAP directory!');
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user information based on the LDAP settings.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param \Adldap\Models\User $user
|
||||
* @param null|Collection $defaultLocation
|
||||
* @param null|Collection $mappedLocations
|
||||
*
|
||||
* @return null|\App\Models\User
|
||||
*/
|
||||
public function processUser(AdldapUser $user, ?Collection $defaultLocation = null, ?Collection $mappedLocations = null): ?User
|
||||
{
|
||||
// Only sync active users <- I think this actually means 'existing', not 'activated/deactivated'
|
||||
if (! $user) {
|
||||
return null;
|
||||
}
|
||||
$snipeUser = [];
|
||||
$snipeUser['username'] = $user->{$this->ldapSettings['ldap_username_field']}[0] ?? '';
|
||||
$snipeUser['employee_number'] = $user->{$this->ldapSettings['ldap_emp_num']}[0] ?? '';
|
||||
$snipeUser['lastname'] = $user->{$this->ldapSettings['ldap_lname_field']}[0] ?? '';
|
||||
$snipeUser['firstname'] = $user->{$this->ldapSettings['ldap_fname_field']}[0] ?? '';
|
||||
$snipeUser['email'] = $user->{$this->ldapSettings['ldap_email']}[0] ?? '';
|
||||
$snipeUser['title'] = $user->getTitle() ?? '';
|
||||
$snipeUser['telephonenumber'] = $user->getTelephoneNumber() ?? '';
|
||||
|
||||
/*
|
||||
* $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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the User model information.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param array $userInfo The user info to save to the database
|
||||
*
|
||||
* @return \App\Models\User
|
||||
*/
|
||||
public function setUserModel(array $userInfo): User
|
||||
{
|
||||
// If the username exists, return the user object, otherwise create a new user object
|
||||
$user = User::firstOrNew([
|
||||
'username' => $userInfo['username'],
|
||||
]);
|
||||
$user->username = $user->username ?? trim($userInfo['username']);
|
||||
$user->password = $user->password ?? Helper::generateEncyrptedPassword();
|
||||
$user->first_name = trim($userInfo['firstname']);
|
||||
$user->last_name = trim($userInfo['lastname']);
|
||||
$user->email = trim($userInfo['email']);
|
||||
$user->employee_num = trim($userInfo['employee_number']);
|
||||
$user->jobtitle = trim($userInfo['title']);
|
||||
$user->phone = trim($userInfo['telephonenumber']);
|
||||
if (array_key_exists('activated', $userInfo)) {
|
||||
$user->activated = $userInfo['activated'];
|
||||
} elseif (! $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a user who has logged in by LDAP.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param \Adldap\Models\User $record
|
||||
* @param string $password
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function syncUserLdapLogin(AdldapUser $record, string $password): void
|
||||
{
|
||||
$user = $this->processUser($record);
|
||||
|
||||
if (is_null($user->last_login)) {
|
||||
$user->notes = 'Imported on first login from LDAP2';
|
||||
}
|
||||
|
||||
if ($this->ldapSettings['ldap_pw_sync']) {
|
||||
Log::debug('Syncing users password with LDAP directory.');
|
||||
$user->password = bcrypt($password);
|
||||
}
|
||||
|
||||
if (! $user->save()) {
|
||||
Log::debug('Could not save user. '.$user->getErrors());
|
||||
throw new Exception('Could not save user: '.$user->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if we should sync the user with the LDAP directory.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param \Adldap\Models\User $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isLdapSync(AdldapUser $user): bool
|
||||
{
|
||||
if (! $this->ldapSettings['ldap_active_flag']) {
|
||||
return true; // always sync if you didn't define an 'active' flag
|
||||
}
|
||||
|
||||
if ($user->{$this->ldapSettings['ldap_active_flag']} && // if your LDAP user has the aforementioned flag as an attribute *AND*
|
||||
count($user->{$this->ldapSettings['ldap_active_flag']}) == 1 && // if that attribute has exactly one value *AND*
|
||||
strtolower($user->{$this->ldapSettings['ldap_active_flag']}[0]) == 'false') { // that value is the string 'false' (regardless of case),
|
||||
return false; // then your user is *INACTIVE* - return false
|
||||
}
|
||||
// otherwise, return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param \Adldap\Models\User $user
|
||||
*
|
||||
* @return int (or null)
|
||||
*/
|
||||
private function getActiveStatus(AdldapUser $user): ?int
|
||||
{
|
||||
/*
|
||||
* 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()) && ! $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 {
|
||||
|
||||
//\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']) {
|
||||
\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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default selected location, or a OU mapped location if available.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param \Adldap\Models\User $user
|
||||
* @param Collection|null $defaultLocation
|
||||
* @param Collection|null $mappedLocations
|
||||
*
|
||||
* @return null|int
|
||||
*/
|
||||
private function getLocationId(AdldapUser $user, ?Collection $defaultLocation, ?Collection $mappedLocations): ?int
|
||||
{
|
||||
$locationId = null;
|
||||
// Set the users default locations, if set
|
||||
if ($defaultLocation) {
|
||||
$locationId = $defaultLocation->keys()->first();
|
||||
}
|
||||
|
||||
// Check to see if the user is in a mapped location
|
||||
if ($mappedLocations) {
|
||||
$location = $mappedLocations->filter(function ($value, $key) use ($user) {
|
||||
//if ($user->inOu($value)) { // <----- *THIS* seems not to be working, and it seems more 'intelligent' - but it's literally just a strpos() call, and it doesn't work quite right against plain strings
|
||||
$user_ou = substr($user->getDn(), -strlen($value)); // get the LAST chars of the user's DN, the count of those chars being the length of the thing we're checking against
|
||||
if (strcasecmp($user_ou, $value) === 0) { // case *IN*sensitive comparision - some people say OU=blah, some say ou=blah. returns 0 when strings are identical (which is a little odd, yeah)
|
||||
return $key; // WARNING: we are doing a 'filter' - not a regular for-loop. So the answer(s) get "return"ed into the $location array
|
||||
}
|
||||
});
|
||||
|
||||
if ($location->count() > 0) {
|
||||
$locationId = $location->keys()->first(); // from the returned $location array from the ->filter() method above, we return the first match - there should be only one
|
||||
}
|
||||
}
|
||||
|
||||
return $locationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base dn for the query.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getBaseDn(): string
|
||||
{
|
||||
if (! is_null($this->baseDn)) {
|
||||
return $this->baseDn;
|
||||
}
|
||||
|
||||
return $this->ldapSettings['ldap_basedn'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the ldap filter if needed.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
private function getFilter(): ?string
|
||||
{
|
||||
$filter = $this->ldapSettings['ldap_filter'];
|
||||
if (! $filter) {
|
||||
return null;
|
||||
}
|
||||
// Add surrounding parentheses as needed
|
||||
$paren = mb_substr($filter, 0, 1, 'utf-8');
|
||||
if ('(' !== $paren) {
|
||||
return '('.$filter.')';
|
||||
}
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected fields to return
|
||||
* This should help with memory on large result sets as we are not returning all fields.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getSelectedFields(): array
|
||||
{
|
||||
/** @var Schema $schema */
|
||||
$schema = new $this->ldapConfig['schema'];
|
||||
|
||||
return array_values(array_filter([
|
||||
$this->ldapSettings['ldap_username_field'],
|
||||
$this->ldapSettings['ldap_fname_field'],
|
||||
$this->ldapSettings['ldap_lname_field'],
|
||||
$this->ldapSettings['ldap_email'],
|
||||
$this->ldapSettings['ldap_emp_num'],
|
||||
$this->ldapSettings['ldap_active_flag'],
|
||||
$schema->memberOf(),
|
||||
$schema->userAccountControl(),
|
||||
$schema->title(),
|
||||
$schema->telephone(),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the bind user connection.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
* @throws \Exception
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function testLdapAdBindConnection(): void
|
||||
{
|
||||
try {
|
||||
$this->ldap->search()->ous()->get()->count(); //it's saying this is null?
|
||||
} catch (Exception $th) {
|
||||
Log::error($th->getMessage());
|
||||
throw new Exception('Unable to search LDAP directory!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the user can connect to the LDAP server.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
* @throws \Exception
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function testLdapAdUserConnection(): void
|
||||
{
|
||||
try {
|
||||
$this->ldap->connect();
|
||||
} catch (\Exception $e) {
|
||||
Log::debug('LDAP ERROR: '.$e->getMessage());
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the LDAP configuration by returning up to 10 users.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function testUserImportSync(): Collection
|
||||
{
|
||||
$testUsers = collect($this->getLdapUsers()->getResults())->chunk(10)->first();
|
||||
if ($testUsers) {
|
||||
return $testUsers->map(function ($item) {
|
||||
return (object) [
|
||||
'username' => $item->{$this->ldapSettings['ldap_username_field']}[0] ?? null,
|
||||
'employee_number' => $item->{$this->ldapSettings['ldap_emp_num']}[0] ?? null,
|
||||
'lastname' => $item->{$this->ldapSettings['ldap_lname_field']}[0] ?? null,
|
||||
'firstname' => $item->{$this->ldapSettings['ldap_fname_field']}[0] ?? null,
|
||||
'email' => $item->{$this->ldapSettings['ldap_email']}[0] ?? null,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return collect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the LDAP server to get the users to process and return a page set.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return \Adldap\Query\Paginator
|
||||
*/
|
||||
public function getLdapUsers(): Paginator
|
||||
{
|
||||
$search = $this->ldap->search()->users()->in($this->getBaseDn()); //this looks wrong; we should instead have a passable parameter that does this, and use this as a 'sane' default, yeah?
|
||||
|
||||
$filter = $this->getFilter();
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,311 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Setting;
|
||||
use Exception;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* LDAP configuration merge for Adldap2.
|
||||
*
|
||||
* @see https://github.com/Adldap2/Adldap2
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
class LdapAdConfiguration
|
||||
{
|
||||
const LDAP_PORT = 389;
|
||||
const CONNECTION_TIMEOUT = 5;
|
||||
const DEFAULT_LDAP_VERSION = 3;
|
||||
const LDAP_BOOLEAN_SETTINGS = [
|
||||
'ldap_enabled',
|
||||
'ldap_server_cert_ignore',
|
||||
'ldap_tls',
|
||||
'ldap_tls',
|
||||
'ldap_pw_sync',
|
||||
'is_ad',
|
||||
'ad_append_domain',
|
||||
];
|
||||
|
||||
/**
|
||||
* Ldap Settings.
|
||||
*
|
||||
* @var Collection
|
||||
*/
|
||||
public $ldapSettings;
|
||||
|
||||
/**
|
||||
* LDAP Config.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $ldapConfig;
|
||||
|
||||
/**
|
||||
* Initialize LDAP from user settings
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
|
||||
// This try/catch is dumb, but is necessary to run initial migrations, since
|
||||
// this service provider is booted even during migrations. :( - snipe
|
||||
try {
|
||||
$this->ldapSettings = $this->getSnipeItLdapSettings();
|
||||
if ($this->isLdapEnabled()) {
|
||||
$this->setSnipeItConfig();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
$this->ldapSettings = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the default Adlap config with the SnipeIT config.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function setSnipeItConfig()
|
||||
{
|
||||
$this->ldapConfig = $this->setLdapConnectionConfiguration();
|
||||
$this->certificateCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LDAP settings from the Settings model.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
private function getSnipeItLdapSettings(): Collection
|
||||
{
|
||||
$ldapSettings = collect();
|
||||
if (Setting::first()) { // during early migration steps, there may be no settings table entry to start with
|
||||
$ldapSettings = Setting::getLdapSettings()
|
||||
->map(function ($item, $key) {
|
||||
// Trim the items
|
||||
if (is_string($item)) {
|
||||
$item = trim($item);
|
||||
}
|
||||
// Get the boolean value of the LDAP setting, makes it easier to work with them
|
||||
if (in_array($key, self::LDAP_BOOLEAN_SETTINGS)) {
|
||||
return boolval($item);
|
||||
}
|
||||
|
||||
// Decrypt the admin password
|
||||
if ('ldap_pword' === $key && ! empty($item)) {
|
||||
try {
|
||||
return decrypt($item);
|
||||
} 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 ($item && 'ldap_server' === $key) {
|
||||
return collect(parse_url($item));
|
||||
}
|
||||
|
||||
return $item;
|
||||
});
|
||||
}
|
||||
|
||||
return $ldapSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the server certificate environment variable.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function certificateCheck(): void
|
||||
{
|
||||
// If we are ignoring the SSL cert we need to setup the environment variable
|
||||
// before we create the connection
|
||||
if ($this->ldapSettings['ldap_server_cert_ignore']) {
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Adlap2 connection configuration values based on SnipeIT settings.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function setLdapConnectionConfiguration(): array
|
||||
{
|
||||
// Create the configuration array.
|
||||
$ldap_settings = [
|
||||
// Mandatory Configuration Options
|
||||
'hosts' => $this->getServerUrlBase(),
|
||||
'base_dn' => $this->ldapSettings['ldap_basedn'],
|
||||
'username' => $this->ldapSettings['ldap_uname'],
|
||||
'password' => $this->ldapSettings['ldap_pword'],
|
||||
|
||||
// Optional Configuration Options
|
||||
'schema' => $this->getSchema(), // FIXME - we probably ought not to be using this, right?
|
||||
'account_prefix' => '',
|
||||
'account_suffix' => '',
|
||||
'port' => $this->getPort(),
|
||||
'follow_referrals' => false,
|
||||
'use_ssl' => $this->isSsl(),
|
||||
'use_tls' => $this->ldapSettings['ldap_tls'],
|
||||
'version' => $this->ldapSettings['ldap_version'] ?? self::DEFAULT_LDAP_VERSION,
|
||||
'timeout' => self::CONNECTION_TIMEOUT,
|
||||
|
||||
// Custom LDAP Options
|
||||
'custom_options' => [
|
||||
// See: http://php.net/ldap_set_option
|
||||
// LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
|
||||
],
|
||||
];
|
||||
|
||||
if($this->ldapSettings['ldap_client_tls_cert'] || $this->ldapSettings['ldap_client_tls_key']) {
|
||||
$ldap_settings['custom_options'] = [
|
||||
LDAP_OPT_X_TLS_CERTFILE => Setting::get_client_side_cert_path(),
|
||||
LDAP_OPT_X_TLS_KEYFILE => Setting::get_client_side_key_path()
|
||||
];
|
||||
}
|
||||
return $ldap_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema to use for the connection.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getSchema(): string //wait, what? This is a little weird, since we have completely separate variables for this; we probably shoulnd't be using any 'schema' at all
|
||||
{
|
||||
$schema = \Adldap\Schemas\OpenLDAP::class;
|
||||
if ($this->ldapSettings['is_ad']) {
|
||||
$schema = \Adldap\Schemas\ActiveDirectory::class;
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port number from the connection url.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getPort(): int
|
||||
{
|
||||
$port = $this->getLdapServerData('port');
|
||||
if ($port && is_int($port)) {
|
||||
return $port;
|
||||
}
|
||||
|
||||
return self::LDAP_PORT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ldap scheme from url to determin ssl use.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isSsl(): bool
|
||||
{
|
||||
$scheme = $this->getLdapServerData('scheme');
|
||||
if ($scheme && 'ldaps' === strtolower($scheme)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base url to the LDAP server.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getServerUrlBase(): array
|
||||
{
|
||||
/* if ($this->ldapSettings['is_ad']) {
|
||||
return collect(explode(',', $this->ldapSettings['ad_domain']))->map(function ($item) {
|
||||
return trim($item);
|
||||
})->toArray();
|
||||
} */ // <- this was the *original* intent of the PR for AdLdap2, but we've been moving away from having
|
||||
// two separate fields - one for "ldap_host" and one for "ad_domain" - towards just using "ldap_host"
|
||||
// ad_domain for us just means "append this domain to your usernames for login, if you click that checkbox"
|
||||
// that's all, nothing more (I hope).
|
||||
|
||||
$url = $this->getLdapServerData('host');
|
||||
|
||||
return $url ? [$url] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ldap enabled setting
|
||||
*
|
||||
* @author Steffen Buehl <sb@sbuehl.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLdapEnabled(): bool
|
||||
{
|
||||
return $this->ldapSettings && $this->ldapSettings->get('ldap_enabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parsed ldap server information
|
||||
*
|
||||
* @author Steffen Buehl <sb@sbuehl.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param $key
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function getLdapServerData($key)
|
||||
{
|
||||
if ($this->ldapSettings) {
|
||||
$ldapServer = $this->ldapSettings->get('ldap_server');
|
||||
if ($ldapServer && $ldapServer instanceof Collection) {
|
||||
return $ldapServer->get($key);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-pdo": "*",
|
||||
"adldap2/adldap2": "^10.2",
|
||||
"alek13/slack": "^2.0",
|
||||
"bacon/bacon-qr-code": "^2.0",
|
||||
"barryvdh/laravel-debugbar": "^3.6",
|
||||
|
|
661
composer.lock
generated
661
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -250,8 +250,21 @@ return [
|
|||
|
||||
'enable_csp' => env('ENABLE_CSP', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Require SAML Login
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Disable the ability to login via form login, and disables the 'nosaml'
|
||||
| workaround. It requires all logins to process via SAML login.
|
||||
| (This is for high security setups. If your SAML configuration is not
|
||||
| working, this option should be set to false. This option is not needed
|
||||
| to successfully configure SAML authentication.)
|
||||
|
|
||||
*/
|
||||
|
||||
|
||||
'require_saml' => env('REQUIRE_SAML', false),
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -345,7 +358,6 @@ return [
|
|||
* Custom service provider
|
||||
*/
|
||||
App\Providers\MacroServiceProvider::class,
|
||||
App\Providers\LdapServiceProvider::class,
|
||||
App\Providers\SamlServiceProvider::class,
|
||||
|
||||
],
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
<?php
|
||||
|
||||
$allowed_origins = env('CORS_ALLOWED_ORIGINS') !== null ?
|
||||
explode(',', env('CORS_ALLOWED_ORIGINS')) : [];
|
||||
|
||||
|
||||
return [
|
||||
|
||||
'supportsCredentials' => false,
|
||||
|
||||
'allowedOrigins' => ['*'],
|
||||
'allowedOrigins' => $allowed_origins,
|
||||
|
||||
'allowedHeaders' => ['*'],
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<?php
|
||||
return array (
|
||||
'app_version' => 'v6-pre-alpha',
|
||||
'full_app_version' => 'v6-pre-alpha - build 6506-ge75a5f13e',
|
||||
'build_version' => '6506',
|
||||
'app_version' => 'v6.0.0-RC3',
|
||||
'full_app_version' => 'v6.0.0-RC3 - build 6627-g2815e0d36',
|
||||
'build_version' => '6627',
|
||||
'prerelease_version' => '',
|
||||
'hash_version' => 'ge75a5f13e',
|
||||
'full_hash' => 'v6-pre-alpha-13-ge75a5f13e',
|
||||
'hash_version' => 'g2815e0d36',
|
||||
'full_hash' => 'v6.0.0-RC3-6-g2815e0d36',
|
||||
'branch' => 'develop',
|
||||
);
|
|
@ -39,7 +39,6 @@ class ActionlogFactory extends Factory
|
|||
'item_type' => get_class($asset),
|
||||
'item_id' => 1,
|
||||
'user_id' => 1,
|
||||
'filename' => $this->faker->word,
|
||||
'action_type' => 'uploaded',
|
||||
];
|
||||
}
|
||||
|
@ -49,7 +48,7 @@ class ActionlogFactory extends Factory
|
|||
{
|
||||
return $this->state(function () {
|
||||
$target = \App\Models\User::inRandomOrder()->first();
|
||||
$item = \App\Models\Asset::inRandomOrder()->RTD()->first();
|
||||
$item = \App\Models\Asset::RTD()->inRandomOrder()->first();
|
||||
$user_id = rand(1, 2); // keep it simple - make it one of the two superadmins
|
||||
$asset = Asset::where('id', $item->id)
|
||||
->update(
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Category;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/*
|
||||
|
@ -37,18 +38,22 @@ class LicenseFactory extends Factory
|
|||
*/
|
||||
public function definition()
|
||||
{
|
||||
|
||||
|
||||
return [
|
||||
'user_id' => 1,
|
||||
'license_name' => $this->faker->name,
|
||||
'name' => $this->faker->name,
|
||||
'license_email' => $this->faker->safeEmail,
|
||||
'serial' => $this->faker->uuid,
|
||||
'notes' => 'Created by DB seeder',
|
||||
'seats' => $this->faker->numberBetween(1, 10),
|
||||
'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get()),
|
||||
'order_number' => $this->faker->numberBetween(1000000, 50000000),
|
||||
'expiration_date' => $this->faker->dateTimeBetween('now', '+3 years', date_default_timezone_get())->format('Y-m-d H:i:s'),
|
||||
'reassignable' => $this->faker->boolean(),
|
||||
'termination_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d H:i:s'),
|
||||
'supplier_id' => $this->faker->numberBetween(1, 5),
|
||||
'category_id' => Category::where('category_type', '=', 'license')->inRandomOrder()->first()->id
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class SettingFactory extends Factory
|
|||
'alerts_enabled' => true,
|
||||
'brand' => 1,
|
||||
'default_currency' => $this->faker->currencyCode,
|
||||
'locale' => $this->faker->locale,
|
||||
'locale' => 'en',
|
||||
'pwd_secure_min' => 10, // Match web setup
|
||||
'email_domain' => 'test.com',
|
||||
];
|
||||
|
|
|
@ -26,7 +26,7 @@ class UserFactory extends Factory
|
|||
'first_name' => $this->faker->firstName(),
|
||||
'jobtitle' => $this->faker->jobTitle(),
|
||||
'last_name' => $this->faker->lastName(),
|
||||
'locale' => $this->faker->locale(),
|
||||
'locale' => 'en',
|
||||
'notes' => 'Created by DB seeder',
|
||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
||||
'permissions' => '{"user":"0"}',
|
||||
|
|
|
@ -6,30 +6,15 @@ class CreateTempLicensesTable extends Migration
|
|||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* This migration is overwritten by a later migration - 2013_11_25_recreate_licenses_table.php
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
if (!Schema::hasTable('licenses')) {
|
||||
Schema::create('licenses', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->string('name');
|
||||
$table->integer('model_id');
|
||||
$table->text('serial');
|
||||
$table->string('license_email');
|
||||
$table->string('license_name');
|
||||
$table->date('purchase_date')->nullable();
|
||||
$table->decimal('purchase_cost', 8, 2)->nullable();
|
||||
$table->string('order_number');
|
||||
$table->integer('assigned_to');
|
||||
$table->text('notes');
|
||||
$table->integer('user_id')->nullable();
|
||||
$table->timestamps();
|
||||
$table->engine = 'InnoDB';
|
||||
});
|
||||
}
|
||||
//
|
||||
|
||||
|
||||
}
|
||||
|
@ -41,6 +26,6 @@ class CreateTempLicensesTable extends Migration
|
|||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('licenses');
|
||||
// Schema::dropIfExists('licenses');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,22 +11,23 @@ class ReCreateLicensesTable extends Migration
|
|||
*/
|
||||
public function up()
|
||||
{
|
||||
//
|
||||
Schema::create('licenses', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->string('name');
|
||||
$table->string('serial');
|
||||
$table->date('purchase_date')->nullable();
|
||||
$table->decimal('purchase_cost', 8, 2)->nullable();
|
||||
$table->string('order_number');
|
||||
$table->integer('seats');
|
||||
$table->text('notes');
|
||||
$table->integer('user_id')->nullable();
|
||||
$table->integer('depreciation_id');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->engine = 'InnoDB';
|
||||
});
|
||||
if (!Schema::hasTable('licenses')) {
|
||||
Schema::create('licenses', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->string('name');
|
||||
$table->string('serial');
|
||||
$table->date('purchase_date')->nullable();
|
||||
$table->decimal('purchase_cost', 8, 2)->nullable();
|
||||
$table->string('order_number');
|
||||
$table->integer('seats');
|
||||
$table->text('notes');
|
||||
$table->integer('user_id')->nullable();
|
||||
$table->integer('depreciation_id');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->engine = 'InnoDB';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,11 +21,11 @@ class UpdateGroupFieldForReporting extends Migration
|
|||
// or a fresh install, we have to check which table is being used.
|
||||
|
||||
if (Schema::hasTable('permission_groups')) {
|
||||
Group::where('id', 1)->update(['permissions' => '{"users-poop":1,"reports":1}']);
|
||||
Group::where('id', 2)->update(['permissions' => '{"users-pop":1,"reports":1}']);
|
||||
Group::where('id', 1)->update(['permissions' => '{"users-foo":1,"reports":1}']);
|
||||
Group::where('id', 2)->update(['permissions' => '{"users-foo":1,"reports":1}']);
|
||||
} elseif (Schema::hasTable('groups')) {
|
||||
DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"admin-farts":1,"users":1,"reports":1}', 1]);
|
||||
DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"users-farts":1,"reports":1}', 2]);
|
||||
DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"admin-foo":1,"users":1,"reports":1}', 1]);
|
||||
DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"users-foo":1,"reports":1}', 2]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class AddLdapFieldsToSettings extends Migration
|
|||
$table->string('ldap_username_field')->nullable()->default('samaccountname');
|
||||
$table->string('ldap_lname_field')->nullable()->default('sn');
|
||||
$table->string('ldap_fname_field')->nullable()->default('givenname');
|
||||
$table->string('ldap_auth_filter_query')->nullable()->default('uid=samaccountname');
|
||||
$table->string('ldap_auth_filter_query')->nullable()->default('uid=');
|
||||
$table->integer('ldap_version')->nullable()->default(3);
|
||||
$table->string('ldap_active_flag')->nullable()->default(null);
|
||||
$table->string('ldap_emp_num')->nullable()->default(null);
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ChangeSupplierAddressLength extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('suppliers', function (Blueprint $table) {
|
||||
//
|
||||
$table->string('address', 250)->nullable()->change();
|
||||
$table->string('address2', 250)->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('suppliers', function (Blueprint $table) {
|
||||
//
|
||||
$table->text('address', 50)->nullable()->default(null)->change();
|
||||
$table->text('address2', 50)->nullable()->default(null)->change();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddLicenseIdIndexToLicenseSeats extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('license_seats', function (Blueprint $table) {
|
||||
$table->index(['license_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('license_seats', function (Blueprint $table) {
|
||||
$table->dropIndex(['license_id']);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use App\Models\Setting;
|
||||
|
||||
class BlankOutLdapActiveFlag extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if ($s = Setting::getSettings()) {
|
||||
$s->ldap_active_flag = '';
|
||||
$s->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddUniqueConstraintToCustomField extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('custom_fields', function (Blueprint $table) {
|
||||
$table->boolean('is_unique')->nullable()->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('custom_fields', function (Blueprint $table) {
|
||||
$table->dropColumn('is_unique');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\AssetModel;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class AssetModelSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
AssetModel::truncate();
|
||||
|
||||
// Laptops
|
||||
factory(AssetModel::class, 1)->states('mbp-13-model')->create(); // 1
|
||||
factory(AssetModel::class, 1)->states('mbp-air-model')->create(); // 2
|
||||
factory(AssetModel::class, 1)->states('surface-model')->create(); // 3
|
||||
factory(AssetModel::class, 1)->states('xps13-model')->create(); // 4
|
||||
factory(AssetModel::class, 1)->states('spectre-model')->create(); // 5
|
||||
factory(AssetModel::class, 1)->states('zenbook-model')->create(); // 6
|
||||
factory(AssetModel::class, 1)->states('yoga-model')->create(); // 7
|
||||
|
||||
// Desktops
|
||||
factory(AssetModel::class, 1)->states('macpro-model')->create(); // 8
|
||||
factory(AssetModel::class, 1)->states('lenovo-i5-model')->create(); // 9
|
||||
factory(AssetModel::class, 1)->states('optiplex-model')->create(); // 10
|
||||
|
||||
// Conference Phones
|
||||
factory(AssetModel::class, 1)->states('polycom-model')->create(); // 11
|
||||
factory(AssetModel::class, 1)->states('polycomcx-model')->create(); // 12
|
||||
|
||||
// Tablets
|
||||
factory(AssetModel::class, 1)->states('ipad-model')->create(); // 13
|
||||
factory(AssetModel::class, 1)->states('tab3-model')->create(); // 14
|
||||
|
||||
// Phones
|
||||
factory(AssetModel::class, 1)->states('iphone11-model')->create(); // 15
|
||||
factory(AssetModel::class, 1)->states('iphone12-model')->create(); // 16
|
||||
|
||||
// Displays
|
||||
factory(AssetModel::class, 1)->states('ultrafine')->create(); // 17
|
||||
factory(AssetModel::class, 1)->states('ultrasharp')->create(); // 18
|
||||
|
||||
$src = public_path('/img/demo/models/');
|
||||
$dst = 'models'.'/';
|
||||
$del_files = Storage::files($dst);
|
||||
|
||||
foreach($del_files as $del_file){ // iterate files
|
||||
$file_to_delete = str_replace($src,'',$del_file);
|
||||
\Log::debug('Deleting: '.$file_to_delete);
|
||||
try {
|
||||
Storage::disk('public')->delete($dst.$del_file);
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$add_files = glob($src."/*.*");
|
||||
foreach($add_files as $add_file){
|
||||
$file_to_copy = str_replace($src,'',$add_file);
|
||||
\Log::debug('Copying: '.$file_to_copy);
|
||||
try {
|
||||
Storage::disk('public')->put($dst.$file_to_copy, file_get_contents($src.$file_to_copy));
|
||||
} catch (\Exception $e) {
|
||||
\Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
2
docker/column-statistics.cnf
Normal file
2
docker/column-statistics.cnf
Normal file
|
@ -0,0 +1,2 @@
|
|||
[mysqldump]
|
||||
column-statistics=0
|
40581
package-lock.json
generated
40581
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -17,14 +17,16 @@
|
|||
"axios": "^0.20.0",
|
||||
"babel-preset-latest": "^6.24.1",
|
||||
"jquery": "^3.6.0",
|
||||
"laravel-mix": "^6.0.12",
|
||||
"laravel-mix": "^6.0.39",
|
||||
"lodash": "^4.17.20",
|
||||
"postcss": "^8.2.8",
|
||||
"postcss": "^8.4.5",
|
||||
"vue": "2.4.4",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-template-compiler": "2.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": "^8.6.0",
|
||||
"acorn-import-assertions": "^1.8.0",
|
||||
"admin-lte": "^2.4.18",
|
||||
"ajv": "^6.12.6",
|
||||
"blueimp-file-upload": "^9.34.0",
|
||||
|
@ -44,14 +46,16 @@
|
|||
"jquery-ui": "^1.13.0",
|
||||
"jquery-ui-bundle": "^1.12.1",
|
||||
"jquery.iframe-transport": "^1.0.0",
|
||||
"less": "^4.1.1",
|
||||
"jspdf-autotable": "^3.5.23",
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^5.0.0",
|
||||
"list.js": "^1.5.0",
|
||||
"papaparse": "^4.3.3",
|
||||
"select2": "4.0.13",
|
||||
"sheetjs": "^2.0.0",
|
||||
"tableexport.jquery.plugin": "^1.10.26",
|
||||
"tableexport.jquery.plugin": "^1.21.0",
|
||||
"tether": "^1.4.0",
|
||||
"vue-resource": "^1.5.2"
|
||||
"vue-resource": "^1.5.2",
|
||||
"webpack": "^5.65.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
RewriteEngine On
|
||||
|
||||
# Needed for https://letsencrypt.org/ certificates.
|
||||
RewriteRule ^\.well-known/acme-challenge/ - [END]
|
||||
RewriteRule ^\.well-known/acme-challenge/ - [L]
|
||||
|
||||
# Uncomment these two lines to force SSL redirect in Apache
|
||||
# RewriteCond %{HTTPS} off
|
||||
|
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue