Compare commits

...

607 Commits

Author SHA1 Message Date
a61f5e9b97 Release v2.1.0
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m24s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-04-11 15:38:06 +10:00
d845d87a6e Laravel framework and javascript modules update 2025-04-11 15:38:06 +10:00
b501dfe824 During create we were passing the wrong objectlcasses to the ajax call when adding a new attribute. 2025-04-11 15:38:06 +10:00
3fad9770a3 When submitting an import form and validation fails, there is no DN returned, so dont update one. 2025-04-11 14:59:24 +10:00
b1d153aa9f Change Attribute/UserCertificate into Syntax/Certificate for any Certificate attributes. Add Syntax/CertificateList.
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m30s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-04-11 08:55:32 +10:00
8b0af505a1 When viewing the schema, highlight Structural and non-Structural classes
Some checks failed
Create Docker Image / Test Application (x86_64) (push) Has been cancelled
Create Docker Image / Build Docker Image (arm64) (push) Has been cancelled
Create Docker Image / Build Docker Image (x86_64) (push) Has been cancelled
Create Docker Image / Final Docker Image Manifest (push) Has been cancelled
2025-04-11 08:55:32 +10:00
f0eaff7d42 Removing debugging that made it into LDIF import 2025-04-11 08:55:32 +10:00
352bbe2b75 Capture PLA version when submitting a bug report 2025-04-11 08:50:31 +10:00
0fe4894192 Create config.yml to disable blank issues reporting 2025-04-11 08:43:37 +10:00
a7be4e00b4 Fix rendering new attributes, so that they dont render as dynamic. Fix adding new objectClasses to entries, need langtag to render the component
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 28s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m28s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m47s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-08 22:04:48 +10:00
2abc321eca Fix for showing no_lang_tag attrs (which are displayed without values) on a lang_tag attr pane when viewing a DN 2025-04-08 14:50:23 +10:00
6b2fb8dee4 Dont add hints for internal attributes. Our hints now also returns a collection. 2025-04-08 11:04:31 +10:00
66537dcec8 Revert version to 2.1.0-dev
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m29s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-04-07 22:35:08 +10:00
1bf8830887 When rendering dynamic attributes, dont make them editable. Closes #10 and #89.
Also some minor fixes when returning from a post for a DN with attribute tags.
2025-04-07 22:35:08 +10:00
c4d28c8a23 Add support for displaying user certificates, that are recorded in the directory with a ;binary tag. Closes #75
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 28s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m22s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-07 14:34:56 +10:00
29c460fd4b Ensure our validation message is shown when not selecting an objectclass when creating a new entry 2025-04-07 14:34:56 +10:00
3196b10aed Add OID description for searchguide attributes 2025-04-07 14:34:56 +10:00
f41b484dc4 More ldap configuration settings for demo ldap environment.
Should help when working on #10, #89, #287.
2025-04-06 22:50:46 +10:00
855d7ae75c Move entry-edit javascript out of architect theme 2025-04-06 22:50:46 +10:00
ffa8cdc826 Fix User Password Check now that we have attribute tags 2025-04-06 22:50:46 +10:00
8f39603f9f Improved determination of attribute object being dirty, improved detection of blank input and processing
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m36s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-06 19:33:04 +10:00
bcea6de791 Validation of inputs for a DN with language tags - work for #16
Some checks failed
Create Docker Image / Build Docker Image (arm64) (push) Has been cancelled
Create Docker Image / Build Docker Image (x86_64) (push) Has been cancelled
Create Docker Image / Final Docker Image Manifest (push) Has been cancelled
Create Docker Image / Test Application (x86_64) (push) Has been cancelled
2025-04-06 13:54:32 +10:00
28f4869628 Attribute is no longer iterable - cant be used now that we manage attribute tags 2025-04-06 13:54:32 +10:00
cf535286c5 Render HTML inputs for a DN with language tags - work for #16 2025-04-06 13:54:32 +10:00
633513d3e9 Display a DN entry with language tags - work for #16 2025-04-06 13:54:32 +10:00
705bfb2d64 Update page_actions to be consistent with what we can do so far 2025-04-05 23:24:45 +11:00
3a3bf2addb Make select automatically selecting one item when there is only one configurable 2025-04-05 23:24:45 +11:00
5bb573100b Further to eab4f04 we need some attributes to render tree icons 2025-04-04 20:48:42 +11:00
a57ee78492 Ensure that Attribute::required() doesnt work with NULL $this->schema. Avoids issue as reported by #306 2025-04-04 20:48:42 +11:00
eab4f0427c No need to retrieve all records by default when getting children. By default sort records by DN until we implemented configurable sorting.
Should help the timeout issues reported in #301
2025-03-20 21:17:28 +11:00
fd2c5d1286 Add some attribute tags messages when we cant handle some attributes. 2025-03-19 09:41:47 +11:00
b35b44b2b8 Import and Export work with attribute tags 2025-03-19 09:41:47 +11:00
ce66dcb2b5 Remove deprecated Attribute::lang_tags 2025-03-19 09:41:47 +11:00
56a91f853c Fix export to work with no_attr_tags 2025-03-19 09:41:47 +11:00
81e0e58650 Handle no attribute tags at an Attribute::class level, added form/disabled components 2025-03-19 09:41:47 +11:00
1470170928 Internal attributes are now handled by the new backend setup for attribute tags 2025-03-19 09:41:47 +11:00
85c7132b30 Start of work to handle attribute tags - should help with #75 and #16 2025-03-19 09:41:47 +11:00
7e050954c3 Release v2.0.3
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m32s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-19 09:04:22 +11:00
16880cd0e2 Revert "Dont run CI/CD on master"
This reverts commit 9b33a20cc4ce2a9aa36821dec9fc569326f8c2c4.
2025-03-19 09:04:11 +11:00
696d87d190 Improve entry validation to only require the first item of multi value attributes 2025-03-19 08:36:01 +11:00
87bae89ea3 Fix validation when creating a new entry and not identifying required attributes, broken by 4a84c25 2025-03-18 23:40:38 +11:00
1abc2cc6e1 Move userpassword check to its own modal, leveraging page-modal 2025-03-18 23:40:38 +11:00
1abab9db94 Move DN export to its own modal, leveraging page-modal 2025-03-18 23:40:38 +11:00
410daf649e Squash with Move our page-actions out of the theme... 2025-03-18 23:40:38 +11:00
9666841c3c Move our page-actions out of the theme into frame/dn. Add some attribute tags messages when we cant handle some attributes. 2025-03-18 23:40:38 +11:00
9b33a20cc4 Dont run CI/CD on master 2025-03-16 10:19:23 +11:00
649749f9c1 MD5Update attributes cannot handle validation failures with a redirect back to the form, so restore the old values for now 2025-03-16 10:13:03 +11:00
5d3b8609bb Added an entry with a binary certification to test environment, with example LDIF to implement #75 2025-03-16 10:13:03 +11:00
93640959db Add our request()->root() to our debug page, implement Entry::getSortKeyAttribute() 2025-03-16 10:13:03 +11:00
f667250b2c Some PHP 8.4 deprecration fixes regarding NULL assignment to cast values on class instantiation 2025-03-16 10:13:03 +11:00
4a84c25ac7 Add Attribute required by ObjectClasses in schema viewer,
Attribute is_rdn dynamically calculated,
Fix Required by Objectclasses when viewing a DN
2025-03-16 10:13:03 +11:00
8ab5b4f35c Move direct controller direct view calls to route/web, add global $server to use in views, negating the need to use config('server') 2025-03-16 10:13:03 +11:00
de2d139288 Some DN rendering fixes, so that our Server Info renders correctly (aligned values) 2025-03-16 10:13:03 +11:00
d326d3c308 Store our DN and objectclasses in Attribute::class entries, so that we can dynamically calculate is_rdn and required objects (to be implemented) 2025-03-16 10:13:03 +11:00
d3fc9c135f When creating a new entry, and an RDN attribute has more than 1 input, only take over the first input when selecting the RDN attribute 2025-03-16 10:13:03 +11:00
eb6e0b8d43 Include LDAP diagnostic error message when we have an LDAP error 2025-03-16 10:13:03 +11:00
b01f7d5baf Attribute cleanup and optimisation in preparation to support attribute tags, HomeController return casting 2025-03-16 10:13:03 +11:00
1ddb58ebbb Buttons that trigger ajax activity cant be buttons, change them back to span 2025-03-13 23:25:04 +11:00
b260912e01 Revert changing buttons in 49fd9b419a 2025-03-13 21:22:31 +11:00
7debd9ff2b Node updates to address vulnerabilities in babel/helpers and axios. Framework update too. 2025-03-13 09:43:47 +11:00
49fd9b419a Some jquery selector changes, change some button spans to buttons, set readonly on the form for attribute javascript, fix krbTicketFlags to only be changed when in edit mode
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m29s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m38s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-13 09:33:45 +11:00
3161fe4fcb Fix password hash select list, was not being editable when choosing edit mode 2025-03-13 09:33:44 +11:00
add3f85812 Improved handling for Kerberous attributes - closes #154 2025-03-13 09:33:44 +11:00
853bd92340 Fix detection of zero values when rendering update NEW/DELETED tags 2025-03-13 09:33:44 +11:00
a56b2d8002 Add some opendj internal attributes. Remove some unused variables in APIController 2025-03-13 09:33:44 +11:00
af7ca851d5 Release v2.0.2
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m40s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-11 20:11:36 +11:00
b34dad8836 Fix when adding a new objectclass with required attributes, validation errors are correctly display on the returned form 2025-03-10 13:25:43 +11:00
ef2ea5e266 Fix detection of new attributes added to an entry 2025-03-10 13:25:43 +11:00
91b5b53137 When making new attributes available, only render unique attributes 2025-03-10 13:25:43 +11:00
d4c916923d When adding new attributes as a result of adding a new objectclass, dont duplicate existing attributes already present 2025-03-10 12:35:38 +11:00
e94a7d58e1 Disable buttons that we havent implemented yet, update README with some more todos 2025-03-09 14:08:45 +11:00
15d5bf605a Include loopback in our trusted proxies configuration - fixes #294 2025-03-09 13:32:58 +11:00
33c59e5e65 Release v2.0.1
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m23s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-07 16:56:03 +11:00
c86d3c97a1 CSS fix to remove border around logged in user icon 2025-03-07 16:56:03 +11:00
be87a12f21 We need to start the application after we've swapped the user details from the cookie, otherwise $user is initialised by the LDAP_USERNAME credentials - which may not have access to all the attributes 2025-03-07 16:36:35 +11:00
e99e349c0b Make the file-note responsive to screen size, with a more appropriate size 2025-03-07 13:32:09 +11:00
baf5acc01a When creating a new entry, and validation redirects back to the form, ensure our RDN readonly is preserved
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m22s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-07 11:00:11 +11:00
00a8350f1d Fix rendering of error message, minor changes to login as a result of ba9124c. Record in README we can now do deletes
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m30s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m32s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-03-07 09:10:35 +11:00
732f777c75 Rename our configuration keys to ldap/ldaps/startls, they are not openldap specific 2025-03-07 08:33:08 +11:00
c588e13bd8 Clear some javascript @todos: fancytree options, optionclass processing
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 3m27s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m25s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-06 23:47:24 +11:00
dc623b18ae Laravel framework and npm modules update 2025-03-06 23:47:24 +11:00
d97087b83f Implemented DN delete 2025-03-06 21:09:05 +11:00
c8c3939d59 Style changes when rendering the DN header 2025-03-06 20:43:19 +11:00
daf240e363 When the session expired, automatically refresh the page with the intended desitination without the alert 2025-03-06 20:43:19 +11:00
070aabfc88 Switch to using icons when rendering a DN, and move the server icons to the topmenu 2025-03-06 20:43:19 +11:00
57b6b8c1f1 Fix search close btn and other css fixes as need after upgrading to ArchitectUI v4 2025-03-06 20:43:19 +11:00
4c09e767bc Add search to README as a pending item
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m25s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-04 10:23:26 +11:00
07836f3d30 Update CI/CD to build the image with the appropriate tag
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 3m27s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m29s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-04 10:16:13 +11:00
41d6948f3c Fixes to customation of ArchitectUI for mobile displays, hamburger should now be visibile and search is not black on black.
Closes #292
2025-03-04 10:15:41 +11:00
ba9124ce0f Update ArchitectUI to v4 2025-03-04 10:15:41 +11:00
949c7f30c3 Revert version to 2.0.1-dev 2025-03-04 10:15:41 +11:00
a59bbc8790 Improve rendering of objectclasses in entries 2025-03-03 16:56:29 +11:00
9b3ef7a3ba Revert "Only run CI/CD on master/sandpit"
Trying to leverage tag id during build

This reverts commit 9e39e607cf70b4b4e36eab83976cf277cde0fa8c.
2025-03-03 16:56:29 +11:00
9e39e607cf Only run CI/CD on master/sandpit
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m24s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-02 10:30:36 +11:00
54a007ff68 v2.0.0 initial release
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 26s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m22s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m34s
Create Docker Image / Final Docker Image Manifest (push) Successful in 8s
2025-03-02 10:30:36 +11:00
32aed0f458 Remove old 1.2 code no longer in use, and same laravel framework items not used 2025-03-02 10:30:36 +11:00
37c7d91744 Set our HTML to tell browsers not to translate the page - closes #290 2025-03-02 10:30:36 +11:00
da7e88e834 Enable getDNSecure to include a command, that is encrypted with the DN 2025-03-02 10:30:36 +11:00
dc2f3f37f6 Fix for artisan optimize as a result of a config file having a validation rule 2025-03-02 10:30:36 +11:00
996d7bb1dc This commit is mainly as a result of creating DN entries and improves some backend functions:
* Enable creation of new entries,
* Change all our ajax frames to go through /frames URI instead of /dn,
* Add our frame command to the encrypted DN,
* Automatically redirect to root URL when selecting a tree item and currently in another path (as a result of a prior POST activity),
* Some validation improvements DNExists/HasStructuralObjectClass
2025-03-02 10:30:36 +11:00
f08fdb1bcd Update Request validation, so that it also knows about required schema attributes 2025-03-01 19:32:40 +11:00
0684424328 Force PLA to not allow guests viewing the site, and thus requiring a login.
This should close #288
2025-02-26 17:28:54 +11:00
f20d9891f2 Fix user swap broken in db4b901 2025-02-26 17:21:00 +11:00
f9bd352bfb Get version into build image automatically, add docker image labels 2025-02-25 17:30:17 +11:00
e0e4b0264d Remove data- elements in resources/ we didnt end on using them 2025-02-24 22:42:18 +11:00
03c2eba9e3 Add a STARTTLS example to the configuration file 2025-02-23 22:30:39 +11:00
176be19043 The call to dns_get_record() in error.blade is not returning IP addresses, so use DNS_A|DNS_AAAA instead of the default DNS_ANY
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m37s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
This is referenced in #211, but not the actual cause of that issue though
2025-02-23 13:59:56 +11:00
ff0bbc758d Removed some old files where functionality has been adapted in PLA v2 2025-02-22 17:45:20 +11:00
8cbd4eaed5 Use the same component to render internal attributes 2025-02-21 22:56:15 +11:00
1cc8681b5a Add example ldaps configuration, set TLS_REQCERT to never so php_ldap does validate ldap server SSL certs
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m32s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-02-21 21:42:02 +11:00
d64478e449 Improved trapping of DNS errors when unable to contact LDAP server, should help #211
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m32s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m41s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-02-21 12:22:45 +11:00
7950cc3404 Some php 8.4 deprecation fixes
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 27s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m29s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m35s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-02-16 14:24:23 +11:00
7e0d1eb0e3 Get our server name from config 2025-02-16 14:24:22 +11:00
724a2f02be Fix import, missing sprintf() and should use has() not contains() 2025-02-16 14:24:22 +11:00
f82cf33f7f Minor adjustments to Dockerfile build and init-docker startup
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m49s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-02-12 23:05:49 +11:00
9506a01016 Update to php 8.4 and framework/js updates
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 3m53s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m51s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m56s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-02-12 15:18:28 +11:00
29f7ce276d Fix userPassword hash selection, broken by bb9374e 2025-02-12 15:12:56 +11:00
8170e81d13 Install in /app now, not /var/www/html 2025-02-12 13:02:22 +11:00
bb9374ec01 When removing added objectClasses, blank out any attributes added by those objectClasses
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 34s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m23s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-02-06 23:10:47 +11:00
c0e6b62ee5 Fix rendering Add Objectclasses, remove existing OCs from the list and dont rebuild the select list after the first invocation 2025-02-04 20:54:55 +11:00
7513ed6def More work on work on adding/removing objectclasses to an entry, still need to automatically remove attrs from removed objectclasses
Some checks failed
Create Docker Image / Build Docker Image (arm64) (push) Has been cancelled
Create Docker Image / Build Docker Image (x86_64) (push) Has been cancelled
Create Docker Image / Final Docker Image Manifest (push) Has been cancelled
Create Docker Image / Test Application (x86_64) (push) Has been cancelled
2025-02-04 09:23:12 +11:00
bbef155fd2 Fix for 'Couldnt figure out a password hash for {SSHA}' fixes #286 2025-02-04 09:23:09 +11:00
13e645dde0 Schema items no longer used for test/demo 2025-02-04 08:56:12 +11:00
1f1db14ae9 Fix getMissingAttributes(), wasnt evaluating the different objects correctly 2025-02-04 08:56:12 +11:00
b2335e26f2 Consistent calling of btn css, no functional changes 2025-02-04 08:56:12 +11:00
d61685a5b2 Work on adding additional objectclasses to an entry 2025-02-04 08:56:12 +11:00
3a4b0bfe05 Remove hardcoded use of default LDAP server, added example for opendj 2025-02-04 08:56:12 +11:00
16452ebfa9 Change use of Config::class for consistency 2025-01-19 22:17:36 +11:00
4dfebe9053 For the schema browser, highlight structural object classes when showing attributes. Expose objectclass objects instead of names for objectclasses of a DN 2025-01-19 22:01:20 +11:00
05012c9e6c Consistent naming for modal items and move dn into javascript variable for DN entry 2025-01-19 21:54:01 +11:00
3d40288506 Enhancement to Add Value to include the input group. 2025-01-19 11:32:05 +11:00
6a461d320a Added labeleduri to test environment, with example LDIF to implement #89 2025-01-18 23:16:45 +11:00
673f070cb7 Add support for SASL Kerberous realms. Closes #114
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m22s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m39s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-18 22:55:50 +11:00
cad0a11bd2 Update Readme 2025-01-18 22:08:01 +11:00
d1b4334870 Move PLA configurable items to config/pla.php
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 36s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m22s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m30s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-18 21:54:21 +11:00
2445cac6a6 Add Crypt based password functions 2025-01-18 21:47:49 +11:00
d3d7881e3b Added additional password hashing functions
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 33s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m26s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2025-01-18 16:43:49 +11:00
77a139016b Fix when cloning an attribute, ensure we blank out the previous value. When processing request submission '0' could be a valid value. 2025-01-18 16:43:49 +11:00
5a922fe202 For rebuild of cache assets, since hashFiles() doesnt work 2025-01-18 16:43:49 +11:00
08e838d40a Foundation for Check Password and password functions - only Clear is currently implemented 2025-01-18 16:43:49 +11:00
30f964b849 Use our Attribute::class when rendering update_confirm 2025-01-18 16:43:49 +11:00
293f1ab9ce Remove usage of search() === to contains() 2025-01-18 16:43:49 +11:00
960e0de5c8 Fix to getDirty() when using MD5Updates Trait on attributes 2025-01-18 16:43:49 +11:00
6e06caa83b Some code optimisation and de-duplication with components 2025-01-18 16:43:48 +11:00
8b922b2e8b Add select2 bootstrap 5 theme 2025-01-18 16:43:48 +11:00
026b3f5a20 Use components for form buttons and file notes 2025-01-18 16:43:48 +11:00
db4b90183f Fix excess memory being used when building schema
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 37s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m23s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m29s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2025-01-13 22:03:47 +11:00
fcec58441f Autocreate our encryption key when container starts if it isnt already set
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 31s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m23s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m37s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-13 09:27:25 +11:00
565435403f Added kerberos to test environment, with example LDIF to implement #154 2025-01-12 22:31:40 +11:00
08e2ee2d1b Some mailHost/mailRoutingAddress attributes for testing 2025-01-12 22:10:20 +11:00
6fcb8911a1 Change dunglas/frankenphp base image to something more recent
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 34s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m23s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m32s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-12 21:34:21 +11:00
9d97bb0f96 Remove mcamara/laravel-localization it doesnt work with laravel 11 (yet)
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m20s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m26s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-01-12 18:26:26 +11:00
d838e07072 Update npm dependancies 2025-01-12 18:26:26 +11:00
81014b9509 Update laravel framework, to laravel 11 2025-01-12 18:26:26 +11:00
f460af7a47 Cache page assets during CI/CD 2025-01-12 18:26:26 +11:00
fd161d108c Add building web assets to CI/CD 2025-01-12 18:26:26 +11:00
a71cb13847 Change CI/CD build from gitlab to gitea 2025-01-12 18:26:26 +11:00
a9e6c82ce7 Swap out base docker container for dunglas/frankenphp, enabling us to run as non-root, addressing #279.
By default the container web address is now port 8080, so port mapping of -p 80:8080 will now be required
2025-01-12 18:26:16 +11:00
bd62897e80 Turn down the verbosity with an internal config attr when parsing the schema.
This helps while developing, without memcached running we get 100,000's of logs while rendering the test environment.
Also fixes a deprecated parsing null to strlen().
2025-01-02 19:53:14 +11:00
e399b733e9 Deprecate using osixia/openldap and setup test configuration using our own alpine/ldap container 2025-01-02 19:53:14 +11:00
7e25000e68 Test needs npm 2025-01-01 17:47:28 +11:00
41fb40983b Enable builds for armv7l and arm64 2025-01-01 17:47:28 +11:00
37cf1292df Updates to PHP 8.3 2025-01-01 17:47:28 +11:00
14f895a964
Update bug_report.md 2024-07-16 14:18:12 +10:00
59c8ed95c5 Fixes for testing 2024-01-21 18:15:23 +11:00
4c8bd1c81f Start of implementation of Import and Export using LDIF 2024-01-21 15:56:25 +11:00
ded1f74285 Remove some no longer to be referenced 1.2 files 2024-01-20 16:07:57 +11:00
b6d1124d4e Improve javascript when selecting sidebar items 2024-01-20 16:07:44 +11:00
be40178234 Move frames/schema items to fragment/schema 2024-01-20 16:07:44 +11:00
acc6598da1 Move ApplicationSession::class earlier, we are dependant on config('server') existing when loading user details 2024-01-20 16:07:44 +11:00
c1ba6df90d DN updates some array values can be NULL (to delete the value), so validation show allow for that 2024-01-20 16:07:44 +11:00
76306b9a1b Add nunomaduro/collision to dev environment, and update phpunit for testing 2024-01-20 16:07:44 +11:00
332aa279a8 Enable navigating directly to frames via a url fragment 2024-01-20 16:07:43 +11:00
0f9bb07d21 Enable returning to form frames by the existance of a frame input 2024-01-20 16:07:43 +11:00
b92157a987 Put back APIController::bases() removed by 851010d. It's used by a JS query if are not given to a view 2024-01-20 10:37:47 +11:00
6991983743 Rework Components to use consistent variables and interface 2024-01-20 10:37:47 +11:00
cb06f3dcb6 Catch exception when trying to connect to update server 2024-01-20 10:37:47 +11:00
eda3680997 Fix for modals not displaying correctly 2024-01-12 00:23:44 +11:00
6cef2dfa99 Set container in production mode, to remove debugging 2024-01-10 00:01:03 +11:00
3b6ee582dd Fix adding new attributes, show that blank values will delete the attribute 2024-01-09 23:37:15 +11:00
1f753c4dc6 Standardise attribute layout 2024-01-09 23:28:17 +11:00
c02f390f64 Fix display of password attributes and update processing with jpegphoto and password 2024-01-09 17:44:50 +11:00
c8fffd6d81 With 74bd996 enable login via DN.
Enhances #253
2024-01-09 13:29:15 +11:00
cb783da34b Swap out nunomaduro/collision for spatie/laravel-ignition 2024-01-09 00:19:30 +11:00
12da43828e Update parent container to address vulnerabilities 2024-01-09 00:02:59 +11:00
74bd996f7a Enable login by any attribute - defaults to uid.
Implements #253
2024-01-08 15:09:17 +11:00
ef355e8193 Implement LdapRule to limit user logins by objectclass.
Now logins are allowed by any objectclass unless LDAP_LOGIN_OBJECTCLASS is defined, we should be an array of allowed objectClass (any match).
Improvement for #245
2024-01-08 15:08:26 +11:00
18f9f1a9b3 Update directorytree/ldaprecord-laravel to v3 2024-01-08 12:28:11 +11:00
8529b1fd18 Javascript updates 2024-01-08 11:24:22 +11:00
290ea279b9 Framework and dependancy update to v10.39.0 2024-01-08 11:16:35 +11:00
00fb3e9312 Our favicon needs to be an absolute path 2023-09-02 23:24:18 +10:00
652cdee034 Enabled adding new attributes to a DN 2023-09-02 23:24:18 +10:00
6d900d0964 Work out which attributes are available to a DN 2023-09-02 22:16:18 +10:00
9d1d969113 Update javascript components - should close #213 2023-08-30 11:36:30 +10:00
c8b5b2303a Framework update that should close #212 2023-08-30 10:42:37 +10:00
7382394783 Fixes #226 when the tree was longer than the page height 2023-08-30 10:05:59 +10:00
8b72933be2
Update bug_report.md 2023-08-08 08:51:19 +10:00
c907180882 Framework update - addressing #206 2023-04-21 20:18:23 +10:00
36a985554d Fix for when user changes their own password, and thus the password in the cookie is no longer valid 2023-04-13 21:01:15 +10:00
9207d4e698 Information on docker container 2023-04-13 14:42:03 +10:00
c3f9e80b78 Fine tune CI cache paths 2023-04-13 10:40:01 +10:00
a4c05002a1 Ensure docker build updates public/ and remove other unnessary files from image 2023-04-13 10:40:01 +10:00
9e90820bfd Debugging docker image source validation 2023-04-13 10:40:01 +10:00
eafae02c7b Enabled form validation 2023-04-13 10:40:01 +10:00
f01f88b3bd Work on DN edit rendering 2023-04-13 10:40:01 +10:00
20a2fede08 Update framework to Laravel 10 2023-04-06 09:34:45 +10:00
Deon George
5b046a95eb Remove old PLA files that are no longer required. 2023-04-05 10:34:35 +10:00
409b0301dc Update CI/CD to use specific test ldap instance 2023-04-03 10:37:26 +10:00
02f3152ffd Change to consistent use of @lang in views where possible 2023-04-03 10:14:20 +10:00
a62e7ddeca Change Schema classes to final 2023-04-03 10:14:20 +10:00
4fd51abcb1 More work on displaying and editing an LDAP entry 2023-04-03 10:14:20 +10:00
d6f833f6eb Add sudorole schema 2023-04-02 11:50:40 +10:00
2accc63091 Set git version to numeric 0 2023-04-01 12:11:09 +11:00
dcb0269fc5 Demo environment reconfigure.
It is now possible to login with admin@test/password and update entries in dc=example.com
2023-03-31 16:39:56 +11:00
c36383b0fc Start of enabling DN update. 2023-03-31 16:39:56 +11:00
a1a9b8ba76 PHP framework and npm modules update, should close #198,#199,#200 2023-03-30 20:34:12 +11:00
e9cb41e5e4 Added fancytree persist 2023-03-30 20:08:20 +11:00
30b749dc75 Show a debug tag while in local 2023-03-30 20:08:20 +11:00
f043c74ae6 Handle RFC3866 Language Tags 2023-03-30 20:08:20 +11:00
61202d3617 CI to build javascript/css 2023-03-30 20:08:20 +11:00
dd17873905 Update architect-ui, bootstrap, javascript and css 2023-03-27 19:22:47 +11:00
a46a61249e
Update issue templates 2023-03-03 17:25:35 +11:00
f90706d140 Merge BRANCH-2.0 with master 2023-03-03 16:32:49 +11:00
583ec23f99 Add link to the demo site 2023-03-03 16:20:01 +11:00
7458001f5a Enabled version update check 2023-03-03 16:07:11 +11:00
Deon George
08678ce929 Adding .dockerignore to trim the docker container, removing some redundant files and updated README 2023-03-02 22:07:58 +11:00
a99770951d Implemented more attribute classes 2023-03-02 19:07:45 +11:00
7d19b89637 Implemented can_addvalue 2023-03-02 19:07:45 +11:00
c0c9a5576e Added rendering attribute hints 2023-03-02 19:07:45 +11:00
35596ec867 Rename GuestUser to ApplicationSession as middleware to hold any site wide variables 2023-03-02 19:07:45 +11:00
e0fb057c84 Implemented attribute sorting with configuration to determine sort order 2023-03-02 10:17:15 +11:00
4767eb3a4f JS fixes for when we get a 419 2023-03-02 09:56:09 +11:00
ee556582d2 Start of hook to check for version updates 2023-03-02 09:55:33 +11:00
64d1a09db4 Minor schema cosmetic code fixes, more Attribute implementation from old pla, start of LDAP DN view/edit 2023-03-02 09:54:30 +11:00
933ab44b99
Create FUNDING.yml 2023-02-27 10:08:24 +11:00
Deon George
491f04cd5d Updated server info 2023-02-19 20:25:32 +11:00
Deon George
4f9accbadf Move some server function to Server::class (from Entry::class) 2023-02-19 16:35:07 +11:00
Deon George
92e5afd614 Improved caching of schema 2023-02-19 00:32:46 +11:00
Deon George
8ec1d2b1fe Ported the schema browser 2023-02-18 23:46:41 +11:00
Deon George
815cd49868 Add bootstrap 2023-02-18 20:15:50 +11:00
Deon George
651fb9f3bf Show version in the footer 2023-02-18 00:16:25 +11:00
Deon George
cb55a660e5 Reduce size of the sidebar, to give more realestate to the data side 2023-02-16 20:27:32 +11:00
Deon George
66409c6688 Fixes to CI/CD now that we use osixia/openldap 2023-02-05 16:32:58 +11:00
Deon George
637a0cd0f4 Change docker build to use alpine directly, with PHP and ldap module 2023-02-05 16:12:04 +11:00
Deon George
482d9670e3 Capture LDAP authentication failure when querying baseDNs 2023-01-31 14:16:56 +11:00
Deon George
6751c9dd81 Enable authentication if the LDAP server has multiple base DNs. Store the user's credentials in a cookie/session, and swap them out to the configured credentials when logged in. 2023-01-31 14:16:56 +11:00
Deon George
413f1ec065 Implemented caching of our base_dn 2023-01-31 10:44:35 +11:00
Deon George
210793e814 Validate unit testing is working 2023-01-31 10:44:35 +11:00
Deon George
daeea9a1f6 Update laravel to 9.x 2023-01-31 10:44:35 +11:00
Deon George
e0185345c8 Start of a debug screen 2023-01-31 10:44:35 +11:00
Deon George
58e171aea1 PLA now starts at the root of the HTML request, favicon setup 2023-01-31 10:44:35 +11:00
Deon George
d0242ce3d8 Move our sample schema/data into a tests/server, we'll use osixia/openldap for the demo/testing 2023-01-31 10:44:34 +11:00
Deon George
181a57586c Remove some old PLA 2021-12-12 14:13:55 +11:00
Deon George
10a2d2161b Start of using Attribute objects, rendering jpegphoto 2021-12-12 14:13:55 +11:00
Deon George
dabca67fc8 Updated directorytree/ldaprecord-laravel to v2 2021-12-11 00:24:00 +11:00
Deon George
a80a2725bc Start of using Attribute objects, rendering jpegphoto 2021-12-11 00:24:00 +11:00
Deon George
2ccc1d3b83 Framework update and updates from other projects,remove leenooks/laravel
Framework updates, and hack to get CI testing working
2021-12-11 00:24:00 +11:00
Deon George
88eb35a567 Some CSS fixes, to fix rendering the sitemap when the sidebar is collapsed 2021-12-11 00:24:00 +11:00
Deon George
0b867abbac Reorganise docker CI configuration 2021-12-11 00:24:00 +11:00
Deon George
48131c1b4e Fix showing DN icon for RootDSE, fix readme git clone 2021-12-11 00:24:00 +11:00
Deon George
851010d6d5 Add icons for each DN based on objectClass 2021-12-11 00:24:00 +11:00
Deon George
2a099e2dc4 Move getBaseDN to Entry class, some cleanup 2021-12-10 23:51:49 +11:00
Deon George
4ef074fac4 More unit testing, setup for localisation 2021-12-10 23:51:48 +11:00
Deon George
b043e3bc93 OID update, fix sidebar icon rendering 2021-12-10 23:51:48 +11:00
Deon George
902330e734 Added home screen note, renamed custom login note to html 2021-12-10 23:51:48 +11:00
Deon George
cec8775f8e Composer updates 2021-12-10 23:51:48 +11:00
Deon George
d20a17d3fe Added server info 2021-12-10 23:51:48 +11:00
Deon George
db61e0d1ce Login validation, user profile icon 2021-12-10 23:51:48 +11:00
Deon George
c549d28340 Change query() to children() - expose hassuborinates 2021-12-10 23:51:48 +11:00
Deon George
1ebdffa358 Fixes for testing now that we are using directorytree/ldaprecord-laravel 2021-12-10 23:51:48 +11:00
Deon George
15ff508429 Swap out adldap2/adldap2 for directorytree/ldaprecord-laravel 2021-12-10 23:51:48 +11:00
Deon George
f323be3d7f Start on fetching DN from server 2021-12-10 23:51:48 +11:00
Deon George
130ae005a3 Added Architect UI 2021-12-10 23:51:48 +11:00
Deon George
e89b4d3287 Updated composer dependancies 2021-12-10 23:51:48 +11:00
Deon George
7a195bb844 Improved tree rendering 2021-12-10 23:51:48 +11:00
Deon George
6620b9147e API query and CI to build the docker demo image
Remove unused CI, removed some debugging for the demo
2021-12-10 23:51:48 +11:00
Deon George
de4fa04d3b Start of tree being rendered by API/AJAX calls 2021-12-10 23:51:48 +11:00
Deon George
1e3e4b2196 Setup CI testing 2021-12-10 23:51:47 +11:00
Deon George
f3282bed38 Framework upgrade to Laravel 7 2021-12-10 23:51:47 +11:00
Deon George
f8717480fd CSS/JS updates, initial page rendering 2021-12-10 23:51:47 +11:00
Deon George
4c90ce11f2 Initial login working 2021-12-10 23:51:47 +11:00
Deon George
ed7087c802 Initial Laravel Base 2021-12-10 23:51:47 +11:00
Deon George
fc7ab06358 Fix broken git command in readme - closes #124 2021-12-10 15:05:13 +11:00
Deon George
a4924f7453 Updated README with info on PLA v2 2020-09-12 22:41:52 +10:00
Deon George
0011184a3f Documenting OID 1.3.6.1.1.22 - Thank you. Closes #102 2020-08-30 22:27:03 +10:00
Deon George
aa5be41b06 Add autocomplete=off - closes #122 2020-08-30 22:09:52 +10:00
Gurvinder Dadyala
bdfd68c3b6
Added Bcrypt support (#116)
* Set minimum PHP version to 5.5.0| Bcrypt Support
* Added Bcrypt hash support
* Update Install.md
2020-08-30 21:58:50 +10:00
Armin Leuprecht
fb437b037e
Decode plainpassword before check (#115)
When the user's password contains HTML special chars
the password check would always fail if the
the given plainpassword is not decoded first.
2020-08-30 21:57:40 +10:00
Bennet Bleßmann
34d4f20222
Fixes usage of deprecated array/string access syntax. (#97)
PHP 7.4 Compatibility.
2020-08-30 21:56:25 +10:00
JamesCordell
0b65747110
Changes required so the sudoRole objectClass will present a link so members can be modified by default. (#101) 2020-02-20 09:17:37 +11:00
Jakub Filak
4661aa2114
Hooks fixes (#99)
* repace deprecated each with foreach

I tried to enable the example.php hooks and the use of the keyword each
was causing crashes in the docker image osixia/phpldapadmin:0.9.0

* check if DEBUG_ENABLED is defined

I enabled the hooks example.php and I started getting crashes caused by
undefined constant.

Tested with the docker image osixia/phpldapadmin:0.9.0
2020-02-20 09:17:01 +11:00
sshambar
0a57b2f80e
Added appearance option show_authz (#94)
Enabling displays the authorization ID rather than the authentication ID,
similar to using ldapwhoami.  Requires PHP 7.2+
2020-02-20 09:14:18 +11:00
sshambar
0fe1758572
Add SASL PLAIN authentication support (#92)
Adds a new sasl mech 'plain' which converts all simple authentication
methods to SASL PLAIN.  NOTE: doesn't use auth_type 'sasl' as
credentials may come from login form, stored in cookies etc...
2020-02-20 09:12:39 +11:00
Noone404
4eb3737d31
Added option to use template string for bind DN (#90)
* Language update from launchpad

* Added login option 'bind_dn_template'
2020-02-20 09:11:17 +11:00
Genaro Contreras Gutierrez
cbdc0dacd6
Auth Form wiht Google reCAPTCHA (#87)
* reCaptcha config

* config reCaptcha

* check reCAPTCHA

* add reCAPTCHA to form login

* config attributes for reCAPTCHA

* Function to verify request with reCAPTCHA

* doc reCaptcha
2020-02-20 09:04:20 +11:00
Deon George
8f4ced96f9 Release 1.2.5 2019-08-20 22:24:40 +10:00
Deon George
722fefad1c
Merge pull request #84 from nayo/patch-2
Fix error and set by default to preventXSS. Closes #84 and #85
2019-08-07 16:34:53 +10:00
Genaro Contreras Gutierrez
c87571f6b7
Fix error and set by default to preventXSS 2019-07-31 08:21:14 -07:00
Deon George
cb9c0cce3e
Merge pull request #82 from nayo/patch-1
Function to prevent XSS attacks
2019-07-31 07:38:06 +08:00
Genaro Contreras Gutierrez
0b10c30c79
other usage of function preventXSS
Other example of usage:
preventXSS(get_request('cmd','REQUEST'))

Additionally, the $ preventXSS parameter of the get_request function can set the default to true and in the specific fields set the parameter to false
2019-07-30 08:49:41 -07:00
Genaro Contreras Gutierrez
c22c98c463
update get_request when an error occurs
Example to use to prevent XSS attack from get_request

get_request('cmd','REQUEST',false,null,true)
2019-07-30 08:44:10 -07:00
Genaro Contreras Gutierrez
25cbb26e1d
update function get_request to preventXSS
The XSS prevent function was created and used
2019-07-30 08:38:14 -07:00
Genaro Contreras Gutierrez
08c21fe7ca
Prevent XSS attack since function get_request
The $preventXSS parameter was added to the get_request function to avoid XSS attacks.
It was not set by default as $preventXSS=true, because it can affect fields such as passwords.

Using "htmlspecialchars" and "addslashes" functions of PHP.
2019-07-30 08:29:17 -07:00
Deon George
1bd14ddf68 Removed reference to missing function - closes #65 2019-07-15 14:49:52 +10:00
Deon George
95411c05e1 Release 1.2.4 2019-05-14 15:01:32 +10:00
Deon George
7b1f6b5132 Fix for PHP 7.3 - deprecated continue in switch 2019-05-14 15:00:28 +10:00
Deon George
3c0ca27477 Remove SF branding 2019-04-21 23:37:10 +10:00
Deon George
511ead3ec6 Revert #63 - Add attribute not rendering correctly 2019-04-20 15:39:48 +10:00
Deon George
e37b498de1 PHP 7.2 compatibility fixes - closes #64 2019-04-19 22:48:22 +10:00
Deon George
29d7d4b2f7 Fixes #31 - Glue entries are not browsable through phpldapadmin 2019-04-19 21:01:02 +10:00
Deon George
c494078550 Closes pull request #22 and fixes #18 - preg_replace_callback changes 2019-04-19 20:08:53 +10:00
Deon George
73b7795bc0 Fixes #21 - Undefined variable: _SESSION 2019-04-18 23:17:24 +10:00
Deon George
c1af05f403
Merge pull request #63 from dago/renderfix
Fixes for translation of "Add new attribute"
2019-04-18 12:34:00 +10:00
Deon George
49ef60f26b
Merge pull request #62 from spagu/patch-1
Fix php7.2 errors for function __autoload and create_function as they were deprecated.
2019-04-18 12:31:49 +10:00
Deon George
aa11e318ec
Merge pull request #60 from NHellFire/php7.1
Use OpenSSL for blowfish when available (fixes #58)
2019-04-18 12:16:08 +10:00
Deon George
f3aad72b57
Merge pull request #66 from MichaelIT/master
Incompatable with openLDAP >=2.1.2
2019-04-18 11:58:12 +10:00
Deon George
6a55d808a2
Merge pull request #69 from RoyChaudhuri/master
Fix for bug #68, long redirect response
2019-04-18 11:56:25 +10:00
Deon George
aec5053f55
Merge pull request #71 from anarcat/CVE-2017-11107
Fix multiple XSS in file htdocs/entry_chooser.php (CVE-2017-11107)
2019-04-18 11:54:45 +10:00
Antoine Beaupré
4484129a41
Fix multiple XSS in file htdocs/entry_chooser.php (CVE-2017-11107)
Closes: #50

From: Ismail Belkacim <xd4rker@gmail.com>
2018-10-31 14:04:44 -04:00
Roy Chaudhuri
2e43cf95b9 Fix for bug #68, exit after redirect response when URI parameter is received by index.php 2018-09-17 15:45:42 +01:00
Michael
7569423f11
Update functions.php
Since openLDAP >=2.1.2,ldap_explode_dn turns unprintable chars (in the ASCII sense, UTF8 encoded) into \<hexcode>.
2018-07-17 19:59:11 +08:00
Dagobert Michelsen
5c0f787fbf Add URL for translation 2018-04-20 13:10:20 +02:00
Dagobert Michelsen
6c85d61525 Fix invocation of layout in TemplateRenderer 2018-04-20 12:33:58 +02:00
spagu
884cce1475
Update functions.php 2018-04-19 11:10:12 +01:00
NHellFire
53e005c1f4 Use OpenSSL for blowfish when available (fixes #58) 2018-02-11 07:22:36 +00:00
Deon George
733a10a1c5 Merge pull request #40 from PatrickBaus/master
Fixed detection of SSL encryption behind proxy server
2016-10-30 16:53:36 +08:00
Deon George
708bc5ed83 Merge pull request #37 from mr-GreyWolf/patch-1
Update functions.php
2016-10-30 16:53:05 +08:00
Deon George
e46579b34e Merge pull request #34 from gulikoza/master
Fix moving ldap entries and login error with 'fallback_dn'
2016-10-30 16:52:22 +08:00
Deon George
4fefe2aa8c Merge pull request #42 from ptomulik/crypt-sha
add support for SHA-256 and SHA-512 via crypt(3)
2016-10-30 16:47:44 +08:00
Paweł Tomulik
ee9034f24c add support for SHA-256 and SHA-512 via crypt(3) 2016-10-08 21:24:33 +02:00
Patrick Baus
61af45e872 Enabled HTTP_X_FORWARDED_PROTO header detection. It was disabled for testing. 2016-08-11 02:45:18 +02:00
Patrick Baus
dd6e9583a2 Fixed request smuggling vulnerability. See: https://www.owasp.org/index.php/OWASP_Periodic_Table_of_Vulnerabilities_-_HTTP_Request/Response_Smuggling
According to https://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader%28%29-method, the header should be ignored anyway if those properties were set.
2016-08-11 01:48:12 +02:00
Patrick Baus
665dbc2690 Fixed detection of SSL encryption, when a reverse proxy is used, that does the encryption.
If the server sets the HTTP_X_FORWARDED_PROTO header to 'https' or the
HTTP_X_FORWARDED_SSL header to 'on' SSL encryption is assumed
2016-08-11 01:32:41 +02:00
mr-GreyWolf
599d55700d Update functions.php 2016-03-30 23:07:02 +04:00
gulikoza
726190e5b8 Fix moving entries when confirm['copy'] is set.
If 'Delete after copy (move)' is selected and confirm['copy'] is set (which is default),
the entry will be copyied (created) not moved. This patch will skip confirm when entry
is being moved as there is no reason to confirm the move again.
2016-01-24 12:02:42 +01:00
gulikoza
0b8375fd2a Add additional check that full dn has been entered on login.
Fixes 'invalid dn syntax (34) for user' error when fallback_dn set and username was not found while trying to use it as dn.
2016-01-24 11:52:21 +01:00
Deon George
fa88250f0e Merge pull request #32 from jsdevel/fixing-sflogo-protocol-for-reverse-proxies
Changing the sourceforge logo to be protocol relative.
2015-12-07 16:56:41 +11:00
jsdevel
0491916d90 Changing the sourceforge logo to be protocol relative.
* This allows the browser to resolve the URL against the protocol the user used, not what a reverse proxy used.
2015-12-05 23:02:11 -07:00
Deon George
c004a291d7 Merge pull request #19 from scollin/master
Fix some monitor information problems
2015-02-23 12:40:32 -08:00
Sébastien Collin
54191d7ffb Fix some monitor information problems
Fix some monitor information problems as reported by @brendankearney
2015-01-30 13:56:29 +01:00
Deon George
9e283f369f Merge pull request #13 from DevoKun/master
Changed password_hash to pla_password_hash in a few places where it was still password_hash.
2014-10-08 12:30:58 +11:00
Devon Hubner
19114385fc Changed password_hash to pla_password_hash in a few places where it was still password_hash. 2014-10-07 14:25:32 -04:00
Deon George
7701e98bcc Merge pull request #11 from robgloess/patch-1
Fixed parse error in TemplateRender.php
2014-10-07 12:54:51 +11:00
robgloess
d4c2fb52ab Update TemplateRender.php
Fixed typo on 1682 - parse issue, non escaped " ' " causing error to be thrown
2014-09-30 22:28:09 +01:00
Deon George
7cbdd0c8db Merge pull request #9 from uda/master
Minor doc changes
2014-09-23 15:59:42 +10:00
Yehuda Deutsch
afec12d163 Rename INSTALL to INSTALL.md 2014-09-21 11:11:07 +03:00
Yehuda Deutsch
a4a602b6ec Created README.md 2014-09-21 11:10:30 +03:00
Deon George
e1952cddb6 Merge pull request #6 from marclaporte/patch-2
typos
2014-09-18 12:32:56 +10:00
Deon George
ee415fe8c6 Merge pull request #5 from marclaporte/patch-1
typo
2014-09-18 12:32:08 +10:00
Deon George
eca5c4ea9f Merge pull request #8 from pteague/master
Modified posixAccount Shell selection
2014-09-18 12:31:19 +10:00
Patrick Teague
a01752a68c * Fixed posixAccount Shell so that 'Bash' is actually bash and not shell. Also added Shell, Dash, False, and No Login 2014-09-16 14:53:52 -05:00
Marc Laporte
ba90f86e7b typos 2014-07-25 23:04:40 -04:00
Marc Laporte
6135f94a51 typo 2014-07-25 20:36:21 -04:00
Deon George
f7c4bd311a Merge pull request #4 from ivdmeer/master
Bugfix: fixed call to renamed function pla_password_hash.
2014-06-05 13:00:27 +10:00
Ivo van der Meer
c736ecd8c2 Bugfix: fixed call to renamed function pla_password_hash. 2014-06-04 10:48:06 +02:00
Deon George
d2a800878f Merge pull request #3 from bchavet/master
Use preg_replace_callback instead of /e in preg_replace
2014-06-04 13:43:52 +10:00
Ben Chavet
5a7edc892f Use preg_replace_callback instead of /e in preg_replace to fix E_DEPRECATED warnings 2014-05-29 18:57:44 +00:00
Deon George
d258398b68 Merge pull request #2 from archayl/php55fix
Php55fix
2014-05-14 09:05:39 +10:00
Mohamad Elrashidin Bin Sajeli
b082cf1742 Changed preg_replace to preg_replace callback 2014-05-08 20:40:57 +08:00
Mohamad Elrashidin Bin Sajeli
e673df3ba8 Changed password_hash to pla_password_hash 2014-05-08 20:22:30 +08:00
Deon George
cfbee19721 Release 1.2.3 2012-10-01 16:54:14 +10:00
Deon George
092db24f99 Update template to show multiselect values 2012-10-01 16:47:53 +10:00
Deon George
927e515df3 Language update from launchpad for 1.2.3 (also see #30) 2012-09-06 13:00:06 +10:00
Deon George
bbedf18b7e SF Bug #3531956 - Search / Show Attributes must be lowercase 2012-09-05 22:44:46 +10:00
Deon George
f1ed59a35e SF Bug #3518548 - Missing attributes on some custom forms 2012-09-05 22:18:31 +10:00
Deon George
55fa21af26 SF Bug #3513210 - Export to VCARD only exports the last entry in the list 2012-09-05 21:57:17 +10:00
Deon George
f28d535948 SF Bug #3510648 - Cannot copy between servers 2012-09-05 21:54:42 +10:00
Deon George
2f70eb41b3 SF Bug #3510114 - Unable to check passwords when samba hashes are in lowercase 2012-09-05 21:25:50 +10:00
Deon George
6b9834a054 SF Bug #3452416 - templates <order> non-functional 2012-09-05 20:23:17 +10:00
Deon George
caf24e3662 SF Bug #3427748 - value id is ignored in select attribute 2012-09-05 20:02:14 +10:00
Roland Gruber
c4b6695beb SF Bug #3448530 - Treat krbExtraData and krbPrincipalKey as binary 2012-09-04 15:09:24 +10:00
Deon George
74434e5ca3 SF Bug #3497660 - XSS flaws via 'export', 'add_value_form' and 'dn' variables 2012-09-03 07:16:34 +10:00
Deon George
88d41216f9 SF Bug #3426575 - clicking 'logout' does not unset _SESSION['ACTIVITY'] 2012-09-03 06:19:19 +10:00
Paweł Tomulik
09c5e3a8da SF Feature #3555472 - User-friendly items in entry chooser window. 2012-09-01 11:43:14 +10:00
Jean-Philippe Ghibaudo
21959715c3 SF Feature #3509651 - Add support for SHA512 with OpenLDAP 2012-09-01 11:31:38 +10:00
Roland Gruber
3690ad16f0 SF Patch #3469148 - Display mass edit actions as buttons 2012-08-29 22:01:43 +10:00
Deon George
7dc8d57d69 SF Bug #3477910 - XSS vulnerability in query 2012-01-24 12:38:47 +11:00
Deon George
dece0f496f Release 1.2.2 2011-10-27 13:07:09 +11:00
Deon George
d58f011fbb Language Translation merge from launchpad 2011-10-27 13:06:53 +11:00
Deon George
696c266eee Additional fix for SF Feature #3387473 2011-10-27 12:55:24 +11:00
Caleb Callaway
2d018aad7b SF Feature #3387473 - Support for schema discovery using OpenLDAP's cn=config DN 2011-10-13 08:18:10 +11:00
Deon George
cddf783c27 Add an alert when RFC3866 tags are being used 2011-10-06 16:16:27 +11:00
Deon George
1e1fcabb3d SF Bug #3398344 - Import LDIF overwrites entries 2011-10-06 14:29:35 +11:00
Roland Gruber
d8ab7fc2f0 SF Patch #3391547 - Option for minmal mode 2011-10-06 12:31:12 +11:00
Roland Gruber
56830f1fa4 SF Patch #3391389 - Option to initially open the tree 2011-10-06 12:22:55 +11:00
Roland Gruber
6c8b623788 SF Patch #3391371 - Fix for schema link deactivation 2011-10-06 11:57:06 +11:00
Roland Gruber
7fc4f0c7e4 SF Patch #3391039 - Remove eval commands from PHP code 2011-10-06 11:43:40 +11:00
Deon George
059b83befb SF Bug #3391046 - Loading entries with many attributes is very slow 2011-10-06 11:06:43 +11:00
Deon George
4089ffa9fe SF Bug #3392644 - Cannot authenticate if password starts or ends with spaces 2011-10-06 10:40:41 +11:00
Deon George
c57a927311 Disable supplied modifiction templates, it confused too many people 2011-10-06 09:35:58 +11:00
Deon George
d5744b055a SF Bug #3370546 - AjaxEnabled create and delete entry fails on IE9 2011-10-06 09:12:54 +11:00
Deon George
76e6dad13e SF Bug #3417184 - PHP Code Injection Vulnerability 2011-10-06 09:03:20 +11:00
Deon George
5d4245f93a SF Bug #3395004 - config.php.example refers to lang/en.php 2011-09-08 22:51:02 +10:00
Deon George
80d027d569 SF Bug #3373466 - Unable to define force_may attributes 2011-09-08 22:30:35 +10:00
Deon George
64668e882b Remove XSS vulnerabilty in debug code 2011-07-27 07:30:06 +10:00
Felix Chelu
caeba72171 SF Bug #3355722 - Issue in MultiList attribute type 2011-07-07 23:45:21 +10:00
Deon George
07827304b7 SF Bug #3355732 - Cosmetic issue in functions.php -> get_icon() 2011-07-07 23:12:23 +10:00
Deon George
446faf78fb FIX SASL configuration example 2011-06-21 13:45:19 +10:00
Deon George
afa4a95b37 Fix SASL implementation - enabled GSSAPI 2011-06-20 20:34:55 +10:00
Deon George
5987194dec SF Bug #3304785 - posixGroup creation template uses cn instead of uid 2011-05-20 23:58:48 +10:00
Deon George
ddb5ed0346 Enabled hiding base DNs that users dont have access to 2011-05-20 23:53:39 +10:00
Deon George
7649b9b826 SF Feature #3298820 - Only custom templates 2011-05-14 10:42:12 +10:00
Deon George
43ae011c0e Release 1.2.1.1 2011-05-11 19:40:18 +10:00
Deon George
92acf6f158 Fix an E_WARNING when using Mass Delete 2011-05-04 10:23:01 +10:00
Deon George
6c93c1fc72 Fix deletion special char DNs, and refresh tree on delete 2011-05-04 00:02:33 +10:00
Deon George
66e24fb86c SF Feature #2997986 - DTD stuff 2011-05-03 23:14:16 +10:00
Deon George
a2828b2cf0 SF Feature #3294932 - Hiding not used templates 2011-05-03 20:49:16 +10:00
Deon George
3919825000 SF Bug #3294924 - Template Selects dosen't work properly 2011-05-01 11:39:54 +10:00
Deon George
6eb6641454 SF Bug #3294980 - Template rdn (in lowercase) 2011-04-30 10:41:15 +10:00
Deon George
41c2821395 Release 1.2.1 2011-04-29 21:20:47 +10:00
Deon George
caebef364a Translation import from launchpad 2011-04-29 18:15:05 +10:00
Deon George
1121dd01df SF Feature #2879726 - sort the server select list 2011-04-29 14:08:07 +10:00
Dmitry Bakshaev
775e6f40d4 SF Feature #2900545 - blowfish using mcrypt 2011-04-29 13:31:17 +10:00
Deon George
e083f5f8b5 SF Feature #2931999 - Upload file and view for "picture" fields 2011-04-29 13:25:57 +10:00
Deon George
c97d4afe17 SF Feature #3108047 - Add support for smbk5pwd overlay K5KEY "encryption" type 2011-04-29 13:04:44 +10:00
Deon George
62d645123c Addition for commit a35298 2011-04-29 13:04:10 +10:00
Marcel van Dorp
880a86f666 SF Feature #3122736 - HTTP authentication realm 2011-04-29 12:46:49 +10:00
Deon George
a35298e7f3 SF Bug #3036033 - Error if CN begins with a % sign 2011-04-29 12:08:38 +10:00
Deon George
2ea1fc6314 SF Bug #3003777 - Multivalue attributes with hundred of values hangs on modify 2011-04-29 00:19:53 +10:00
Deon George
c23db377c2 Updates to sample configuration for the confirm operations 2011-04-28 23:20:45 +10:00
Deon George
1f9308dc4d Fixes for jpegPhoto attributes during copy operations 2011-04-28 23:20:06 +10:00
Deon George
db241f1c98 Fix for when JS not loaded in time for TemplateRender 2011-04-28 17:38:14 +10:00
Deon George
b6500224d3 Minor cosmetic updates 2011-04-28 11:53:40 +10:00
Deon George
75640ccc3e SF Bug #2987374 - autofill problem on samba passwords 2011-04-28 00:22:00 +10:00
Deon George
bf8ac5306e SF Bug #3139097 - Argument for PickList sorting does not work 2011-04-27 23:27:31 +10:00
Deon George
d5c8d42adc SF Bug #3004012 - password sync for sambaSamAccount template broken. 2011-04-27 23:09:43 +10:00
Deon George
9e9960bc3d SF Bug #3003779 - Unable to check password for NT and LN samba hashed 2011-04-27 21:53:47 +10:00
Deon George
4cf6b17ba3 SF Bug #3141226 - Password change/encrypted upon modification 2011-04-27 17:28:45 +10:00
Deon George
04e41f7272 SF Bug #3292533 - Non standard schema 2011-04-27 16:57:55 +10:00
Deon George
6e5ec75b55 SF Bug #3077852 - Default template being used after modificaiton of entry 2011-04-27 00:02:05 +10:00
Deon George
97eff7383c SF Bug #3276528 - Problem with + and , signs in dn 2011-04-26 23:21:19 +10:00
Deon George
fc5885b0d9 SF Bug #3288434 - Security bug 2011-04-26 22:35:43 +10:00
Deon George
c28a609799 Updated demo configuration 2011-04-26 21:41:44 +10:00
Deon George
ea4ae7f831 SF Bug #3161571 - Error 0x02 A protocol error was detected 2011-04-26 12:06:33 +10:00
Deon George
be623ce3f5 SF Bug #3136564 - Undefined variable: result (E_NOTICE) 2011-04-26 11:40:35 +10:00
Deon George
aa8a353c38 SF Bug #2997552 - Unable to verify password using SMD5 scheme 2011-04-26 11:27:32 +10:00
Deon George
ed7f899361 Fix JS error created by f713af 2011-04-26 10:15:41 +10:00
Deon George
2cf20fcf44 SF Bug #2981355 - rawurldecode killing complex passwords 2011-04-26 10:10:43 +10:00
Deon George
cc860371d6 SF Bug #2958882 - Single quote in french translation causes error 2011-04-26 00:35:40 +10:00
Deon George
7aba733961 Enabled cookie as a valid auth_type 2011-04-26 00:11:23 +10:00
Deon George
c5f045756e SF Bug #2980701 - Creation templates get used for modification post creation 2011-04-26 00:10:58 +10:00
Dan Duvall
b3874bf958 Implemented better SASL/GSSAPI authentication.
Implemented a 'sasl' auth_type for better control over authentication
flow specific to SASL.

Implemented 'sasl_dn_regex' and 'sasl_dn_replacement' config variables
for mapping from a SASL authentication ID to a bind DN, a necessary step
when using GSSAPI/Kerberos where there is no explicit bind DN provided.

Fixed setting of Kerberos credentials cache location in environment
variable. The location is derived from either an already set
environement variable or the SERVER variable set by the Apache
mod_auth_kerb module.
See http://modauthkerb.sourceforge.net/configure.html
2010-11-16 22:14:24 +11:00
Deon George
7980d1c131 SF Patch #2974901 - enable modify member form to use netgroups 2010-11-16 22:05:18 +11:00
Patrick MONNERAT
43f31912b6 SF Patch #2990856 - Add parent class attribute 2010-11-16 20:55:11 +11:00
Patrick MONNERAT
ab0717e0e3 SF Patch #3054517 - Suppress PHP 5.3 E_DEPRECATED exceptions 2010-11-16 20:47:07 +11:00
Deon George
f9c56bc4ff SF Bug #2997703 - SourceForge logo should load from HTTPS when using HTTPS 2010-11-16 20:27:37 +11:00
Deon George
6fdab2c308 SF Bug #2958613 - password_checker.php md5crypt explode() function bug 2010-11-16 20:21:38 +11:00
Deon George
c3a286cfee SF Bug #3033924 - typo in fnctions.php breaking smd5 2010-11-16 20:06:39 +11:00
Deon George
e77d39deb5 Suppress create base when base DN is not level 1 2010-11-16 19:43:54 +11:00
Deon George
34e5bbb545 Fix sambaGroupMapping template 2010-05-17 22:08:52 +10:00
Deon George
7d17676fd7 Enabled create_base 2010-03-18 13:25:53 +11:00
Deon George
1c467a6115 New feature: Copy a DN and edit values before creation 2010-03-18 13:24:04 +11:00
Deon George
2e8e9625d6 AJAX work on create/update 2010-03-15 09:37:37 +11:00
Deon George
f713afc8d1 HTML Validation work 2010-03-15 09:37:35 +11:00
Deon George
1b55c84f06 Added launchpad import 2010-03-15 09:37:05 +11:00
Deon George
206c142b99 Language files import from launchpad 2010-03-15 09:14:44 +11:00
Deon George
e4f5c22e18 SF Bug #2959415 - Misspelling 'Retieving' 2010-03-15 08:30:18 +11:00
Deon George
0f782569e9 SF Bug #2969826 - XSS found in cmd.php 2010-03-14 23:57:16 +11:00
Deon George
7b4d11f1f5 Addition to Fix template engine rending attributes with multiselect configured 2010-03-14 23:57:15 +11:00
Deon George
a1c714bdb8 Fix template engine rending attributes with multiselect configured 2010-02-23 12:11:15 +11:00
Deon George
ac1d121b0d SF Bug #2922727 - PLA ignores login attr 2010-02-20 09:10:14 +11:00
Deon George
9dbf53acf3 Fix template engine to accept server,custom_attrs 2010-02-13 09:45:45 +11:00
Deon George
5080e204cf Release 1.2.0.5 2010-01-30 16:21:20 +11:00
Deon George
b46941f7fd Merge translations from launchpad for PLA 1.2.0.5 2010-01-30 16:19:36 +11:00
Deon George
03d1166103 Fix the multiple unnecessary 'attribute is required' popups 2010-01-30 15:59:03 +11:00
Deon George
676a675c7c SF Bug #2901854 - E_WARNING: implode(): Invalid arguments passed 2010-01-30 15:10:00 +11:00
Deon George
4598d3ae39 SF Patch #2919169 - SASL bind 2010-01-30 14:57:10 +11:00
Cristian Rigamonti
2416230c61 SF Patch #2901666 - Add localisation for hint elements in templates 2010-01-30 14:41:57 +11:00
Deon George
d90fe5a6fa Enabled configuring template to ask for more than 1 attribute value 2010-01-30 12:24:46 +11:00
Deon George
ec8902a223 Added default noleaf setting to supplied templates 2010-01-30 11:02:54 +11:00
Deon George
2090e7c34a Added default sort of PickList attributes to supplied templates 2010-01-30 10:58:06 +11:00
Deon George
2393c5d5e3 Trim _REQUEST vars mainly to avoid null terminated strings 2009-12-23 09:03:13 +11:00
Deon George
efd1860a91 SF Bug #2554402 - template autofill command not work on appearance,date_attrs 2009-11-21 12:11:45 +11:00
Deon George
23a2da1f26 SF Bug #2898426 - Can't update own password 2009-11-21 11:17:53 +11:00
Deon George
d4483f961f SF Bug #2885907 - samba domain sid blank 2009-11-21 11:04:37 +11:00
Deon George
b1139658bf Change the multilist key to cn 2009-11-21 10:39:09 +11:00
Deon George
f8cacb7dd0 Change default size_limit to 0, enabled setting of time_limit (default 0) 2009-11-21 10:36:23 +11:00
Deon George
ee35f81ce5 Release 1.2.0.4 2009-09-20 11:49:27 +10:00
Deon George
088ebf4a2b Changed to use ldap_modify 2009-09-20 11:44:28 +10:00
Deon George
a6dc80616b Fix rendering of js_calendar on add_attr, when no previous DateAttributes existed 2009-09-20 11:44:26 +10:00
Deon George
9196bb9e41 SF Feature #2846546 - Add URL to config/config.php.example 2009-09-20 11:44:26 +10:00
Deon George
7121f560e9 SF Bug #2859242 - Call to undefined function syslog_notice() 2009-09-20 11:44:25 +10:00
Deon George
e7466c948b SF Bug #2859221 - Problem between diffrent browsers and utf-8 2009-09-20 11:44:24 +10:00
Deon George
f0a6d312ab Enable control of creating children in templates 2009-09-20 11:44:23 +10:00
Deon George
d062308f32 Added javascript for search form 2009-09-09 08:08:10 +10:00
Deon George
352a87fee8 Auto run query for unique attributes when link is clicked 2009-09-08 15:41:32 +10:00
Deon George
242d06673a SF Bug #2853633 - activating hooks gives error undefined constant DEBUG_ENABLE 2009-09-08 15:38:49 +10:00
Deon George
a3ac658756 Release 1.2.0.3 2009-09-07 00:39:38 +10:00
Deon George
e83aba595c Merge translations from launchpad for PLA 1.2.0.3 2009-09-07 00:39:06 +10:00
Deon George
3ffe6878f3 Minor updates 2009-09-07 00:13:58 +10:00
Deon George
02e9f8477a Suppress more template warnings 2009-08-30 12:11:18 +10:00
Ethan Moore
259179a1b3 Enable SASL (GSSAPI) 2009-08-29 12:19:10 +10:00
Deon George
96bad27d64 Fix: clear passwords were being encoded with blowfish by default 2009-08-29 00:11:24 +10:00
Deon George
c69cd68fcb SF Bug #2828378 - fallback_dn config option no longer works in 1.2.0 2009-08-29 00:11:23 +10:00
Deon George
9cb27e3a70 Miscellaneous minor updates 2009-08-29 00:11:23 +10:00
Deon George
52fbd24b2c Merge PickList and MultiList functions (also fixes SF Bug #2844714) 2009-08-28 20:06:13 +10:00
Deon George
f83a922589 SF Bug #2845986 - Missing config/config.php file not detected in 1.2.0.2 2009-08-28 17:18:08 +10:00
Deon George
3e446fddf4 SF Bug #2845904 - session.autostart breaks 2009-08-28 17:01:15 +10:00
Deon George
bdb423d0d3 SF Bug #2844186 - fail to add and edit entries (delete work fine) 2009-08-28 16:31:19 +10:00
Deon George
2c99cc9016 Fix export only exports 500 entries 2009-08-28 15:03:24 +10:00
Deon George
b93b92f430 Rework javascript 2009-08-22 21:30:50 +10:00
Deon George
26fa2ba2c5 Only sort children after reading additional entries 2009-08-22 13:30:30 +10:00
Deon George
76ddeccf8c Release 1.2.0.2 2009-08-21 23:42:44 +10:00
Deon George
1336fc21b6 Merge translations from launchpad for PLA 1.2.0.2 2009-08-21 23:40:21 +10:00
Deon George
356f319291 Fix up calls with ldap_read 2009-08-21 18:32:52 +10:00
Deon George
6e6a7a6e4e Multiple fixes, changes and enhancements
* mass edit selection,
* child search during edit,
* attr login with bind_id,
* performance fix broke ldapservers that dont have havesubordinate attrs),
* enable "login,class",
* enable "login,base".
2009-08-21 15:02:12 +10:00
Deon George
95aedef718 Remove CVS tags 2009-08-20 12:25:48 +10:00
Deon George
b0f9fa8806 Fix URL to favicon 2009-08-20 09:25:34 +10:00
Deon George
ec482c70fe Fix list address 2009-08-19 22:30:19 +10:00
Deon George
9fda881a8e Add size_limit to default search 2009-08-19 22:26:33 +10:00
Deon George
46c100660b Fix query default sorting and query form when no templates 2009-08-19 22:18:00 +10:00
Deon George
4c56f3e678 Fix AJAX rendering of query results 2009-08-19 21:20:51 +10:00
Deon George
ebe2cb6eda Performance improvements for the tree. 2009-08-19 21:19:35 +10:00
Deon George
5669c92371 Improvements to debug_log 2009-08-19 13:39:37 +10:00
Harri
0eaf3bb67b SF Bug #2839213 - Few localization -related bugs 2009-08-18 19:16:36 +10:00
Deon George
9f1a207eba Ammendment for SF Feature #2837687 - Invalid method for Queries 2009-08-18 19:13:50 +10:00
Deon George
5706ad1b4a Ammendment for SF Feature #2837687, dont render the only template if it is invalid 2009-08-17 16:30:05 +10:00
Deon George
ba553353b0 SF Feature #2837687 - Automatic template selection from one of one valid templates 2009-08-17 16:13:55 +10:00
Aurélien Requiem
803a4c821a SF Feature #2827191 - warnings for missing objectClass could be disabled 2009-08-17 15:09:32 +10:00
Deon George
a5b9e43a57 Fix for when renaming entries only changes case (https://bugs.launchpad.net/bugs/384157) 2009-08-17 10:52:46 +10:00
Deon George
df404d435a login,attr only applies to user sessions 2009-08-17 10:46:22 +10:00
Deon George
cf4f339b5c Fix missing encoding SambaPasswordAttributes 2009-08-14 11:47:45 +10:00
Deon George
48faaba955 Merge translations from launchpad for PLA 1.2.0.1 2009-08-13 22:04:16 +10:00
Deon George
898eabaa11 Release 1.2.0.1 2009-08-13 21:40:59 +10:00
Deon George
9eca46d0cc Fixed display of Windows AD binary attributes 2009-08-13 21:39:43 +10:00
Deon George
8a21bbee12 Fix render of ReadOnly binary attributes 2009-08-13 21:22:41 +10:00
Aurélien Requiem
59c4e7896f SF Bug #2827012 - the modification tpl for inetOrgPerson is partially invalid 2009-08-13 09:00:04 +10:00
Deon George
cc3b67b71a More login processing fixes 2009-08-12 23:54:01 +10:00
Deon George
a0816d068c Sync menu/tree processing with other projects, variable/function naming 2009-08-12 23:53:14 +10:00
Deon George
fb48055d2d Fix for when method supplies null for login details 2009-07-27 19:17:40 +10:00
Deon George
86c8f13065 Fixed missing uid.png 2009-07-27 19:16:12 +10:00
Deon George
7dd52f8219 More issues for anon login to LDAP server to get user details 2009-07-27 17:19:53 +10:00
Deon George
29cb490571 Fixes for issues introduced by commit bbe87c6e2 2009-07-27 17:18:25 +10:00
Deon George
5938302012 Fix the simple ACL configuration 2009-07-26 01:21:23 +10:00
Deon George
196aa00218 Fix for when a root_dse query doesnt return any results 2009-07-26 01:21:23 +10:00
Deon George
dc3e477778 Improve getLogin() for multiple method logins and fix anonymous logout 2009-07-26 01:21:23 +10:00
Deon George
869b9be7e9 Fix IMGDIR/CSSDIR when no config.php yet and creating setting to disable remoteurls for sflogo 2009-07-26 01:21:23 +10:00
Deon George
a1cfa2f60c SF Bug #2826569 - missing information in config.php.example 2009-07-25 20:17:36 +10:00
Deon George
3ada58fb7d Fix version number detection 2009-07-22 00:01:20 +10:00
Deon George
22bcaf0136 Release 1.2.0 2009-07-21 23:22:43 +10:00
Deon George
008b463e8e Language import from launchpad 2009-07-21 23:22:00 +10:00
Deon George
0de55e552b New language from launchpad 2009-07-21 23:21:43 +10:00
Deon George
2725bb16da Updated to process launchpad exports 2009-07-21 23:21:25 +10:00
Deon George
bbe87c6e2f SF Bug #2820854 - ldap_first_attribute error 2009-07-14 19:07:43 +10:00
Deon George
45ca83411f Fix: Setting to null after getting the error messages 2009-07-12 23:49:39 +10:00
Deon George
3c9f63ae6b Initial language merge 2009-07-12 23:28:46 +10:00
Deon George
0171853f1d Fix tree was remembered empty with anonymous 2009-07-12 22:03:05 +10:00
Deon George
6627c7bea4 Fix spelling 2009-07-12 22:02:30 +10:00
Deon George
223086b58e Fix for when invalid objectclass entered 2009-07-12 22:02:19 +10:00
Deon George
5481f61ce3 Use calls to getRootDSE() 2009-07-12 12:28:39 +10:00
Deon George
57d405fe3b SF Feature #2073323 - Using Single Sign On authentication 2009-07-12 12:01:59 +10:00
Deon George
ed4784b1b6 Fix isLoggedIn() caching for multiple servers 2009-07-11 17:20:21 +10:00
Deon George
d364af141f Minor display change 2009-07-11 14:19:04 +10:00
Deon George
e2c74f9467 Comment fixes 2009-07-11 14:15:17 +10:00
Deon George
0d71a96e34 Display error if there is only 1 autoFill param 2009-07-11 10:19:30 +10:00
Deon George
4eed1d8982 Enabled HTTP auth 2009-07-11 10:18:48 +10:00
Deon George
899f83aa17 Release candidate 1.2.0-rc1 2009-07-08 21:29:57 +10:00
Deon George
775c7751ae Re-evaluate how we determine the version number now we are using GIT 2009-07-08 21:29:38 +10:00
Deon George
664c05decd Removed stylesheet from index 2009-07-08 20:17:35 +10:00
Deon George
ea0fdab6ba SF Feature #1954220 - New icon theme Tango 2009-07-08 20:11:58 +10:00
Deon George
1aa88e4dc3 Standardised some naming of the icons to allow for other themes 2009-07-08 20:10:57 +10:00
Deon George
7273ad292b New Feature: PLA icons and CSS can now be themeble 2009-07-08 16:14:50 +10:00
Deon George
b4653e84a2 SF Feature #1719978 - GetNextNumber with uid interval support 2009-07-07 21:54:18 +10:00
Deon George
4cab46a55e New Feature: Mass Edit 2009-07-07 20:16:28 +10:00
Deon George
e7ce1030c0 Enchanced getLDAPmodify() to allow multiple update calls 2009-07-07 20:16:28 +10:00
Deon George
6270967ec0 Improve processing in drawSubTitle() 2009-07-07 20:16:28 +10:00
Deon George
d501393d49 Enabled Feature: Mass Delete 2009-07-07 20:16:28 +10:00
Deon George
08199e67e0 Fixed failed copy message 2009-07-07 20:16:28 +10:00
Deon George
03bc1ce70e Fix query to find children, add size_limit to query to ensure we get all children 2009-07-07 20:16:28 +10:00
Deon George
fcaa4d8db6 Fix server_id used in URL 2009-07-07 20:16:28 +10:00
Deon George
ee9533c770 Merge branch 'master' of ssh://wurley@phpldapadmin.git.sf.net/gitroot/phpldapadmin 2009-07-06 18:01:26 +10:00
Deon George
07b40c1c34 Ackownledge source for our icons 2009-07-06 17:50:06 +10:00
Deon George
84bf604cba Enabled inetOrgPerson in modification 2009-07-06 17:50:06 +10:00
Deon George
4f6d661c12 Added posixGroup modification template 2009-07-06 17:50:06 +10:00
Deon George
97fcaed8f7 Added .gitignore 2009-07-06 17:50:06 +10:00
Deon George
d601121dba Fix added detection of password already encode when submitting updates 2009-07-06 17:50:06 +10:00
Deon George
aa2719f8fe Template modifications now add/remove attributes as per the template definition 2009-07-06 17:50:06 +10:00
Deon George
54df56bb1a Fixed invalid return variable 2009-07-06 17:50:06 +10:00
Deon George
db2e8861fa Enabled a param so that some calls to getValue() are not fatal 2009-07-06 17:50:05 +10:00
Deon George
c230058d3f Added .gitignore 2009-07-06 10:57:58 +10:00
Deon George
a0ae7bf111 Fix added detection of password already encode when submitting updates 2009-07-05 23:40:09 +10:00
Deon George
7591eefb70 Template modifications now add/remove attributes as per the template definition 2009-07-05 22:13:17 +10:00
Deon George
20bdaa4663 Fixed invalid return variable 2009-07-05 13:55:53 +10:00
Deon George
640abcbf3b Enabled a param so that some calls to getValue() are not fatal 2009-07-05 13:55:27 +10:00
Deon George
8e8db2e4ed Improved layout 2009-07-02 18:35:59 +10:00
Deon George
ea17aadef4 Latest SANDPIT - MERGE from CVS (MERGE-GIT) 2009-07-01 16:09:17 +10:00
Deon George
388783fc84 RELEASE 1.1.0.7 2009-06-30 22:05:31 +10:00
Deon George
647f86562f RELEASE 1.1.0.6 2009-06-30 21:52:55 +10:00
Deon George
d5f4f91f1b RELEASE 1.1.0.5 2009-06-30 21:51:50 +10:00
Deon George
d5b4aa54ea RELEASE 1.1.0.4 2009-06-30 21:50:46 +10:00
Deon George
23a33eef99 RELEASE 1.1.0.3 2009-06-30 21:48:22 +10:00
Deon George
5c88e0a098 RELEASE 1.1.0.2 2009-06-30 21:46:44 +10:00
Deon George
f990f72eb5 RELEASE 1.1.0.1 2009-06-30 20:46:41 +10:00
Deon George
dd581eb5c8 RELEASE 1.1.0 2009-06-30 20:46:00 +10:00
Deon George
a08bc4e9e1 RELEASE 1.0.2 2009-06-30 20:41:18 +10:00
Deon George
5f261ded38 RELEASE 1.0.1 2009-06-30 20:40:33 +10:00
Deon George
eccabca011 RELEASE 1.0.0 2009-06-30 20:40:03 +10:00
Deon George
c3713350e2 RELEASE 0.9.8.5 2009-06-30 20:28:51 +10:00
Deon George
c131e8b479 RELEASE 0.9.8.4 2009-06-30 20:28:19 +10:00
Deon George
a01f7c8289 RELEASE 0.9.8.3 2009-06-30 20:27:56 +10:00
Deon George
7741110caf RELEASE 0.9.8.2 2009-06-30 20:27:11 +10:00
Deon George
abc62c7fdc RELEASE 0.9.8.1 2009-06-30 20:26:45 +10:00
Deon George
fdee1bdbd1 RELEASE 0.9.8 2009-06-30 20:26:08 +10:00
Deon George
1f7f96122f RELEASE 0.9.7.2 2009-06-30 19:41:11 +10:00
Deon George
b443271175 RELEASE 0.9.7.1 2009-06-30 19:40:37 +10:00
Deon George
bbcd2cb3b6 Moved files 2009-06-30 19:39:45 +10:00
Deon George
df48b8ff9b RELEASE 0.9.7 2009-06-30 19:29:51 +10:00
772 changed files with 50737 additions and 36358 deletions

14
.dockerignore Normal file
View File

@ -0,0 +1,14 @@
.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

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
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 Normal file
View File

@ -0,0 +1,18 @@
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 Normal file
View File

@ -0,0 +1,51 @@
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 Normal file
View File

@ -0,0 +1,5 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore

View File

@ -0,0 +1,202 @@
name: Create Docker Image
run-name: ${{ gitea.actor }} Building Docker Image 🐳
on: [push]
env:
DOCKER_HOST: tcp://127.0.0.1:2375
ASSETS: c2780a3
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 Normal file
View File

@ -0,0 +1,13 @@
# 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']

41
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,41 @@
---
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.)
**Version of PLA**
What version of PLA are you using. Are you using the docker container, an distribution package or running from GIT source?
**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.

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1 @@
blank_issues_enabled: false

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
/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

37
INSTALL
View File

@ -1,37 +0,0 @@
For install instructions in non-English languages, see the files
in the "doc" directory.
* Requirements
phpLDAPadmin requires the following:
a. A web server (Apache, IIS, etc).
b. PHP 4.1.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.
4. Then, point your browser to the phpldapadmin directory.
* For additional help
See the files in the "doc" directory.
Join our mailing list:
https://lists.sourceforge.net/lists/listinfo/phpldapadmin-devel
* Platform specific notes
* OpenBSD with chroot'ed Apache:
For jpeg photos to work properly, you must do this:
# mkdir /var/www/tmp, and then
# chown root:daemon /var/www/tmp
# chmod 1755 /var/www/tmp
Where tmp is the $jpeg_temp_dir configured in config.php
* Windows
For jpeg photos to work properly, be sure to change $jpeg_temp_dir
from "/tmp" to "c:\\temp" or similar.

93
README.md Normal file
View File

@ -0,0 +1,93 @@
# 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
- [ ] Autopopulate attribute values
- [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
- [ ] Enforcing attribute uniqueness
- [ ] Is there something missing?
Support is known for these LDAP servers:
- [X] OpenLDAP
- [X] OpenDJ
- [ ] Microsoft Active Directory
- [ ] 389 Directory Server
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)

View File

@ -1 +1 @@
0.9.5 00000000

View File

@ -1,141 +0,0 @@
<?php
// $Header: /cvsroot/phpldapadmin/phpldapadmin/add_attr.php,v 1.10 2004/08/15 17:39:20 uugdave Exp $
/*
* add_attr.php
* Adds an attribute/value pair to an object
*
* Variables that come in as POST vars:
* - dn
* - server_id
* - attr
* - val
* - binary
*/
require './common.php';
require 'templates/template_config.php';
$server_id = $_POST['server_id'];
$attr = $_POST['attr'];
$val = isset( $_POST['val'] ) ? $_POST['val'] : false;;
$dn = $_POST['dn'] ;
$encoded_dn = rawurlencode( $dn );
$encoded_attr = rawurlencode( $attr );
$is_binary_val = isset( $_POST['binary'] ) ? true : false;
if( ! $is_binary_val && $val == "" ) {
pla_error( $lang['left_attr_blank'] );
}
if( is_server_read_only( $server_id ) )
pla_error( $lang['no_updates_in_read_only_mode'] );
check_server_id( $server_id ) or pla_error( $lang['bad_server_id'] );
have_auth_info( $server_id ) or pla_error( $lang['not_enough_login_info'] );
// special case for binary attributes (like jpegPhoto and userCertificate):
// we must go read the data from the file and override $val with the binary data
// Secondly, we must check if the ";binary" option has to be appended to the name
// of the attribute.
// Check to see if this is a unique Attribute
if( $badattr = checkUniqueAttr( $server_id, $dn, $attr, array($val) ) ) {
$search_href='search.php?search=true&form=advanced&server_id=' . $server_id . '&filter=' . $attr . '=' . $badattr;
pla_error(sprintf( $lang['unique_attr_failed'] , $attr,$badattr,$dn,$search_href ) );
}
if( $is_binary_val ) {
if( 0 == $_FILES['val']['size'] )
pla_error( $lang['file_empty'] );
if( ! is_uploaded_file( $_FILES['val']['tmp_name'] ) ) {
if( isset( $_FILES['val']['error'] ) )
switch($_FILES['val']['error']){
case 0: //no error; possible file attack!
pla_error( $lang['invalid_file'] );
case 1: //uploaded file exceeds the upload_max_filesize directive in php.ini
pla_error( $lang['uploaded_file_too_big'] );
case 2: //uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the html form
pla_error( $lang['uploaded_file_too_big'] );
case 3: //uploaded file was only partially uploaded
pla_error( $lang['uploaded_file_partial'] );
case 4: //no file was uploaded
pla_error( $lang['left_attr_blank'] );
default: //a default error, just in case! :)
pla_error( $lang['invalid_file'] );
break;
}
else
pla_error( $lang['invalid_file'] );
}
$file = $_FILES['val']['tmp_name'];
$f = fopen( $file, 'r' );
$binary_data = fread( $f, filesize( $file ) );
fclose( $f );
$val = $binary_data;
if( is_binary_option_required( $server_id, $attr ) )
$attr .=";binary";
}
// Automagically hash new userPassword attributes according to the
// chosen in config.php.
if( 0 == strcasecmp( $attr, 'userpassword' ) )
{
if( isset( $servers[$server_id]['default_hash'] ) &&
$servers[$server_id]['default_hash'] != '' )
{
$enc_type = $servers[$server_id]['default_hash'];
$val = password_hash( $val, $enc_type );
}
}
elseif( ( 0 == strcasecmp( $attr , 'sambantpassword' ) || 0 == strcasecmp( $attr , 'sambalmpassword') ) ){
$mkntPassword = new MkntPasswdUtil();
$mkntPassword->createSambaPasswords( $val );
$val = $mkntPassword->valueOf($attr);
}
$ds = pla_ldap_connect( $server_id ) or pla_error( $lang['could_not_connect'] );
$new_entry = array( $attr => $val );
$result = @ldap_mod_add( $ds, $dn, $new_entry );
if( $result )
header( "Location: edit.php?server_id=$server_id&dn=$encoded_dn&modified_attrs[]=$encoded_attr" );
else
pla_error( $lang['failed_to_add_attr'], ldap_error( $ds ) , ldap_errno( $ds ) );
// check if we need to append the ;binary option to the name
// of some binary attribute
function is_binary_option_required( $server_id, $attr ){
// list of the binary attributes which need the ";binary" option
$binary_attributes_with_options = array(
// Superior: Ldapv3 Syntaxes (1.3.6.1.4.1.1466.115.121.1)
'1.3.6.1.4.1.1466.115.121.1.8' => "userCertificate",
'1.3.6.1.4.1.1466.115.121.1.8' => "caCertificate",
'1.3.6.1.4.1.1466.115.121.1.10' => "crossCertificatePair",
'1.3.6.1.4.1.1466.115.121.1.9' => "certificateRevocationList",
'1.3.6.1.4.1.1466.115.121.1.9' => "authorityRevocationList",
// Superior: Netscape Ldap attributes types (2.16.840.1.113730.3.1)
'2.16.840.1.113730.3.1.40' => "userSMIMECertificate"
);
// quick check by attr name (short circuits the schema check if possible)
//foreach( $binary_attributes_with_options as $oid => $name )
//if( 0 == strcasecmp( $attr, $name ) )
//return true;
$schema_attr = get_schema_attribute( $server_id, $attr );
if( ! $schema_attr )
return false;
$syntax = $schema_attr->getSyntaxOID();
if( isset( $binary_attributes_with_options[ $syntax ] ) )
return true;
return false;
}
?>

View File

@ -1,174 +0,0 @@
<?php
// $Header: /cvsroot/phpldapadmin/phpldapadmin/add_attr_form.php,v 1.9 2004/09/15 12:31:52 uugdave Exp $
/*
* add_attr_form.php
* Displays a form for adding an attribute/value to an LDAP entry.
*
* Variables that come in as GET vars:
* - dn (rawurlencoded)
* - server_id
*/
require './common.php';
$dn = $_GET['dn'];
$encoded_dn = rawurlencode( $dn );
$server_id = $_GET['server_id'];
$rdn = get_rdn( $dn );
$server_name = $servers[$server_id]['name'];
if( is_server_read_only( $server_id ) )
pla_error( $lang['no_updates_in_read_only_mode'] );
check_server_id( $server_id ) or pla_error( $lang['bad_server_id'] );
have_auth_info( $server_id ) or pla_error( $lang['not_enough_login_info'] );
$friendly_attrs = process_friendly_attr_table();
include './header.php'; ?>
<body>
<h3 class="title"><?php echo sprintf( $lang['add_new_attribute'], htmlspecialchars( $rdn ) ); ?></b></h3>
<h3 class="subtitle"><?php echo $lang['server']; ?>: <b><?php echo $server_name; ?></b> &nbsp;&nbsp;&nbsp; <?php echo $lang['distinguished_name']; ?>: <b><?php echo htmlspecialchars( ( $dn ) ); ?></b></h3>
<?php
$attrs = get_object_attrs( $server_id, $dn );
$oclasses = get_object_attr( $server_id, $dn, 'objectClass' );
if( ! is_array( $oclasses ) )
$oclasses = array( $oclasses );
$avail_attrs = array();
$schema_oclasses = get_schema_objectclasses( $server_id, $dn );
foreach( $oclasses as $oclass ) {
$schema_oclass = get_schema_objectclass( $server_id, $oclass, $dn );
if( $schema_oclass && 0 == strcasecmp( 'objectclass', get_class( $schema_oclass ) ) )
$avail_attrs = array_merge( $schema_oclass->getMustAttrNames( $schema_oclasses ),
$schema_oclass->getMayAttrNames( $schema_oclasses ),
$avail_attrs );
}
$avail_attrs = array_unique( $avail_attrs );
$avail_attrs = array_filter( $avail_attrs, "not_an_attr" );
sort( $avail_attrs );
$avail_binary_attrs = array();
foreach( $avail_attrs as $i => $attr ) {
if( is_attr_binary( $server_id, $attr ) ) {
$avail_binary_attrs[] = $attr;
unset( $avail_attrs[ $i ] );
}
}
?>
<br />
<center>
<?php echo $lang['add_new_attribute']; ?>
<?php if( is_array( $avail_attrs ) && count( $avail_attrs ) > 0 ) { ?>
<br />
<br />
<form action="add_attr.php" method="post">
<input type="hidden" name="server_id" value="<?php echo $server_id; ?>" />
<input type="hidden" name="dn" value="<?php echo htmlspecialchars($dn); ?>" />
<select name="attr"><?php
$attr_select_html = '';
usort($avail_attrs,"sortAttrs");
foreach( $avail_attrs as $a ) {
// is there a user-friendly translation available for this attribute?
if( isset( $friendly_attrs[ strtolower( $a ) ] ) ) {
$attr_display = htmlspecialchars( $friendly_attrs[ strtolower( $a ) ] ) . " (" .
htmlspecialchars($a) . ")";
} else {
$attr_display = htmlspecialchars( $a );
}
echo $attr_display;
$attr_select_html .= "<option>$attr_display</option>\n";
echo "<option value=\"" . htmlspecialchars($a) . "\">$attr_display</option>";
} ?>
</select>
<input type="text" name="val" size="20" />
<input type="submit" name="submit" value="<?php echo $lang['add']; ?>" class="update_dn" />
</form>
<?php } else { ?>
<br />
<br />
<small>(<?php echo $lang['no_new_attrs_available']; ?>)</small>
<br />
<br />
<?php } ?>
<?php echo $lang['add_new_binary_attr']; ?>
<?php if( count( $avail_binary_attrs ) > 0 ) { ?>
<!-- Form to add a new BINARY attribute to this entry -->
<form action="add_attr.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="server_id" value="<?php echo $server_id; ?>" />
<input type="hidden" name="dn" value="<?php echo $dn; ?>" />
<input type="hidden" name="binary" value="true" />
<br />
<select name="attr">
<?php
$attr_select_html = '';
usort($avail_binary_attrs,"sortAttrs");
foreach( $avail_binary_attrs as $a ) {
// is there a user-friendly translation available for this attribute?
if( isset( $friendly_attrs[ strtolower( $a ) ] ) ) {
$attr_display = htmlspecialchars( $friendly_attrs[ strtolower( $a ) ] ) . " (" .
htmlspecialchars($a) . ")";
} else {
$attr_display = htmlspecialchars( $a );
}
echo $attr_display;
$attr_select_html .= "<option>$attr_display</option>\n";
echo "<option value=\"" . htmlspecialchars($a) . "\">$attr_display</option>";
} ?>
</select>
<input type="file" name="val" size="20" />
<input type="submit" name="submit" value="<?php echo $lang['add']; ?>" class="update_dn" />
<?php
if( ! ini_get( 'file_uploads' ) )
echo "<br><small><b>" . $lang['warning_file_uploads_disabled'] . "</b></small><br>";
else
echo "<br><small><b>" . sprintf( $lang['max_file_size'], ini_get( 'upload_max_filesize' ) ) . "</b></small><br>";
?>
</form>
<?php } else { ?>
<br />
<br />
<small>(<?php echo $lang['no_new_binary_attrs_available']; ?>)</small>
<?php } ?>
</center>
</body>
</html>
<?php
/**
* Given an attribute $x, this returns true if it is NOT already specified
* in the current entry, returns false otherwise.
*/
function not_an_attr( $x )
{
global $attrs;
//return ! isset( $attrs[ strtolower( $x ) ] );
foreach( $attrs as $attr => $values )
if( 0 == strcasecmp( $attr, $x ) )
return false;
return true;
}
?>

View File

@ -1,70 +0,0 @@
<?php
// $Header: /cvsroot/phpldapadmin/phpldapadmin/add_oclass.php,v 1.11 2004/08/15 17:39:20 uugdave Exp $
/*
* add_oclass.php
* Adds an objectClass to the specified dn.
* Variables that come in as POST vars:
*
* Note, this does not do any schema violation checking. That is
* performed in add_oclass_form.php.
*
* Vars that come in as POST:
* - dn (rawurlencoded)
* - server_id
* - new_oclass
* - new_attrs (array, if any)
*/
require './common.php';
$dn = rawurldecode( $_POST['dn'] );
$encoded_dn = rawurlencode( $dn );
$new_oclass = $_POST['new_oclass'];
$server_id = $_POST['server_id'];
$new_attrs = $_POST['new_attrs'];
if( is_attr_read_only( $server_id, 'objectClass' ) )
pla_error( "ObjectClasses are flagged as read only in the phpLDAPadmin configuration." );
if( is_server_read_only( $server_id ) )
pla_error( $lang['no_updates_in_read_only_mode'] );
check_server_id( $server_id ) or pla_error( $lang['bad_server_id'] );
have_auth_info( $server_id ) or pla_error( $lang['not_enough_login_info'] );
$new_entry = array();
$new_entry['objectClass'] = $new_oclass;
$new_attrs_entry = array();
$new_oclass_entry = array( 'objectClass' => $new_oclass );
if( is_array( $new_attrs ) && count( $new_attrs ) > 0 )
foreach( $new_attrs as $attr => $val ) {
// Check to see if this is a unique Attribute
if( $badattr = checkUniqueAttr( $server_id, $dn, $attr, array($val) ) ) {
$search_href='search.php?search=true&form=advanced&server_id=' . $server_id . '&filter=' . $attr . '=' . $badattr;
pla_error(sprintf( $lang['unique_attr_failed'] , $attr,$badattr,$dn,$search_href ) );
}
$new_entry[ $attr ] = $val;
}
//echo "<pre>";
//print_r( $new_entry );
//exit;
$ds = pla_ldap_connect( $server_id );
pla_ldap_connection_is_error( $ds );
$add_res = @ldap_mod_add( $ds, $dn, $new_entry );
if( ! $add_res )
{
pla_error( $lang['could_not_perform_ldap_mod_add'], ldap_error( $ds ), ldap_errno( $ds ) );
}
else
{
header( "Location: edit.php?server_id=$server_id&dn=$encoded_dn&modified_attrs[]=objectclass" );
}
?>

View File

@ -1,127 +0,0 @@
<?php
// $Header: /cvsroot/phpldapadmin/phpldapadmin/add_oclass_form.php,v 1.15 2004/10/22 13:58:59 uugdave Exp $
/*
* add_oclass_form.php
* This page may simply add the objectClass and take you back to the edit page,
* but, in one condition it may prompt the user for input. That condition is this:
*
* If the user has requested to add an objectClass that requires a set of
* attributes with 1 or more not defined by the object. In that case, we will
* present a form for the user to add those attributes to the object.
*
* Variables that come in as POST vars:
* - dn (rawurlencoded)
* - server_id
* - new_oclass
*/
require './common.php';
$dn = rawurldecode( $_POST['dn'] );
$encoded_dn = rawurlencode( $dn );
$new_oclass = $_POST['new_oclass'];
$server_id = $_POST['server_id'];
if( is_server_read_only( $server_id ) )
pla_error( $lang['no_updates_in_read_only_mode'] );
check_server_id( $server_id ) or pla_error( $lang['bad_server_id'] );
have_auth_info( $server_id ) or pla_error( $lang['not_enough_login_info'] );
/* Ensure that the object has defined all MUST attrs for this objectClass.
* If it hasn't, present a form to have the user enter values for all the
* newly required attrs. */
$entry = get_object_attrs( $server_id, $dn, true );
$current_attrs = array();
foreach( $entry as $attr => $junk )
$current_attrs[] = strtolower($attr);
// grab the required attributes for the new objectClass
$oclass = get_schema_objectclass( $server_id, $new_oclass );
if( $oclass )
$must_attrs = $oclass->getMustAttrs();
else
$must_attrs = array();
// We don't want any of the attr meta-data, just the string
//foreach( $must_attrs as $i => $attr )
//$must_attrs[$i] = $attr->getName();
// build a list of the attributes that this new objectClass requires,
// but that the object does not currently contain
$needed_attrs = array();
foreach( $must_attrs as $attr ) {
$attr = get_schema_attribute( $server_id, $attr->getName() );
//echo "<pre>"; var_dump( $attr ); echo "</pre>";
// First, check if one of this attr's aliases is already an attribute of this entry
foreach( $attr->getAliases() as $alias_attr_name )
if( in_array( strtolower( $alias_attr_name ), $current_attrs ) )
// Skip this attribute since it's already in the entry
continue;
if( in_array( strtolower($attr->getName()), $current_attrs ) )
continue;
// We made it this far, so the attribute needs to be added to this entry in order
// to add this objectClass
$needed_attrs[] = $attr;
}
if( count( $needed_attrs ) > 0 )
{
include './header.php'; ?>
<body>
<h3 class="title"><?php echo $lang['new_required_attrs']; ?></h3>
<h3 class="subtitle"><?php echo $lang['requires_to_add'] . ' ' . count($needed_attrs) .
' ' . $lang['new_attributes']; ?></h3>
<small>
<?php
echo $lang['new_required_attrs_instructions'];
echo ' ' . count( $needed_attrs ) . ' ' . $lang['new_attributes'] . ' ';
echo $lang['that_this_oclass_requires']; ?>
</small>
<br />
<br />
<form action="add_oclass.php" method="post">
<input type="hidden" name="new_oclass" value="<?php echo htmlspecialchars( $new_oclass ); ?>" />
<input type="hidden" name="dn" value="<?php echo $encoded_dn; ?>" />
<input type="hidden" name="server_id" value="<?php echo $server_id; ?>" />
<table class="edit_dn" cellspacing="0">
<tr><th colspan="2"><?php echo $lang['new_required_attrs']; ?></th></tr>
<?php foreach( $needed_attrs as $count => $attr ) { ?>
<tr><td class="attr"><b><?php echo htmlspecialchars($attr->getName()); ?></b></td></tr>
<tr><td class="val"><input type="text" name="new_attrs[<?php echo htmlspecialchars($attr->getName()); ?>]" value="" size="40" /></tr>
<?php } ?>
</table>
<br />
<br />
<center><input type="submit" value="<?php echo $lang['add_oclass_and_attrs']; ?>" /></center>
</form>
</body>
</html>
<?php
}
else
{
$ds = pla_ldap_connect( $server_id );
pla_ldap_connection_is_error( $ds );
$add_res = @ldap_mod_add( $ds, $dn, array( 'objectClass' => $new_oclass ) );
if( ! $add_res )
pla_error( "Could not perform ldap_mod_add operation.", ldap_error( $ds ), ldap_errno( $ds ) );
else
header( "Location: edit.php?server_id=$server_id&dn=$encoded_dn&modified_attrs[]=objectClass" );
}
?>

View File

@ -1,71 +0,0 @@
<?php
// $Header: /cvsroot/phpldapadmin/phpldapadmin/add_value.php,v 1.13 2004/08/15 17:39:20 uugdave Exp $
/*
* add_value.php
* Adds a value to an attribute for a given dn.
* Variables that come in as POST vars:
* - dn (rawurlencoded)
* - attr (rawurlencoded) the attribute to which we are adding a value
* - server_id
* - new_value (form element)
* - binary
*
* On success, redirect to the edit_dn page.
* On failure, echo an error.
*/
require './common.php';
$dn = rawurldecode( $_POST['dn'] );
$encoded_dn = rawurlencode( $dn );
$attr = $_POST['attr'];
$encoded_attr = rawurlencode( $attr );
$server_id = $_POST['server_id'];
$new_value = $_POST['new_value'];
$is_binary_val = isset( $_POST['binary'] ) ? true : false;
if( is_server_read_only( $server_id ) )
pla_error( $lang['no_updates_in_read_only_mode'] );
if( is_attr_read_only( $server_id, $attr ) )
pla_error( "The attribute '" . htmlspecialchars( $attr ) . "' is flagged as read only in the phpLDAPadmin configuration." );
check_server_id( $server_id ) or pla_error( $lang['bad_server_id'] );
have_auth_info( $server_id ) or pla_error( $lang['not_enough_login_info'] );
$ds = pla_ldap_connect( $server_id );
pla_ldap_connection_is_error( $ds );
// special case for binary attributes:
// we must go read the data from the file.
if( $is_binary_val )
{
$file = $_FILES['new_value']['tmp_name'];
$f = fopen( $file, 'r' );
$binary_value = fread( $f, filesize( $file ) );
fclose( $f );
$new_value = $binary_value;
}
$new_entry = array( $attr => $new_value );
// Check to see if this is a unique Attribute
if( $badattr = checkUniqueAttr( $server_id, $dn, $attr, $new_entry ) ) {
$search_href='search.php?search=true&form=advanced&server_id=' . $server_id . '&filter=' . $attr . '=' . $badattr;
pla_error(sprintf( $lang['unique_attr_failed'] , $attr,$badattr,$dn,$search_href ) );
}
// Call the custom callback for each attribute modification
// and verify that it should be modified.
if( preAttrAdd( $server_id, $dn, $attr, $new_entry ) ) {
$add_result = @ldap_mod_add( $ds, $dn, $new_entry );
if( ! $add_result )
pla_error( $lang['could_not_perform_ldap_mod_add'], ldap_error( $ds ), ldap_errno( $ds ) );
}
header( "Location: edit.php?server_id=$server_id&dn=$encoded_dn&modified_attrs[]=$encoded_attr" );
?>

View File

@ -1,187 +0,0 @@
<?php
// $Header: /cvsroot/phpldapadmin/phpldapadmin/add_value_form.php,v 1.26 2004/08/15 17:39:20 uugdave Exp $
/*
* add_value_form.php
* Displays a form to allow the user to enter a new value to add
* to the existing list of values for a multi-valued attribute.
* Variables that come in as GET vars:
* - dn (rawurlencoded)
* - attr (rawurlencoded) the attribute to which we are adding a value
* - server_id
*
*/
require './common.php';
$dn = isset( $_GET['dn'] ) ? $_GET['dn'] : null;
$encoded_dn = rawurlencode( $dn );
$server_id = $_GET['server_id'];
check_server_id( $server_id ) or pla_error( $lang['bad_server_id'] );
have_auth_info( $server_id ) or pla_error( $lang['not_enough_login_info'] );
if( null != $dn ) {
$rdn = get_rdn( $dn );
} else {
$rdn = null;
}
$server_name = $servers[$server_id]['name'];
$attr = $_GET['attr'];
$encoded_attr = rawurlencode( $attr );
$current_values = get_object_attr( $server_id, $dn, $attr );
$num_current_values = ( is_array($current_values) ? count($current_values) : 1 );
$is_object_class = ( 0 == strcasecmp( $attr, 'objectClass' ) ) ? true : false;
$is_jpeg_photo = is_jpeg_photo( $server_id, $attr ); //( 0 == strcasecmp( $attr, 'jpegPhoto' ) ) ? true : false;
if( is_server_read_only( $server_id ) )
pla_error( $lang['no_updates_in_read_only_mode'] );
if( $is_object_class ) {
// fetch all available objectClasses and remove those from the list that are already defined in the entry
$schema_oclasses = get_schema_objectclasses( $server_id );
foreach( $current_values as $oclass )
unset( $schema_oclasses[ strtolower( $oclass ) ] );
} else {
$schema_attr = get_schema_attribute( $server_id, $attr );
}
include './header.php'; ?>
<body>
<h3 class="title">
<?php echo $lang['add_new']; ?>
<b><?php echo htmlspecialchars($attr); ?></b>
<?php echo $lang['value_to']; ?>
<b><?php echo htmlspecialchars($rdn); ?></b></h3>
<h3 class="subtitle">
<?php echo $lang['server']; ?>:
<b><?php echo $server_name; ?></b> &nbsp;&nbsp;&nbsp;
<?php echo $lang['distinguished_name']; ?>: <b><?php echo htmlspecialchars( $dn ); ?></b></h3>
<?php echo $lang['current_list_of']; ?> <b><?php echo $num_current_values; ?></b>
<?php echo $lang['values_for_attribute']; ?> <b><?php echo htmlspecialchars($attr); ?></b>:
<?php if( $is_jpeg_photo ) { ?>
<table><td>
<?php draw_jpeg_photos( $server_id, $dn, $attr, false ); ?>
</td></table>
<!-- Temporary warning until we find a way to add jpegPhoto values without an INAPROPRIATE_MATCHING error -->
<p><small>
<?php echo $lang['inappropriate_matching_note']; ?>
</small></p>
<!-- End of temporary warning -->
<?php } else if( is_attr_binary( $server_id, $attr ) ) { ?>
<ul>
<?php if( is_array( $vals ) ) { for( $i=1; $i<=count($vals); $i++ ) {
$href = "download_binary_attr.php?server_id=$server_id&amp;dn=$encoded_dn&amp;attr=$attr&amp;value_num=" . ($i-1); ?>
<li><a href="<?php echo $href; ?>"><img src="images/save.png" /> <?php echo $lang['download_value'] . ' ' . $i; ?>)</a></li>
<?php } } else {
$href = "download_binary_attr.php?server_id=$server_id&amp;dn=$encoded_dn&amp;attr=$attr"; ?>
<li><a href="<?php echo $href; ?>"><img src="images/save.png" /> <?php echo $lang['download_value']; ?></a></li>
<?php } ?>
</ul>
<!-- Temporary warning until we find a way to add jpegPhoto values without an INAPROPRIATE_MATCHING error -->
<p><small>
<?php echo $lang['inappropriate_matching_note']; ?>
</small></p>
<!-- End of temporary warning -->
<?php } else { ?>
<ul class="current_values">
<?php if( is_array( $current_values ) ) /*$num_current_values > 1 )*/ {
foreach( $current_values as $val ) { ?>
<li><nobr><?php echo htmlspecialchars(($val)); ?></nobr></li>
<?php } ?>
<?php } else { ?>
<li><nobr><?php echo htmlspecialchars(($current_values)); ?></nobr></li>
<?php } ?>
</ul>
<?php } ?>
<?php echo $lang['enter_value_to_add']; ?>
<br />
<br />
<?php if( $is_object_class ) { ?>
<form action="add_oclass_form.php" method="post" class="new_value">
<input type="hidden" name="server_id" value="<?php echo $server_id; ?>" />
<input type="hidden" name="dn" value="<?php echo $encoded_dn; ?>" />
<select name="new_oclass">
<?php foreach( $schema_oclasses as $name => $oclass ) {
// exclude any structural ones, as they'll only generate an LDAP_OBJECT_CLASS_VIOLATION
if ($oclass->type == "structural") continue;
?>
<option value="<?php echo $oclass->getName(); ?>"><?php echo $oclass->getName(); ?></option>
<?php } ?>
</select> <input type="submit" value="<?php echo $lang['add_new_objectclass']; ?>" />
<br />
<?php if( show_hints() ) { ?>
<small>
<br />
<img src="images/light.png" /><span class="hint"><?php echo $lang['new_required_attrs_note']; ?></span>
</small>
<?php } ?>
<?php } else { ?>
<form action="add_value.php" method="post" class="new_value" name="new_value_form"<?php
if( is_attr_binary( $server_id, $attr ) ) echo "enctype=\"multipart/form-data\""; ?>>
<input type="hidden" name="server_id" value="<?php echo $server_id; ?>" />
<input type="hidden" name="dn" value="<?php echo $encoded_dn; ?>" />
<input type="hidden" name="attr" value="<?php echo $encoded_attr; ?>" />
<?php if( is_attr_binary( $server_id, $attr ) ) { ?>
<input type="file" name="new_value" />
<input type="hidden" name="binary" value="true" />
<?php } else { ?>
<?php if( is_multi_line_attr( $attr, $server_id ) ) { ?>
<textarea name="new_value" rows="3" cols="30"></textarea>
<?php } else { ?>
<input type="text" <?php
if( $schema_attr->getMaxLength() )
echo "maxlength=\"" . $schema_attr->getMaxLength() . "\" ";
?>name="new_value" size="40" value="" /><?php
// draw the "browse" button next to this input box if this attr houses DNs:
if( is_dn_attr( $server_id, $attr ) ) draw_chooser_link( "new_value_form.new_value", false ); ?>
<?php } ?>
<?php } ?>
<input type="submit" name="submit" value="<?php echo $lang['add_new_value']; ?>" />
<br />
<?php if( $schema_attr->getDescription() ) { ?>
<small><b><?php echo $lang['desc']; ?>:</b> <?php echo $schema_attr->getDescription(); ?></small><br />
<?php } ?>
<?php if( $schema_attr->getType() ) { ?>
<small><b><?php echo $lang['syntax']; ?>:</b> <?php echo $schema_attr->getType(); ?></small><br />
<?php } ?>
<?php if( $schema_attr->getMaxLength() ) { ?>
<small><b><?php echo $lang['maximum_length']; ?>:</b> <?php echo number_format( $schema_attr->getMaxLength() ); ?> <?php echo $lang['characters']; ?></small><br />
<?php } ?>
</form>
<?php } ?>
</body>
</html>

3
app/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
q*
!public/
!.gitignore

View File

@ -0,0 +1,371 @@
<?php
namespace App\Classes\LDAP;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Schema\AttributeType;
use App\Ldap\Entry;
/**
* Represents an attribute of an LDAP Object
*/
class Attribute implements \Countable, \ArrayAccess
{
// Attribute Name
protected string $name;
// Is this attribute an internal attribute
protected(set) bool $is_internal = FALSE;
protected(set) bool $no_attr_tags = FALSE;
// MIN/MAX number of values
protected(set) int $min_values_count = 0;
protected(set) int $max_values_count = 0;
// The schema's representation of this attribute
protected(set) ?AttributeType $schema;
// The DN this object is in
protected(set) string $dn;
// The old values for this attribute - helps with isDirty() to determine if there is an update pending
private Collection $_values_old;
// Current Values
private Collection $_values;
// The objectclasses of the entry that has this attribute
protected(set) Collection $oc;
private const SYNTAX_CERTIFICATE = '1.3.6.1.4.1.1466.115.121.1.8';
private const SYNTAX_CERTIFICATE_LIST = '1.3.6.1.4.1.1466.115.121.1.9';
/*
# 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();
*/
/**
* Create an Attribute
*
* @param string $dn DN this attribute is used in
* @param string $name Name of the attribute
* @param array $values Current Values
* @param array $oc The objectclasses that the DN of this attribute has
*/
public function __construct(string $dn,string $name,array $values,array $oc=[])
{
$this->dn = $dn;
$this->name = $name;
$this->_values = collect($values);
$this->_values_old = collect($values);
$this->oc = collect($oc);
$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 __call(string $name,array $arguments)
{
abort(555,'Method not handled: '.$name);
}
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,
// Objectclasses that required this attribute for an LDAP entry
'required' => $this->required(),
// Is this attribute an RDN attribute
'is_rdn' => $this->isRDN(),
// 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),
// 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(),
// The current attribute values
'values' => $this->no_attr_tags ? $this->tagValues() : $this->_values,
// The original attribute values
'values_old' => $this->no_attr_tags ? $this->tagValuesOld() : $this->_values_old,
default => throw new \Exception('Unknown key:' . $key),
};
}
public function __set(string $key,mixed $values): void
{
switch ($key) {
case 'values':
$this->_values = $values;
break;
case 'values_old':
$this->_values_old = $values;
break;
default:
throw new \Exception('Unknown key:'.$key);
}
}
public function __toString(): string
{
return $this->name;
}
/* INTERFACE */
public function count(): int
{
return $this->_values->dot()->count();
}
public function offsetExists(mixed $offset): bool
{
return $this->_values->dot()->has($offset);
}
public function offsetGet(mixed $offset): mixed
{
return $this->_values->dot()->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
}
/* METHODS */
public function addValue(string $tag,array $values): void
{
$this->_values->put(
$tag,
array_unique(array_merge($this->_values
->get($tag,[]),$values)));
}
public function addValueOld(string $tag,array $values): void
{
$this->_values_old->put(
$tag,
array_unique(array_merge($this->_values_old
->get($tag,[]),$values)));
}
/**
* Return the hints about this attribute, ie: RDN, Required, etc
*
* @return Collection
*/
public function hints(): Collection
{
$result = collect();
if ($this->is_internal)
return $result;
// 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
if ($this->required()->count())
$result->put(__('required'),sprintf('%s: %s',__('Required Attribute by ObjectClass(es)'),$this->required()->join(', ')));
// If this attribute is a dynamic attribute
if ($this->isDynamic())
$result->put(__('dynamic'),__('These are dynamic values present as a result of another attribute'));
return $result;
}
/**
* Determine if this attribute has changes
*
* @return bool
*/
public function isDirty(): bool
{
return (($a=$this->values_old->dot()->filter())->keys()->count() !== ($b=$this->values->dot()->filter())->keys()->count())
|| ($a->count() !== $b->count())
|| ($a->diff($b)->count() !== 0);
}
/**
* Are these values as a result of a dynamic attribute
*
* @return bool
*/
public function isDynamic(): bool
{
return $this->schema->used_in_object_classes
->keys()
->intersect($this->schema->heirachy($this->oc))
->count() === 0;
}
/**
* Work out if this attribute is an RDN attribute
*
* @return bool
*/
public function isRDN(): bool
{
// If we dont have an DN, then we cant know
if (! $this->dn)
return FALSE;
$rdns = collect(explode('+',substr($this->dn,0,strpos($this->dn,','))));
return $rdns->filter(fn($item) => str_starts_with($item,$this->name.'='))->count() > 0;
}
/**
* 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
{
$view = match ($this->schema->syntax_oid) {
self::SYNTAX_CERTIFICATE => view('components.syntax.certificate'),
self::SYNTAX_CERTIFICATE_LIST => view('components.syntax.certificatelist'),
default => view()->exists($x = 'components.attribute.' . $this->name_lc)
? view($x)
: view('components.attribute'),
};
return $view
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new);
}
public function render_item_old(string $dotkey): ?string
{
return match ($this->schema->syntax_oid) {
self::SYNTAX_CERTIFICATE => join("\n",str_split(base64_encode(Arr::get($this->values_old->dot(),$dotkey)),80)),
self::SYNTAX_CERTIFICATE_LIST => join("\n",str_split(base64_encode(Arr::get($this->values_old->dot(),$dotkey)),80)),
default => Arr::get($this->values_old->dot(),$dotkey),
};
}
public function render_item_new(string $dotkey): ?string
{
return Arr::get($this->values->dot(),$dotkey);
}
/**
* Work out if this attribute is required by an objectClass the entry has
*
* @return Collection
*/
public function required(): Collection
{
// If we dont have any objectclasses then we cant know if it is required
return $this->oc->count()
? $this->oc->intersect($this->required_by->keys())->sort()
: collect();
}
public function tagValues(string $tag=Entry::TAG_NOTAG): Collection
{
return collect($this->_values
->filter(fn($item,$key)=>($key===$tag))
->get($tag,[]));
}
public function tagValuesOld(string $tag=Entry::TAG_NOTAG): Collection
{
return collect($this->_values_old
->filter(fn($item,$key)=>($key===$tag))
->get($tag,[]));
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute;
use App\Classes\LDAP\Attribute;
/**
* Represents an attribute whose values are binary
*/
abstract class Binary extends Attribute
{
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Classes\LDAP\Attribute\Binary;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Binary;
use App\Ldap\Entry;
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,string $langtag=Entry::TAG_NOTAG): View
{
return view('components.attribute.binary.jpegphoto')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new)
->with('langtag',$langtag)
->with('f',new \finfo);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use App\Classes\LDAP\Attribute;
use App\Traits\MD5Updates;
/**
* Represents an attribute whose values is a binary user certificate
*/
final class Certificate extends Attribute
{
use MD5Updates;
private array $_object = [];
public function certificate(int $key=0): string
{
return sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----",
join("\n",str_split(base64_encode(Arr::get($this->values_old,'binary.'.$key)),80))
);
}
public function cert_info(string $index,int $key=0): mixed
{
if (! array_key_exists($key,$this->_object))
$this->_object[$key] = openssl_x509_parse(openssl_x509_read($this->certificate($key)));
return Arr::get($this->_object[$key],$index);
}
public function expires($key=0): Carbon
{
return Carbon::createFromTimestampUTC($this->cert_info('validTo_time_t',$key));
}
public function subject($key=0): string
{
$subject = collect($this->cert_info('subject',$key))->reverse();
return $subject->map(fn($item,$key)=>sprintf("%s=%s",$key,$item))->join(',');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use App\Classes\LDAP\Attribute;
use App\Traits\MD5Updates;
/**
* Represents an attribute whose values is a binary user certificate
*/
final class CertificateList extends Attribute
{
use MD5Updates;
}

View File

@ -0,0 +1,78 @@
<?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 = [
'authorityrevocationlist' => CertificateList::class,
'cacertificate' => Certificate::class,
'certificaterevocationlist' => CertificateList::class,
'createtimestamp' => Internal\Timestamp::class,
'creatorsname' => Internal\DN::class,
'configcontext' => Schema\Generic::class,
'contextcsn' => Internal\CSN::class,
'entrycsn' => Internal\CSN::class,
'entrydn' => Internal\DN::class,
'entryuuid' => Internal\UUID::class,
'etag' => Internal\Etag::class,
'krblastfailedauth' => Attribute\NoAttrTags\Generic::class,
'krblastpwdchange' => Attribute\NoAttrTags\Generic::class,
'krblastsuccessfulauth' => Attribute\NoAttrTags\Generic::class,
'krbpasswordexpiration' => Attribute\NoAttrTags\Generic::class,
'krbloginfailedcount' => Attribute\NoAttrTags\Generic::class,
'krbprincipalkey' => KrbPrincipalKey::class,
'krbticketflags' => KrbTicketFlags::class,
'gidnumber' => GidNumber::class,
'hassubordinates' => Internal\HasSubordinates::class,
'jpegphoto' => Binary\JpegPhoto::class,
'modifytimestamp' => Internal\Timestamp::class,
'modifiersname' => Internal\DN::class,
'monitorcontext' => Schema\Generic::class,
'namingcontexts' => Schema\Generic::class,
'numsubordinates' => Internal\NumSubordinates::class,
'objectclass' => ObjectClass::class,
'pwdpolicysubentry' => Internal\PwdPolicySubentry::class,
'structuralobjectclass' => Internal\StructuralObjectClass::class,
'subschemasubentry' => Internal\SubschemaSubentry::class,
'supportedcontrol' => Schema\OID::class,
'supportedextension' => Schema\OID::class,
'supportedfeatures' => Schema\OID::class,
'supportedldapversion' => Schema\Generic::class,
'supportedsaslmechanisms' => Schema\Mechanisms::class,
'usercertificate' => Certificate::class,
'userpassword' => Password::class,
];
/**
* Create the new Object for an attribute
*
* @param string $dn
* @param string $attribute
* @param array $values
* @param array $oc
* @return Attribute
*/
public static function create(string $dn,string $attribute,array $values,array $oc=[]): 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($dn,$attribute,$values,$oc);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Classes\LDAP\Attribute;
use App\Classes\LDAP\Attribute;
/**
* Represents an GidNumber Attribute
*/
final class GidNumber extends Attribute
{
protected(set) bool $no_attr_tags = FALSE;
}

View File

@ -0,0 +1,23 @@
<?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(set) bool $is_internal = TRUE;
protected(set) bool $no_attr_tags = 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);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an CSN Attribute
*/
final class CSN extends Internal
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an DN Attribute
*/
final class DN extends Internal
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an Etag Attribute
*/
final class Etag extends Internal
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an HasSubordinates Attribute
*/
final class HasSubordinates extends Internal
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an NumSubordinates Attribute
*/
final class NumSubordinates extends Internal
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an PwdPolicySubentry Attribute
*/
final class PwdPolicySubentry extends Internal
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an StructuralObjectClass Attribute
*/
final class StructuralObjectClass extends Internal
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an SubschemaSubentry Attribute
*/
final class SubschemaSubentry extends Internal
{
}

View File

@ -0,0 +1,20 @@
<?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);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Classes\LDAP\Attribute\Internal;
use App\Classes\LDAP\Attribute\Internal;
/**
* Represents an UUID Attribute
*/
final class UUID extends Internal
{
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Arr;
use App\Classes\LDAP\Attribute;
use App\Traits\MD5Updates;
/**
* Represents an attribute whose values are passwords
*/
final class KrbPrincipalKey extends Attribute
{
use MD5Updates;
protected(set) bool $no_attr_tags = TRUE;
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.krbprincipalkey')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new);
}
public function render_item_old(string $dotkey): ?string
{
return parent::render_item_old($dotkey)
? str_repeat('*',16)
: NULL;
}
public function render_item_new(string $dotkey): ?string
{
return parent::render_item_new($dotkey)
? str_repeat('*',16)
: NULL;
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
/**
* Represents an attribute whose value is a Kerberos Ticket Flag
* See RFC4120
*/
final class KrbTicketFlags extends Attribute
{
protected(set) bool $no_attr_tags = TRUE;
private const DISALLOW_POSTDATED = 0x00000001;
private const DISALLOW_FORWARDABLE = 0x00000002;
private const DISALLOW_TGT_BASED = 0x00000004;
private const DISALLOW_RENEWABLE = 0x00000008;
private const DISALLOW_PROXIABLE = 0x00000010;
private const DISALLOW_DUP_SKEY = 0x00000020;
private const DISALLOW_ALL_TIX = 0x00000040;
private const REQUIRES_PRE_AUTH = 0x00000080;
private const REQUIRES_HW_AUTH = 0x00000100;
private const REQUIRES_PWCHANGE = 0x00000200;
private const DISALLOW_SVR = 0x00001000;
private const PWCHANGE_SERVICE = 0x00002000;
private static function helpers(): Collection
{
$helpers = collect([
log(self::DISALLOW_POSTDATED,2) => __('KRB_DISALLOW_POSTDATED'),
log(self::DISALLOW_FORWARDABLE,2) => __('KRB_DISALLOW_FORWARDABLE'),
log(self::DISALLOW_TGT_BASED,2) => __('KRB_DISALLOW_TGT_BASED'),
log(self::DISALLOW_RENEWABLE,2) => __('KRB_DISALLOW_RENEWABLE'),
log(self::DISALLOW_PROXIABLE,2) => __('KRB_DISALLOW_PROXIABLE'),
log(self::DISALLOW_DUP_SKEY,2) => __('KRB_DISALLOW_DUP_SKEY'),
log(self::DISALLOW_ALL_TIX,2) => __('KRB_DISALLOW_ALL_TIX'),
log(self::REQUIRES_PRE_AUTH,2) => __('KRB_REQUIRES_PRE_AUTH'),
log(self::REQUIRES_HW_AUTH,2) => __('KRB_REQUIRES_HW_AUTH'),
log(self::REQUIRES_PWCHANGE,2) => __('KRB_REQUIRES_PWCHANGE'),
log(self::DISALLOW_SVR,2) => __('KRB_DISALLOW_SVR'),
log(self::PWCHANGE_SERVICE,2) => __('KRB_PWCHANGE_SERVICE'),
])
->replace(config('pla.krb.bits',[]));
return $helpers;
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.krbticketflags')
->with('o',$this)
->with('edit',$edit)
->with('old',$old)
->with('new',$new)
->with('helper',static::helpers());
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Classes\LDAP\Attribute\NoAttrTags;
use App\Classes\LDAP\Attribute;
/**
* Represents an Attribute that doesnt have Lang Tags
*/
class Generic extends Attribute
{
protected(set) bool $no_attr_tags = TRUE;
}

View File

@ -0,0 +1,89 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
/**
* Represents an ObjectClass Attribute
*/
final class ObjectClass extends Attribute
{
protected(set) bool $no_attr_tags = TRUE;
// The schema ObjectClasses for this objectclass of a DN
protected Collection $oc_schema;
/**
* Create an ObjectClass Attribute
*
* @param string $dn DN this attribute is used in
* @param string $name Name of the attribute
* @param array $values Current Values
* @param array $oc The objectclasses that the DN of this attribute has (ignored for objectclasses)
*/
public function __construct(string $dn,string $name,array $values,array $oc=[])
{
parent::__construct($dn,$name,$values,['top']);
$this->set_oc_schema($this->tagValuesOld());
}
public function __get(string $key): mixed
{
return match ($key) {
'structural' => $this->oc_schema->filter(fn($item)=>$item->isStructural()),
default => parent::__get($key),
};
}
public function __set(string $key,mixed $values): void
{
switch ($key) {
case 'values':
parent::__set($key,$values);
// We need to populate oc_schema, if we are a new OC and thus dont have any old values
if (! $this->values_old->count() && $this->values->count())
$this->set_oc_schema($this->tagValues());
break;
default: parent::__set($key,$values);
}
}
/**
* 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('langtag',Entry::TAG_NOTAG)
->with('old',$old)
->with('new',$new);
}
private function set_oc_schema(Collection $tv): void
{
$this->oc_schema = config('server')
->schema('objectclasses')
->filter(fn($item)=>$tv->contains($item->name));
}
}

View File

@ -0,0 +1,110 @@
<?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;
protected(set) bool $no_attr_tags = TRUE;
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(string $dotkey): ?string
{
$pw = parent::render_item_old($dotkey);
return $pw
? (((($x=$this->hash($pw)) && ($x::id() === '*clear*')) ? sprintf('{%s}',$x::shortid()) : '')
.str_repeat('*',16))
: NULL;
}
public function render_item_new(string $dotkey): ?string
{
$pw = parent::render_item_new($dotkey);
return $pw
? (((($x=$this->hash($pw)) && ($x::id() !== '*clear*')) ? sprintf('{%s}',$x::shortid()) : '')
.str_repeat('*',16))
: NULL;
}
}

View File

@ -0,0 +1,25 @@
<?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)));
}
}

View File

@ -0,0 +1,25 @@
<?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)));
}
}

View File

@ -0,0 +1,68 @@
<?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);
}
}

View File

@ -0,0 +1,22 @@
<?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)));
}
}

View File

@ -0,0 +1,30 @@
<?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));
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Classes\LDAP\Attribute\Password;
final class Clear extends Base
{
public const key = '*clear*';
public function encode(string $password): string
{
return $password;
}
}

View File

@ -0,0 +1,29 @@
<?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));
}
}

View File

@ -0,0 +1,29 @@
<?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));
}
}

View File

@ -0,0 +1,13 @@
<?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)));
}
}

View File

@ -0,0 +1,29 @@
<?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));
}
}

View File

@ -0,0 +1,19 @@
<?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));
}
}

View File

@ -0,0 +1,18 @@
<?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);
}
}

View File

@ -0,0 +1,13 @@
<?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)));
}
}

View File

@ -0,0 +1,29 @@
<?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));
}
}

View File

@ -0,0 +1,13 @@
<?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)));
}
}

View File

@ -0,0 +1,13 @@
<?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)));
}
}

View File

@ -0,0 +1,29 @@
<?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));
}
}

View File

@ -0,0 +1,22 @@
<?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));
}
}

View File

@ -0,0 +1,24 @@
<?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);
}
}

View File

@ -0,0 +1,19 @@
<?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));
}
}

View File

@ -0,0 +1,19 @@
<?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));
}
}

View File

@ -0,0 +1,19 @@
<?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));
}
}

View File

@ -0,0 +1,49 @@
<?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(): Collection
{
return collect([
'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;
}
}

View File

@ -0,0 +1,62 @@
<?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(set) bool $no_attr_tags = 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) || 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,
__('No description available, can you help with one?'));
}
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);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Classes\LDAP\Attribute\Schema;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute\Schema;
/**
* Represents a Generic Schema Attribute
*/
class Generic extends Schema
{
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
// @note Schema attributes cannot be edited
return view('components.attribute.schema.generic')
->with('o',$this);
}
}

View File

@ -0,0 +1,42 @@
<?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);
}
}

View File

@ -0,0 +1,43 @@
<?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);
}
}

View File

@ -0,0 +1,53 @@
<?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;
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Classes\LDAP\Export;
use Illuminate\Support\Str;
use App\Classes\LDAP\Export;
use App\Ldap\Entry;
/**
* 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) {
if ($ao->no_attr_tags)
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);
}
else
foreach ($ao->values as $tag => $tagvalues) {
foreach ($tagvalues as $value) {
$result .= $this->multiLineDisplay(
Str::isAscii($value)
? sprintf('%s: %s',$ao->name.(($tag !== Entry::TAG_NOTAG) ? ';'.$tag : ''),$value)
: sprintf('%s:: %s',$ao->name.(($tag !== Entry::TAG_NOTAG) ? ';'.$tag : ''),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;
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\Classes\LDAP;
use Illuminate\Support\Collection;
use LdapRecord\LdapRecordException;
use App\Exceptions\Import\GeneralException;
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
*/
final protected function commit(Entry $o,int $action): Collection
{
switch ($action) {
case static::LDAP_IMPORT_ADD:
try {
$o->save();
} catch (LdapRecordException $e) {
if ($e->getDetailedError())
return collect([
'dn'=>$o->getDN(),
'result'=>sprintf('%d: %s (%s)',
($x=$e->getDetailedError())->getErrorCode(),
$x->getErrorMessage(),
$x->getDiagnosticMessage(),
)
]);
else
return collect([
'dn'=>$o->getDN(),
'result'=>sprintf('%d: %s',
$e->getCode(),
$e->getMessage(),
)
]);
}
return collect(['dn'=>$o->getDN(),'result'=>__('Created')]);
default:
throw new GeneralException('Unhandled action during commit: '.$action);
}
}
abstract public function process(): Collection;
}

View File

@ -0,0 +1,230 @@
<?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->addAttributeItem($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 = '';
}
continue;
}
$m = [];
preg_match('/^([a-zA-Z0-9;-]+)(:+)\s+(.*)$/',$line,$m);
switch (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->addAttributeItem($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] === '::');
$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->addAttributeItem($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 xreadEntry() {
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(_('Unknown change type'),$lines);
}
} else
return $this->error(_('A valid dn line is required'),$lines);
} else
return FALSE;
}
}

View File

@ -0,0 +1,599 @@
<?php
namespace App\Classes\LDAP\Schema;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use App\Classes\LDAP\Attribute;
use App\Ldap\Entry;
/**
* 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.
* @param bool $structural
*/
public function addRequiredByObjectClass(string $name,bool $structural): void
{
if (! $this->required_by_object_classes->has($name))
$this->required_by_object_classes->put($name,$structural);
}
/**
* 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.
* @param bool $structural
*/
public function addUsedInObjectClass(string $name,bool $structural): void
{
if (! $this->used_in_object_classes->has($name))
$this->used_in_object_classes->put($name,$structural);
}
private function factory(): Attribute
{
return Attribute\Factory::create(dn:'',attribute:$this->name,values:[]);
}
/**
* 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;
}
/**
* For a list of objectclasses return all parent objectclasses as well
*
* @param Collection $ocs
* @return Collection
*/
public function heirachy(Collection $ocs): Collection
{
$result = collect();
foreach ($ocs as $oc) {
$schema = config('server')
->schema('objectclasses',$oc)
->getParents(TRUE)
->pluck('name');
$result = $result->merge($schema)->push($oc);
}
return $result;
}
/**
* @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 hierarchy
$heirachy = $this->heirachy(collect($array)
->flatten()
->filter());
// Get any config validation
$validation = collect(Arr::get(config('ldap.validation'),$this->name_lc,[]));
$nolangtag = sprintf('%s.%s.0',$this->name_lc,Entry::TAG_NOTAG);
// Add in schema required by conditions
if (($heirachy->intersect($this->required_by_object_classes->keys())->count() > 0)
&& (! collect($validation->get($this->name_lc))->contains('required'))) {
$validation
->prepend(array_merge(['required','min:1'],$validation->get($nolangtag,[])),$nolangtag)
->prepend(array_merge(['required','array','min:1',($this->factory()->no_attr_tags ? 'max:1' : NULL)],$validation->get($this->name_lc,[])),$this->name_lc);
}
return $validation->toArray();
}
}

View File

@ -0,0 +1,122 @@
<?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;
}
}

View File

@ -0,0 +1,79 @@
<?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);
}
}
}

View File

@ -0,0 +1,142 @@
<?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;
}
}

View File

@ -0,0 +1,99 @@
<?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;
}
}

View File

@ -0,0 +1,552 @@
<?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;
}
}

View File

@ -0,0 +1,40 @@
<?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),
};
}
}

542
app/Classes/LDAP/Server.php Normal file
View File

@ -0,0 +1,542 @@
<?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 string|null $connection
* @param Carbon|null $cachetime
* @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 string|null $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
* @param array $attrs
* @return LDAPCollection|NULL
*/
public function children(string $dn,array $attrs=['dn']): ?LDAPCollection
{
return ($x=(new Entry)
->on($this->connection)
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
->select(array_merge($attrs,[
'hassubordinates', // Needed for the tree to know if an entry has children
'c' // Needed for the tree to show icons for countries
]))
->setDn($dn)
->list()
->orderBy('dn')
->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,$object_class->isStructural());
// 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
*/
public function schemaSyntaxName(string $oid): ?LDAPSyntax
{
return $this->schema('ldapsyntaxes',$oid);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions\Import;
use Exception;
class AttributeException extends Exception {}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions\Import;
use Exception;
class GeneralException extends Exception {}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions\Import;
use Exception;
class ObjectExistsException extends Exception {}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions\Import;
use Exception;
class VersionException extends Exception {}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidUsage extends Exception
{
//
}

View File

@ -0,0 +1,112 @@
<?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
{
$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]',__METHOD__,$dn));
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 string $objectclass
* @return array
*/
public function schema_objectclass_attrs(string $objectclass): array
{
$oc = config('server')->schema('objectclasses',$objectclass);
return [
'must' => $oc->getMustAttrs()->pluck('name'),
'may' => $oc->getMayAttrs()->pluck('name'),
];
}
}

View File

@ -0,0 +1,103 @@
<?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();
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller extends \Illuminate\Routing\Controller
{
//
}

View File

@ -0,0 +1,544 @@
<?php
namespace App\Http\Controllers;
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(),
];
});
}
/**
* Create a new object in the LDAP server
*
* @param EntryAddRequest $request
* @return View
* @throws InvalidUsage
*/
public function entry_add(EntryAddRequest $request): \Illuminate\View\View
{
if (! old('step',$request->validated('step')))
abort(404);
$key = $this->request_key($request,collect(old()));
$o = new Entry;
if (count($x=array_filter(old('objectclass',$request->objectclass)))) {
$o->objectclass = $x;
// Also add in our required attributes
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
$o->{$ao->name} = [Entry::TAG_NOTAG=>''];
$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 \Illuminate\View\View
*/
public function entry_attr_add(Request $request,string $id): \Illuminate\View\View
{
$xx = new \stdClass;
$xx->index = 0;
$dn = $request->dn ? Crypt::decrypt($request->dn) : '';
return $request->noheader
? view(sprintf('components.attribute.widget.%s',$id))
->with('o',Factory::create(dn: $dn,attribute: $id,values: [],oc: $request->objectclasses))
->with('value',$request->value)
->with('langtag',Entry::TAG_NOTAG)
->with('loop',$xx)
: new AttributeType(Factory::create($dn,$id,[],$request->objectclasses),new: TRUE,edit: TRUE)
->render();
}
public function entry_create(EntryAddRequest $request): \Illuminate\Http\RedirectResponse
{
$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) {
return Redirect::back()
->withInput()
->withErrors(sprintf('%s: %s - %s: %s',
__('LDAP Server Error Code'),
$e->getDetailedError()->getErrorCode(),
__($e->getDetailedError()->getErrorMessage()),
$e->getDetailedError()->getDiagnosticMessage(),
));
}
return Redirect::to('/')
->withFragment($o->getDNSecure());
}
public function entry_delete(Request $request): \Illuminate\Http\RedirectResponse
{
$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): \Illuminate\View\View
{
$dn = Crypt::decryptString($id);
$result = (new Entry)
->query()
->setDn($dn)
->recursive()
->get();
return view('fragment.export')
->with('result',new LDIFExport($result));
}
/**
* Render an available list of objectclasses for an Entry
*
* @param Request $request
* @return Collection
*/
public function entry_objectclass_add(Request $request): Collection
{
$dn = $request->key ? Crypt::decryptString($request->dn) : '';
$oc = Factory::create($dn,'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): Collection
{
$dn = Crypt::decryptString($request->dn);
$o = config('server')->fetch($dn);
$password = $o->getObject('userpassword');
$result = collect();
foreach ($password->values->dot() 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\Http\RedirectResponse|\Illuminate\View\View
* @throws ObjectNotFoundException
*/
public function entry_pending_update(EntryRequest $request): \Illuminate\Http\RedirectResponse|\Illuminate\View\View
{
$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));
// @todo Need to handle incoming attributes that were modified by MD5Updates Trait (eg: jpegphoto)
// We need to process and encrypt the password
if ($request->userpassword) {
$passwords = [];
$po = $o->getObject('userpassword');
foreach (Arr::dot($request->userpassword) as $dotkey => $value) {
// If the password is still the MD5 of the old password, then it hasnt changed
if (($old=Arr::get($po,$dotkey)) && ($value === md5($old))) {
$passwords[$dotkey] = $value;
continue;
}
if ($value) {
$type = Arr::get($request->userpassword_hash,$dotkey);
$passwords[$dotkey] = Password::hash_id($type)
->encode($value);
}
}
$o->userpassword = Arr::undot($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
* @todo When removing an attribute value, from a multi-value attribute, we have a ghost record showing after the update
* @todo Need to check when removing a single attribute value, do we have a ghost as well? Might be because we are redirecting with input?
*/
public function entry_update(EntryRequest $request): \Illuminate\Http\RedirectResponse
{
$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) {
return Redirect::to('/')
->withInput()
->withErrors(sprintf('%s: %s - %s: %s',
__('LDAP Server Error Code'),
$e->getDetailedError()->getErrorCode(),
__($e->getDetailedError()->getErrorMessage()),
$e->getDetailedError()->getDiagnosticMessage(),
));
}
return Redirect::to('/')
->withInput()
->with('updated',collect($dirty)->map(fn($item,$key)=>$o->getObject(collect(explode(';',$key))->first())));
}
/**
* 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\View\View
*/
public function frame(Request $request,?Collection $old=NULL): \Illuminate\View\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());
// If we are rendering a DN, rebuild our object
if ($key['dn']) {
$o = config('server')->fetch($key['dn']);
foreach (collect(old())->except(['key','dn','step','_token','userpassword_hash']) as $attr => $value)
$o->{$attr} = $value;
}
return match ($key['cmd']) {
'create' => $view
->with('container',old('container',$key['dn']))
->with('step',1),
'dn' => $view
->with('dn',$key['dn'])
->with('o',$o)
->with('page_actions',collect([
'copy'=>FALSE,
'create'=>FALSE,
'delete'=>TRUE,
'edit'=>TRUE,
'export'=>TRUE,
])),
'import' => $view,
default => abort(404),
};
}
/**
* This is the main page render function
*/
public function home(Request $request): \Illuminate\View\View
{
// 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\View\View
* @throws GeneralException
* @throws VersionException
*/
public function import(ImportRequest $request,string $type): \Illuminate\View\View
{
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));
}
/**
* 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\View\View
* @throws InvalidUsage
*/
public function schema_frame(Request $request): \Illuminate\View\View
{
// 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 \Illuminate\Http\Response
*/
public function user_image(Request $request): \Illuminate\Http\Response
{
$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);
}
}

View File

@ -0,0 +1,26 @@
<?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);
}
}

View File

@ -0,0 +1,33 @@
<?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('server', Config::get('server'));
view()->share('user', auth()->user() ?: new User);
return $next($request);
}
}

View File

@ -0,0 +1,62 @@
<?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;
});
}
}

View File

@ -0,0 +1,53 @@
<?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);
}
}

View File

@ -0,0 +1,78 @@
<?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 [];
$r = request() ?: collect();
return config('server')
->schema('attributetypes')
->intersectByKeys($r->all())
->map(fn($item)=>$item->validation($r->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',
'max:1',
],
'objectclass._null_'=>[
'required',
'array',
'min:1',
new HasStructuralObjectClass,
]
])
->toArray();
}
}

View File

@ -0,0 +1,26 @@
<?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
{
$r = request() ?: collect();
return config('server')
->schema('attributetypes')
->intersectByKeys($r->all())
->map(fn($item)=>$item->validation($r->get('objectclass',[])))
->filter()
->flatMap(fn($item)=>$item)
->toArray();
}
}

View File

@ -0,0 +1,16 @@
<?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',
];
}
}

21
app/Ldap/Connection.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace App\Ldap;
use LdapRecord\Configuration\DomainConfiguration;
use LdapRecord\Connection as ConnectionBase;
use LdapRecord\LdapInterface;
class Connection extends ConnectionBase
{
public function __construct(DomainConfiguration|array $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);
};
}
}

562
app/Ldap/Entry.php Normal file
View File

@ -0,0 +1,562 @@
<?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;
/**
* An Entry in an LDAP server
*
* @notes https://ldap.com/ldap-dns-and-rdns
*/
class Entry extends Model
{
private const TAG_CHARS = 'a-zA-Z0-9-';
private const TAG_CHARS_LANG = 'lang-['.self::TAG_CHARS.']';
public const TAG_NOTAG = '_null_';
// Our Attribute objects
private Collection $objects;
/* @deprecated */
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.
*
* This returns an array that should be consistent with $this->attributes
*
* @return array
*/
public function getAttributes(): array
{
return $this->objects
->flatMap(fn($item)=>
($item->no_attr_tags)
? [strtolower($item->name)=>$item->values]
: $item->values
->flatMap(fn($v,$k)=>[strtolower($item->name.($k !== self::TAG_NOTAG ? ';'.$k : ''))=>$v]))
->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);
list($attribute,$tag) = $this->keytag($key);
return ((! array_key_exists($key,$this->original)) && (! $this->objects->has($attribute)))
|| (! $this->getObject($attribute)->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. This is called when we $o->key = $value
*
* This function should update $this->attributes and correctly reflect changes in $this->objects
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function setAttribute(string $key,mixed $value): static
{
foreach ($value as $k => $v)
parent::setAttribute($key.($k !== self::TAG_NOTAG ? ';'.$k : ''),$v);
$key = $this->normalizeAttributeKey($key);
list($attribute,$tags) = $this->keytag($key);
$o = $this->objects->get($attribute) ?: Factory::create($this->dn ?: '',$attribute,[],Arr::get($this->attributes,'objectclass',[]));
$o->values = collect($value);
$this->objects->put($key,$o);
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
*/
public function getSortKeyAttribute(): string
{
return collect(explode(',',$this->getDn()))->reverse()->join(',');
}
/* METHODS */
/**
* Add an attribute to this entry, if the attribute already exists, then we'll add the value to the existing item.
*
* This is primarily used by LDIF imports, where attributes have multiple entries over multiple lines
*
* @param string $key
* @param mixed $value
* @return void
* @throws AttributeException
* @note Attributes added this way dont have objectclass information, and the Model::attributes are not populated
*/
public function addAttributeItem(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(strtolower($key));
// If the attribute name has tags
list($attribute,$tag) = $this->keytag($key);
if (! config('server')->schema('attributetypes')->has($attribute))
throw new AttributeException(sprintf('Schema doesnt have attribute [%s]',$attribute));
$o = $this->objects->get($attribute) ?: Attribute\Factory::create($this->dn ?: '',$attribute,[]);
$o->addValue($tag,[$value]);
$this->objects->put($attribute,$o);
}
/**
* 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);
}
}
/**
* Convert all our attribute values into an array of Objects
*
* @return Collection
*/
private function getAttributesAsObjects(): Collection
{
$result = collect();
$entry_oc = Arr::get($this->attributes,'objectclass',[]);
foreach ($this->attributes as $attrtag => $values) {
list($attribute,$tags) = $this->keytag($attrtag);
$orig = Arr::get($this->original,$attrtag,[]);
// If the attribute doesnt exist we'll create it
$o = Arr::get(
$result,
$attribute,
Factory::create(
$this->dn,
$attribute,
[$tags=>$orig],
$entry_oc,
));
$o->addValue($tags,$values);
$o->addValueOld($tags,Arr::get($this->original,$attrtag));
$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->getObject('objectclass')->values 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);
}
/**
* Identify the language tags (RFC 3866) used by this entry
*
* @return Collection
*/
public function getLangTags(): Collection
{
return $this->getObjects()
->filter(fn($item)=>! $item->no_attr_tags)
->map(fn($item)=>$item
->values
->keys()
->filter(fn($item)=>preg_match(sprintf('/%s+;?/',self::TAG_CHARS_LANG),$item))
->map(fn($item)=>preg_replace('/lang-/','',$item))
)
->filter(fn($item)=>$item->count());
}
/**
* Of all the items with lang tags, which ones have more than 1 lang tag
*
* @return Collection
*/
public function getLangMultiTags(): Collection
{
return $this->getLangTags()
->map(fn($item)=>$item->values()
->map(fn($item)=>explode(';',$item))
->filter(fn($item)=>count($item) > 1))
->filter(fn($item)=>$item->count());
}
/**
* 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;
}
/**
* Find other attribute tags used by this entry
*
* @return Collection
*/
public function getOtherTags(): Collection
{
return $this->getObjects()
->filter(fn($item)=>! $item->no_attr_tags)
->map(fn($item)=>$item
->values
->keys()
->filter(fn($item)=>
$item && collect(explode(';',$item))->filter(
fn($item)=>
(! preg_match(sprintf('/^%s$/',self::TAG_NOTAG),$item))
&& (! preg_match(sprintf('/^%s+$/',self::TAG_CHARS_LANG),$item))
)
->count())
)
->filter(fn($item)=>$item->count());
}
/**
* Return a list of attributes without any values
*
* @return Collection
* @todo Dont show attributes that are not provided by an objectclass, make a new function to show those
* This is for dynamic list items eg: labeledURI, which are not editable.
* We can highlight those values that are as a result of a dynamic module
*/
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, rdnbase would be null, so dynamically get it from the DN.
$o->setBase($this->rdnbase);
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required));
return $o;
}
/**
* Return this list of user attributes
*
* @param string $tag If null return all tags
* @return Collection
*/
public function getVisibleAttributes(string $tag=''): Collection
{
static $cache = [];
if (! Arr::get($cache,$tag ?: '_all_')) {
$ot = $this->getOtherTags();
$cache[$tag ?: '_all_'] = $this->objects
->filter(fn($item)=>(! $item->is_internal) && ((! $item->no_attr_tags) || (! $tag) || ($tag === Entry::TAG_NOTAG)))
->filter(fn($item)=>(! $tag) || $ot->has($item->name_lc) || count($item->tagValues($tag)) > 0);
}
return $cache[$tag ?: '_all_'];
}
public function hasAttribute(int|string $key): bool
{
return $this->objects
->has($key);
}
/**
* Return an icon for a DN based on objectClass
*
* @return string
*/
public function icon(): string
{
$objectclasses = $this->getObject('objectclass')
->tagValues()
->map(fn($item)=>strtolower($item));
// Return icon based upon objectClass value
if ($objectclasses->intersect([
'account',
'inetorgperson',
'organizationalperson',
'person',
'posixaccount',
])->count())
return 'fas fa-user';
elseif ($objectclasses->contains('organization'))
return 'fas fa-university';
elseif ($objectclasses->contains('organizationalunit'))
return 'fas fa-object-group';
elseif ($objectclasses->intersect([
'posixgroup',
'groupofnames',
'groupofuniquenames',
'group',
])->count())
return 'fas fa-users';
elseif ($objectclasses->intersect([
'dcobject',
'domainrelatedobject',
'domain',
'builtindomain',
])->count())
return 'fas fa-network-wired';
elseif ($objectclasses->contains('alias'))
return 'fas fa-theater-masks';
elseif ($objectclasses->contains('country'))
return sprintf('flag %s',strtolower(Arr::get($this->c ?: [],0)));
elseif ($objectclasses->contains('device'))
return 'fas fa-mobile-alt';
elseif ($objectclasses->contains('document'))
return 'fas fa-file-alt';
elseif ($objectclasses->contains('iphost'))
return 'fas fa-wifi';
elseif ($objectclasses->contains('room'))
return 'fas fa-door-open';
elseif ($objectclasses->contains('server'))
return 'fas fa-server';
elseif ($objectclasses->contains('openldaprootdse'))
return 'fas fa-info';
// Default
return 'fa-fw fas fa-cog';
}
/**
* Given an LDAP attribute, this will return the attribute name and the tag
* eg: description;lang-cn will return [description,lang-cn]
*
* @param string $key
* @return array
*/
private function keytag(string $key): array
{
$matches = [];
if (preg_match(sprintf('/^([%s]+);+([%s;]+)/',self::TAG_CHARS,self::TAG_CHARS),$key,$matches)) {
$attribute = $matches[1];
$tags = $matches[2];
} else {
$attribute = $key;
$tags = self::TAG_NOTAG;
}
return [$attribute,$tags];
}
/**
* Dont convert our $this->attributes to $this->objects when creating a new Entry::class
*
* @return $this
* @deprecated
*/
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;
}
}

Some files were not shown because too many files have changed in this diff Show More