diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index 82c22ef9..0e2c9380 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -16,7 +16,6 @@ class Attribute implements \Countable, \ArrayAccess { // Attribute Name protected string $name; - private int $counter = 0; // Is this attribute an internal attribute protected(set) bool $is_internal = FALSE; diff --git a/app/Classes/LDAP/Attribute/Factory.php b/app/Classes/LDAP/Attribute/Factory.php index 3d23fc9e..457f633b 100644 --- a/app/Classes/LDAP/Attribute/Factory.php +++ b/app/Classes/LDAP/Attribute/Factory.php @@ -52,6 +52,7 @@ class Factory 'supportedfeatures' => Schema\OID::class, 'supportedldapversion' => Schema\Generic::class, 'supportedsaslmechanisms' => Schema\Mechanisms::class, + 'usercertificate' => UserCertificate::class, 'userpassword' => Password::class, ]; diff --git a/app/Classes/LDAP/Attribute/UserCertificate.php b/app/Classes/LDAP/Attribute/UserCertificate.php new file mode 100644 index 00000000..eba299bb --- /dev/null +++ b/app/Classes/LDAP/Attribute/UserCertificate.php @@ -0,0 +1,52 @@ +<?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 UserCertificate 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 render_item_old(string $dotkey): ?string + { + return join("\n",str_split(base64_encode(parent::render_item_old($dotkey)),80)); + } + + 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(','); + } +} \ No newline at end of file diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index 741ee998..5eddd64f 100644 --- a/app/Ldap/Entry.php +++ b/app/Ldap/Entry.php @@ -389,7 +389,6 @@ class Entry extends Model fn($item)=> (! preg_match(sprintf('/^%s$/',self::TAG_NOTAG),$item)) && (! preg_match(sprintf('/^%s+$/',self::TAG_CHARS_LANG),$item)) - && (! preg_match('/^binary$/',$item)) ) ->count()) ) @@ -428,9 +427,17 @@ class Entry extends Model */ public function getVisibleAttributes(?string $tag=NULL): Collection { - return $this->objects - ->filter(fn($item)=>! $item->is_internal) - ->filter(fn($item)=>is_null($tag) || count($item->tagValues($tag)) > 0); + static $cache = NULL; + + if (is_null($cache)) { + $ot = $this->getOtherTags(); + + $cache = $this->objects + ->filter(fn($item)=>! $item->is_internal) + ->filter(fn($item)=>is_null($tag) || $ot->has($item->name_lc) || count($item->tagValues($tag)) > 0); + } + + return $cache; } public function hasAttribute(int|string $key): bool diff --git a/composer.json b/composer.json index ff2cc10d..eeb735db 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "require": { "ext-fileinfo": "*", "ext-ldap": "*", + "ext-openssl": "*", "php": "^8.4", "directorytree/ldaprecord-laravel": "^3.0", "laravel/framework": "^11.9", diff --git a/resources/views/components/attribute/usercertificate.blade.php b/resources/views/components/attribute/usercertificate.blade.php new file mode 100644 index 00000000..41e37457 --- /dev/null +++ b/resources/views/components/attribute/usercertificate.blade.php @@ -0,0 +1,25 @@ +<!-- $o=UserCertificate::class --> +<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o" langtag="binary"> + @foreach($o->tagValuesOld('binary') as $key => $value) + @if($edit) + <input type="hidden" name="name={{ $o->name_lc }}[binary][]" value="{{ md5($value) }}"> + + <div class="input-group has-validation mb-3"> + <textarea class="form-control mb-1 font-monospace" rows="{{ count(explode("\n",$x=$o->certificate())) }}" style="overflow:hidden" disabled>{{ $x }}</textarea> + + <div class="invalid-feedback pb-2"> + @if($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)) + {{ join('|',$e) }} + @endif + </div> + </div> + <div class="input-helper"> + @lang('Certificate Subject'): <strong>{{ $o->subject($loop->index) }}</strong><br/> + {{ ($expire=$o->expires($loop->index))->isPast() ? __('Expired') : __('Expires') }}: <strong>{{ $expire->format(config('pla.datetime_format','Y-m-d H:i:s')) }}</strong> + </div> + + @else + <span class="form-control mb-1"><pre class="m-0">{{ $o->render_item_old('binary.'.$key) }}</pre></span> + @endif + @endforeach +</x-attribute.layout> \ No newline at end of file diff --git a/resources/views/components/attribute/widget/options.blade.php b/resources/views/components/attribute/widget/options.blade.php index 75065534..f33b42ba 100644 --- a/resources/views/components/attribute/widget/options.blade.php +++ b/resources/views/components/attribute/widget/options.blade.php @@ -1,16 +1,12 @@ @use(App\Classes\LDAP\Attribute\Binary\JpegPhoto) @use(App\Classes\LDAP\Attribute\ObjectClass) +@use(App\Classes\LDAP\Attribute\UserCertificate) @php($clone=FALSE) <span class="p-0 m-0"> @if($o->is_rdn) <button class="btn btn-sm btn-outline-focus mt-3" disabled><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</button> @elseif($edit && $o->can_addvalues) @switch(get_class($o)) - @case(JpegPhoto::class) - <span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}" disabled><i class="fas fa-fw fa-plus"></i> @lang('Upload JpegPhoto')</span> - - @break - @case(ObjectClass::class) <span type="button" @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) data-bs-toggle="modal" data-bs-target="#new_objectclass-modal"><i class="fas fa-fw fa-plus"></i> @lang('Add Objectclass')</span> @@ -216,6 +212,36 @@ @append @break + @case(JpegPhoto::class) + <span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-upload" disabled><i class="fas fa-fw fa-file-arrow-up"></i> @lang('Upload JpegPhoto')</span> + @section('page-scripts') + <script type="text/javascript"> + $(document).ready(function() { + $('#{{ $o->name }}-upload.addable').click(function(e) { + alert('Sorry, not implemented yet'); + e.preventDefault(); + return false; + }); + }); + </script> + @append + @break + + @case(UserCertificate::class) + <span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-replace" disabled><i class="fas fa-fw fa-certificate"></i> @lang('Replace Certificate')</span> + @section('page-scripts') + <script type="text/javascript"> + $(document).ready(function() { + $('#{{ $o->name }}-replace.addable').click(function(e) { + alert('Sorry, not implemented yet'); + e.preventDefault(); + return false; + }); + }); + </script> + @append + @break + <!-- All other attributes --> @default @php($clone=TRUE) diff --git a/resources/views/frames/dn.blade.php b/resources/views/frames/dn.blade.php index 6cd68ba2..02cd7223 100644 --- a/resources/views/frames/dn.blade.php +++ b/resources/views/frames/dn.blade.php @@ -54,7 +54,7 @@ <div class="row"> <div class="col"> - @if(($x=$o->getOtherTags())->count()) + @if(($x=$o->getOtherTags()->filter(fn($item)=>$item->diff(['binary'])->count()))->count()) <div class="ms-4 mt-4 alert alert-danger p-2" style="max-width: 30em; font-size: 0.80em;"> This entry has [<strong>{!! $x->flatten()->join('</strong>, <strong>') !!}</strong>] tags used by [<strong>{!! $x->keys()->join('</strong>, <strong>') !!}</strong>] that cant be managed by PLA. You can though manage those tags with an LDIF import. </div>