Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
caf89ff4e5 | ||
|
e084621082 | ||
|
181cc4ca20 | ||
|
a8f534b463 | ||
|
b140dbb1b6 | ||
|
2e134ea609 | ||
|
715f7efe9b | ||
|
5ba2cf67e9 |
@ -1,14 +0,0 @@
|
||||
.dockerignore
|
||||
.editorconfig
|
||||
.env.testing
|
||||
.idea
|
||||
.git*
|
||||
.phpunit.result.cache
|
||||
.styleci.yml
|
||||
node_modules/
|
||||
package.json
|
||||
package-lock.json
|
||||
phpunit.xml
|
||||
vendor/
|
||||
webpack.mix.js
|
||||
yarn.lock
|
@ -1,18 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
18
.env.example
18
.env.example
@ -1,18 +0,0 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=production
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
|
||||
LOG_CHANNEL=daily
|
||||
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
LDAP_HOST=
|
||||
LDAP_BASE_DN=
|
||||
LDAP_USERNAME=
|
||||
LDAP_PASSWORD=
|
||||
LDAP_CACHE=true
|
51
.env.testing
51
.env.testing
@ -1,51 +0,0 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=dev
|
||||
APP_KEY=base64:KvIecx8zoy6RjcbJM8s98ZKs9IDGUHFVqBRn3Awfmso=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=homestead
|
||||
DB_USERNAME=homestead
|
||||
DB_PASSWORD=secret
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
LDAP_HOST=test_ldap
|
||||
LDAP_PORT=389
|
||||
LDAP_BASE_DN="dc=Test"
|
||||
LDAP_USERNAME="cn=admin,dc=Test"
|
||||
LDAP_PASSWORD="test"
|
||||
LDAP_CACHE=false
|
5
.gitattributes
vendored
5
.gitattributes
vendored
@ -1,5 +0,0 @@
|
||||
* text=auto
|
||||
*.css linguist-vendored
|
||||
*.scss linguist-vendored
|
||||
*.js linguist-vendored
|
||||
CHANGELOG.md export-ignore
|
@ -1,202 +0,0 @@
|
||||
name: Create Docker Image
|
||||
run-name: ${{ gitea.actor }} Building Docker Image 🐳
|
||||
on: [push]
|
||||
env:
|
||||
DOCKER_HOST: tcp://127.0.0.1:2375
|
||||
ASSETS: 509b1a1
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
# arm64
|
||||
|
||||
name: Test Application
|
||||
runs-on: docker-${{ matrix.arch }}
|
||||
container:
|
||||
image: docker:dind
|
||||
privileged: true
|
||||
|
||||
steps:
|
||||
- name: Environment Setup
|
||||
run: |
|
||||
# If we have a proxy use it
|
||||
if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
|
||||
# Some pre-reqs
|
||||
apk add git nodejs npm tar zstd
|
||||
## Some debugging info
|
||||
# env|sort
|
||||
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build Assets
|
||||
run: |
|
||||
# Build assets
|
||||
npm i
|
||||
npm run prod
|
||||
|
||||
# - name: Run Tests
|
||||
# run: |
|
||||
# mv .env.testing .env
|
||||
# # Install Composer and project dependencies.
|
||||
# mkdir -p ${COMPOSER_HOME}
|
||||
# if [ -n "${{ secrets.COMPOSER_GITHUB_TOKEN }}" ]; then composer config github-oauth.github.com ${{ secrets.COMPOSER_GITHUB_TOKEN }}; fi
|
||||
# composer install
|
||||
# # Generate an application key. Re-cache.
|
||||
# php artisan key:generate
|
||||
# php artisan migrate
|
||||
# php artisan db:seed
|
||||
# # run laravel tests
|
||||
# # XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-text --colors=never
|
||||
|
||||
- name: Cache page assets
|
||||
id: cache-page-assets
|
||||
uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: page-assets
|
||||
with:
|
||||
path: |
|
||||
public/css/app.css
|
||||
public/fonts
|
||||
public/images
|
||||
public/js/app.js
|
||||
public/js/manifest.js
|
||||
public/js/vendor.js
|
||||
#key: build-pla-page-assets-${{ hashFiles('**/package-lock.json') }}
|
||||
key: build-pla-page-assets-${{ env.ASSETS }}
|
||||
#restore-keys: |
|
||||
# build-pla-page-assets-
|
||||
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- arm64
|
||||
needs: [test]
|
||||
|
||||
name: Build Docker Image
|
||||
runs-on: docker-${{ matrix.arch }}
|
||||
container:
|
||||
image: docker:dind
|
||||
privileged: true
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
|
||||
steps:
|
||||
- name: Environment Setup
|
||||
run: |
|
||||
# If we have a proxy use it
|
||||
if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
|
||||
# Some pre-reqs
|
||||
apk add git curl nodejs npm tar zstd
|
||||
# Start docker
|
||||
( dockerd --host=tcp://0.0.0.0:2375 --tls=false & ) && sleep 3
|
||||
## Some debugging info
|
||||
# docker info && docker version
|
||||
# env|sort
|
||||
|
||||
- name: Registry FQDN Setup
|
||||
id: registry
|
||||
run: |
|
||||
registry=${{ github.server_url }}
|
||||
echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Container Registry Login
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ steps.registry.outputs.registry }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.PKG_WRITE_TOKEN }}
|
||||
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache page assets
|
||||
id: cache-page-assets
|
||||
uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: page-assets
|
||||
with:
|
||||
path: |
|
||||
public/css/app.css
|
||||
public/fonts
|
||||
public/images
|
||||
public/js/app.js
|
||||
public/js/manifest.js
|
||||
public/js/vendor.js
|
||||
#key: build-pla-page-assets-${{ hashFiles('**/package-lock.json') }}
|
||||
key: build-pla-page-assets-${{ env.ASSETS }}
|
||||
#restore-keys: |
|
||||
# build-pla-page-assets-
|
||||
|
||||
- if: ${{ steps.cache-page-assets.outputs.cache-hit != 'true' }}
|
||||
name: List the state of page assets
|
||||
continue-on-error: false
|
||||
run: |
|
||||
echo CACHE-MISS:${{ steps.cache-page-assets.outputs.cache-hit }}
|
||||
ls -al public/css/
|
||||
ls -al public/js/
|
||||
|
||||
- name: Record version and Delete Unnecessary files
|
||||
id: prebuild
|
||||
run: |
|
||||
echo ${GITHUB_SHA::8} > VERSION
|
||||
# [ "${GITHUB_REF_TYPE}" -eq "tag" ] && echo v${GITHUB_REF_NAME}-rel > public/VERSION
|
||||
rm -rf .git* tests/ storage/app/test/
|
||||
cat VERSION public/VERSION
|
||||
# ls -al public/css/
|
||||
# ls -al public/js/
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
push: true
|
||||
tags: "${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.GITHUB_REF_NAME }}-${{ env.ARCH }}"
|
||||
build-args: |
|
||||
BUILD_REVISION=${{ env.GITHUB_SHA }}
|
||||
BUILD_VERSION=v${{ env.GITHUB_REF_NAME }}
|
||||
|
||||
manifest:
|
||||
name: Final Docker Image Manifest
|
||||
runs-on: docker-x86_64
|
||||
container:
|
||||
image: docker:dind
|
||||
privileged: true
|
||||
needs: [build]
|
||||
|
||||
steps:
|
||||
- name: Environment Setup
|
||||
run: |
|
||||
# If we have a proxy use it
|
||||
if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
|
||||
# Some pre-reqs
|
||||
apk add git curl nodejs
|
||||
# Start docker
|
||||
( dockerd --host=tcp://0.0.0.0:2375 --tls=false & ) && sleep 3
|
||||
|
||||
- name: Registry FQDN Setup
|
||||
id: registry
|
||||
run: |
|
||||
registry=${{ github.server_url }}
|
||||
echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Container Registry Login
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ steps.registry.outputs.registry }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.PKG_WRITE_TOKEN }}
|
||||
|
||||
- name: Build Docker Manifest
|
||||
run: |
|
||||
docker manifest create ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.GITHUB_REF_NAME }} \
|
||||
${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.GITHUB_REF_NAME }}-x86_64 \
|
||||
${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.GITHUB_REF_NAME }}-arm64
|
||||
docker manifest push --purge ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.GITHUB_REF_NAME }}
|
||||
echo "Built container: ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.GITHUB_REF_NAME }}"
|
13
.github/FUNDING.yml
vendored
13
.github/FUNDING.yml
vendored
@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [leenooks]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://www.buymeacoffee.com/dege']
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,38 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is. (One issue per report please.)
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem. Also include any logs or backtrace information
|
||||
|
||||
**LDAP Server details (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Server Name [e.g. OpenLDAP, Windows AD,...]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
22
.gitignore
vendored
22
.gitignore
vendored
@ -1,19 +1,3 @@
|
||||
/node_modules
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
public/css/app.css
|
||||
public/js/app.js
|
||||
public/js/vendor.js
|
||||
public/js/manifest.js
|
||||
public/fonts/vendor
|
||||
public/images/vendor
|
||||
public/mix-manifest.json
|
||||
config/config.php
|
||||
queries/custom_*
|
||||
templates/*/custom_*
|
||||
|
21
.htaccess
Normal file
21
.htaccess
Normal file
@ -0,0 +1,21 @@
|
||||
# Turn on URL rewriting
|
||||
RewriteEngine On
|
||||
|
||||
# Installation directory
|
||||
RewriteBase /pla
|
||||
|
||||
# Protect hidden files from being viewed
|
||||
<Files .*>
|
||||
Order Deny,Allow
|
||||
Deny From All
|
||||
</Files>
|
||||
|
||||
# Protect application and system files from being viewed
|
||||
RewriteRule ^(?:application|modules|includes/kohana)\b.* index.php/$0 [L]
|
||||
|
||||
# Allow any files or directories that exist to be displayed directly
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
||||
# Rewrite all other URLs to index.php/URL
|
||||
RewriteRule .* index.php/$0 [PT]
|
23
INSTALL
Normal file
23
INSTALL
Normal file
@ -0,0 +1,23 @@
|
||||
For install instructions in non-English languages, see the wiki:
|
||||
http://phpldapadmin.sourceforge.net
|
||||
|
||||
* Requirements
|
||||
|
||||
phpLDAPadmin requires the following:
|
||||
a. A web server (Apache, IIS, etc).
|
||||
b. PHP 5.0.0 or newer (with LDAP support)
|
||||
|
||||
* To install
|
||||
|
||||
1. Unpack the archive (if you're reading this, you already did that).
|
||||
2. Put the resulting 'phpldapadmin' directory somewhere in your webroot.
|
||||
3. Copy 'config.php.example' to 'config.php' and edit to taste (this is in the config/ directory).
|
||||
4. Then, point your browser to the phpldapadmin directory.
|
||||
|
||||
* For additional help
|
||||
|
||||
See the wiki:
|
||||
http://phpldapadmin.sourceforge.net
|
||||
|
||||
Join our mailing list:
|
||||
https://lists.sourceforge.net/lists/listinfo/phpldapadmin-devel
|
90
README.md
90
README.md
@ -1,90 +0,0 @@
|
||||
# phpLDAPadmin
|
||||
phpLDAPadmin is a web based LDAP data management tool for system administrators. It is commonly known and referred by many as "PLA".
|
||||
|
||||
PLA is designed to be compliant with LDAP RFCs, enabling it to be used with any LDAP server.
|
||||
If you come across an LDAP server, where PLA exhibits problems, please open an issue with full details of the problem so that we can have it fixed.
|
||||
|
||||
For up to date information on PLA, please head to the [wiki](https://github.com/leenooks/phpLDAPadmin/wiki).
|
||||
|
||||
> **NOTE**
|
||||
> PLA v2 is a complete rewrite of PLA.
|
||||
>
|
||||
> PLA v1.2 was written well over 10 years ago for PHP 5, and over time has been patched to work with later versions of PHP. There are logged vulnerabilities with v1.2 that have not been addressed.
|
||||
>
|
||||
> Not all PLA v1.2 functionality has been included in v2 (yet) - see below for details
|
||||
>
|
||||
> **The release of PHP v2 officially deprecates v1.2, which is no longer supported or enhanced/fixed.** It is recommended to upgrade to v2.
|
||||
|
||||
## Demo
|
||||
If you havent seen PLA in action, you can head here to the [demo](https://demo.phpldapadmin.org) site.
|
||||
|
||||
## Running PLA
|
||||
PLA v2 is now available as a docker container. You can also download the code and install it yourself on your PHP server, or even build your own docker container.
|
||||
|
||||
Take a look at the [Docker Container](https://github.com/leenooks/phpLDAPadmin/wiki/Docker-Container) page for more details.
|
||||
|
||||
> If you come across any bugs/issues, it would be helpful if you could reproduce those issues using the docker container (or the demo website). This should help confirm that there isnt a site related issue with the issue you are having.
|
||||
>
|
||||
> Open an issue (details below) with enough information for me to be able to recreate the problem. An `LDIF` will be invaluable if it is not handling data correctly.
|
||||
|
||||
## Version 2 Progress
|
||||
|
||||
The update to v2 is progressing well - here is a list of work to do and done:
|
||||
|
||||
- [X] Creating new LDAP entries
|
||||
- [X] Delete existing LDAP entries
|
||||
- [X] Updating existing LDAP Entries
|
||||
- [X] Password attributes
|
||||
- [X] Support different password hash options
|
||||
- [X] Validate password is correct
|
||||
- [ ] JpegPhoto Create/Delete
|
||||
- [X] JpegPhoto Display
|
||||
- [X] ObjectClass Add/Remove
|
||||
- [X] Add additional required attributes (for ObjectClass Addition)
|
||||
- [ ] Remove existing required attributes (for ObjectClass Removal)
|
||||
- [X] Add additional values to Attributes that support multiple values
|
||||
- [X] Delete extra values for Attributes that support multiple values
|
||||
- [ ] Delete Attributes
|
||||
- [ ] Templates to enable entries to conform to a custom standard
|
||||
- [X] Login to LDAP server
|
||||
- [X] Configure login by a specific attribute
|
||||
- [X] Logout LDAP server
|
||||
- [X] Export entries as an LDAP
|
||||
- [X] Import LDIF
|
||||
- [X] Schema Browser
|
||||
- [ ] Searching
|
||||
- [ ] Is there something missing?
|
||||
|
||||
Support is known for these LDAP servers:
|
||||
- [X] OpenLDAP
|
||||
- [X] OpenDJ
|
||||
- [ ] Microsoft Active Directory
|
||||
|
||||
If there is an LDAP server that you have that you would like to have supported, please open an issue to request it.
|
||||
You might need to provide access, provide a copy or instructions to get an environment for testing. If you have enabled
|
||||
support for an LDAP server not listed above, please provide a pull request for consideration.
|
||||
|
||||
## Getting Help
|
||||
The best place to get help with PLA (new and old) is on [Stack Overflow](https://stackoverflow.com/tags/phpldapadmin/info).
|
||||
|
||||
## Found a bug?
|
||||
If you have found a bug, and can provide detailed instructions so that it can be reproduced, please open an [issue](https://github.com/leenooks/phpLDAPadmin/issues) and provide those details.
|
||||
|
||||
Before opening a ticket, please check to see if it hasnt already been reported, and if it has, please provide any additional information that will help it be fixed.
|
||||
|
||||
*TIP*: Issues opened with:
|
||||
|
||||
* details enabling the problem to be reproduced,
|
||||
* including (if appropriate) an LDIF with the data that exhibits the problem,
|
||||
* a patch (or a git pull request) to fix the problem
|
||||
|
||||
will be looked at first :)
|
||||
|
||||
## THANK YOU
|
||||
Over the years, many, many, many people have supported PLA with either their time, their coding or with financial donations.
|
||||
I have tried to send an email to acknowledge each contribution, and if you havent seen anything personally from me, I am sorry, but please know that I do appreciate all the help I get, in whatever form it is provided.
|
||||
|
||||
Again, Thank You.
|
||||
|
||||
## License
|
||||
[LICENSE](LICENSE)
|
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
q*
|
||||
!public/
|
||||
!.gitignore
|
@ -1,315 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Schema\AttributeType;
|
||||
|
||||
/**
|
||||
* Represents an attribute of an LDAP Object
|
||||
*/
|
||||
class Attribute implements \Countable, \ArrayAccess, \Iterator
|
||||
{
|
||||
// Attribute Name
|
||||
protected string $name;
|
||||
private int $counter = 0;
|
||||
|
||||
protected ?AttributeType $schema = NULL;
|
||||
|
||||
/*
|
||||
# Source of this attribute definition
|
||||
protected $source;
|
||||
*/
|
||||
|
||||
// Current and Old Values
|
||||
protected Collection $values;
|
||||
|
||||
// Is this attribute an internal attribute
|
||||
protected bool $is_internal = FALSE;
|
||||
|
||||
// Is this attribute the RDN?
|
||||
protected bool $is_rdn = FALSE;
|
||||
|
||||
// MIN/MAX number of values
|
||||
protected int $min_values_count = 0;
|
||||
protected int $max_values_count = 0;
|
||||
|
||||
// RFC3866 Language Tags
|
||||
protected Collection $lang_tags;
|
||||
|
||||
// The old values for this attribute - helps with isDirty() to determine if there is an update pending
|
||||
protected Collection $oldValues;
|
||||
|
||||
/*
|
||||
# Has the attribute been modified
|
||||
protected $modified = false;
|
||||
# Is the attribute being deleted because of an object class removal
|
||||
protected $forcedelete = false;
|
||||
# Is the attribute visible
|
||||
protected $visible = false;
|
||||
protected $forcehide = false;
|
||||
# Is the attribute modifiable
|
||||
protected $readonly = false;
|
||||
# LDAP attribute type MUST/MAY
|
||||
protected $ldaptype = null;
|
||||
# Attribute property type (eg password, select, multiselect)
|
||||
protected $type = '';
|
||||
# Attribute value to keep unique
|
||||
protected $unique = false;
|
||||
|
||||
# Display parameters
|
||||
protected $display = '';
|
||||
protected $icon = '';
|
||||
protected $hint = '';
|
||||
# Helper details
|
||||
protected $helper = array();
|
||||
protected $helpervalue = array();
|
||||
# Onchange details
|
||||
protected $onchange = array();
|
||||
# Show spacer after this attribute is rendered
|
||||
protected $spacer = false;
|
||||
protected $verify = false;
|
||||
|
||||
# Component size
|
||||
protected $size = 0;
|
||||
# Value max length
|
||||
protected $maxlength = 0;
|
||||
# Text Area sizings
|
||||
protected $cols = 0;
|
||||
protected $rows = 0;
|
||||
|
||||
# Public for sorting
|
||||
public $page = 1;
|
||||
public $order = 255;
|
||||
public $ordersort = 255;
|
||||
|
||||
# Schema Aliases for this attribute (stored in lowercase)
|
||||
protected $aliases = array();
|
||||
|
||||
# Configuration for automatically generated values
|
||||
protected $autovalue = array();
|
||||
protected $postvalue = array();
|
||||
*/
|
||||
|
||||
public function __construct(string $name,array $values)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->values = collect($values);
|
||||
$this->lang_tags = collect();
|
||||
$this->oldValues = collect($values);
|
||||
|
||||
$this->schema = (new Server)
|
||||
->schema('attributetypes',$name);
|
||||
|
||||
/*
|
||||
# Should this attribute be hidden
|
||||
if ($server->isAttrHidden($this->name))
|
||||
$this->forcehide = true;
|
||||
|
||||
# Should this attribute value be read only
|
||||
if ($server->isAttrReadOnly($this->name))
|
||||
$this->readonly = true;
|
||||
|
||||
# Should this attribute value be unique
|
||||
if ($server->isAttrUnique($this->name))
|
||||
$this->unique = true;
|
||||
*/
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match ($key) {
|
||||
// List all the attributes
|
||||
'attributes' => $this->attributes(),
|
||||
// Can this attribute have more values
|
||||
'can_addvalues' => $this->schema && (! $this->schema->is_single_value) && ((! $this->max_values_count) || ($this->values->count() < $this->max_values_count)),
|
||||
// Schema attribute description
|
||||
'description' => $this->schema ? $this->schema->{$key} : NULL,
|
||||
// Attribute hints
|
||||
'hints' => $this->hints(),
|
||||
// Can this attribute be edited
|
||||
'is_editable' => $this->schema ? $this->schema->{$key} : NULL,
|
||||
// Is this an internal attribute
|
||||
'is_internal' => isset($this->{$key}) && $this->{$key},
|
||||
// Is this attribute the RDN
|
||||
'is_rdn' => $this->is_rdn,
|
||||
// We prefer the name as per the schema if it exists
|
||||
'name' => $this->schema ? $this->schema->{$key} : $this->{$key},
|
||||
// Attribute name in lower case
|
||||
'name_lc' => strtolower($this->name),
|
||||
// Old Values
|
||||
'old_values' => $this->oldValues,
|
||||
// Attribute values
|
||||
'values' => $this->values,
|
||||
// Required by Object Classes
|
||||
'required_by' => $this->schema?->required_by_object_classes ?: collect(),
|
||||
// Used in Object Classes
|
||||
'used_in' => $this->schema?->used_in_object_classes ?: collect(),
|
||||
|
||||
default => throw new \Exception('Unknown key:' . $key),
|
||||
};
|
||||
}
|
||||
|
||||
public function __set(string $key,mixed $values): void
|
||||
{
|
||||
switch ($key) {
|
||||
case 'value':
|
||||
$this->values = collect($values);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function addValue(string $value): void
|
||||
{
|
||||
$this->values->push($value);
|
||||
}
|
||||
|
||||
public function current(): mixed
|
||||
{
|
||||
return $this->values->get($this->counter);
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
$this->counter++;
|
||||
}
|
||||
|
||||
public function key(): mixed
|
||||
{
|
||||
return $this->counter;
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return $this->values->has($this->counter);
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->counter = 0;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return $this->values->count();
|
||||
}
|
||||
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
return ! is_null($this->values->has($offset));
|
||||
}
|
||||
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
return $this->values->get($offset);
|
||||
}
|
||||
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
// We cannot set new values using array syntax
|
||||
}
|
||||
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
// We cannot clear values using array syntax
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hints about this attribute, ie: RDN, Required, etc
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function hints(): array
|
||||
{
|
||||
$result = collect();
|
||||
|
||||
// Is this Attribute an RDN
|
||||
if ($this->is_rdn)
|
||||
$result->put(__('rdn'),__('This attribute is required for the RDN'));
|
||||
|
||||
// If this attribute name is an alias for the schema attribute name
|
||||
// @todo
|
||||
|
||||
// objectClasses requiring this attribute
|
||||
// @todo limit this to this DNs objectclasses
|
||||
// eg: $result->put('required','Required by objectClasses: a,b');
|
||||
if ($this->required_by->count())
|
||||
$result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required_by->join(',')));
|
||||
|
||||
// This attribute has language tags
|
||||
if ($this->lang_tags->count())
|
||||
$result->put(__('language tags'),sprintf('%s: %d',__('This Attribute has Language Tags'),$this->lang_tags->count()));
|
||||
|
||||
return $result->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this attribute has changes
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDirty(): bool
|
||||
{
|
||||
return ($this->oldValues->count() !== $this->values->count())
|
||||
|| ($this->values->diff($this->oldValues)->count() !== 0);
|
||||
}
|
||||
|
||||
public function oldValues(array $array): void
|
||||
{
|
||||
$this->oldValues = collect($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the attribute value
|
||||
*
|
||||
* @param bool $edit Render an edit form
|
||||
* @param bool $old Use old value
|
||||
* @param bool $new Enable adding values
|
||||
* @return View
|
||||
*/
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
return view('components.attribute')
|
||||
->with('o',$this)
|
||||
->with('edit',$edit)
|
||||
->with('old',$old)
|
||||
->with('new',$new);
|
||||
}
|
||||
|
||||
public function render_item_old(int $key): ?string
|
||||
{
|
||||
return Arr::get($this->old_values,$key);
|
||||
}
|
||||
|
||||
public function render_item_new(int $key): ?string
|
||||
{
|
||||
return Arr::get($this->values,$key);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this attribute has RFC3866 Language Tags, this will enable those values to be captured
|
||||
*
|
||||
* @param string $tag
|
||||
* @param array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setLangTag(string $tag,array $value): void
|
||||
{
|
||||
$this->lang_tags->put($tag,$value);
|
||||
}
|
||||
|
||||
public function setRDN(): void
|
||||
{
|
||||
$this->is_rdn = TRUE;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
|
||||
/**
|
||||
* Represents an attribute whose values are binary
|
||||
*/
|
||||
class Binary extends Attribute
|
||||
{
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Binary;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Binary;
|
||||
use App\Traits\MD5Updates;
|
||||
|
||||
/**
|
||||
* Represents an JpegPhoto Attribute
|
||||
*/
|
||||
final class JpegPhoto extends Binary
|
||||
{
|
||||
use MD5Updates;
|
||||
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
return view('components.attribute.binary.jpegphoto')
|
||||
->with('o',$this)
|
||||
->with('edit',$edit)
|
||||
->with('old',$old)
|
||||
->with('new',$new)
|
||||
->with('f',new \finfo);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
|
||||
/**
|
||||
* This factory is used to return LDAP attributes as an object
|
||||
*
|
||||
* If there is no specific Attribute defined, then the default Attribute::class is return
|
||||
*/
|
||||
class Factory
|
||||
{
|
||||
private const LOGKEY = 'LAf';
|
||||
|
||||
/**
|
||||
* Map of attributes to appropriate class
|
||||
*/
|
||||
public const map = [
|
||||
'createtimestamp' => Internal\Timestamp::class,
|
||||
'creatorsname' => Internal\DN::class,
|
||||
'contextcsn' => Internal\CSN::class,
|
||||
'entrycsn' => Internal\CSN::class,
|
||||
'entrydn' => Internal\DN::class,
|
||||
'entryuuid' => Internal\UUID::class,
|
||||
'gidnumber' => GidNumber::class,
|
||||
'hassubordinates' => Internal\HasSubordinates::class,
|
||||
'jpegphoto' => Binary\JpegPhoto::class,
|
||||
'modifytimestamp' => Internal\Timestamp::class,
|
||||
'modifiersname' => Internal\DN::class,
|
||||
'objectclass' => ObjectClass::class,
|
||||
'structuralobjectclass' => Internal\StructuralObjectClass::class,
|
||||
'subschemasubentry' => Internal\SubschemaSubentry::class,
|
||||
'supportedcontrol' => Schema\OID::class,
|
||||
'supportedextension' => Schema\OID::class,
|
||||
'supportedfeatures' => Schema\OID::class,
|
||||
'supportedsaslmechanisms' => Schema\Mechanisms::class,
|
||||
'userpassword' => Password::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Create the new Object for an attribute
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param array $values
|
||||
* @return Attribute
|
||||
*/
|
||||
public static function create(string $attribute,array $values): Attribute
|
||||
{
|
||||
$class = Arr::get(self::map,strtolower($attribute),Attribute::class);
|
||||
Log::debug(sprintf('%s:Creating LDAP Attribute [%s] as [%s]',static::LOGKEY,$attribute,$class));
|
||||
|
||||
return new $class($attribute,$values);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
|
||||
/**
|
||||
* Represents an GidNumber Attribute
|
||||
*/
|
||||
final class GidNumber extends Attribute
|
||||
{
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
|
||||
/**
|
||||
* Represents an attribute whose values are internal
|
||||
*/
|
||||
abstract class Internal extends Attribute
|
||||
{
|
||||
protected bool $is_internal = TRUE;
|
||||
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
// @note Internal attributes cannot be edited
|
||||
return view('components.attribute.internal')
|
||||
->with('o',$this);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
/**
|
||||
* Represents an CSN Attribute
|
||||
*/
|
||||
final class CSN extends Internal
|
||||
{
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
/**
|
||||
* Represents an DN Attribute
|
||||
*/
|
||||
final class DN extends Internal
|
||||
{
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
/**
|
||||
* Represents an HasSubordinates Attribute
|
||||
*/
|
||||
final class HasSubordinates extends Internal
|
||||
{
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
/**
|
||||
* Represents an StructuralObjectClass Attribute
|
||||
*/
|
||||
final class StructuralObjectClass extends Internal
|
||||
{
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
/**
|
||||
* Represents an SubschemaSubentry Attribute
|
||||
*/
|
||||
final class SubschemaSubentry extends Internal
|
||||
{
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
/**
|
||||
* Represents an attribute whose values are timestamps
|
||||
*/
|
||||
final class Timestamp extends Internal
|
||||
{
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
// @note Internal attributes cannot be edited
|
||||
return view('components.attribute.internal.timestamp')
|
||||
->with('o',$this);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Internal;
|
||||
|
||||
/**
|
||||
* Represents an UUID Attribute
|
||||
*/
|
||||
final class UUID extends Internal
|
||||
{
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
|
||||
/**
|
||||
* Represents an ObjectClass Attribute
|
||||
*/
|
||||
final class ObjectClass extends Attribute
|
||||
{
|
||||
// The schema ObjectClasses for this objectclass of a DN
|
||||
protected Collection $oc_schema;
|
||||
|
||||
public function __construct(string $name,array $values)
|
||||
{
|
||||
parent::__construct($name,$values);
|
||||
|
||||
$this->oc_schema = config('server')
|
||||
->schema('objectclasses')
|
||||
->filter(fn($item)=>$this->values->contains($item->name));
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match ($key) {
|
||||
'structural' => $this->oc_schema->filter(fn($item) => $item->isStructural()),
|
||||
default => parent::__get($key),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a specific value the structural objectclass
|
||||
*
|
||||
* @param string $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isStructural(string $value): bool
|
||||
{
|
||||
return $this->structural
|
||||
->map(fn($item)=>$item->name)
|
||||
->contains($value);
|
||||
}
|
||||
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
return view('components.attribute.objectclass')
|
||||
->with('o',$this)
|
||||
->with('edit',$edit)
|
||||
->with('old',$old)
|
||||
->with('new',$new);
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Traits\MD5Updates;
|
||||
|
||||
/**
|
||||
* Represents an attribute whose values are passwords
|
||||
*/
|
||||
final class Password extends Attribute
|
||||
{
|
||||
use MD5Updates;
|
||||
private const password_helpers = 'Classes/LDAP/Attribute/Password';
|
||||
public const commands = 'App\\Classes\\LDAP\\Attribute\\Password\\';
|
||||
|
||||
private static function helpers(): Collection
|
||||
{
|
||||
$helpers = collect();
|
||||
|
||||
foreach (preg_grep('/^([^.])/',scandir(app_path(self::password_helpers))) as $file) {
|
||||
if (($file === 'Base.php') || (! str_ends_with(strtolower($file),'.php')))
|
||||
continue;
|
||||
|
||||
$class = self::commands.preg_replace('/\.php$/','',$file);
|
||||
|
||||
$helpers = $helpers
|
||||
->merge([$class::id()=>$class]);
|
||||
}
|
||||
|
||||
return $helpers->sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an LDAP password syntax {xxx}yyyyyy, this function will return the object for xxx
|
||||
*
|
||||
* @param string $password
|
||||
* @return Attribute\Password\Base|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function hash(string $password): ?Attribute\Password\Base
|
||||
{
|
||||
$m = [];
|
||||
preg_match('/^{([A-Z0-9]+)}(.*)$/',$password,$m);
|
||||
|
||||
$hash = Arr::get($m,1,'*clear*');
|
||||
|
||||
if (($potential=static::helpers()->filter(fn($hasher)=>str_starts_with($hasher::key,$hash)))->count() > 1) {
|
||||
foreach ($potential as $item) {
|
||||
if ($item::subid($password))
|
||||
return new $item;
|
||||
}
|
||||
|
||||
throw new \Exception(sprintf('Couldnt figure out a password hash for %s',$password));
|
||||
|
||||
} elseif (! $potential->count()) {
|
||||
throw new \Exception(sprintf('Couldnt figure out a password hash for %s',$password));
|
||||
}
|
||||
|
||||
return new ($potential->pop());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object that will process a password
|
||||
*
|
||||
* @param string $id
|
||||
* @return Attribute\Password\Base|null
|
||||
*/
|
||||
public static function hash_id(string $id): ?Attribute\Password\Base
|
||||
{
|
||||
return ($helpers=static::helpers())->has($id) ? new ($helpers->get($id)) : NULL;
|
||||
}
|
||||
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
return view('components.attribute.password')
|
||||
->with('o',$this)
|
||||
->with('edit',$edit)
|
||||
->with('old',$old)
|
||||
->with('new',$new)
|
||||
->with('helpers',static::helpers()->map(fn($item,$key)=>['id'=>$key,'value'=>$key])->sort());
|
||||
}
|
||||
|
||||
public function render_item_old(int $key): ?string
|
||||
{
|
||||
$pw = Arr::get($this->oldValues,$key);
|
||||
return $pw
|
||||
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16))
|
||||
: NULL;
|
||||
}
|
||||
|
||||
public function render_item_new(int $key): ?string
|
||||
{
|
||||
$pw = Arr::get($this->values,$key);
|
||||
return $pw
|
||||
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '').str_repeat('*',16))
|
||||
: NULL;
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class Argon2i extends Base
|
||||
{
|
||||
public const key = 'ARGON2';
|
||||
protected const subkey = 'argon2i';
|
||||
protected const identifier = '$argon2i';
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return str_starts_with(base64_decode(self::password($password)),self::identifier.'$');
|
||||
}
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return password_verify($compare,base64_decode($this->password($source)));
|
||||
}
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,base64_encode(password_hash($password,PASSWORD_ARGON2I)));
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class Argon2id extends Base
|
||||
{
|
||||
public const key = 'ARGON2';
|
||||
protected const subkey = 'argon2id';
|
||||
protected const identifier = '$argon2id';
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return str_starts_with(base64_decode(self::password($password)),self::identifier.'$');
|
||||
}
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return password_verify($compare,base64_decode($this->password($source)));
|
||||
}
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,base64_encode(password_hash($password,PASSWORD_ARGON2ID)));
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
abstract class Base
|
||||
{
|
||||
protected const subkey = '';
|
||||
|
||||
abstract public function encode(string $password): string;
|
||||
|
||||
public static function id(): string
|
||||
{
|
||||
return static::subkey ? strtoupper(static::subkey) : static::key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the hash {TEXT}xxxx from the password
|
||||
*
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
protected static function password(string $password): string
|
||||
{
|
||||
return preg_replace('/^{'.static::key.'}/','',$password);
|
||||
}
|
||||
|
||||
public static function shortid(): string
|
||||
{
|
||||
return static::key;
|
||||
}
|
||||
|
||||
/**
|
||||
* When multiple passwords share the same ID, this determines which hash is responsible for the presented password
|
||||
*
|
||||
* @param string $password
|
||||
* @return bool
|
||||
*/
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare our password to see if it is the same as that stored
|
||||
*
|
||||
* @param string $source Encoded source password
|
||||
* @param string $compare Password entered by user
|
||||
* @return bool
|
||||
*/
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return $source === $this->encode($compare);
|
||||
}
|
||||
|
||||
protected function salted_hash(string $password,string $algo,int $salt_size=8,?string $salt=NULL): string
|
||||
{
|
||||
if (is_null($salt))
|
||||
$salt = hex2bin(random_salt($salt_size));
|
||||
|
||||
return base64_encode(hash($algo,$password.$salt,true).$salt);
|
||||
}
|
||||
|
||||
protected function salted_salt(string $source): string
|
||||
{
|
||||
$hash = base64_decode(substr($source,strlen(static::key)+2));
|
||||
return substr($hash,strlen($hash)-static::salt/2);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class Bcrypt extends Base
|
||||
{
|
||||
public const key = 'BCRYPT';
|
||||
|
||||
private const options = [
|
||||
'cost' => 8,
|
||||
];
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return password_verify($compare,base64_decode($this->password($source)));
|
||||
}
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,base64_encode(password_hash($password,PASSWORD_BCRYPT,self::options)));
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class Blowfish extends Base
|
||||
{
|
||||
public const key = 'CRYPT';
|
||||
protected const subkey = 'blowfish';
|
||||
private const cost = 12;
|
||||
protected const salt = 22;
|
||||
private const identifier = '$2a$';
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return preg_match('/^\\$2.\\$/',self::password($password));
|
||||
}
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return hash_equals($cp=self::password($source),crypt($compare,$cp));
|
||||
}
|
||||
|
||||
public function encode(string $password,?string $salt=NULL): string
|
||||
{
|
||||
if (is_null($salt))
|
||||
$salt = sprintf('%s%d$%s',self::identifier,self::cost,random_salt(self::salt));
|
||||
|
||||
return sprintf('{%s}%s',self::key,crypt($password,$salt));
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class Clear extends Base
|
||||
{
|
||||
public const key = '*clear*';
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
return $password;
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class Crypt extends Base
|
||||
{
|
||||
public const key = 'CRYPT';
|
||||
protected const subkey = 'crypt';
|
||||
protected const salt = 2;
|
||||
private const identifier = '';
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return preg_match('/^[\da-f]{2}/',self::password($password));
|
||||
}
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return hash_equals($cp=self::password($source),crypt($compare,$cp));
|
||||
}
|
||||
|
||||
public function encode(string $password,?string $salt=NULL): string
|
||||
{
|
||||
if (is_null($salt))
|
||||
$salt = sprintf('%s%s',self::identifier,random_salt(self::salt));
|
||||
|
||||
return sprintf('{%s}%s',self::key,crypt($password,$salt));
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class ExtDes extends Base
|
||||
{
|
||||
public const key = 'CRYPT';
|
||||
protected const subkey = 'ext_des';
|
||||
protected const salt = 8;
|
||||
private const identifier = '_';
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return str_starts_with(self::password($password),self::identifier);
|
||||
}
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return hash_equals($cp=self::password($source),crypt($compare,$cp));
|
||||
}
|
||||
|
||||
public function encode(string $password,?string $salt=NULL): string
|
||||
{
|
||||
if (is_null($salt))
|
||||
$salt = sprintf('%s%s',self::identifier,random_salt(self::salt));
|
||||
|
||||
return sprintf('{%s}%s',self::key,crypt($password,$salt));
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class MD5 extends Base
|
||||
{
|
||||
public const key = 'MD5';
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,base64_encode(hash('md5',$password,true)));
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class MD5crypt extends Base
|
||||
{
|
||||
public const key = 'CRYPT';
|
||||
protected const subkey = 'md5crypt';
|
||||
protected const salt = 9;
|
||||
private const identifier = '$1$';
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return str_starts_with(self::password($password),self::identifier);
|
||||
}
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return hash_equals($cp=self::password($source),crypt($compare,$cp));
|
||||
}
|
||||
|
||||
public function encode(string $password,?string $salt=NULL): string
|
||||
{
|
||||
if (is_null($salt))
|
||||
$salt = sprintf('%s$%s',self::identifier,random_salt(self::salt));
|
||||
|
||||
return sprintf('{%s}%s',self::key,crypt($password,$salt));
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SASL extends Base
|
||||
{
|
||||
public const key = 'SASL';
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
if (! str_contains($password,'@'))
|
||||
return '';
|
||||
|
||||
// Ensure our id is lowercase, and realm is uppercase
|
||||
list($id,$realm) = explode('@',$password);
|
||||
|
||||
return sprintf('{%s}%s@%s',self::key,strtolower($id),strtoupper($realm));
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SHA extends Base
|
||||
{
|
||||
public const key = 'SHA';
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,base64_encode(hash('sha1',$password,true)));
|
||||
}
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return preg_match('/^{'.static::key.'}/',$password);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SHA256 extends Base
|
||||
{
|
||||
public const key = 'SHA256';
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,base64_encode(hash('sha256',$password,true)));
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SHA256crypt extends Base
|
||||
{
|
||||
public const key = 'CRYPT';
|
||||
protected const subkey = 'sha256crypt';
|
||||
protected const salt = 5;
|
||||
private const identifier = '$5$';
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return str_starts_with(self::password($password),self::identifier);
|
||||
}
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return hash_equals($cp=self::password($source),crypt($compare,$cp));
|
||||
}
|
||||
|
||||
public function encode(string $password,?string $salt=NULL): string
|
||||
{
|
||||
if (is_null($salt))
|
||||
$salt = sprintf('%s%s',self::identifier,random_salt(self::salt));
|
||||
|
||||
return sprintf('{%s}%s',self::key,crypt($password,$salt));
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SHA384 extends Base
|
||||
{
|
||||
public const key = 'SHA384';
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,base64_encode(hash('sha384',$password,true)));
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SHA512 extends Base
|
||||
{
|
||||
public const key = 'SHA512';
|
||||
|
||||
public function encode(string $password): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,base64_encode(hash('sha512',$password,true)));
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SHA512crypt extends Base
|
||||
{
|
||||
public const key = 'CRYPT';
|
||||
protected const subkey = 'sha512crypt';
|
||||
protected const salt = 2;
|
||||
private const identifier = '$6$';
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return str_starts_with(self::password($password),self::identifier);
|
||||
}
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return hash_equals($cp=self::password($source),crypt($compare,$cp));
|
||||
}
|
||||
|
||||
public function encode(string $password,?string $salt=NULL): string
|
||||
{
|
||||
if (is_null($salt))
|
||||
$salt = sprintf('%s%s',self::identifier,random_salt(self::salt));
|
||||
|
||||
return sprintf('{%s}%s',self::key,crypt($password,$salt));
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SMD5 extends Base
|
||||
{
|
||||
public const key = 'SMD5';
|
||||
protected const salt = 8;
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return $source === $this->encode($compare,$this->salted_salt($source));
|
||||
}
|
||||
|
||||
public function encode(string $password,string $salt=NULL): string
|
||||
{
|
||||
if (is_null($salt))
|
||||
$salt = hex2bin(random_salt(self::salt));
|
||||
|
||||
return sprintf('{%s}%s',self::key,$this->salted_hash($password,'md5',self::salt,$salt));
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SSHA extends Base
|
||||
{
|
||||
public const key = 'SSHA';
|
||||
protected const salt = 8;
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return $source === $this->encode($compare,$this->salted_salt($source));
|
||||
}
|
||||
|
||||
public function encode(string $password,string $salt=NULL): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,$this->salted_hash($password,'sha1',self::salt,$salt));
|
||||
}
|
||||
|
||||
public static function subid(string $password): bool
|
||||
{
|
||||
return preg_match('/^{'.static::key.'}/',$password);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SSHA256 extends Base
|
||||
{
|
||||
public const key = 'SSHA256';
|
||||
protected const salt = 8;
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return $source === $this->encode($compare,$this->salted_salt($source));
|
||||
}
|
||||
|
||||
public function encode(string $password,?string $salt=NULL): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,$this->salted_hash($password,'sha256',self::salt,$salt));
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SSHA384 extends Base
|
||||
{
|
||||
public const key = 'SSHA384';
|
||||
protected const salt = 8;
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return $source === $this->encode($compare,$this->salted_salt($source));
|
||||
}
|
||||
|
||||
public function encode(string $password,?string $salt=NULL): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,$this->salted_hash($password,'sha384',self::salt,$salt));
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Password;
|
||||
|
||||
final class SSHA512 extends Base
|
||||
{
|
||||
public const key = 'SSHA512';
|
||||
protected const salt = 8;
|
||||
|
||||
public function compare(string $source,string $compare): bool
|
||||
{
|
||||
return $source === $this->encode($compare,$this->salted_salt($source));
|
||||
}
|
||||
|
||||
public function encode(string $password,?string $salt=NULL): string
|
||||
{
|
||||
return sprintf('{%s}%s',self::key,$this->salted_hash($password,'sha512',self::salt,$salt));
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
|
||||
/**
|
||||
* Represents the RDN for an Entry
|
||||
*/
|
||||
final class RDN extends Attribute
|
||||
{
|
||||
private string $base;
|
||||
private Collection $attrs;
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match ($key) {
|
||||
'base' => $this->base,
|
||||
'attrs' => $this->attrs->pluck('name'),
|
||||
default => parent::__get($key),
|
||||
};
|
||||
}
|
||||
|
||||
public function hints(): array
|
||||
{
|
||||
return [
|
||||
'required' => __('RDN is required')
|
||||
];
|
||||
}
|
||||
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
return view('components.attribute.rdn')
|
||||
->with('o',$this);
|
||||
}
|
||||
|
||||
public function setAttributes(Collection $attrs): void
|
||||
{
|
||||
$this->attrs = $attrs;
|
||||
}
|
||||
|
||||
public function setBase(string $base): void
|
||||
{
|
||||
$this->base = $base;
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
|
||||
/**
|
||||
* Represents an attribute whose values are schema related
|
||||
*/
|
||||
abstract class Schema extends Attribute
|
||||
{
|
||||
protected bool $internal = TRUE;
|
||||
|
||||
protected static function _get(string $filename,string $string,string $key): ?string
|
||||
{
|
||||
$array = Cache::remember($filename,86400,function() use ($filename) {
|
||||
try {
|
||||
$f = fopen($filename,'r');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$result = collect();
|
||||
|
||||
while (! feof($f)) {
|
||||
$line = trim(fgets($f));
|
||||
|
||||
if (! $line OR preg_match('/^#/',$line))
|
||||
continue;
|
||||
|
||||
$fields = explode(':',$line);
|
||||
|
||||
$result->put($x=Arr::get($fields,0),[
|
||||
'title'=>Arr::get($fields,1,$x),
|
||||
'ref'=>Arr::get($fields,2),
|
||||
'desc'=>Arr::get($fields,3,__('No description available, can you help with one?')),
|
||||
]);
|
||||
}
|
||||
fclose($f);
|
||||
|
||||
return $result;
|
||||
});
|
||||
|
||||
return Arr::get(($array ? $array->get($string) : []),$key);
|
||||
}
|
||||
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
// @note Schema attributes cannot be edited
|
||||
return view('components.attribute.internal')
|
||||
->with('o',$this);
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Schema;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Schema;
|
||||
|
||||
/**
|
||||
* Represents a Mechanisms Attribute
|
||||
*/
|
||||
final class Mechanisms extends Schema
|
||||
{
|
||||
/**
|
||||
* Given an SASL Mechanism name, returns a verbose description of the Mechanism.
|
||||
* This function parses ldap_supported_saslmechanisms.txt and looks up the specified
|
||||
* Mechanism, and returns the verbose message defined in that file.
|
||||
*
|
||||
* <code>
|
||||
* "SCRAM-SHA-1" => array:3 [▼
|
||||
* "title" => "Salted Challenge Response Authentication Mechanism (SCRAM) SHA1"
|
||||
* "ref" => "RFC 5802"
|
||||
* "desc" => "This specification describes a family of authentication mechanisms called the Salted Challenge Response Authentication Mechanism (SCRAM) which addresses the req ▶"
|
||||
* ]
|
||||
* </code>
|
||||
*
|
||||
* @param string $string The SASL Mechanism (ie, "SCRAM-SHA-1") of interest.
|
||||
* @param string $key The title|ref|desc to return
|
||||
* @return string|NULL
|
||||
*/
|
||||
public static function get(string $string,string $key): ?string
|
||||
{
|
||||
return parent::_get(config_path('ldap_supported_saslmechanisms.txt'),$string,$key);
|
||||
}
|
||||
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
// @note Schema attributes cannot be edited
|
||||
return view('components.attribute.schema.mechanisms')
|
||||
->with('o',$this);
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Attribute\Schema;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
use App\Classes\LDAP\Attribute\Schema;
|
||||
|
||||
/**
|
||||
* Represents an OID Attribute
|
||||
*/
|
||||
final class OID extends Schema
|
||||
{
|
||||
/**
|
||||
* Given an LDAP OID number, returns a verbose description of the OID.
|
||||
* This function parses ldap_supported_oids.txt and looks up the specified
|
||||
* OID, and returns the verbose message defined in that file.
|
||||
*
|
||||
* <code>
|
||||
* "1.3.6.1.4.1.4203.1.5.1" => array:3 [
|
||||
* [title] => All Operational Attribute
|
||||
* [ref] => RFC 3673
|
||||
* [desc] => An LDAP extension which clients may use to request the return of all operational attributes.
|
||||
* ]
|
||||
* </code>
|
||||
*
|
||||
* @param string $string The OID number (ie, "1.3.6.1.4.1.4203.1.5.1") of the OID of interest.
|
||||
* @param string $key The title|ref|desc to return
|
||||
* @return string|null
|
||||
* @testedby TranslateOidTest::testRootDSE()
|
||||
*/
|
||||
public static function get(string $string,string $key): ?string
|
||||
{
|
||||
return parent::_get(config_path('ldap_supported_oids.txt'),$string,$key);
|
||||
}
|
||||
|
||||
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
|
||||
{
|
||||
// @note Schema attributes cannot be edited
|
||||
return view('components.attribute.schema.oid')
|
||||
->with('o',$this);
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use LdapRecord\Query\Collection;
|
||||
|
||||
/**
|
||||
* Export Class
|
||||
*
|
||||
* This abstract classes provides all the common methods and variables for the
|
||||
* export classes.
|
||||
*/
|
||||
abstract class Export
|
||||
{
|
||||
// Line Break
|
||||
protected string $br = "\r\n";
|
||||
|
||||
// Item(s) being Exported
|
||||
protected Collection $items;
|
||||
|
||||
// Type of export
|
||||
protected const type = 'Unknown';
|
||||
|
||||
public function __construct(Collection $items)
|
||||
{
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
abstract public function __toString(): string;
|
||||
|
||||
protected function header()
|
||||
{
|
||||
$output = '';
|
||||
|
||||
$output .= sprintf('# %s %s',__(static::type.' for'),($x=$this->items->first())).$this->br;
|
||||
$output .= sprintf('# %s: %s (%s)',
|
||||
__('Server'),
|
||||
$x->getConnection()->getConfiguration()->get('name'),
|
||||
$x->getConnection()->getLdapConnection()->getHost()).$this->br;
|
||||
//$output .= sprintf('# %s: %s',__('Search Scope'),$this->scope).$this->br;
|
||||
//$output .= sprintf('# %s: %s',__('Search Filter'),$this->entry->dn).$this->br;
|
||||
$output .= sprintf('# %s: %s',__('Total Entries'),$this->items->count()).$this->br;
|
||||
$output .= '#'.$this->br;
|
||||
$output .= sprintf('# %s %s (%s) on %s',__('Generated by'),config('app.name'),config('app.url'),date('F j, Y g:i a')).$this->br;
|
||||
$output .= sprintf('# %s %s',__('Exported by'),Auth::user() ?: 'Anonymous').$this->br;
|
||||
$output .= sprintf('# %s: %s',__('Version'),config('app.version')).$this->br;
|
||||
|
||||
$output .= $this->br;
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Export;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\Classes\LDAP\Export;
|
||||
|
||||
/**
|
||||
* Export from LDAP using an LDIF format
|
||||
*/
|
||||
class LDIF extends Export
|
||||
{
|
||||
// The maximum length of the ldif line
|
||||
private int $line_length = 76;
|
||||
protected const type = 'LDIF Export';
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$result = parent::header();
|
||||
$result .= 'version: 1';
|
||||
$result .= $this->br;
|
||||
|
||||
$c = 1;
|
||||
foreach ($this->items as $o) {
|
||||
if ($c > 1)
|
||||
$result .= $this->br;
|
||||
|
||||
$title = (string)$o;
|
||||
if (strlen($title) > $this->line_length)
|
||||
$title = Str::of($title)->limit($this->line_length-3-5,'...'.substr($title,-5));
|
||||
|
||||
$result .= sprintf('# %s %s: %s',__('Entry'),$c++,$title).$this->br;
|
||||
|
||||
// Display DN
|
||||
$result .= $this->multiLineDisplay(
|
||||
Str::isAscii($o)
|
||||
? sprintf('dn: %s',$o)
|
||||
: sprintf('dn:: %s',base64_encode($o))
|
||||
,$this->br);
|
||||
|
||||
// Display Attributes
|
||||
foreach ($o->getObjects() as $ao) {
|
||||
foreach ($ao->values as $value) {
|
||||
$result .= $this->multiLineDisplay(
|
||||
Str::isAscii($value)
|
||||
? sprintf('%s: %s',$ao->name,$value)
|
||||
: sprintf('%s:: %s',$ao->name,base64_encode($value))
|
||||
,$this->br);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to wrap LDIF lines
|
||||
*
|
||||
* @param string $str The line to be wrapped if needed.
|
||||
*/
|
||||
private function multiLineDisplay(string $str,string $br): string
|
||||
{
|
||||
$length_string = strlen($str);
|
||||
$length_max = $this->line_length;
|
||||
|
||||
$output = '';
|
||||
while ($length_string > $length_max) {
|
||||
$output .= substr($str,0,$length_max).$br;
|
||||
$str = ' '.substr($str,$length_max);
|
||||
$length_string = strlen($str);
|
||||
}
|
||||
|
||||
$output .= $str.$br;
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Exceptions\Import\GeneralException;
|
||||
use App\Exceptions\Import\ObjectExistsException;
|
||||
use App\Ldap\Entry;
|
||||
|
||||
/**
|
||||
* Import Class
|
||||
*
|
||||
* This abstract classes provides all the common methods and variables for the
|
||||
* import classes.
|
||||
*/
|
||||
abstract class Import
|
||||
{
|
||||
// Valid LDIF commands
|
||||
protected const LDAP_IMPORT_ADD = 1;
|
||||
protected const LDAP_IMPORT_DELETE = 2;
|
||||
protected const LDAP_IMPORT_MODRDN = 3;
|
||||
protected const LDAP_IMPORT_MODDN = 4;
|
||||
protected const LDAP_IMPORT_MODIFY = 5;
|
||||
|
||||
protected const LDAP_ACTIONS = [
|
||||
'add' => self::LDAP_IMPORT_ADD,
|
||||
'delete' => self::LDAP_IMPORT_DELETE,
|
||||
'modrdn' => self::LDAP_IMPORT_MODRDN,
|
||||
'moddn' => self::LDAP_IMPORT_MODDN,
|
||||
'modify' => self::LDAP_IMPORT_MODIFY,
|
||||
];
|
||||
|
||||
// The import data to process
|
||||
protected string $input;
|
||||
// The attributes the server knows about
|
||||
protected Collection $server_attributes;
|
||||
|
||||
public function __construct(string $input) {
|
||||
$this->input = $input;
|
||||
$this->server_attributes = config('server')->schema('attributetypes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to commit an entry and return the result.
|
||||
*
|
||||
* @param Entry $o
|
||||
* @param int $action
|
||||
* @return Collection
|
||||
* @throws GeneralException
|
||||
* @throws ObjectExistsException
|
||||
*/
|
||||
final protected function commit(Entry $o,int $action): Collection
|
||||
{
|
||||
switch ($action) {
|
||||
case static::LDAP_IMPORT_ADD:
|
||||
try {
|
||||
$o->save();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return collect([
|
||||
'dn'=>$o->getDN(),
|
||||
'result'=>sprintf('%d: %s (%s)',
|
||||
($x=$e->getDetailedError())->getErrorCode(),
|
||||
$x->getErrorMessage(),
|
||||
$x->getDiagnosticMessage(),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
return collect(['dn'=>$o->getDN(),'result'=>__('Created')]);
|
||||
|
||||
default:
|
||||
throw new GeneralException('Unhandled action during commit: '.$action);
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function process(): Collection;
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Import;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Nette\NotImplementedException;
|
||||
|
||||
use App\Classes\LDAP\Import;
|
||||
use App\Exceptions\Import\{GeneralException,VersionException};
|
||||
use App\Ldap\Entry;
|
||||
|
||||
/**
|
||||
* Import LDIF to LDAP using an LDIF format
|
||||
*
|
||||
* The LDIF spec is described by RFC2849
|
||||
* http://www.ietf.org/rfc/rfc2849.txt
|
||||
*/
|
||||
class LDIF extends Import
|
||||
{
|
||||
private const LOGKEY = 'ILF';
|
||||
|
||||
public function process(): Collection
|
||||
{
|
||||
$c = 0;
|
||||
$action = NULL;
|
||||
$attribute = NULL;
|
||||
$base64encoded = FALSE;
|
||||
$o = NULL;
|
||||
$value = '';
|
||||
$version = NULL;
|
||||
$result = collect();
|
||||
|
||||
// @todo When renaming DNs, the hotlink should point to the new entry on success, or the old entry on failure.
|
||||
foreach (preg_split('/(\r?\n|\r)/',$this->input) as $line) {
|
||||
$c++;
|
||||
Log::debug(sprintf('%s: LDIF Line [%s]',self::LOGKEY,$line));
|
||||
$line = trim($line);
|
||||
|
||||
// If the line starts with a comment, ignore it
|
||||
if (preg_match('/^#/',$line))
|
||||
continue;
|
||||
|
||||
// If we have a blank line, then that completes this command
|
||||
if (! $line) {
|
||||
if (! is_null($o)) {
|
||||
// Add the last attribute;
|
||||
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
|
||||
|
||||
Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN()));
|
||||
|
||||
// Commit
|
||||
$result->push($this->commit($o,$action));
|
||||
$result->last()->put('line',$c);
|
||||
|
||||
$o = NULL;
|
||||
$action = NULL;
|
||||
$base64encoded = FALSE;
|
||||
$attribute = NULL;
|
||||
$value = '';
|
||||
|
||||
// Else its a blank line
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$m = [];
|
||||
preg_match('/^([a-zA-Z0-9;-]+)(:+)\s+(.*)$/',$line,$m);
|
||||
|
||||
switch ($x=Arr::get($m,1)) {
|
||||
case 'changetype':
|
||||
if ($m[2] !== ':')
|
||||
throw new GeneralException(sprintf('ChangeType cannot be base64 encoded set at [%d]. (line %d)',$version,$c));
|
||||
|
||||
switch ($m[3]) {
|
||||
// if (preg_match('/^changetype:[ ]*(delete|add|modrdn|moddn|modify)/i',$lines[0])) {
|
||||
default:
|
||||
throw new NotImplementedException(sprintf('Unknown change type [%s]? (line %d)',$m[3],$c));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'version':
|
||||
if (! is_null($version))
|
||||
throw new VersionException(sprintf('Version has already been set at [%d]. (line %d)',$version,$c));
|
||||
|
||||
if ($m[2] !== ':')
|
||||
throw new VersionException(sprintf('Version cannot be base64 encoded set at [%d]. (line %d)',$version,$c));
|
||||
|
||||
$version = (int)$m[3];
|
||||
break;
|
||||
|
||||
// Treat it as an attribute
|
||||
default:
|
||||
// If $m is NULL, then this is the 2nd (or more) line of a base64 encoded value
|
||||
if (! $m) {
|
||||
$value .= $line;
|
||||
Log::debug(sprintf('%s: Attribute [%s] adding [%s] (%d)',self::LOGKEY,$attribute,$line,$c));
|
||||
|
||||
// add to last attr value
|
||||
continue 2;
|
||||
}
|
||||
|
||||
// We are ready to create the entry or add the attribute
|
||||
if ($attribute) {
|
||||
if ($attribute === 'dn') {
|
||||
if (! is_null($o))
|
||||
throw new GeneralException(sprintf('Previous Entry not complete? (line %d)',$c));
|
||||
|
||||
$dn = $base64encoded ? base64_decode($value) : $value;
|
||||
Log::debug(sprintf('%s: Creating new entry:',self::LOGKEY,$dn));
|
||||
//$o = Entry::find($dn);
|
||||
|
||||
// If it doesnt exist, we'll create it
|
||||
//if (! $o) {
|
||||
$o = new Entry;
|
||||
$o->setDn($dn);
|
||||
//}
|
||||
|
||||
$action = self::LDAP_IMPORT_ADD;
|
||||
|
||||
} else {
|
||||
Log::debug(sprintf('%s: Adding Attribute [%s] value [%s] (%d)',self::LOGKEY,$attribute,$value,$c));
|
||||
|
||||
if ($value)
|
||||
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
|
||||
else
|
||||
throw new GeneralException(sprintf('Attribute has no value [%s] (line %d)',$attribute,$c));
|
||||
}
|
||||
}
|
||||
|
||||
// Start of a new attribute
|
||||
$base64encoded = ($m[2] === '::');
|
||||
// @todo Need to parse attributes with ';' options
|
||||
$attribute = $m[1];
|
||||
$value = $m[3];
|
||||
|
||||
Log::debug(sprintf('%s: New Attribute [%s] with [%s] (%d)',self::LOGKEY,$attribute,$value,$c));
|
||||
}
|
||||
|
||||
if ($version !== 1)
|
||||
throw new VersionException('LDIF import cannot handle version: '.($version ?: __('NOT DEFINED')));
|
||||
}
|
||||
|
||||
// We may still have a pending action
|
||||
if ($action) {
|
||||
// Add the last attribute;
|
||||
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
|
||||
|
||||
Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN()));
|
||||
|
||||
// Commit
|
||||
$result->push($this->commit($o,$action));
|
||||
$result->last()->put('line',$c);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function readEntry() {
|
||||
static $haveVersion = false;
|
||||
|
||||
if ($lines = $this->nextLines()) {
|
||||
|
||||
$server = $this->getServer();
|
||||
|
||||
# The first line should be the DN
|
||||
if (preg_match('/^dn:/',$lines[0])) {
|
||||
list($text,$dn) = $this->getAttrValue(array_shift($lines));
|
||||
|
||||
# The second line should be our changetype
|
||||
if (preg_match('/^changetype:[ ]*(delete|add|modrdn|moddn|modify)/i',$lines[0])) {
|
||||
$attrvalue = $this->getAttrValue($lines[0]);
|
||||
$changetype = $attrvalue[1];
|
||||
array_shift($lines);
|
||||
|
||||
} else
|
||||
$changetype = 'add';
|
||||
|
||||
$this->template = new Template($this->server_id,null,null,$changetype);
|
||||
|
||||
switch ($changetype) {
|
||||
case 'add':
|
||||
$rdn = get_rdn($dn);
|
||||
$container = $server->getContainer($dn);
|
||||
|
||||
$this->template->setContainer($container);
|
||||
$this->template->accept();
|
||||
|
||||
$this->getAddDetails($lines);
|
||||
$this->template->setRDNAttributes($rdn);
|
||||
|
||||
return $this->template;
|
||||
|
||||
break;
|
||||
|
||||
case 'modify':
|
||||
if (! $server->dnExists($dn))
|
||||
return $this->error(sprintf('%s %s',_('DN does not exist'),$dn),$lines);
|
||||
|
||||
$this->template->setDN($dn);
|
||||
$this->template->accept(false,true);
|
||||
|
||||
return $this->getModifyDetails($lines);
|
||||
|
||||
break;
|
||||
|
||||
case 'moddn':
|
||||
case 'modrdn':
|
||||
if (! $server->dnExists($dn))
|
||||
return $this->error(sprintf('%s %s',_('DN does not exist'),$dn),$lines);
|
||||
|
||||
$this->template->setDN($dn);
|
||||
$this->template->accept();
|
||||
|
||||
return $this->getModRDNAttributes($lines);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
if (! $server->dnExists($dn))
|
||||
return $this->error(_('Unkown change type'),$lines);
|
||||
}
|
||||
|
||||
} else
|
||||
return $this->error(_('A valid dn line is required'),$lines);
|
||||
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,566 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Represents an LDAP AttributeType
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class AttributeType extends Base {
|
||||
// The attribute from which this attribute inherits (if any)
|
||||
private ?string $sup_attribute = NULL;
|
||||
|
||||
// Array of AttributeTypes which inherit from this one
|
||||
private Collection $children;
|
||||
|
||||
// The equality rule used
|
||||
private ?string $equality = NULL;
|
||||
|
||||
// The ordering of the attributeType
|
||||
private ?string $ordering = NULL;
|
||||
|
||||
// Supports substring matching?
|
||||
private ?string $sub_str_rule = NULL;
|
||||
|
||||
// The full syntax string, ie 1.2.3.4{16}
|
||||
private ?string $syntax = NULL;
|
||||
private ?string $syntax_oid = NULL;
|
||||
|
||||
// boolean: is single valued only?
|
||||
private bool $is_single_value = FALSE;
|
||||
|
||||
// boolean: is collective?
|
||||
private bool $is_collective = FALSE;
|
||||
|
||||
// boolean: can use modify?
|
||||
private bool $is_no_user_modification = FALSE;
|
||||
|
||||
// The usage string set by the LDAP schema
|
||||
private ?string $usage = NULL;
|
||||
|
||||
// An array of alias attribute names, strings
|
||||
private Collection $aliases;
|
||||
|
||||
// The max number of characters this attribute can be
|
||||
private ?int $max_length = NULL;
|
||||
|
||||
// A string description of the syntax type (taken from the LDAPSyntaxes)
|
||||
/**
|
||||
* @deprecated - reference syntaxes directly if possible
|
||||
* @var string
|
||||
*/
|
||||
private ?string $type = NULL;
|
||||
|
||||
// An array of objectClasses which use this attributeType (must be set by caller)
|
||||
private Collection $used_in_object_classes;
|
||||
|
||||
// A list of object class names that require this attribute type.
|
||||
private Collection $required_by_object_classes;
|
||||
|
||||
// This attribute has been forced a MAY attribute by the configuration.
|
||||
private bool $forced_as_may = FALSE;
|
||||
|
||||
/**
|
||||
* Creates a new AttributeType object from a raw LDAP AttributeType string.
|
||||
*
|
||||
* eg: ( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
|
||||
*/
|
||||
public function __construct(string $line) {
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('Parsing AttributeType [%s]',$line));
|
||||
|
||||
parent::__construct($line);
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
// Init
|
||||
$this->children = collect();
|
||||
$this->aliases = collect();
|
||||
$this->used_in_object_classes = collect();
|
||||
$this->required_by_object_classes = collect();
|
||||
|
||||
for ($i=0; $i < count($strings); $i++) {
|
||||
switch ($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
case 'NAME':
|
||||
// @note Some schema's return a (' instead of a ( '
|
||||
if ($strings[$i+1] != '(' && ! preg_match('/^\(/',$strings[$i+1])) {
|
||||
do {
|
||||
$this->name .= ($this->name ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
// This attribute has no aliases
|
||||
//$this->aliases = collect();
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
// In case we came here becaues of a ('
|
||||
if (preg_match('/^\(/',$strings[$i]))
|
||||
$strings[$i] = preg_replace('/^\(/','',$strings[$i]);
|
||||
else
|
||||
$i++;
|
||||
|
||||
$this->name .= ($this->name ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
// Add alias names for this attribute
|
||||
while ($strings[++$i] != ')') {
|
||||
$alias = $strings[$i];
|
||||
$alias = preg_replace("/^\'(.*)\'$/",'$1',$alias);
|
||||
$this->addAlias($alias);
|
||||
}
|
||||
}
|
||||
|
||||
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case NAME returned (%s)',$this->name),['aliases'=>$this->aliases]);
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
do {
|
||||
$this->description .= ($this->description ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
|
||||
break;
|
||||
|
||||
case 'OBSOLETE':
|
||||
$this->is_obsolete = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete));
|
||||
break;
|
||||
|
||||
case 'SUP':
|
||||
$i++;
|
||||
$this->sup_attribute = preg_replace("/^\'(.*)\'$/",'$1',$strings[$i]);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SUP returned (%s)',$this->sup_attribute));
|
||||
break;
|
||||
|
||||
case 'EQUALITY':
|
||||
$this->equality = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case EQUALITY returned (%s)',$this->equality));
|
||||
break;
|
||||
|
||||
case 'ORDERING':
|
||||
$this->ordering = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case ORDERING returned (%s)',$this->ordering));
|
||||
break;
|
||||
|
||||
case 'SUBSTR':
|
||||
$this->sub_str_rule = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SUBSTR returned (%s)',$this->sub_str_rule));
|
||||
break;
|
||||
|
||||
case 'SYNTAX':
|
||||
$this->syntax = $strings[++$i];
|
||||
$this->syntax_oid = preg_replace('/{\d+}$/','',$this->syntax);
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('/ Evaluating SYNTAX returned (%s) [%s]',$this->syntax,$this->syntax_oid));
|
||||
|
||||
// Does this SYNTAX string specify a max length (ie, 1.2.3.4{16})
|
||||
$m = [];
|
||||
if (preg_match('/{(\d+)}$/',$this->syntax,$m))
|
||||
$this->max_length = $m[1];
|
||||
else
|
||||
$this->max_length = NULL;
|
||||
|
||||
if ($i < count($strings) - 1 && $strings[$i+1] == '{')
|
||||
do {
|
||||
$this->name .= ' '.$strings[++$i];
|
||||
} while ($strings[$i] != '}');
|
||||
|
||||
$this->syntax = preg_replace("/^\'(.*)\'$/",'$1',$this->syntax);
|
||||
$this->syntax_oid = preg_replace("/^\'(.*)\'$/",'$1',$this->syntax_oid);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SYNTAX returned (%s) [%s] {%d}',$this->syntax,$this->syntax_oid,$this->max_length));
|
||||
break;
|
||||
|
||||
case 'SINGLE-VALUE':
|
||||
$this->is_single_value = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SINGLE-VALUE returned (%s)',$this->is_single_value));
|
||||
break;
|
||||
|
||||
case 'COLLECTIVE':
|
||||
$this->is_collective = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case COLLECTIVE returned (%s)',$this->is_collective));
|
||||
break;
|
||||
|
||||
case 'NO-USER-MODIFICATION':
|
||||
$this->is_no_user_modification = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case NO-USER-MODIFICATION returned (%s)',$this->is_no_user_modification));
|
||||
break;
|
||||
|
||||
case 'USAGE':
|
||||
$this->usage = $strings[++$i];
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case USAGE returned (%s)',$this->usage));
|
||||
break;
|
||||
|
||||
// @note currently not captured
|
||||
case 'X-ORDERED':
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::error(sprintf('- Case X-ORDERED returned (%s)',$strings[++$i]));
|
||||
break;
|
||||
|
||||
// @note currently not captured
|
||||
case 'X-ORIGIN':
|
||||
$value = '';
|
||||
|
||||
do {
|
||||
$value .= ($value ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::error(sprintf('- Case X-ORIGIN returned (%s)',$value));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
// When we clone, we need to break the reference too
|
||||
$this->aliases = clone $this->aliases;
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'aliases': return $this->aliases;
|
||||
case 'children': return $this->children;
|
||||
case 'forced_as_may': return $this->forced_as_may;
|
||||
case 'is_collective': return $this->is_collective;
|
||||
case 'is_editable': return ! $this->is_no_user_modification;
|
||||
case 'is_no_user_modification': return $this->is_no_user_modification;
|
||||
case 'is_single_value': return $this->is_single_value;
|
||||
case 'equality': return $this->equality;
|
||||
case 'max_length': return $this->max_length;
|
||||
case 'ordering': return $this->ordering;
|
||||
case 'required_by_object_classes': return $this->required_by_object_classes;
|
||||
case 'sub_str_rule': return $this->sub_str_rule;
|
||||
case 'sup_attribute': return $this->sup_attribute;
|
||||
case 'syntax': return $this->syntax;
|
||||
case 'syntax_oid': return $this->syntax_oid;
|
||||
case 'type': return $this->type;
|
||||
case 'usage': return $this->usage;
|
||||
case 'used_in_object_classes': return $this->used_in_object_classes;
|
||||
|
||||
default: return parent::__get($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute name to the alias array.
|
||||
*
|
||||
* @param string $alias The name of a new attribute to add to this attribute's list of aliases.
|
||||
*/
|
||||
public function addAlias(string $alias): void
|
||||
{
|
||||
$this->aliases->push($alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Children of this attribute type that inherit from this one
|
||||
*
|
||||
* @param string $child
|
||||
* @return void
|
||||
*/
|
||||
public function addChild(string $child): void
|
||||
{
|
||||
$this->children->push($child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an objectClass name to this attribute's list of "required by" objectClasses,
|
||||
* that is the list of objectClasses which must have this attribute.
|
||||
*
|
||||
* @param string $name The name of the objectClass to add.
|
||||
*/
|
||||
public function addRequiredByObjectClass(string $name): void
|
||||
{
|
||||
if (! $this->required_by_object_classes->contains($name))
|
||||
$this->required_by_object_classes->push($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an objectClass name to this attribute's list of "used in" objectClasses,
|
||||
* that is the list of objectClasses which provide this attribute.
|
||||
*
|
||||
* @param string $name The name of the objectClass to add.
|
||||
*/
|
||||
public function addUsedInObjectClass(string $name,bool $structural): void
|
||||
{
|
||||
if (! $this->used_in_object_classes->has($name))
|
||||
$this->used_in_object_classes->put($name,$structural);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of attributes that are an alias for this attribute (if any).
|
||||
*
|
||||
* @return Collection An array of names of attributes which alias this attribute or
|
||||
* an empty array if no attribute aliases this object.
|
||||
* @deprecated use class->aliases
|
||||
*/
|
||||
public function getAliases(): Collection
|
||||
{
|
||||
return $this->aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's equality string
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $this->equality
|
||||
*/
|
||||
public function getEquality()
|
||||
{
|
||||
return $this->equality;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this attribute is collective.
|
||||
*
|
||||
* @return boolean Returns TRUE if this attribute is collective and FALSE otherwise.
|
||||
* @deprecated use $this->is_collective
|
||||
*/
|
||||
public function getIsCollective(): bool
|
||||
{
|
||||
return $this->is_collective;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this attribute is not modifiable by users.
|
||||
*
|
||||
* @return boolean Returns TRUE if this attribute is not modifiable by users.
|
||||
* @deprecated use $this->is_no_user_modification
|
||||
*/
|
||||
public function getIsNoUserModification(): bool
|
||||
{
|
||||
return $this->is_no_user_modification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this attribute is single-valued. If this attribute only supports single values, TRUE
|
||||
* is returned. If this attribute supports multiple values, FALSE is returned.
|
||||
*
|
||||
* @return boolean Returns TRUE if this attribute is single-valued or FALSE otherwise.
|
||||
* @deprecated use class->is_single_value
|
||||
*/
|
||||
public function getIsSingleValue(): bool
|
||||
{
|
||||
return $this->is_single_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's the maximum length. If no maximum is defined by the LDAP server, NULL is returned.
|
||||
*
|
||||
* @return int The maximum length (in characters) of this attribute or NULL if no maximum is specified.
|
||||
* @deprecated use $this->max_length;
|
||||
*/
|
||||
public function getMaxLength()
|
||||
{
|
||||
return $this->max_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's ordering specification.
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $this->ordering
|
||||
*/
|
||||
public function getOrdering(): string
|
||||
{
|
||||
return $this->ordering;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's substring matching specification
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $this->sub_str_rule;
|
||||
*/
|
||||
public function getSubstr() {
|
||||
return $this->sub_str_rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's parent attribute (if any). If this attribute does not
|
||||
* inherit from another attribute, NULL is returned.
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $class->sup_attribute directly
|
||||
*/
|
||||
public function getSupAttribute() {
|
||||
return $this->sup_attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's syntax OID. Differs from getSyntaxString() in that this
|
||||
* function only returns the actual OID with any length specification removed.
|
||||
* Ie, if the syntax string is "1.2.3.4{16}", this function only retruns
|
||||
* "1.2.3.4".
|
||||
*
|
||||
* @return string The syntax OID string.
|
||||
* @deprecated use $this->syntax_oid;
|
||||
*/
|
||||
public function getSyntaxOID()
|
||||
{
|
||||
return $this->syntax_oid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this attribute's usage string as defined by the LDAP server
|
||||
*
|
||||
* @return string
|
||||
* @deprecated use $this->usage
|
||||
*/
|
||||
public function getUsage()
|
||||
{
|
||||
return $this->usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of "used in" objectClasses, that is the list of objectClasses
|
||||
* which provide this attribute.
|
||||
*
|
||||
* @return Collection An array of names of objectclasses (strings) which provide this attribute
|
||||
* @deprecated use $this->used_in_object_classes
|
||||
*/
|
||||
public function getUsedInObjectClasses(): Collection
|
||||
{
|
||||
return $this->used_in_object_classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @deprecated use $this->forced_as_may
|
||||
*/
|
||||
public function isForceMay(): bool
|
||||
{
|
||||
return $this->forced_as_may;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an attribute name from this attribute's alias array.
|
||||
*
|
||||
* @param string $alias The name of the attribute to remove.
|
||||
*/
|
||||
public function removeAlias(string $alias): void
|
||||
{
|
||||
if (($x=$this->aliases->search($alias)) !== FALSE)
|
||||
$this->aliases->forget($x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this attribute's list of aliases.
|
||||
*
|
||||
* @param Collection $aliases The array of alias names (strings)
|
||||
* @deprecated use $this->aliases =
|
||||
*/
|
||||
public function setAliases(Collection $aliases): void
|
||||
{
|
||||
$this->aliases = $aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will mark this attribute as a forced MAY attribute
|
||||
*/
|
||||
public function setForceMay() {
|
||||
$this->forced_as_may = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this attribute is single-valued.
|
||||
*
|
||||
* @param boolean $is
|
||||
*/
|
||||
public function setIsSingleValue(bool $is): void
|
||||
{
|
||||
$this->is_single_value = $is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this attribute's SUP attribute (ie, the attribute from which this attribute inherits).
|
||||
*
|
||||
* @param string $attr The name of the new parent (SUP) attribute
|
||||
*/
|
||||
public function setSupAttribute(string $attr): void
|
||||
{
|
||||
$this->sup_attribute = trim($attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Request validation array
|
||||
*
|
||||
* This will merge configured validation with schema required attributes
|
||||
*
|
||||
* @param array $array
|
||||
* @return array|null
|
||||
*/
|
||||
public function validation(array $array): ?array
|
||||
{
|
||||
// For each item in array, we need to get the OC heirachy
|
||||
$heirachy = collect($array)
|
||||
->filter()
|
||||
->map(fn($item)=>config('server')
|
||||
->schema('objectclasses',$item)
|
||||
->getSupClasses()
|
||||
->push($item))
|
||||
->flatten()
|
||||
->unique();
|
||||
|
||||
$validation = collect(Arr::get(config('ldap.validation'),$this->name_lc,[]));
|
||||
if (($heirachy->intersect($this->required_by_object_classes)->count() > 0)
|
||||
&& (! collect($validation->get($this->name_lc))->contains('required'))) {
|
||||
$validation->put($this->name_lc,array_merge(['required','min:1'],$validation->get($this->name_lc,[])))
|
||||
->put($this->name_lc.'.*',array_merge(['required','min:1'],$validation->get($this->name_lc.'.*',[])));
|
||||
}
|
||||
|
||||
return $validation->toArray();
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
use App\Exceptions\InvalidUsage;
|
||||
|
||||
/**
|
||||
* Generic parent class for all schema items.
|
||||
*
|
||||
* A schema item is an ObjectClass, an AttributeBype, a MatchingRule, or a Syntax.
|
||||
* All schema items have at least two things in common: An OID and a Description.
|
||||
*/
|
||||
abstract class Base {
|
||||
protected const DEBUG_VERBOSE = FALSE;
|
||||
|
||||
// Record the LDAP String
|
||||
private string $line;
|
||||
|
||||
// The schema item's name.
|
||||
protected string $name = '';
|
||||
|
||||
// The OID of this schema item.
|
||||
protected string $oid;
|
||||
|
||||
# The description of this schema item.
|
||||
protected string $description = '';
|
||||
|
||||
// Boolean value indicating whether this objectClass is obsolete
|
||||
private bool $is_obsolete = FALSE;
|
||||
|
||||
public function __construct(string $line)
|
||||
{
|
||||
$this->line = $line;
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'description': return $this->description;
|
||||
case 'is_obsolete': return $this->is_obsolete;
|
||||
case 'line': return $this->line;
|
||||
case 'name': return $this->name;
|
||||
case 'name_lc': return strtolower($this->name);
|
||||
case 'oid': return $this->oid;
|
||||
|
||||
default:
|
||||
throw new InvalidUsage('Unknown key:'.$key);
|
||||
}
|
||||
}
|
||||
|
||||
public function __isset(string $key): bool
|
||||
{
|
||||
return isset($this->{$key});
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated replace with $class->description
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this item is flagged as obsolete by the LDAP server.
|
||||
*
|
||||
* @deprecated replace with $this->is_obsolete
|
||||
*/
|
||||
public function getIsObsolete(): bool
|
||||
{
|
||||
return $this->is_obsolete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the objects name.
|
||||
*
|
||||
* @param boolean $lower Return the name in lower case (default)
|
||||
* @return string The name
|
||||
* @deprecated use object->name
|
||||
*/
|
||||
public function getName(bool $lower=TRUE): string
|
||||
{
|
||||
return $lower ? strtolower($this->name) : $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the objects name.
|
||||
*
|
||||
* @return string The name
|
||||
* @deprecated use object->oid
|
||||
*/
|
||||
public function getOID(): string
|
||||
{
|
||||
return $this->oid;
|
||||
}
|
||||
|
||||
public function setDescription(string $desc): void
|
||||
{
|
||||
$this->description = $desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this attribute's name.
|
||||
*
|
||||
* @param string $name The new name to give this attribute.
|
||||
*/
|
||||
public function setName($name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function setOID(string $oid): void
|
||||
{
|
||||
$this->oid = $oid;
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Represents an LDAP Syntax
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class LDAPSyntax extends Base {
|
||||
// Is human readable?
|
||||
private ?bool $is_not_human_readable = NULL;
|
||||
|
||||
// Binary transfer required?
|
||||
private ?bool $binary_transfer_required = NULL;
|
||||
|
||||
/**
|
||||
* Creates a new Syntax object from a raw LDAP syntax string.
|
||||
*/
|
||||
public function __construct(string $line) {
|
||||
Log::debug(sprintf('Parsing LDAPSyntax [%s]',$line));
|
||||
|
||||
parent::__construct($line);
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
for ($i=0; $i<count($strings); $i++) {
|
||||
switch($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
do {
|
||||
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
|
||||
|
||||
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
|
||||
break;
|
||||
|
||||
case 'X-BINARY-TRANSFER-REQUIRED':
|
||||
$this->binary_transfer_required = (str_replace("'",'',$strings[++$i]) === 'TRUE');
|
||||
|
||||
Log::debug(sprintf('- Case X-BINARY-TRANSFER-REQUIRED returned (%s)',$this->binary_transfer_required));
|
||||
break;
|
||||
|
||||
case 'X-NOT-HUMAN-READABLE':
|
||||
$this->is_not_human_readable = (str_replace("'",'',$strings[++$i]) === 'TRUE');
|
||||
|
||||
Log::debug(sprintf('- Case X-NOT-HUMAN-READABLE returned (%s)',$this->is_not_human_readable));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'binary_transfer_required': return $this->binary_transfer_required;
|
||||
case 'is_not_human_readable': return $this->is_not_human_readable;
|
||||
|
||||
default: return parent::__get($key);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Represents an LDAP MatchingRule
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class MatchingRule extends Base {
|
||||
// This rule's syntax OID
|
||||
private ?string $syntax = NULL;
|
||||
|
||||
// An array of attribute names who use this MatchingRule
|
||||
private Collection $used_by_attrs;
|
||||
|
||||
/**
|
||||
* Creates a new MatchingRule object from a raw LDAP MatchingRule string.
|
||||
*/
|
||||
function __construct(string $line) {
|
||||
Log::debug(sprintf('Parsing MatchingRule [%s]',$line));
|
||||
|
||||
parent::__construct($line);
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
// Init
|
||||
$this->used_by_attrs = collect();
|
||||
|
||||
for ($i=0; $i<count($strings); $i++) {
|
||||
switch ($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
case 'NAME':
|
||||
if ($strings[$i+1] != '(') {
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
do {
|
||||
$i++;
|
||||
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i]));
|
||||
}
|
||||
|
||||
$this->name = preg_replace("/^\'/",'',$this->name);
|
||||
$this->name = preg_replace("/\'$/",'',$this->name);
|
||||
|
||||
Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name)));
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
do {
|
||||
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
|
||||
|
||||
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
|
||||
break;
|
||||
|
||||
case 'OBSOLETE':
|
||||
$this->is_obsolete = TRUE;
|
||||
|
||||
Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete));
|
||||
break;
|
||||
|
||||
case 'SYNTAX':
|
||||
$this->syntax = $strings[++$i];
|
||||
|
||||
Log::debug(sprintf('- Case SYNTAX returned (%s)',$this->syntax));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'syntax': return $this->syntax;
|
||||
case 'used_by_attrs': return $this->used_by_attrs;
|
||||
|
||||
default: return parent::__get($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute name to the list of attributes who use this MatchingRule
|
||||
*/
|
||||
public function addUsedByAttr(string $name): void
|
||||
{
|
||||
$name = trim($name);
|
||||
|
||||
if (! $this->used_by_attrs->contains($name))
|
||||
$this->used_by_attrs->push($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of attribute names (strings) which use this MatchingRule
|
||||
*
|
||||
* @return array The array of attribute names (strings).
|
||||
* @deprecated use $this->used_by_attrs
|
||||
*/
|
||||
public function getUsedByAttrs()
|
||||
{
|
||||
return $this->used_by_attrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of used_by_attrs to the array specified by $attrs;
|
||||
*
|
||||
* @param Collection $attrs The array of attribute names (strings) which use this MatchingRule
|
||||
*/
|
||||
public function setUsedByAttrs(Collection $attrs): void
|
||||
{
|
||||
$this->used_by_attrs = $attrs;
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Represents an LDAP schema matchingRuleUse entry
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class MatchingRuleUse extends Base {
|
||||
// An array of attribute names who use this MatchingRule
|
||||
private Collection $used_by_attrs;
|
||||
|
||||
function __construct(string $line) {
|
||||
Log::debug(sprintf('Parsing MatchingRuleUse [%s]',$line));
|
||||
|
||||
parent::__construct($line);
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
// Init
|
||||
$this->used_by_attrs = collect();
|
||||
|
||||
for ($i=0; $i<count($strings); $i++) {
|
||||
switch ($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
case 'NAME':
|
||||
if ($strings[$i+1] != '(') {
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match("/\'$/s",$strings[$i]));
|
||||
|
||||
do {
|
||||
$i++;
|
||||
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i]));
|
||||
}
|
||||
|
||||
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
|
||||
|
||||
Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name)));
|
||||
break;
|
||||
|
||||
case 'APPLIES':
|
||||
if ($strings[$i+1] != '(') {
|
||||
// Has a single attribute name
|
||||
$this->used_by_attrs = collect($strings[++$i]);
|
||||
|
||||
} else {
|
||||
// Has multiple attribute names
|
||||
while ($strings[++$i] != ')') {
|
||||
$new_attr = $strings[++$i];
|
||||
$new_attr = preg_replace("/^\'(.*)\'$/",'$1',$new_attr);
|
||||
|
||||
$this->used_by_attrs->push($new_attr);
|
||||
}
|
||||
}
|
||||
|
||||
Log::debug(sprintf('- Case APPLIES returned (%s)',$this->used_by_attrs->join(',')));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of attribute names (strings) which use this MatchingRuleUse object.
|
||||
*
|
||||
* @return array The array of attribute names (strings).
|
||||
* @deprecated use $this->used_by_attrs
|
||||
*/
|
||||
public function getUsedByAttrs()
|
||||
{
|
||||
return $this->used_by_attrs;
|
||||
}
|
||||
}
|
@ -1,552 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\LDAP\Server;
|
||||
use App\Exceptions\InvalidUsage;
|
||||
|
||||
/**
|
||||
* Represents an LDAP Schema objectClass
|
||||
*
|
||||
* @package phpLDAPadmin
|
||||
* @subpackage Schema
|
||||
*/
|
||||
final class ObjectClass extends Base
|
||||
{
|
||||
// Array of objectClass names from which this objectClass inherits
|
||||
private Collection $sup_classes;
|
||||
|
||||
// One of STRUCTURAL, ABSTRACT, or AUXILIARY
|
||||
private int $type;
|
||||
|
||||
// Arrays of attribute names that this objectClass requires
|
||||
private Collection $must_attrs;
|
||||
|
||||
// Arrays of attribute names that this objectClass allows, but does not require
|
||||
private Collection $may_attrs;
|
||||
|
||||
// Arrays of attribute names that this objectClass has been forced to MAY attrs, due to configuration
|
||||
private Collection $may_force;
|
||||
|
||||
// Array of objectClasses which inherit from this one
|
||||
private Collection $child_objectclasses;
|
||||
|
||||
private bool $is_obsolete;
|
||||
|
||||
/**
|
||||
* Creates a new ObjectClass object given a raw LDAP objectClass string.
|
||||
*
|
||||
* eg: ( 2.5.6.0 NAME 'top' DESC 'top of the superclass chain' ABSTRACT MUST objectClass )
|
||||
*
|
||||
* @param string $line Schema Line
|
||||
* @param Server $server
|
||||
* @todo Change $server to $connection, no need to store the server object here
|
||||
*/
|
||||
public function __construct(string $line,Server $server)
|
||||
{
|
||||
parent::__construct($line);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('Parsing ObjectClass [%s]',$line));
|
||||
|
||||
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
// Init
|
||||
$this->may_attrs = collect();
|
||||
$this->may_force = collect();
|
||||
$this->must_attrs = collect();
|
||||
$this->sup_classes = collect();
|
||||
$this->child_objectclasses = collect();
|
||||
|
||||
for ($i=0; $i < count($strings); $i++) {
|
||||
switch ($strings[$i]) {
|
||||
case '(':
|
||||
case ')':
|
||||
break;
|
||||
|
||||
case 'NAME':
|
||||
if ($strings[$i+1] != '(') {
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match('/\'$/s',$strings[$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match('/\'$/s',$strings[$i]));
|
||||
|
||||
do {
|
||||
$i++;
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i]));
|
||||
}
|
||||
|
||||
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name)));
|
||||
break;
|
||||
|
||||
case 'DESC':
|
||||
do {
|
||||
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
|
||||
|
||||
} while (! preg_match('/\'$/s',$strings[$i]));
|
||||
|
||||
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
|
||||
break;
|
||||
|
||||
case 'OBSOLETE':
|
||||
$this->is_obsolete = TRUE;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete));
|
||||
break;
|
||||
|
||||
case 'SUP':
|
||||
if ($strings[$i+1] != '(') {
|
||||
$this->sup_classes->push(preg_replace("/'/",'',$strings[++$i]));
|
||||
|
||||
} else {
|
||||
$i++;
|
||||
|
||||
do {
|
||||
$i++;
|
||||
|
||||
if ($strings[$i] != '$')
|
||||
$this->sup_classes->push(preg_replace("/'/",'',$strings[$i]));
|
||||
|
||||
} while (! preg_match('/\)+\)?/',$strings[$i+1]));
|
||||
}
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case SUP returned (%s)',$this->sup_classes->join(',')));
|
||||
break;
|
||||
|
||||
case 'ABSTRACT':
|
||||
$this->type = Server::OC_ABSTRACT;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case ABSTRACT returned (%s)',$this->type));
|
||||
break;
|
||||
|
||||
case 'STRUCTURAL':
|
||||
$this->type = Server::OC_STRUCTURAL;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case STRUCTURAL returned (%s)',$this->type));
|
||||
break;
|
||||
|
||||
case 'AUXILIARY':
|
||||
$this->type = Server::OC_AUXILIARY;
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case AUXILIARY returned (%s)',$this->type));
|
||||
break;
|
||||
|
||||
case 'MUST':
|
||||
$attrs = collect();
|
||||
|
||||
$i = $this->parseList(++$i,$strings,$attrs);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('= parseList returned %d (%s)',$i,$attrs->join(',')));
|
||||
|
||||
foreach ($attrs as $string) {
|
||||
$attr = new ObjectClassAttribute($string,$this->name);
|
||||
|
||||
if ($server->isForceMay($attr->getName())) {
|
||||
$this->may_force->push($attr);
|
||||
$this->may_attrs->push($attr);
|
||||
|
||||
} else
|
||||
$this->must_attrs->push($attr);
|
||||
}
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case MUST returned (%s) (%s)',$this->must_attrs->join(','),$this->may_force->join(',')));
|
||||
break;
|
||||
|
||||
case 'MAY':
|
||||
$attrs = collect();
|
||||
|
||||
$i = $this->parseList(++$i,$strings,$attrs);
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('parseList returned %d (%s)',$i,$attrs->join(',')));
|
||||
|
||||
foreach ($attrs as $string) {
|
||||
$attr = new ObjectClassAttribute($string,$this->name);
|
||||
$this->may_attrs->push($attr);
|
||||
}
|
||||
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case MAY returned (%s)',$this->may_attrs->join(',')));
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
|
||||
$this->oid = $strings[$i];
|
||||
if (static::DEBUG_VERBOSE)
|
||||
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
|
||||
|
||||
} elseif ($strings[$i])
|
||||
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match ($key) {
|
||||
'attributes' => $this->getAllAttrs(TRUE),
|
||||
'sup' => $this->sup_classes,
|
||||
'type_name' => match ($this->type) {
|
||||
Server::OC_STRUCTURAL => 'Structural',
|
||||
Server::OC_ABSTRACT => 'Abstract',
|
||||
Server::OC_AUXILIARY => 'Auxiliary',
|
||||
default => throw new InvalidUsage('Unknown ObjectClass Type: ' . $this->type),
|
||||
},
|
||||
default => parent::__get($key),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of attributes that this objectClass provides
|
||||
*
|
||||
* @param bool $parents
|
||||
* @return Collection
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function getAllAttrs(bool $parents=FALSE): Collection
|
||||
{
|
||||
return $this->getMustAttrs($parents)
|
||||
->transform(function($item) {
|
||||
$item->required = true;
|
||||
return $item;
|
||||
})
|
||||
->merge($this->getMayAttrs($parents));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an objectClass to the list of objectClasses that inherit
|
||||
* from this objectClass.
|
||||
*
|
||||
* @param String $name The name of the objectClass to add
|
||||
*/
|
||||
public function addChildObjectClass(string $name): void
|
||||
{
|
||||
if (! $this->child_objectclasses->contains($name))
|
||||
$this->child_objectclasses->push($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of objectClass names which inherit from this objectClass.
|
||||
*
|
||||
* @return Collection Names of objectClasses which inherit from this objectClass.
|
||||
* @deprecated use $this->child_objectclasses
|
||||
*/
|
||||
public function getChildObjectClasses(): Collection
|
||||
{
|
||||
return $this->child_objectclasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves identically to addMustAttrs, but it operates on the MAY
|
||||
* attributes of this objectClass.
|
||||
*
|
||||
* @param array $attr An array of attribute names (strings) to add.
|
||||
*/
|
||||
private function addMayAttrs(array $attr): void
|
||||
{
|
||||
if (! is_array($attr) || ! count($attr))
|
||||
return;
|
||||
|
||||
$this->may_attrs = $this->may_attrs->merge($attr)->unique();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified array of attributes to this objectClass' list of
|
||||
* MUST attributes. The resulting array of must attributes will contain
|
||||
* unique members.
|
||||
*
|
||||
* @param array $attr An array of attribute names (strings) to add.
|
||||
*/
|
||||
private function addMustAttrs(array $attr): void
|
||||
{
|
||||
if (! is_array($attr) || ! count($attr))
|
||||
return;
|
||||
|
||||
$this->must_attrs = $this->must_attrs->merge($attr)->unique();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
* @deprecated use $this->may_force
|
||||
*/
|
||||
public function getForceMayAttrs(): Collection
|
||||
{
|
||||
return $this->may_force;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of AttributeType objects that entries of this ObjectClass may define.
|
||||
* This differs from getMayAttrNames in that it returns an array of AttributeType objects
|
||||
*
|
||||
* @param bool $parents Also get the may attrs of our parents.
|
||||
* @return Collection The array of allowed AttributeType objects.
|
||||
*
|
||||
* @throws InvalidUsage
|
||||
* @see getMustAttrNames
|
||||
* @see getMustAttrs
|
||||
* @see getMayAttrNames
|
||||
* @see AttributeType
|
||||
*/
|
||||
public function getMayAttrs(bool $parents=FALSE): Collection
|
||||
{
|
||||
// If we dont need our parents, then we'll just return ours.
|
||||
if (! $parents)
|
||||
return $this->may_attrs
|
||||
->sortBy(fn($item)=>strtolower($item->name.$item->source));
|
||||
|
||||
$attrs = $this->may_attrs;
|
||||
|
||||
foreach ($this->getParents() as $object_class)
|
||||
$attrs = $attrs->merge($object_class->getMayAttrs($parents));
|
||||
|
||||
// Remove any duplicates
|
||||
$attrs = $attrs->unique(function($item) { return $item->name; });
|
||||
|
||||
// Return a sorted list
|
||||
return $attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of attribute names (strings) that entries of this ObjectClass must define.
|
||||
* This differs from getMayAttrs in that it returns an array of strings rather than
|
||||
* array of AttributeType objects
|
||||
*
|
||||
* @param bool $parents An array of ObjectClass objects to use when traversing
|
||||
* the inheritance tree. This presents some what of a bootstrapping problem
|
||||
* as we must fetch all objectClasses to determine through inheritance which
|
||||
* attributes this objectClass provides.
|
||||
* @return Collection The array of allowed attribute names (strings).
|
||||
*
|
||||
* @throws InvalidUsage
|
||||
* @see getMustAttrs
|
||||
* @see getMayAttrs
|
||||
* @see getMustAttrNames
|
||||
*/
|
||||
public function getMayAttrNames(bool $parents=FALSE): Collection
|
||||
{
|
||||
return $this->getMayAttrs($parents)->ppluck('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of AttributeType objects that entries of this ObjectClass must define.
|
||||
* This differs from getMustAttrNames in that it returns an array of AttributeType objects
|
||||
*
|
||||
* @param bool $parents Also get the must attrs of our parents.
|
||||
* @return Collection The array of required AttributeType objects.
|
||||
*
|
||||
* @throws InvalidUsage
|
||||
* @see getMustAttrNames
|
||||
* @see getMayAttrs
|
||||
* @see getMayAttrNames
|
||||
*/
|
||||
public function getMustAttrs(bool $parents=FALSE): Collection
|
||||
{
|
||||
// If we dont need our parents, then we'll just return ours.
|
||||
if (! $parents)
|
||||
return $this->must_attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
|
||||
|
||||
$attrs = $this->must_attrs;
|
||||
|
||||
foreach ($this->getParents() as $object_class)
|
||||
$attrs = $attrs->merge($object_class->getMustAttrs($parents));
|
||||
|
||||
// Remove any duplicates
|
||||
$attrs = $attrs->unique(function($item) { return $item->name; });
|
||||
|
||||
// Return a sorted list
|
||||
return $attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of attribute names (strings) that entries of this ObjectClass must define.
|
||||
* This differs from getMustAttrs in that it returns an array of strings rather than
|
||||
* array of AttributeType objects
|
||||
*
|
||||
* @param bool $parents An array of ObjectClass objects to use when traversing
|
||||
* the inheritance tree. This presents some what of a bootstrapping problem
|
||||
* as we must fetch all objectClasses to determine through inheritance which
|
||||
* attributes this objectClass provides.
|
||||
* @return Collection The array of allowed attribute names (strings).
|
||||
*
|
||||
* @throws InvalidUsage
|
||||
* @see getMustAttrs
|
||||
* @see getMayAttrs
|
||||
* @see getMayAttrNames
|
||||
*/
|
||||
public function getMustAttrNames(bool $parents=FALSE): Collection
|
||||
{
|
||||
return $this->getMustAttrs($parents)->ppluck('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return all our parent ObjectClass Objects
|
||||
*/
|
||||
public function getParents(): Collection
|
||||
{
|
||||
// If the only class is 'top', then we have no more parents
|
||||
if (($this->sup_classes->count() === 1) && (strtolower($this->sup_classes->first()) === 'top'))
|
||||
return collect();
|
||||
|
||||
$result = collect();
|
||||
|
||||
foreach ($this->sup_classes as $object_class) {
|
||||
$oc = config('server')
|
||||
->schema('objectclasses',$object_class);
|
||||
|
||||
if ($oc) {
|
||||
$result->push($oc);
|
||||
$result = $result->merge($oc->getParents());
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the objectClass names from which this objectClass inherits.
|
||||
*
|
||||
* @return Collection An array of objectClass names (strings)
|
||||
* @deprecated use $this->sup_classes;
|
||||
*/
|
||||
public function getSupClasses(): Collection
|
||||
{
|
||||
return $this->sup_classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of this objectClass: STRUCTURAL, ABSTRACT, or AUXILIARY.
|
||||
*
|
||||
* @deprecated use $this->type_name
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if this objectclass is auxiliary
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAuxiliary(): bool
|
||||
{
|
||||
return $this->type === Server::OC_AUXILIARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an array is listed in the may_force attrs
|
||||
*/
|
||||
public function isForceMay(string $attr): bool
|
||||
{
|
||||
return $this->may_force->ppluck('name')->contains($attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if this objectClass is related to $oclass
|
||||
*
|
||||
* @param array $oclass ObjectClasses that this attribute may be related to
|
||||
* @return bool
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function isRelated(array $oclass): bool
|
||||
{
|
||||
// If I am in the array, we'll just return false
|
||||
if (in_array_ignore_case($this->name,$oclass))
|
||||
return FALSE;
|
||||
|
||||
foreach ($oclass as $object_class)
|
||||
if ($object_class->isStructural() && in_array_ignore_case($this->name,$object_class->getParents()->pluck('name')))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
public function isStructural(): bool
|
||||
{
|
||||
return $this->type === Server::OC_STRUCTURAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an LDAP schema list
|
||||
*
|
||||
* A list starts with a ( followed by a list of attributes separated by $ terminated by )
|
||||
* The first token can therefore be a ( or a (NAME or a (NAME)
|
||||
* The last token can therefore be a ) or NAME)
|
||||
* The last token may be terminated by more than one bracket
|
||||
*/
|
||||
private function parseList(int $i,array $strings,Collection &$attrs): int
|
||||
{
|
||||
$string = $strings[$i];
|
||||
|
||||
if (! preg_match('/^\(/',$string)) {
|
||||
// A bareword only - can be terminated by a ) if the last item
|
||||
if (preg_match('/\)+$/',$string))
|
||||
$string = preg_replace('/\)+$/','',$string);
|
||||
|
||||
$attrs->push($string);
|
||||
|
||||
} elseif (preg_match('/^\(.*\)$/',$string)) {
|
||||
$string = preg_replace('/^\(/','',$string);
|
||||
$string = preg_replace('/\)+$/','',$string);
|
||||
|
||||
$attrs->push($string);
|
||||
|
||||
} else {
|
||||
// Handle the opening cases first
|
||||
if ($string === '(') {
|
||||
$i++;
|
||||
|
||||
} elseif (preg_match('/^\(./',$string)) {
|
||||
$string = preg_replace('/^\(/','',$string);
|
||||
$attrs->push($string);
|
||||
$i++;
|
||||
}
|
||||
|
||||
// Token is either a name, a $ or a ')'
|
||||
// NAME can be terminated by one or more ')'
|
||||
while (! preg_match('/\)+$/',$strings[$i])) {
|
||||
$string = $strings[$i];
|
||||
|
||||
if ($string === '$') {
|
||||
$i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('/\)$/',$string))
|
||||
$string = preg_replace('/\)+$/','',$string);
|
||||
else
|
||||
$i++;
|
||||
|
||||
$attrs->push($string);
|
||||
}
|
||||
}
|
||||
|
||||
$attrs = $attrs->sort();
|
||||
|
||||
return $i;
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Schema;
|
||||
|
||||
/**
|
||||
* A simple class for representing AttributeTypes used only by the ObjectClass class.
|
||||
*
|
||||
* Users should never instantiate this class. It represents an attribute internal to
|
||||
* an ObjectClass. If PHP supported inner-classes and variable permissions, this would
|
||||
* be interior to class ObjectClass and flagged private. The reason this class is used
|
||||
* and not the "real" class AttributeType is because this class supports the notion of
|
||||
* a "source" objectClass, meaning that it keeps track of which objectClass originally
|
||||
* specified it. This class is therefore used by the class ObjectClass to determine
|
||||
* inheritance.
|
||||
*/
|
||||
final class ObjectClassAttribute extends Base {
|
||||
// This Attribute's root.
|
||||
private string $source;
|
||||
public bool $required = FALSE;
|
||||
|
||||
/**
|
||||
* Creates a new ObjectClassAttribute with specified name and source objectClass.
|
||||
*
|
||||
* @param string $name the name of the new attribute.
|
||||
* @param string $source the name of the ObjectClass which specifies this attribute.
|
||||
*/
|
||||
public function __construct($name,$source)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match ($key) {
|
||||
'source' => $this->source,
|
||||
default => parent::__get($key),
|
||||
};
|
||||
}
|
||||
}
|
@ -1,537 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use LdapRecord\LdapRecordException;
|
||||
use LdapRecord\Models\Model;
|
||||
use LdapRecord\Query\Collection as LDAPCollection;
|
||||
use LdapRecord\Query\ObjectNotFoundException;
|
||||
|
||||
use App\Classes\LDAP\Schema\{AttributeType,Base,LDAPSyntax,MatchingRule,MatchingRuleUse,ObjectClass};
|
||||
use App\Exceptions\InvalidUsage;
|
||||
use App\Ldap\Entry;
|
||||
|
||||
final class Server
|
||||
{
|
||||
// Connection information used for these object and children
|
||||
private ?string $connection;
|
||||
|
||||
// This servers schema objectclasses
|
||||
private Collection $attributetypes;
|
||||
private Collection $ldapsyntaxes;
|
||||
private Collection $matchingrules;
|
||||
private Collection $matchingruleuse;
|
||||
private Collection $objectclasses;
|
||||
|
||||
/* ObjectClass Types */
|
||||
public const OC_STRUCTURAL = 0x01;
|
||||
public const OC_ABSTRACT = 0x02;
|
||||
public const OC_AUXILIARY = 0x03;
|
||||
|
||||
public function __construct(?string $connection=NULL)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return match($key) {
|
||||
'attributetypes' => $this->attributetypes,
|
||||
'connection' => $this->connection,
|
||||
'ldapsyntaxes' => $this->ldapsyntaxes,
|
||||
'matchingrules' => $this->matchingrules,
|
||||
'objectclasses' => $this->objectclasses,
|
||||
'config' => config('ldap.connections.'.config('ldap.default')),
|
||||
'name' => Arr::get($this->config,'name',__('No Server Name Yet')),
|
||||
default => throw new Exception('Unknown key:' . $key),
|
||||
};
|
||||
}
|
||||
|
||||
/* STATIC METHODS */
|
||||
|
||||
/**
|
||||
* Gets the root DN of the specified LDAPServer, or throws an exception if it
|
||||
* can't find it.
|
||||
*
|
||||
* @param string|null $connection Return a collection of baseDNs
|
||||
* @param bool $objects Return a collection of Entry Models
|
||||
* @return Collection
|
||||
* @throws ObjectNotFoundException
|
||||
* @testedin GetBaseDNTest::testBaseDNExists();
|
||||
* @todo Need to allow for the scenario if the baseDN is not readable by ACLs
|
||||
*/
|
||||
public static function baseDNs(?string $connection=NULL,bool $objects=TRUE): Collection
|
||||
{
|
||||
$cachetime = Carbon::now()
|
||||
->addSeconds(Config::get('ldap.cache.time'));
|
||||
|
||||
try {
|
||||
$base = self::rootDSE($connection,$cachetime);
|
||||
|
||||
/**
|
||||
* LDAP Error Codes:
|
||||
* https://ldap.com/ldap-result-code-reference/
|
||||
* + success 0
|
||||
* + operationsError 1
|
||||
* + protocolError 2
|
||||
* + timeLimitExceeded 3
|
||||
* + sizeLimitExceeded 4
|
||||
* + compareFalse 5
|
||||
* + compareTrue 6
|
||||
* + authMethodNotSupported 7
|
||||
* + strongerAuthRequired 8
|
||||
* + referral 10
|
||||
* + adminLimitExceeded 11
|
||||
* + unavailableCriticalExtension 12
|
||||
* + confidentialityRequired 13
|
||||
* + saslBindInProgress 14
|
||||
* + noSuchAttribute 16
|
||||
* + undefinedAttributeType 17
|
||||
* + inappropriateMatching 18
|
||||
* + constraintViolation 19
|
||||
* + attributeOrValueExists 20
|
||||
* + invalidAttributeSyntax 21
|
||||
* + noSuchObject 32
|
||||
* + aliasProblem 33
|
||||
* + invalidDNSyntax 34
|
||||
* + isLeaf 35
|
||||
* + aliasDereferencingProblem 36
|
||||
* + inappropriateAuthentication 48
|
||||
* + invalidCredentials 49
|
||||
* + insufficientAccessRights 50
|
||||
* + busy 51
|
||||
* + unavailable 52
|
||||
* + unwillingToPerform 53
|
||||
* + loopDetect 54
|
||||
* + sortControlMissing 60
|
||||
* + offsetRangeError 61
|
||||
* + namingViolation 64
|
||||
* + objectClassViolation 65
|
||||
* + notAllowedOnNonLeaf 66
|
||||
* + notAllowedOnRDN 67
|
||||
* + entryAlreadyExists 68
|
||||
* + objectClassModsProhibited 69
|
||||
* + resultsTooLarge 70
|
||||
* + affectsMultipleDSAs 71
|
||||
* + virtualListViewError or controlError 76
|
||||
* + other 80
|
||||
* + serverDown 81
|
||||
* + localError 82
|
||||
* + encodingError 83
|
||||
* + decodingError 84
|
||||
* + timeout 85
|
||||
* + authUnknown 86
|
||||
* + filterError 87
|
||||
* + userCanceled 88
|
||||
* + paramError 89
|
||||
* + noMemory 90
|
||||
* + connectError 91
|
||||
* + notSupported 92
|
||||
* + controlNotFound 93
|
||||
* + noResultsReturned 94
|
||||
* + moreResultsToReturn 95
|
||||
* + clientLoop 96
|
||||
* + referralLimitExceeded 97
|
||||
* + invalidResponse 100
|
||||
* + ambiguousResponse 101
|
||||
* + tlsNotSupported 112
|
||||
* + intermediateResponse 113
|
||||
* + unknownType 114
|
||||
* + canceled 118
|
||||
* + noSuchOperation 119
|
||||
* + tooLate 120
|
||||
* + cannotCancel 121
|
||||
* + assertionFailed 122
|
||||
* + authorizationDenied 123
|
||||
* + e-syncRefreshRequired 4096
|
||||
* + noOperation 16654
|
||||
*
|
||||
* LDAP Tag Codes:
|
||||
* + A client bind operation 97
|
||||
* + The entry for which you were searching 100
|
||||
* + The result from a search operation 101
|
||||
* + The result from a modify operation 103
|
||||
* + The result from an add operation 105
|
||||
* + The result from a delete operation 107
|
||||
* + The result from a modify DN operation 109
|
||||
* + The result from a compare operation 111
|
||||
* + A search reference when the entry you perform your search on holds a referral to the entry you require.
|
||||
* + Search references are expressed in terms of a referral.
|
||||
* 115
|
||||
* + A result from an extended operation 120
|
||||
*/
|
||||
// If we cannot get to our LDAP server we'll head straight to the error page
|
||||
} catch (LdapRecordException $e) {
|
||||
switch ($e->getDetailedError()?->getErrorCode()) {
|
||||
case 49:
|
||||
// Since we failed authentication, we should delete our auth cookie
|
||||
if (Cookie::has('password_encrypt')) {
|
||||
Log::alert('Clearing user credentials and logging out');
|
||||
|
||||
Cookie::queue(Cookie::forget('password_encrypt'));
|
||||
Cookie::queue(Cookie::forget('username_encrypt'));
|
||||
|
||||
Session::invalidate();
|
||||
}
|
||||
|
||||
abort(401,$e->getDetailedError()->getErrorMessage());
|
||||
|
||||
default:
|
||||
abort(597,$e->getDetailedError()?->getErrorMessage() ?: $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (! $objects)
|
||||
return collect($base->namingcontexts);
|
||||
|
||||
/**
|
||||
* @note While we are caching our baseDNs, it seems if we have more than 1,
|
||||
* our caching doesnt generate a hit on a subsequent call to this function (before the cache expires).
|
||||
* IE: If we have 5 baseDNs, it takes 5 calls to this function to case them all.
|
||||
* @todo Possibly a bug wtih ldaprecord, so need to investigate
|
||||
*/
|
||||
$result = collect();
|
||||
foreach ($base->namingcontexts as $dn)
|
||||
$result->push((new Entry)->cache($cachetime)->findOrFail($dn));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the rootDSE for the server, that gives us server information
|
||||
*
|
||||
* @param null $connection
|
||||
* @return Entry|null
|
||||
* @throws ObjectNotFoundException
|
||||
* @testedin TranslateOidTest::testRootDSE();
|
||||
*/
|
||||
public static function rootDSE(?string $connection=NULL,?Carbon $cachetime=NULL): ?Model
|
||||
{
|
||||
$e = new Entry;
|
||||
|
||||
return Entry::on($connection ?? $e->getConnectionName())
|
||||
->cache($cachetime)
|
||||
->in(NULL)
|
||||
->read()
|
||||
->select(['+'])
|
||||
->whereHas('objectclass')
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Schema DN
|
||||
*
|
||||
* @param $connection
|
||||
* @return string
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public static function schemaDN(?string $connection=NULL): string
|
||||
{
|
||||
$cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time'));
|
||||
|
||||
return collect(self::rootDSE($connection,$cachetime)->subschemasubentry)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the server for a DN and return its children and if those children have children.
|
||||
*
|
||||
* @param string $dn
|
||||
* @return LDAPCollection|NULL
|
||||
*/
|
||||
public function children(string $dn): ?LDAPCollection
|
||||
{
|
||||
return ($x=(new Entry)
|
||||
->on($this->connection)
|
||||
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
|
||||
->select(['*','hassubordinates'])
|
||||
->setDn($dn)
|
||||
->list()
|
||||
->get()) ? $x : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a DN from the server
|
||||
*
|
||||
* @param string $dn
|
||||
* @param array $attrs
|
||||
* @return Entry|null
|
||||
*/
|
||||
public function fetch(string $dn,array $attrs=['*','+']): ?Entry
|
||||
{
|
||||
return ($x=(new Entry)
|
||||
->on($this->connection)
|
||||
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
|
||||
->select($attrs)
|
||||
->find($dn)) ? $x : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function determines if the specified attribute is contained in the force_may list
|
||||
* as configured in config.php.
|
||||
*
|
||||
* @return boolean True if the specified attribute is configured to be force as a may attribute
|
||||
*/
|
||||
public function isForceMay($attr_name): bool
|
||||
{
|
||||
return in_array($attr_name,config('pla.force_may',[]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this server support RFC3666 language tags
|
||||
* OID: 1.3.6.1.4.1.4203.1.5.4
|
||||
*
|
||||
* @return bool
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public function isLanguageTags(): bool
|
||||
{
|
||||
return in_array('1.3.6.1.4.1.4203.1.5.4',$this->rootDSE()->supportedfeatures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the server's schema
|
||||
*
|
||||
* @param string $item Schema Item to Fetch
|
||||
* @param string|null $key
|
||||
* @return Collection|LDAPSyntax|Base|NULL
|
||||
*/
|
||||
public function schema(string $item,?string $key=NULL): Collection|LDAPSyntax|Base|NULL
|
||||
{
|
||||
// Ensure our item to fetch is lower case
|
||||
$item = strtolower($item);
|
||||
if ($key)
|
||||
$key = strtolower($key);
|
||||
|
||||
$result = Cache::remember('schema'.$item,config('ldap.cache.time'),function() use ($item) {
|
||||
// First pass if we have already retrieved the schema item
|
||||
switch ($item) {
|
||||
case 'attributetypes':
|
||||
if (isset($this->attributetypes))
|
||||
return $this->attributetypes;
|
||||
else
|
||||
$this->attributetypes = collect();
|
||||
|
||||
break;
|
||||
|
||||
case 'ldapsyntaxes':
|
||||
if (isset($this->ldapsyntaxes))
|
||||
return $this->ldapsyntaxes;
|
||||
else
|
||||
$this->ldapsyntaxes = collect();
|
||||
|
||||
break;
|
||||
|
||||
case 'matchingrules':
|
||||
if (isset($this->matchingrules))
|
||||
return $this->matchingrules;
|
||||
else
|
||||
$this->matchingrules = collect();
|
||||
|
||||
break;
|
||||
|
||||
case 'objectclasses':
|
||||
if (isset($this->objectclasses))
|
||||
return $this->objectclasses;
|
||||
else
|
||||
$this->objectclasses = collect();
|
||||
|
||||
break;
|
||||
|
||||
// This error message is not localized as only developers should ever see it
|
||||
default:
|
||||
throw new InvalidUsage('Invalid request to fetch schema: '.$item);
|
||||
}
|
||||
|
||||
// Try to get the schema DN from the specified entry.
|
||||
$schema_dn = $this->schemaDN($this->connection);
|
||||
$schema = $this->fetch($schema_dn);
|
||||
|
||||
// If our schema's null, we didnt find it.
|
||||
if (! $schema)
|
||||
throw new Exception('Couldnt find schema at:'.$schema_dn);
|
||||
|
||||
switch ($item) {
|
||||
case 'attributetypes':
|
||||
Log::debug('Attribute Types');
|
||||
// build the array of attribueTypes
|
||||
//$syntaxes = $this->SchemaSyntaxes($dn);
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new AttributeType($line);
|
||||
$this->attributetypes->put($o->name_lc,$o);
|
||||
}
|
||||
|
||||
// go back and add data from aliased attributeTypes
|
||||
foreach ($this->attributetypes as $o) {
|
||||
/* foreach of the attribute's aliases, create a new entry in the attrs array
|
||||
* with its name set to the alias name, and all other data copied.*/
|
||||
|
||||
if ($o->aliases->count()) {
|
||||
Log::debug(sprintf('\ Attribute [%s] has the following aliases [%s]',$o->name,$o->aliases->join(',')));
|
||||
|
||||
foreach ($o->aliases as $alias) {
|
||||
$new_attr = clone $o;
|
||||
$new_attr->setName($alias);
|
||||
$new_attr->addAlias($o->name);
|
||||
$new_attr->removeAlias($alias);
|
||||
|
||||
$this->attributetypes->put(strtolower($alias),$new_attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now go through and reference the parent/child relationships
|
||||
foreach ($this->attributetypes as $o)
|
||||
if ($o->sup_attribute) {
|
||||
$parent = strtolower($o->sup_attribute);
|
||||
|
||||
if ($this->attributetypes->has($parent) !== FALSE)
|
||||
$this->attributetypes[$parent]->addChild($o->name);
|
||||
}
|
||||
|
||||
// go through any children and add details if the child doesnt have them (ie, cn inherits name)
|
||||
// @todo This doesnt traverse children properly, so children of children may not get the settings they should
|
||||
foreach ($this->attributetypes as $parent) {
|
||||
foreach ($parent->children as $child) {
|
||||
$child = strtolower($child);
|
||||
|
||||
/* only overwrite the child's SINGLE-VALUE property if the parent has it set, and the child doesnt
|
||||
* (note: All LDAP attributes default to multi-value if not explicitly set SINGLE-VALUE) */
|
||||
if (! is_null($parent->is_single_value) && is_null($this->attributetypes[$child]->is_single_value))
|
||||
$this->attributetypes[$child]->setIsSingleValue($parent->is_single_value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the used in and required_by values.
|
||||
foreach ($this->schema('objectclasses') as $object_class) {
|
||||
$must_attrs = $object_class->getMustAttrNames();
|
||||
$may_attrs = $object_class->getMayAttrNames();
|
||||
$oclass_attrs = $must_attrs->merge($may_attrs)->unique();
|
||||
|
||||
// Add Used In.
|
||||
foreach ($oclass_attrs as $attr_name)
|
||||
if ($this->attributetypes->has(strtolower($attr_name)))
|
||||
$this->attributetypes[strtolower($attr_name)]->addUsedInObjectClass($object_class->name,$object_class->isStructural());
|
||||
|
||||
// Add Required By.
|
||||
foreach ($must_attrs as $attr_name)
|
||||
if ($this->attributetypes->has(strtolower($attr_name)))
|
||||
$this->attributetypes[strtolower($attr_name)]->addRequiredByObjectClass($object_class->name);
|
||||
|
||||
// Force May
|
||||
foreach ($object_class->getForceMayAttrs() as $attr_name)
|
||||
if ($this->attributetypes->has(strtolower($attr_name->name)))
|
||||
$this->attributetypes[strtolower($attr_name->name)]->setForceMay();
|
||||
}
|
||||
|
||||
return $this->attributetypes;
|
||||
|
||||
case 'ldapsyntaxes':
|
||||
Log::debug('LDAP Syntaxes');
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new LDAPSyntax($line);
|
||||
$this->ldapsyntaxes->put(strtolower($o->oid),$o);
|
||||
}
|
||||
|
||||
return $this->ldapsyntaxes;
|
||||
|
||||
case 'matchingrules':
|
||||
Log::debug('Matching Rules');
|
||||
$this->matchingruleuse = collect();
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new MatchingRule($line);
|
||||
$this->matchingrules->put($o->name_lc,$o);
|
||||
}
|
||||
|
||||
/*
|
||||
* For each MatchingRuleUse entry, add the attributes who use it to the
|
||||
* MatchingRule in the $rules array.
|
||||
*/
|
||||
if ($schema->matchingruleuse) {
|
||||
foreach ($schema->matchingruleuse as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new MatchingRuleUse($line);
|
||||
$this->matchingruleuse->put($o->name_lc,$o);
|
||||
|
||||
if ($this->matchingrules->has($o->name_lc) !== FALSE)
|
||||
$this->matchingrules[$o->name_lc]->setUsedByAttrs($o->getUsedByAttrs());
|
||||
}
|
||||
|
||||
} else {
|
||||
/* No MatchingRuleUse entry in the subschema, so brute-forcing
|
||||
* the reverse-map for the "$rule->getUsedByAttrs()" data.*/
|
||||
foreach ($this->schema('attributetypes') as $attr) {
|
||||
$rule_key = strtolower($attr->getEquality());
|
||||
|
||||
if ($this->matchingrules->has($rule_key) !== FALSE)
|
||||
$this->matchingrules[$rule_key]->addUsedByAttr($attr->name);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->matchingrules;
|
||||
|
||||
case 'objectclasses':
|
||||
Log::debug('Object Classes');
|
||||
|
||||
foreach ($schema->{$item} as $line) {
|
||||
if (is_null($line) || ! strlen($line))
|
||||
continue;
|
||||
|
||||
$o = new ObjectClass($line,$this);
|
||||
$this->objectclasses->put($o->name_lc,$o);
|
||||
}
|
||||
|
||||
// Now go through and reference the parent/child relationships
|
||||
foreach ($this->objectclasses as $o)
|
||||
foreach ($o->getSupClasses() as $parent) {
|
||||
$parent = strtolower($parent);
|
||||
|
||||
if (! $this->objectclasses->contains($parent))
|
||||
$this->objectclasses[$parent]->addChildObjectClass($o->name);
|
||||
}
|
||||
|
||||
return $this->objectclasses;
|
||||
|
||||
// Shouldnt get here
|
||||
default:
|
||||
throw new InvalidUsage('Invalid request to fetch schema: '.$item);
|
||||
}
|
||||
});
|
||||
|
||||
return is_null($key) ? $result : $result->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an OID, return the ldapsyntax for the OID
|
||||
*
|
||||
* @param string $oid
|
||||
* @return LDAPSyntax|null
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function schemaSyntaxName(string $oid): ?LDAPSyntax
|
||||
{
|
||||
return $this->schema('ldapsyntaxes',$oid);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Import;
|
||||
|
||||
use Exception;
|
||||
|
||||
class AttributeException extends Exception {}
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Import;
|
||||
|
||||
use Exception;
|
||||
|
||||
class GeneralException extends Exception {}
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Import;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ObjectExistsException extends Exception {}
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Import;
|
||||
|
||||
use Exception;
|
||||
|
||||
class VersionException extends Exception {}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidUsage extends Exception
|
||||
{
|
||||
//
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Classes\LDAP\Server;
|
||||
|
||||
class APIController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get the LDAP server BASE DNs
|
||||
*
|
||||
* @return Collection
|
||||
* @throws \LdapRecord\Query\ObjectNotFoundException
|
||||
*/
|
||||
public function bases(): Collection
|
||||
{
|
||||
$base = Server::baseDNs() ?: collect();
|
||||
|
||||
return $base
|
||||
->transform(fn($item)=>
|
||||
[
|
||||
'title'=>$item->getRdn(),
|
||||
'item'=>$item->getDNSecure(),
|
||||
'lazy'=>TRUE,
|
||||
'icon'=>'fa-fw fas fa-sitemap',
|
||||
'tooltip'=>$item->getDn(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return Collection
|
||||
*/
|
||||
public function children(Request $request): Collection
|
||||
{
|
||||
$levels = $request->query('depth',1);
|
||||
$dn = Crypt::decryptString($request->query('key'));
|
||||
|
||||
// Sometimes our key has a command, so we'll ignore it
|
||||
if (str_starts_with($dn,'*') && ($x=strpos($dn,'|')))
|
||||
$dn = substr($dn,$x+1);
|
||||
|
||||
Log::debug(sprintf('%s: Query [%s] - Levels [%d]',__METHOD__,$dn,$levels));
|
||||
|
||||
return (config('server'))
|
||||
->children($dn)
|
||||
->transform(fn($item)=>
|
||||
[
|
||||
'title'=>$item->getRdn(),
|
||||
'item'=>$item->getDNSecure(),
|
||||
'icon'=>$item->icon(),
|
||||
'lazy'=>Arr::get($item->getAttribute('hassubordinates'),0) == 'TRUE',
|
||||
'tooltip'=>$item->getDn(),
|
||||
])
|
||||
->prepend(
|
||||
[
|
||||
'title'=>sprintf('[%s]',__('Create Entry')),
|
||||
'item'=>Crypt::encryptString(sprintf('*%s|%s','create',$dn)),
|
||||
'lazy'=>FALSE,
|
||||
'icon'=>'fas fa-fw fa-square-plus text-warning',
|
||||
'tooltip'=>__('Create new LDAP item here'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function schema_view(Request $request)
|
||||
{
|
||||
$server = new Server;
|
||||
|
||||
switch($request->type) {
|
||||
case 'objectclasses':
|
||||
return view('fragment.schema.objectclasses')
|
||||
->with('objectclasses',$server->schema('objectclasses')->sortBy(fn($item)=>strtolower($item->name)));
|
||||
|
||||
case 'attributetypes':
|
||||
return view('fragment.schema.attributetypes')
|
||||
->with('server',$server)
|
||||
->with('attributetypes',$server->schema('attributetypes')->sortBy(fn($item)=>strtolower($item->name)));
|
||||
|
||||
case 'ldapsyntaxes':
|
||||
return view('fragment.schema.ldapsyntaxes')
|
||||
->with('ldapsyntaxes',$server->schema('ldapsyntaxes')->sortBy(fn($item)=>strtolower($item->description)));
|
||||
|
||||
case 'matchingrules':
|
||||
return view('fragment.schema.matchingrules')
|
||||
->with('matchingrules',$server->schema('matchingrules')->sortBy(fn($item)=>strtolower($item->name)));
|
||||
|
||||
default:
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the required and additional attributes for an object class
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $objectclass
|
||||
* @return array
|
||||
*/
|
||||
public function schema_objectclass_attrs(Request $request,string $objectclass): array
|
||||
{
|
||||
$oc = config('server')->schema('objectclasses',$objectclass);
|
||||
|
||||
return [
|
||||
'must' => $oc->getMustAttrs()->pluck('name'),
|
||||
'may' => $oc->getMayAttrs()->pluck('name'),
|
||||
];
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Login Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller handles authenticating users for the application and
|
||||
| redirecting them to your home screen. The controller uses a trait
|
||||
| to conveniently provide its functionality to your applications.
|
||||
|
|
||||
*/
|
||||
|
||||
use AuthenticatesUsers;
|
||||
|
||||
/**
|
||||
* Where to redirect users after login.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest')->except('logout');
|
||||
}
|
||||
|
||||
protected function credentials(Request $request): array
|
||||
{
|
||||
return [
|
||||
login_attr_name() => $request->get(login_attr_name()),
|
||||
'password' => $request->get('password'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to delete our encrypted username/password cookies
|
||||
*
|
||||
* @note The rest of this function is the same as a normal laravel logout as in AuthenticatesUsers::class
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\Foundation\Application|JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|mixed
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
// Delete our LDAP authentication cookies
|
||||
Cookie::queue(Cookie::forget('username_encrypt'));
|
||||
Cookie::queue(Cookie::forget('password_encrypt'));
|
||||
|
||||
$this->guard()->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
if ($response = $this->loggedOut($request)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $request->wantsJson()
|
||||
? new JsonResponse([], 204)
|
||||
: redirect('/');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Show our themed login page
|
||||
*/
|
||||
public function showLoginForm()
|
||||
{
|
||||
$login_note = '';
|
||||
|
||||
if (file_exists('login_note.txt'))
|
||||
$login_note = file_get_contents('login_note.txt');
|
||||
|
||||
return view('architect::auth.login')->with('login_note',$login_note);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login username to be used by the controller.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function username()
|
||||
{
|
||||
return login_attr_name();
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller extends \Illuminate\Routing\Controller
|
||||
{
|
||||
//
|
||||
}
|
@ -1,553 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use LdapRecord\Exceptions\InsufficientAccessException;
|
||||
use LdapRecord\LdapRecordException;
|
||||
use LdapRecord\Query\ObjectNotFoundException;
|
||||
use Nette\NotImplementedException;
|
||||
|
||||
use App\Classes\LDAP\Attribute\{Factory,Password};
|
||||
use App\Classes\LDAP\Server;
|
||||
use App\Classes\LDAP\Import\LDIF as LDIFImport;
|
||||
use App\Classes\LDAP\Export\LDIF as LDIFExport;
|
||||
use App\Exceptions\Import\{GeneralException,VersionException};
|
||||
use App\Exceptions\InvalidUsage;
|
||||
use App\Http\Requests\{EntryRequest,EntryAddRequest,ImportRequest};
|
||||
use App\Ldap\Entry;
|
||||
use App\View\Components\AttributeType;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
private function bases(): Collection
|
||||
{
|
||||
$base = Server::baseDNs() ?: collect();
|
||||
|
||||
return $base->transform(function($item) {
|
||||
return [
|
||||
'title'=>$item->getRdn(),
|
||||
'item'=>$item->getDNSecure(),
|
||||
'lazy'=>TRUE,
|
||||
'icon'=>'fa-fw fas fa-sitemap',
|
||||
'tooltip'=>$item->getDn(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug Page
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function debug()
|
||||
{
|
||||
return view('debug');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new object in the LDAP server
|
||||
*
|
||||
* @param EntryAddRequest $request
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function entry_add(EntryAddRequest $request)
|
||||
{
|
||||
if (! old('step',$request->validated('step')))
|
||||
abort(404);
|
||||
|
||||
$key = $this->request_key($request,collect(old()));
|
||||
|
||||
$o = new Entry;
|
||||
|
||||
if (count(array_filter($x=old('objectclass',$request->objectclass)))) {
|
||||
$o->objectclass = $x;
|
||||
|
||||
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
|
||||
$o->addAttribute($ao,'');
|
||||
|
||||
$o->setRDNBase($key['dn']);
|
||||
}
|
||||
|
||||
$step = $request->step ? $request->step+1 : old('step');
|
||||
|
||||
return view('frame')
|
||||
->with('subframe','create')
|
||||
->with('bases',$this->bases())
|
||||
->with('o',$o)
|
||||
->with('step',$step)
|
||||
->with('container',old('container',$key['dn']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a new attribute view
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $id
|
||||
* @return \Closure|\Illuminate\Contracts\View\View|string
|
||||
*/
|
||||
public function entry_attr_add(Request $request,string $id): string
|
||||
{
|
||||
$xx = new \stdClass;
|
||||
$xx->index = 0;
|
||||
|
||||
$x = $request->noheader
|
||||
? (string)view(sprintf('components.attribute.widget.%s',$id))
|
||||
->with('o',Factory::create($id,[]))
|
||||
->with('value',$request->value)
|
||||
->with('loop',$xx)
|
||||
: (new AttributeType(Factory::create($id,[]),TRUE,collect($request->oc ?: [])))->render();
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public function entry_create(EntryAddRequest $request)
|
||||
{
|
||||
$key = $this->request_key($request,collect(old()));
|
||||
|
||||
$dn = sprintf('%s=%s,%s',$request->rdn,$request->rdn_value,$key['dn']);
|
||||
|
||||
$o = new Entry;
|
||||
$o->setDn($dn);
|
||||
|
||||
foreach ($request->except(['_token','key','step','rdn','rdn_value']) as $key => $value)
|
||||
$o->{$key} = array_filter($value);
|
||||
|
||||
try {
|
||||
$o->save();
|
||||
|
||||
} catch (InsufficientAccessException $e) {
|
||||
$request->flash();
|
||||
|
||||
switch ($x=$e->getDetailedError()->getErrorCode()) {
|
||||
case 50:
|
||||
return Redirect::to('/')
|
||||
->withInput()
|
||||
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
|
||||
|
||||
default:
|
||||
abort(599,$e->getDetailedError()->getErrorMessage());
|
||||
}
|
||||
|
||||
// @todo when we create an entry, and it already exists, enable a redirect to it
|
||||
} catch (LdapRecordException $e) {
|
||||
$request->flash();
|
||||
|
||||
switch ($x=$e->getDetailedError()->getErrorCode()) {
|
||||
case 8:
|
||||
return Redirect::to('/')
|
||||
->withInput()
|
||||
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
|
||||
|
||||
default:
|
||||
abort(599,$e->getDetailedError()->getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to('/')
|
||||
->withFragment($o->getDNSecure());
|
||||
}
|
||||
|
||||
public function entry_delete(Request $request)
|
||||
{
|
||||
$dn = Crypt::decryptString($request->dn);
|
||||
|
||||
$o = config('server')->fetch($dn);
|
||||
|
||||
try {
|
||||
$o->delete();
|
||||
|
||||
} catch (InsufficientAccessException $e) {
|
||||
$request->flash();
|
||||
|
||||
switch ($x=$e->getDetailedError()->getErrorCode()) {
|
||||
case 50:
|
||||
return Redirect::to('/')
|
||||
->withInput()
|
||||
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
|
||||
|
||||
default:
|
||||
abort(599,$e->getDetailedError()->getErrorMessage());
|
||||
}
|
||||
|
||||
} catch (LdapRecordException $e) {
|
||||
$request->flash();
|
||||
|
||||
switch ($x=$e->getDetailedError()->getErrorCode()) {
|
||||
case 8:
|
||||
return Redirect::to('/')
|
||||
->withInput()
|
||||
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
|
||||
|
||||
default:
|
||||
abort(599,$e->getDetailedError()->getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to('/')
|
||||
->with('success',[sprintf('%s: %s',__('Deleted'),$dn)]);
|
||||
}
|
||||
|
||||
public function entry_export(Request $request,string $id)
|
||||
{
|
||||
$dn = Crypt::decryptString($id);
|
||||
|
||||
$result = (new Entry)
|
||||
->query()
|
||||
//->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
|
||||
//->select(['*'])
|
||||
->setDn($dn)
|
||||
->recursive()
|
||||
->get();
|
||||
|
||||
return view('fragment.export')
|
||||
->with('result',new LDIFExport($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an available list of objectclasses for an Entry
|
||||
*
|
||||
* @param string $id
|
||||
* @return mixed
|
||||
*/
|
||||
public function entry_objectclass_add(Request $request)
|
||||
{
|
||||
$oc = Factory::create('objectclass',$request->oc);
|
||||
|
||||
$ocs = $oc
|
||||
->structural
|
||||
->map(fn($item)=>$item->getParents())
|
||||
->flatten()
|
||||
->merge(
|
||||
config('server')->schema('objectclasses')
|
||||
->filter(fn($item)=>$item->isAuxiliary())
|
||||
)
|
||||
// Remove the original objectlcasses
|
||||
->filter(fn($item)=>(! $oc->values->contains($item)))
|
||||
->sortBy(fn($item)=>$item->name);
|
||||
|
||||
return $ocs->groupBy(fn($item)=>$item->isStructural())
|
||||
->map(fn($item,$key) =>
|
||||
[
|
||||
'text' => sprintf('%s Object Class',$key ? 'Structural' : 'Auxiliary'),
|
||||
'children' => $item->map(fn($item)=>['id'=>$item->name,'text'=>$item->name]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function entry_password_check(Request $request)
|
||||
{
|
||||
$dn = Crypt::decryptString($request->dn);
|
||||
$o = config('server')->fetch($dn);
|
||||
|
||||
$password = $o->getObject('userpassword');
|
||||
|
||||
$result = collect();
|
||||
foreach ($password as $key => $value) {
|
||||
$hash = $password->hash($value);
|
||||
$compare = Arr::get($request->password,$key);
|
||||
//Log::debug(sprintf('comparing [%s] with [%s] type [%s]',$value,$compare,$hash::id()),['object'=>$hash]);
|
||||
|
||||
$result->push((($compare !== NULL) && $hash->compare($value,$compare)) ? 'OK' :'FAIL');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a confirmation to update a DN
|
||||
*
|
||||
* @param EntryRequest $request
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application|\Illuminate\Http\RedirectResponse
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public function entry_pending_update(EntryRequest $request)
|
||||
{
|
||||
$dn = Crypt::decryptString($request->dn);
|
||||
|
||||
$o = config('server')->fetch($dn);
|
||||
|
||||
foreach ($request->except(['_token','dn','userpassword_hash','userpassword']) as $key => $value)
|
||||
$o->{$key} = array_filter($value,fn($item)=>! is_null($item));
|
||||
|
||||
// We need to process and encrypt the password
|
||||
if ($request->userpassword) {
|
||||
$passwords = [];
|
||||
foreach ($request->userpassword as $key => $value) {
|
||||
// If the password is still the MD5 of the old password, then it hasnt changed
|
||||
if (($old=Arr::get($o->userpassword,$key)) && ($value === md5($old))) {
|
||||
array_push($passwords,$old);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value) {
|
||||
$type = Arr::get($request->userpassword_hash,$key);
|
||||
array_push($passwords,Password::hash_id($type)->encode($value));
|
||||
}
|
||||
}
|
||||
$o->userpassword = $passwords;
|
||||
}
|
||||
|
||||
if (! $o->getDirty())
|
||||
return back()
|
||||
->withInput()
|
||||
->with('note',__('No attributes changed'));
|
||||
|
||||
return view('update')
|
||||
->with('bases',$this->bases())
|
||||
->with('dn',$dn)
|
||||
->with('o',$o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a DN entry
|
||||
*
|
||||
* @param EntryRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public function entry_update(EntryRequest $request)
|
||||
{
|
||||
$dn = Crypt::decryptString($request->dn);
|
||||
|
||||
$o = config('server')->fetch($dn);
|
||||
|
||||
foreach ($request->except(['_token','dn']) as $key => $value)
|
||||
$o->{$key} = array_filter($value);
|
||||
|
||||
if (! $dirty=$o->getDirty())
|
||||
return back()
|
||||
->withInput()
|
||||
->with('note',__('No attributes changed'));
|
||||
|
||||
try {
|
||||
$o->update($request->except(['_token','dn']));
|
||||
|
||||
} catch (InsufficientAccessException $e) {
|
||||
$request->flash();
|
||||
|
||||
switch ($x=$e->getDetailedError()->getErrorCode()) {
|
||||
case 50:
|
||||
return Redirect::to('/')
|
||||
->withInput()
|
||||
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
|
||||
|
||||
default:
|
||||
abort(599,$e->getDetailedError()->getErrorMessage());
|
||||
}
|
||||
|
||||
} catch (LdapRecordException $e) {
|
||||
$request->flash();
|
||||
|
||||
switch ($x=$e->getDetailedError()->getErrorCode()) {
|
||||
case 8:
|
||||
return Redirect::to('/')
|
||||
->withInput()
|
||||
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
|
||||
|
||||
default:
|
||||
abort(599,$e->getDetailedError()->getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to('/')
|
||||
->withInput()
|
||||
->with('updated',collect($dirty)->map(fn($key,$item)=>$o->getObject($item)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a frame, normally as a result of an AJAX call
|
||||
* This will render the right frame.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Collection|null $old
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|View
|
||||
*/
|
||||
public function frame(Request $request,?Collection $old=NULL): View
|
||||
{
|
||||
// If our index was not render from a root url, then redirect to it
|
||||
if (($request->root().'/' !== url()->previous()) && $request->method() === 'POST')
|
||||
abort(409);
|
||||
|
||||
$key = $this->request_key($request,$old);
|
||||
|
||||
$view = ($old
|
||||
? view('frame')->with('subframe',$key['cmd'])
|
||||
: view('frames.'.$key['cmd']))
|
||||
->with('bases',$this->bases());
|
||||
|
||||
return match ($key['cmd']) {
|
||||
'create' => $view
|
||||
->with('container',old('container',$key['dn']))
|
||||
->with('step',1),
|
||||
|
||||
'dn' => $view
|
||||
->with('dn',$key['dn'])
|
||||
->with('page_actions',collect(['edit'=>TRUE,'copy'=>TRUE])),
|
||||
|
||||
'import' => $view,
|
||||
|
||||
default => abort(404),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main page render function
|
||||
*/
|
||||
public function home(Request $request)
|
||||
{
|
||||
// Did we come here as a result of a redirect
|
||||
return count(old())
|
||||
? $this->frame($request,collect(old()))
|
||||
: view('home')
|
||||
->with('bases',$this->bases());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the incoming LDIF file or LDIF text
|
||||
*
|
||||
* @param ImportRequest $request
|
||||
* @param string $type
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application
|
||||
* @throws GeneralException
|
||||
* @throws VersionException
|
||||
*/
|
||||
public function import(ImportRequest $request,string $type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'ldif':
|
||||
$import = new LDIFImport($x=($request->text ?: $request->file->get()));
|
||||
break;
|
||||
|
||||
default:
|
||||
abort(404,'Unknown import type: '.$type);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $import->process();
|
||||
|
||||
} catch (NotImplementedException $e) {
|
||||
abort(555,$e->getMessage());
|
||||
|
||||
} catch (\Exception $e) {
|
||||
abort(598,$e->getMessage());
|
||||
}
|
||||
|
||||
return view('frame')
|
||||
->with('subframe','import_result')
|
||||
->with('bases',$this->bases())
|
||||
->with('result',$result)
|
||||
->with('ldif',htmlspecialchars($x));
|
||||
}
|
||||
|
||||
public function import_frame()
|
||||
{
|
||||
return view('frames.import');
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP Server INFO
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function info()
|
||||
{
|
||||
return view('frames.info')
|
||||
->with('s',config('server'));
|
||||
}
|
||||
|
||||
/**
|
||||
* For any incoming request, work out the command and DN involved
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Collection|null $old
|
||||
* @return array
|
||||
*/
|
||||
private function request_key(Request $request,?Collection $old=NULL): array
|
||||
{
|
||||
// Setup
|
||||
$cmd = NULL;
|
||||
$dn = NULL;
|
||||
$key = $request->get('key',old('key'))
|
||||
? Crypt::decryptString($request->get('key',old('key')))
|
||||
: NULL;
|
||||
|
||||
// Determine if our key has a command
|
||||
if (str_contains($key,'|')) {
|
||||
$m = [];
|
||||
|
||||
if (preg_match('/\*([a-z_]+)\|(.+)$/',$key,$m)) {
|
||||
$cmd = $m[1];
|
||||
$dn = ($m[2] !== '_NOP') ? $m[2] : NULL;
|
||||
}
|
||||
|
||||
} elseif (old('dn',$request->get('key'))) {
|
||||
$cmd = 'dn';
|
||||
$dn = Crypt::decryptString(old('dn',$request->get('key')));
|
||||
}
|
||||
|
||||
return ['cmd'=>$cmd,'dn'=>$dn];
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Schema Viewer
|
||||
*
|
||||
* @note Our route will validate that types are valid.
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
* @throws InvalidUsage
|
||||
*/
|
||||
public function schema_frame(Request $request)
|
||||
{
|
||||
// If an invalid key, we'll 404
|
||||
if ($request->type && $request->key && (! config('server')->schema($request->type)->has($request->key)))
|
||||
abort(404);
|
||||
|
||||
return view('frames.schema')
|
||||
->with('type',$request->type)
|
||||
->with('key',$request->key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the attributes
|
||||
*
|
||||
* @param Collection $attrs
|
||||
* @return Collection
|
||||
*/
|
||||
private function sortAttrs(Collection $attrs): Collection
|
||||
{
|
||||
return $attrs->sortKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the image for the logged in user or anonymous
|
||||
*
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function user_image(Request $request)
|
||||
{
|
||||
$image = NULL;
|
||||
$content = NULL;
|
||||
|
||||
if (Auth::check()) {
|
||||
$image = Arr::get(Auth::user()->getAttribute('jpegphoto'),0);
|
||||
$content = 'image/jpeg';
|
||||
}
|
||||
|
||||
if (! $image) {
|
||||
$image = File::get('../resources/images/user-secret-solid.svg');
|
||||
$content = 'image/svg+xml';
|
||||
}
|
||||
|
||||
return response($image)
|
||||
->header('Content-Type',$content);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
|
||||
class AllowAnonymous
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request,Closure $next): mixed
|
||||
{
|
||||
if (((! Cookie::has('username_encrypt')) || (! Cookie::has('password_encrypt'))) && (! config('pla.allow_guest',FALSE)))
|
||||
return redirect()
|
||||
->to('/login');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
use App\Classes\LDAP\Server;
|
||||
use App\Ldap\User;
|
||||
|
||||
/**
|
||||
* This sets up our application session with any required values, ultimately for cache optimisation reasons
|
||||
*/
|
||||
class ApplicationSession
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request,Closure $next): mixed
|
||||
{
|
||||
Config::set('server',new Server);
|
||||
|
||||
view()->share('user', auth()->user() ?: new User);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CheckUpdate
|
||||
{
|
||||
private const UPDATE_SERVER = 'https://version.phpldapadmin.org';
|
||||
private const UPDATE_TIME = 60*60*6;
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
Config::set('update_available',Cache::get('upstream_version'));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tasks after the response has been sent to the browser.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function terminate(): void
|
||||
{
|
||||
Cache::remember('upstream_version',self::UPDATE_TIME,function() {
|
||||
// CURL call to URL to see if there is a new version
|
||||
Log::debug(sprintf('CU_:Checking for updates for [%s]',config('app.version')));
|
||||
|
||||
$client = new Client;
|
||||
|
||||
try {
|
||||
$response = $client->request('POST',sprintf('%s/%s',self::UPDATE_SERVER,strtolower(config('app.version'))));
|
||||
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$result = json_decode($response->getBody());
|
||||
|
||||
Log::debug(sprintf('CU_:- Update server returned...'),['update'=>$result]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::debug(sprintf('CU_:- Exception connecting to update server'),['e'=>get_class($e)]);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LdapRecord\Container;
|
||||
|
||||
use App\Ldap\Connection;
|
||||
|
||||
class SwapinAuthUser
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
* @throws \LdapRecord\Configuration\ConfigurationException
|
||||
*/
|
||||
public function handle(Request $request,Closure $next): mixed
|
||||
{
|
||||
$key = config('ldap.default');
|
||||
|
||||
if (! array_key_exists($key,config('ldap.connections')))
|
||||
abort(599,sprintf('LDAP default server [%s] configuration doesnt exist?',$key));
|
||||
|
||||
/*
|
||||
// Rebuild our connection with the authenticated user.
|
||||
if (Session::has('username_encrypt') && Session::has('password_encrypt')) {
|
||||
Config::set('ldap.connections.'.$key.'.username',Crypt::decryptString(Session::get('username_encrypt')));
|
||||
Config::set('ldap.connections.'.$key.'.password',Crypt::decryptString(Session::get('password_encrypt')));
|
||||
|
||||
} else
|
||||
*/
|
||||
|
||||
// @todo it seems sometimes we have cookies that show the logged in user, but Auth::user() has expired?
|
||||
if (Cookie::has('username_encrypt') && Cookie::has('password_encrypt')) {
|
||||
Config::set('ldap.connections.'.$key.'.username',Cookie::get('username_encrypt'));
|
||||
Config::set('ldap.connections.'.$key.'.password',Cookie::get('password_encrypt'));
|
||||
|
||||
Log::debug('Swapping out configured LDAP credentials with the user\'s cookie.',['key'=>$key,'user'=>Cookie::get('username_encrypt')]);
|
||||
}
|
||||
|
||||
// We need to override our Connection object so that we can store and retrieve the logged in user and swap out the credentials to use them.
|
||||
Container::getInstance()->addConnection(new Connection(config('ldap.connections.'.$key)),$key);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
use App\Rules\{DNExists,HasStructuralObjectClass};
|
||||
|
||||
class EntryAddRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'rdn' => __('RDN is required.'),
|
||||
'rdn_value' => __('RDN value is required.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
if (request()->method() === 'GET')
|
||||
return [];
|
||||
|
||||
return config('server')
|
||||
->schema('attributetypes')
|
||||
->intersectByKeys($this->request)
|
||||
->map(fn($item)=>$item->validation(request()->get('objectclass')))
|
||||
->filter()
|
||||
->flatMap(fn($item)=>$item)
|
||||
->merge([
|
||||
'key' => [
|
||||
'required',
|
||||
new DNExists,
|
||||
function (string $attribute,mixed $value,\Closure $fail) {
|
||||
$cmd = Crypt::decryptString($value);
|
||||
|
||||
// Sometimes our key has a command, so we'll ignore it
|
||||
if (str_starts_with($cmd,'*') && ($x=strpos($cmd,'|')))
|
||||
$cmd = substr($cmd,1,$x-1);
|
||||
|
||||
if ($cmd !== 'create') {
|
||||
$fail(sprintf('Invalid command: %s',$cmd));
|
||||
}
|
||||
},
|
||||
],
|
||||
'rdn' => 'required_if:step,2|string|min:1',
|
||||
'rdn_value' => 'required_if:step,2|string|min:1',
|
||||
'step' => 'int|min:1|max:2',
|
||||
'objectclass'=>[
|
||||
'required',
|
||||
'array',
|
||||
'min:1',
|
||||
new HasStructuralObjectClass,
|
||||
]
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class EntryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return config('server')
|
||||
->schema('attributetypes')
|
||||
->intersectByKeys($this->request)
|
||||
->map(fn($item)=>$item->validation(request()?->get('objectclass') ?: []))
|
||||
->filter()
|
||||
->flatMap(fn($item)=>$item)
|
||||
->toArray();
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ImportRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'file' => 'nullable|extensions:ldif|required_without:text',
|
||||
'text'=> 'nullable|prohibits:file|string|min:16',
|
||||
];
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use LdapRecord\Connection as ConnectionBase;
|
||||
use LdapRecord\LdapInterface;
|
||||
|
||||
class Connection extends ConnectionBase
|
||||
{
|
||||
|
||||
public function __construct($config = [], LdapInterface $ldap = null)
|
||||
{
|
||||
parent::__construct($config,$ldap);
|
||||
|
||||
// We need to override this so that we use our own Guard, that stores the users credentials in the session
|
||||
$this->authGuardResolver = function () {
|
||||
return new Guard($this->ldap, $this->configuration);
|
||||
};
|
||||
}
|
||||
}
|
@ -1,443 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use LdapRecord\Support\Arr;
|
||||
use LdapRecord\Models\Model;
|
||||
use LdapRecord\Query\Model\Builder;
|
||||
|
||||
use App\Classes\LDAP\Attribute;
|
||||
use App\Classes\LDAP\Attribute\Factory;
|
||||
use App\Classes\LDAP\Export\LDIF;
|
||||
use App\Exceptions\Import\AttributeException;
|
||||
use App\Exceptions\InvalidUsage;
|
||||
|
||||
class Entry extends Model
|
||||
{
|
||||
private Collection $objects;
|
||||
private bool $noObjectAttributes = FALSE;
|
||||
// For new entries, this is the container that this entry will be stored in
|
||||
private string $rdnbase;
|
||||
|
||||
/* OVERRIDES */
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
$this->objects = collect();
|
||||
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
public function discardChanges(): static
|
||||
{
|
||||
parent::discardChanges();
|
||||
|
||||
// If we are discharging changes, we need to reset our $objects;
|
||||
$this->objects = $this->getAttributesAsObjects();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function overrides getAttributes to use our collection of Attribute objects instead of the models attributes.
|
||||
*
|
||||
* @return array
|
||||
* @note $this->attributes may not be updated with changes
|
||||
*/
|
||||
public function getAttributes(): array
|
||||
{
|
||||
return $this->objects
|
||||
->map(fn($item)=>$item->values)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the new and old values for a given key are equivalent.
|
||||
*
|
||||
* @todo This function barfs on language tags, eg: key = givenname;lang-ja
|
||||
*/
|
||||
protected function originalIsEquivalent(string $key): bool
|
||||
{
|
||||
$key = $this->normalizeAttributeKey($key);
|
||||
|
||||
// @todo Silently ignore keys of language tags - we should work with them
|
||||
if (str_contains($key,';'))
|
||||
return TRUE;
|
||||
|
||||
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($key)))
|
||||
|| (! $this->getObject($key)->isDirty());
|
||||
}
|
||||
|
||||
public static function query(bool $noattrs=false): Builder
|
||||
{
|
||||
$o = new static;
|
||||
|
||||
if ($noattrs)
|
||||
$o->noObjectAttributes();
|
||||
|
||||
return $o->newQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* As attribute values are updated, or new ones created, we need to mirror that
|
||||
* into our $objects
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttribute(string $key, mixed $value): static
|
||||
{
|
||||
parent::setAttribute($key,$value);
|
||||
|
||||
$key = $this->normalizeAttributeKey($key);
|
||||
|
||||
if ((! $this->objects->get($key)) && $value) {
|
||||
$this->objects->put($key,Factory::create($key,$value));
|
||||
|
||||
} elseif ($this->objects->get($key)) {
|
||||
$this->objects->get($key)->value = $this->attributes[$key];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* We'll shadow $this->attributes to $this->objects - a collection of Attribute objects
|
||||
*
|
||||
* Using the objects, it'll make it easier to work with attribute values
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function setRawAttributes(array $attributes = []): static
|
||||
{
|
||||
parent::setRawAttributes($attributes);
|
||||
|
||||
// We only set our objects on DN entries (otherwise we might get into a recursion loop if this is the schema DN)
|
||||
if ($this->dn && (! in_array($this->dn,Arr::get($this->attributes,'subschemasubentry',[])))) {
|
||||
$this->objects = $this->getAttributesAsObjects();
|
||||
|
||||
} else {
|
||||
$this->objects = collect();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/* ATTRIBUTES */
|
||||
|
||||
/**
|
||||
* Return a key to use for sorting
|
||||
*
|
||||
* @return string
|
||||
* @todo This should be the DN in reverse order
|
||||
*/
|
||||
public function getSortKeyAttribute(): string
|
||||
{
|
||||
return $this->getDn();
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
|
||||
public function addAttribute(string $key,mixed $value): void
|
||||
{
|
||||
// While $value is mixed, it can only be a string
|
||||
if (! is_string($value))
|
||||
throw new \Exception('value should be a string');
|
||||
|
||||
$key = $this->normalizeAttributeKey($key);
|
||||
|
||||
if (! config('server')->schema('attributetypes')->has($key))
|
||||
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$key));
|
||||
|
||||
if ($x=$this->objects->get($key)) {
|
||||
$x->addValue($value);
|
||||
|
||||
} else {
|
||||
$this->objects->put($key,Attribute\Factory::create($key,Arr::wrap($value)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert all our attribute values into an array of Objects
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return Collection
|
||||
*/
|
||||
public function getAttributesAsObjects(): Collection
|
||||
{
|
||||
$result = collect();
|
||||
|
||||
foreach ($this->attributes as $attribute => $value) {
|
||||
// If the attribute name has language tags
|
||||
$matches = [];
|
||||
if (preg_match('/^([a-zA-Z]+)(;([a-zA-Z-;]+))+/',$attribute,$matches)) {
|
||||
$attribute = $matches[1];
|
||||
|
||||
// If the attribute doesnt exist we'll create it
|
||||
$o = Arr::get($result,$attribute,Factory::create($attribute,[]));
|
||||
$o->setLangTag($matches[3],$value);
|
||||
|
||||
} else {
|
||||
$o = Factory::create($attribute,$value);
|
||||
}
|
||||
|
||||
if (! $result->has($attribute)) {
|
||||
// Set the rdn flag
|
||||
if (preg_match('/^'.$attribute.'=/i',$this->dn))
|
||||
$o->setRDN();
|
||||
|
||||
// Store our original value to know if this attribute has changed
|
||||
$o->oldValues(Arr::get($this->original,$attribute,[]));
|
||||
|
||||
$result->put($attribute,$o);
|
||||
}
|
||||
}
|
||||
|
||||
$sort = collect(config('pla.attr_display_order',[]))->map(fn($item)=>strtolower($item));
|
||||
|
||||
// Order the attributes
|
||||
return $result->sortBy([function(Attribute $a,Attribute $b) use ($sort): int {
|
||||
if ($a === $b)
|
||||
return 0;
|
||||
|
||||
// Check if $a/$b are in the configuration to be sorted first, if so get it's key
|
||||
$a_key = $sort->search($a->name_lc);
|
||||
$b_key = $sort->search($b->name_lc);
|
||||
|
||||
// If the keys were not in the sort list, set the key to be the count of elements (ie: so it is last to be sorted)
|
||||
if ($a_key === FALSE)
|
||||
$a_key = $sort->count()+1;
|
||||
|
||||
if ($b_key === FALSE)
|
||||
$b_key = $sort->count()+1;
|
||||
|
||||
// Case where neither $a, nor $b are in pla.attr_display_order, $a_key = $b_key = one greater than num elements.
|
||||
// So we sort them alphabetically
|
||||
if ($a_key === $b_key)
|
||||
return strcasecmp($a->name,$b->name);
|
||||
|
||||
// Case where at least one attribute or its friendly name is in $attrs_display_order
|
||||
// return -1 if $a before $b in $attrs_display_order
|
||||
return ($a_key < $b_key) ? -1 : 1;
|
||||
}]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of available attributes - as per the objectClass entry of the record
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getAvailableAttributes(): Collection
|
||||
{
|
||||
$result = collect();
|
||||
|
||||
foreach ($this->objectclass as $oc)
|
||||
$result = $result->merge(config('server')->schema('objectclasses',$oc)->attributes);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a secure version of the DN
|
||||
* @param string $cmd
|
||||
* @return string
|
||||
*/
|
||||
public function getDNSecure(string $cmd=''): string
|
||||
{
|
||||
return Crypt::encryptString(($cmd ? sprintf('*%s|',$cmd) : '').$this->getDn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of LDAP internal attributes
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getInternalAttributes(): Collection
|
||||
{
|
||||
return $this->objects
|
||||
->filter(fn($item)=>$item->is_internal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute as an object
|
||||
*
|
||||
* @param string $key
|
||||
* @return Attribute|null
|
||||
*/
|
||||
public function getObject(string $key): Attribute|null
|
||||
{
|
||||
return match ($key) {
|
||||
'rdn' => $this->getRDNObject(),
|
||||
|
||||
default => $this->objects
|
||||
->get($this->normalizeAttributeKey($key))
|
||||
};
|
||||
}
|
||||
|
||||
public function getObjects(): Collection
|
||||
{
|
||||
// In case we havent built our objects yet (because they werent available while determining the schema DN)
|
||||
if ((! $this->objects->count()) && $this->attributes)
|
||||
$this->objects = $this->getAttributesAsObjects();
|
||||
|
||||
return $this->objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of attributes without any values
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getMissingAttributes(): Collection
|
||||
{
|
||||
return $this->getAvailableAttributes()
|
||||
->filter(fn($a)=>(! $this->getVisibleAttributes()->contains(fn($b)=>($a->name === $b->name))));
|
||||
}
|
||||
|
||||
private function getRDNObject(): Attribute\RDN
|
||||
{
|
||||
$o = new Attribute\RDN('dn',['']);
|
||||
// @todo for an existing object, return the base.
|
||||
$o->setBase($this->rdnbase);
|
||||
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required));
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this list of user attributes
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getVisibleAttributes(): Collection
|
||||
{
|
||||
return $this->objects
|
||||
->filter(fn($item)=>! $item->is_internal);
|
||||
}
|
||||
|
||||
public function hasAttribute(int|string $key): bool
|
||||
{
|
||||
return $this->objects
|
||||
->has($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this record
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $scope
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function export(string $method,string $scope): string
|
||||
{
|
||||
// @todo To implement
|
||||
switch ($scope) {
|
||||
case 'base':
|
||||
case 'one':
|
||||
case 'sub':
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \Exception('Export scope unknown:'.$scope);
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case 'ldif':
|
||||
return new LDIF(collect($this));
|
||||
|
||||
default:
|
||||
throw new \Exception('Export method not implemented:'.$method);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an icon for a DN based on objectClass
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function icon(): string
|
||||
{
|
||||
$objectclasses = array_map('strtolower',$this->objectclass);
|
||||
|
||||
// Return icon based upon objectClass value
|
||||
if (in_array('person',$objectclasses) ||
|
||||
in_array('organizationalperson',$objectclasses) ||
|
||||
in_array('inetorgperson',$objectclasses) ||
|
||||
in_array('account',$objectclasses) ||
|
||||
in_array('posixaccount',$objectclasses))
|
||||
|
||||
return 'fas fa-user';
|
||||
|
||||
elseif (in_array('organization',$objectclasses))
|
||||
return 'fas fa-university';
|
||||
|
||||
elseif (in_array('organizationalunit',$objectclasses))
|
||||
return 'fas fa-object-group';
|
||||
|
||||
elseif (in_array('posixgroup',$objectclasses) ||
|
||||
in_array('groupofnames',$objectclasses) ||
|
||||
in_array('groupofuniquenames',$objectclasses) ||
|
||||
in_array('group',$objectclasses))
|
||||
|
||||
return 'fas fa-users';
|
||||
|
||||
elseif (in_array('dcobject',$objectclasses) ||
|
||||
in_array('domainrelatedobject',$objectclasses) ||
|
||||
in_array('domain',$objectclasses) ||
|
||||
in_array('builtindomain',$objectclasses))
|
||||
|
||||
return 'fas fa-network-wired';
|
||||
|
||||
elseif (in_array('alias',$objectclasses))
|
||||
return 'fas fa-theater-masks';
|
||||
|
||||
elseif (in_array('country',$objectclasses))
|
||||
return sprintf('flag %s',strtolower(Arr::get($this->c,0)));
|
||||
|
||||
elseif (in_array('device',$objectclasses))
|
||||
return 'fas fa-mobile-alt';
|
||||
|
||||
elseif (in_array('document',$objectclasses))
|
||||
return 'fas fa-file-alt';
|
||||
|
||||
elseif (in_array('iphost',$objectclasses))
|
||||
return 'fas fa-wifi';
|
||||
|
||||
elseif (in_array('room',$objectclasses))
|
||||
return 'fas fa-door-open';
|
||||
|
||||
elseif (in_array('server',$objectclasses))
|
||||
return 'fas fa-server';
|
||||
|
||||
elseif (in_array('openldaprootdse',$objectclasses))
|
||||
return 'fas fa-info';
|
||||
|
||||
// Default
|
||||
return 'fa-fw fas fa-cog';
|
||||
}
|
||||
|
||||
/**
|
||||
* Dont convert our $this->attributes to $this->objects when creating a new Entry::class
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noObjectAttributes(): static
|
||||
{
|
||||
$this->noObjectAttributes = TRUE;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRDNBase(string $bdn): void
|
||||
{
|
||||
if ($this->exists)
|
||||
throw new InvalidUsage('Cannot set RDN base on existing entries');
|
||||
|
||||
$this->rdnbase = $bdn;
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
// use Illuminate\Support\Facades\Crypt;
|
||||
use LdapRecord\Auth\Guard as GuardBase;
|
||||
|
||||
class Guard extends GuardBase
|
||||
{
|
||||
public function attempt(string $username, string $password, bool $stayBound = false): bool
|
||||
{
|
||||
if ($result = parent::attempt($username,$password,$stayBound)) {
|
||||
/*
|
||||
* We can either use our session or cookies to store this. If using session, then Http/Kernel needs to be
|
||||
* updated to start a session for API calls.
|
||||
// We need to store our password so that we can swap in the user in during SwapinAuthUser::class middleware
|
||||
request()->session()->put('username_encrypt',Crypt::encryptString($username));
|
||||
request()->session()->put('password_encrypt',Crypt::encryptString($password));
|
||||
*/
|
||||
|
||||
// For our API calls, we store the cookie - which our cookies are already encrypted
|
||||
Cookie::queue('username_encrypt',$username);
|
||||
Cookie::queue('password_encrypt',$password);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Str;
|
||||
use LdapRecord\Laravel\Events\Auth\DiscoveredWithCredentials;
|
||||
use LdapRecord\Laravel\LdapUserRepository as LdapUserRepositoryBase;
|
||||
use LdapRecord\Models\Model;
|
||||
|
||||
use App\Classes\LDAP\Server;
|
||||
|
||||
class LdapUserRepository extends LdapUserRepositoryBase
|
||||
{
|
||||
/**
|
||||
* Retrieve a user by the given credentials.
|
||||
*
|
||||
* @param array $credentials
|
||||
*
|
||||
* @return Model|null
|
||||
* @throws \LdapRecord\Query\ObjectNotFoundException
|
||||
*/
|
||||
public function findByCredentials(array $credentials = []): ?Model
|
||||
{
|
||||
if (empty($credentials)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// For DN based logins
|
||||
if (! empty($credentials['dn']))
|
||||
return $this->query()->find($credentials['dn']);
|
||||
|
||||
// Look for a user using all our baseDNs
|
||||
foreach (Server::baseDNs() as $base) {
|
||||
$query = $this->query()->setBaseDn($base);
|
||||
|
||||
foreach ($credentials as $key => $value) {
|
||||
if (Str::contains($key, $this->bypassCredentialKeys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($value) || $value instanceof Arrayable) {
|
||||
$query->whereIn($key, $value);
|
||||
} else {
|
||||
$query->where($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (! is_null($user = $query->first())) {
|
||||
event(new DiscoveredWithCredentials($user));
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user by their object GUID.
|
||||
*
|
||||
* @param string $guid
|
||||
*
|
||||
* @return Model|null
|
||||
* @throws \LdapRecord\Query\ObjectNotFoundException
|
||||
*/
|
||||
public function findByGuid($guid): ?Model
|
||||
{
|
||||
// Look for a user using all our baseDNs
|
||||
foreach (Server::baseDNs() as $base) {
|
||||
$user = $this->query()->setBaseDn($base)->findByGuid($guid);
|
||||
|
||||
if ($user)
|
||||
return $user;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ldap\Rules;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model as Eloquent;
|
||||
use LdapRecord\Laravel\Auth\Rule;
|
||||
use LdapRecord\Models\Model as LdapRecord;
|
||||
|
||||
/**
|
||||
* User must have this objectClass to login
|
||||
*
|
||||
* This is overridden by LDAP_LOGIN_OBJECTCLASS
|
||||
* @see User::$objectClasses
|
||||
*/
|
||||
class LoginObjectclassRule implements Rule
|
||||
{
|
||||
public function passes(LdapRecord $user, Eloquent $model = null): bool
|
||||
{
|
||||
if ($x=config('pla.login.objectclass')) {
|
||||
return count(array_intersect($user->objectclass,$x));
|
||||
|
||||
// Otherwise allow the user to login
|
||||
} else {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use LdapRecord\Models\OpenLDAP\User as Model;
|
||||
|
||||
use App\Ldap\Rules\LoginObjectclassRule;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
use HasApiTokens;
|
||||
|
||||
/**
|
||||
* The object classes of the LDAP model.
|
||||
*
|
||||
* @note We set this to an empty array so that any objectclass can login
|
||||
* @see LoginObjectclassRule::class
|
||||
*/
|
||||
public static array $objectClasses = [
|
||||
];
|
||||
|
||||
/* METHODS */
|
||||
|
||||
public function getDn(): string
|
||||
{
|
||||
return $this->exists ? parent::getDn() : 'Anonymous';
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use LdapRecord\Configuration\DomainConfiguration;
|
||||
use LdapRecord\Laravel\LdapRecord;
|
||||
|
||||
use App\Ldap\LdapUserRepository;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Add a new option available to be set in the configuration:
|
||||
DomainConfiguration::extend('name', $default = null);
|
||||
|
||||
// Use our LdapUserRepository to support multiple baseDN querying
|
||||
LdapRecord::locateUsersUsing(LdapUserRepository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadViewsFrom(__DIR__.'/../../resources/themes/architect/views/','architect');
|
||||
|
||||
// Enable pluck on collections to work on private values
|
||||
Collection::macro('ppluck',
|
||||
fn($attr)=>$this
|
||||
->map(fn($item)=>$item->{$attr})
|
||||
->values());
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class DNExists implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute,mixed $value,Closure $fail): void
|
||||
{
|
||||
$dn = Crypt::decryptString($value);
|
||||
|
||||
// Sometimes our key has a command, so we'll ignore it
|
||||
if (str_starts_with($dn,'*') && ($x=strpos($dn,'|')))
|
||||
$dn = substr($dn,$x+1);
|
||||
|
||||
if (! config('server')->fetch($dn))
|
||||
$fail(sprintf('The DN %s doesnt exist.',$dn));
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class HasStructuralObjectClass implements ValidationRule
|
||||
{
|
||||
// Required for artisan optimize
|
||||
public static function __set_state(array $array): self
|
||||
{
|
||||
return new self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute,mixed $value,Closure $fail): void
|
||||
{
|
||||
foreach ($value as $item)
|
||||
if ($item && config('server')->schema('objectclasses',$item)->isStructural())
|
||||
return;
|
||||
|
||||
$fail('There isnt a Structural Objectclass.');
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Determine if a value has changed by comparing its MD5 value
|
||||
*/
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait MD5Updates
|
||||
{
|
||||
public function isDirty(): bool
|
||||
{
|
||||
foreach ($this->values->diff($this->oldValues) as $key => $value)
|
||||
if (md5(Arr::get($this->oldValues,$key)) !== $value)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
use App\Classes\LDAP\Attribute as LDAPAttribute;
|
||||
|
||||
class Attribute extends Component
|
||||
{
|
||||
public ?LDAPAttribute $o;
|
||||
public bool $edit;
|
||||
public bool $new;
|
||||
public bool $old;
|
||||
public ?string $na;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(?LDAPAttribute $o,bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE,?string $na=NULL)
|
||||
{
|
||||
$this->o = $o;
|
||||
$this->edit = $edit;
|
||||
$this->old = $old;
|
||||
$this->new = $new;
|
||||
$this->na = $na;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|\Closure|string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return $this->o
|
||||
? $this->o
|
||||
->render($this->edit,$this->old,$this->new)
|
||||
: $this->na;
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
use App\Classes\LDAP\Attribute as LDAPAttribute;
|
||||
|
||||
class AttributeType extends Component
|
||||
{
|
||||
public Collection $oc;
|
||||
public LDAPAttribute $o;
|
||||
public bool $new;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(LDAPAttribute $o,bool $new=FALSE,?Collection $oc=NULL)
|
||||
{
|
||||
$this->o = $o;
|
||||
$this->oc = $oc;
|
||||
$this->new = $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.attribute-type')
|
||||
->with('o',$this->o)
|
||||
->with('oc',$this->oc)
|
||||
->with('new',$this->new);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
function login_attr_description(): string
|
||||
{
|
||||
return Arr::get(config('pla.login.attr'),login_attr_name());
|
||||
}
|
||||
|
||||
function login_attr_name(): string
|
||||
{
|
||||
return key(config('pla.login.attr'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to generate a random salt for crypt-style passwords. Salt strings are used
|
||||
* to make pre-built hash cracking dictionaries difficult to use as the hash algorithm uses
|
||||
* not only the user's password but also a randomly generated string. The string is
|
||||
* stored as the first N characters of the hash for reference of hashing algorithms later.
|
||||
*
|
||||
* @param int $length The length of the salt string to generate.
|
||||
* @return string The generated salt string.
|
||||
* @throws \Random\RandomException
|
||||
*/
|
||||
function random_salt(int $length): string
|
||||
{
|
||||
$str = bin2hex(random_bytes(ceil($length/2)));
|
||||
if ($length%2 === 1)
|
||||
return substr($str,0,-1);
|
||||
|
||||
return $str;
|
||||
}
|
150
application/bootstrap.php
Normal file
150
application/bootstrap.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
// -- Environment setup --------------------------------------------------------
|
||||
|
||||
// Load the core Kohana class
|
||||
require SYSPATH.'classes/Kohana/Core'.EXT;
|
||||
|
||||
if (is_file(APPPATH.'classes/Kohana'.EXT))
|
||||
{
|
||||
// Application extends the core
|
||||
require APPPATH.'classes/Kohana'.EXT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load empty core extension
|
||||
require SYSPATH.'classes/Kohana'.EXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default time zone.
|
||||
*
|
||||
* @link http://kohanaframework.org/guide/using.configuration
|
||||
* @link http://www.php.net/manual/timezones
|
||||
*/
|
||||
date_default_timezone_set('Australia/Melbourne');
|
||||
|
||||
/**
|
||||
* Set the default locale.
|
||||
*
|
||||
* @link http://kohanaframework.org/guide/using.configuration
|
||||
* @link http://www.php.net/manual/function.setlocale
|
||||
*/
|
||||
setlocale(LC_ALL, 'en_US.utf-8');
|
||||
|
||||
/**
|
||||
* Enable the Kohana auto-loader.
|
||||
*
|
||||
* @link http://kohanaframework.org/guide/using.autoloading
|
||||
* @link http://www.php.net/manual/function.spl-autoload-register
|
||||
*/
|
||||
spl_autoload_register(array('Kohana', 'auto_load'));
|
||||
|
||||
/**
|
||||
* Optionally, you can enable a compatibility auto-loader for use with
|
||||
* older modules that have not been updated for PSR-0.
|
||||
*
|
||||
* It is recommended to not enable this unless absolutely necessary.
|
||||
*/
|
||||
//spl_autoload_register(array('Kohana', 'auto_load_lowercase'));
|
||||
|
||||
/**
|
||||
* Enable the Kohana auto-loader for unserialization.
|
||||
*
|
||||
* @link http://www.php.net/manual/function.spl-autoload-call
|
||||
* @link http://www.php.net/manual/var.configuration#unserialize-callback-func
|
||||
*/
|
||||
ini_set('unserialize_callback_func', 'spl_autoload_call');
|
||||
|
||||
// -- Configuration and initialization -----------------------------------------
|
||||
|
||||
/**
|
||||
* Set the default language
|
||||
*/
|
||||
I18n::lang('en-us');
|
||||
|
||||
/**
|
||||
* Set Kohana::$environment if a 'KOHANA_ENV' environment variable has been supplied.
|
||||
*
|
||||
* Note: If you supply an invalid environment name, a PHP warning will be thrown
|
||||
* saying "Couldn't find constant Kohana::<INVALID_ENV_NAME>"
|
||||
*/
|
||||
if (isset($_SERVER['KOHANA_ENV']))
|
||||
{
|
||||
Kohana::$environment = constant('Kohana::'.strtoupper($_SERVER['KOHANA_ENV']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Kohana, setting the default options.
|
||||
*
|
||||
* The following options are available:
|
||||
*
|
||||
* - string base_url path, and optionally domain, of your application NULL
|
||||
* - string index_file name of your index file, usually "index.php" index.php
|
||||
* - string charset internal character set used for input and output utf-8
|
||||
* - string cache_dir set the internal cache directory APPPATH/cache
|
||||
* - integer cache_life lifetime, in seconds, of items cached 60
|
||||
* - boolean errors enable or disable error handling TRUE
|
||||
* - boolean profile enable or disable internal profiling TRUE
|
||||
* - boolean caching enable or disable internal caching FALSE
|
||||
* - boolean expose set the X-Powered-By header FALSE
|
||||
*/
|
||||
Kohana::init(array(
|
||||
'base_url' => '/pla',
|
||||
'caching' => TRUE,
|
||||
'index_file' => '',
|
||||
));
|
||||
|
||||
/**
|
||||
* Attach the file write to logging. Multiple writers are supported.
|
||||
*/
|
||||
Kohana::$log->attach(new Log_File(APPPATH.'logs'));
|
||||
|
||||
/**
|
||||
* Attach a file reader to config. Multiple readers are supported.
|
||||
*/
|
||||
Kohana::$config->attach(new Config_File);
|
||||
|
||||
/**
|
||||
* Enable modules. Modules are referenced by a relative or absolute path.
|
||||
*/
|
||||
Kohana::modules(array(
|
||||
'lnapp' => MODPATH.'lnApp',
|
||||
'auth' => SMDPATH.'auth', // Basic authentication
|
||||
'cache' => SMDPATH.'cache', // Caching with multiple backends
|
||||
// 'codebench' => SMDPATH.'codebench', // Benchmarking tool
|
||||
'database' => SMDPATH.'database', // Database access
|
||||
// 'image' => SMDPATH.'image', // Image manipulation
|
||||
'minion' => SMDPATH.'minion', // CLI Tasks
|
||||
'orm' => SMDPATH.'orm', // Object Relationship Mapping
|
||||
'pagination' => SMDPATH.'pagination', // Kohana Pagination module for Kohana 3 PHP Framework
|
||||
// 'unittest' => SMDPATH.'unittest', // Unit testing
|
||||
// 'userguide' => SMDPATH.'userguide', // User guide and API documentation
|
||||
'xml' => SMDPATH.'xml', // XML module for Kohana 3 PHP Framework
|
||||
));
|
||||
|
||||
// Static file serving (CSS, JS, images)
|
||||
Route::set('default/media', 'media(/<file>)', array('file' => '.+'))
|
||||
->defaults(array(
|
||||
'controller' => 'media',
|
||||
'action' => 'get',
|
||||
));
|
||||
|
||||
/**
|
||||
* Set the routes. Each route must have a minimum of a name, a URI and a set of
|
||||
* defaults for the URI.
|
||||
*/
|
||||
Route::set('default', '(<controller>(/<action>(/<id>)))', array('id'=>'[a-zA-Z0-9_.-]+'))
|
||||
->defaults(array(
|
||||
'controller' => 'welcome',
|
||||
'action' => 'index',
|
||||
));
|
||||
|
||||
/**
|
||||
* If APC is enabled, and we need to clear the cache
|
||||
*/
|
||||
if (file_exists(APPPATH.'cache/CLEAR_APC_CACHE') AND function_exists('apc_clear_cache') AND (PHP_SAPI !== 'cli')) {
|
||||
if (! apc_clear_cache() OR ! unlink(APPPATH.'cache/CLEAR_APC_CACHE'))
|
||||
throw new Kohana_Exception('Unable to clear the APC cache.');
|
||||
}
|
||||
?>
|
4
application/classes/Auth/LDAP.php
Normal file
4
application/classes/Auth/LDAP.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
class Auth_LDAP extends PLA_Auth_Ldap {}
|
||||
?>
|
4
application/classes/Block.php
Normal file
4
application/classes/Block.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
class Block extends PLA_Block {}
|
||||
?>
|
107
application/classes/Config.php
Normal file
107
application/classes/Config.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* This class extends the core Kohana class by adding some core application
|
||||
* specific functions, and configuration.
|
||||
*
|
||||
* @package PLA
|
||||
* @subpackage Config
|
||||
* @category Helpers
|
||||
* @author Deon George
|
||||
* @copyright (c) phpLDAPadmin Development Team
|
||||
* @license http://dev.phpldapadmin.org/license.html
|
||||
*/
|
||||
class Config extends Kohana_Config {
|
||||
// Our default logo, if there is no site logo
|
||||
public static $logo = 'img/logo-small.png';
|
||||
|
||||
/**
|
||||
* Some early initialisation
|
||||
*
|
||||
* At this point, KH hasnt been fully initialised either, so we cant rely on
|
||||
* too many KH functions yet.
|
||||
*
|
||||
* NOTE: Kohana doesnt provide a parent construct for the Kohana_Config class.
|
||||
*/
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of Config.
|
||||
*
|
||||
* $config = Config::instance();
|
||||
*
|
||||
* @return Config
|
||||
* @compat Restore KH 3.1 functionality
|
||||
*/
|
||||
public static function instance() {
|
||||
if (Config::$_instance === NULL)
|
||||
// Create a new instance
|
||||
Config::$_instance = new Config;
|
||||
|
||||
return Config::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return our caching mechanism
|
||||
*/
|
||||
public static function cachetype() {
|
||||
return is_null(Kohana::$config->load('config')->cache_type) ? 'file' : Kohana::$config->load('config')->cache_type;
|
||||
}
|
||||
|
||||
public static function copywrite() {
|
||||
return '(c) phpLDAPadmin Development Team';
|
||||
}
|
||||
|
||||
public static function country() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
public static function language() {
|
||||
// @todo To implement
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
* The URI to show for the login prompt.
|
||||
* Normally if the user is logged in, we can replace it with something else
|
||||
*/
|
||||
public static function login_uri() {
|
||||
return ($ao = Auth::instance()->get_user() AND is_object($ao)) ? $ao->name() : HTML::anchor('login',_('Login'));
|
||||
}
|
||||
|
||||
public static function logo() {
|
||||
return HTML::image(static::logo_uri(),array('class'=>'headlogo','alt'=>_('Logo')));
|
||||
}
|
||||
|
||||
public static function logo_uri($protocol=NULL) {
|
||||
list ($path,$suffix) = explode('.',static::$logo);
|
||||
|
||||
return URL::site(Route::get('default/media')->uri(array('file'=>$path.'.'.$suffix),array('alt'=>static::sitename())),$protocol);
|
||||
}
|
||||
|
||||
public static function siteid($format=FALSE) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out our site mode (dev,test,prod)
|
||||
*/
|
||||
public static function sitemode() {
|
||||
return Kohana::$config->load('config.site')->mode;
|
||||
}
|
||||
|
||||
public static function sitename() {
|
||||
return 'phpLDAPadmin';
|
||||
}
|
||||
|
||||
public static function theme() {
|
||||
return Kohana::$config->load('config')->theme;
|
||||
}
|
||||
|
||||
public static function version() {
|
||||
// @todo Work out our versioning
|
||||
return 'TBA';
|
||||
}
|
||||
}
|
||||
?>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user