Compare commits

..

1 Commits

Author SHA1 Message Date
1e19213566 Start of work to enable creation of new entries
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 1m33s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m36s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-02-23 18:14:41 +11:00
14 changed files with 50 additions and 278 deletions

View File

@ -105,7 +105,6 @@ jobs:
run: | run: |
registry=${{ github.server_url }} registry=${{ github.server_url }}
echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT" echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT"
echo "version=$(cat public/VERISON)"
- name: Container Registry Login - name: Container Registry Login
uses: docker/login-action@v2 uses: docker/login-action@v2
@ -145,7 +144,6 @@ jobs:
- name: Record version and Delete Unnecessary files - name: Record version and Delete Unnecessary files
run: | run: |
echo Building [${{ steps.registry.outputs.version }}]
echo ${GITHUB_SHA::8} > VERSION echo ${GITHUB_SHA::8} > VERSION
rm -rf .git* tests/ storage/app/test/ rm -rf .git* tests/ storage/app/test/
ls -al public/css/ ls -al public/css/
@ -158,7 +156,6 @@ jobs:
file: docker/Dockerfile file: docker/Dockerfile
push: true push: true
tags: "${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSIONARCH }}" tags: "${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSIONARCH }}"
build-args: BUILD_REVISION=${GITHUB_SHA},BUILD_VERSION=${{ steps.registry.outputs.version }}
manifest: manifest:
name: Final Docker Image Manifest name: Final Docker Image Manifest

View File

@ -145,9 +145,9 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
// Attribute values // Attribute values
'values' => $this->values, 'values' => $this->values,
// Required by Object Classes // Required by Object Classes
'required_by' => $this->schema?->required_by_object_classes ?: collect(), 'required_by' => $this->schema->required_by_object_classes,
// Used in Object Classes // Used in Object Classes
'used_in' => $this->schema?->used_in_object_classes ?: collect(), 'used_in' => $this->schema->used_in_object_classes,
default => throw new \Exception('Unknown key:' . $key), default => throw new \Exception('Unknown key:' . $key),
}; };

View File

@ -1,51 +0,0 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
/**
* Represents the RDN for an Entry
*/
final class RDN extends Attribute
{
private string $base;
private Collection $objectclasses;
public function __get(string $key): mixed
{
return match ($key) {
'base' => $this->base,
default => parent::__get($key),
};
}
public function hints(): array
{
return [
'required' => __('RDN is required')
];
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.rdn')
->with('o',$this);
}
public function setBase(string $base): void
{
$this->base = $base;
}
public function setObjectClasses(array $classes): void
{
$this->objectclasses = collect();
foreach ($classes as $class)
$this->objectclasses->push(cofig('server')->schema('objectclasses',$class));
}
}

View File

@ -91,27 +91,20 @@ class HomeController extends Controller
* *
* @param EntryAdd $request * @param EntryAdd $request
* @return Factory|View|Application|object * @return Factory|View|Application|object
* @throws InvalidUsage
*/ */
public function entry_add(EntryAdd $request) public function entry_add(EntryAdd $request)
{ {
switch ($request->step) { switch ($request->step) {
case 1: case 1:
$container = Crypt::decryptString($request->dn);
$o = new Entry;
$o->objectclass = $request->objectclass;
$o->setRDNBase($container);
return view('frame') return view('frame')
->with('subframe',$request->frame) ->with('subframe',$request->frame)
->with('bases',$this->bases()) ->with('bases',$this->bases())
->with('o',$o) ->with('o',config('server')->fetch($x=Crypt::decryptString($request->dn)))
->with('step',$request->step) ->with('step',$request->step)
->with('dn',$container); ->with('dn',$x);
default: default:
throw new InvalidUsage('Invalid entry step'); dd($request);
} }
} }

View File

@ -12,14 +12,11 @@ use App\Classes\LDAP\Attribute;
use App\Classes\LDAP\Attribute\Factory; use App\Classes\LDAP\Attribute\Factory;
use App\Classes\LDAP\Export\LDIF; use App\Classes\LDAP\Export\LDIF;
use App\Exceptions\Import\AttributeException; use App\Exceptions\Import\AttributeException;
use App\Exceptions\InvalidUsage;
class Entry extends Model class Entry extends Model
{ {
private Collection $objects; private Collection $objects;
private bool $noObjectAttributes = FALSE; private bool $noObjectAttributes = FALSE;
// For new entries, this is the container that this entry will be stored in
private string $rdnbase;
/* OVERRIDES */ /* OVERRIDES */
@ -49,7 +46,7 @@ class Entry extends Model
public function getAttributes(): array public function getAttributes(): array
{ {
return $this->objects return $this->objects
->map(fn($item)=>$item->values) ->map(fn($item)=>$item->values->toArray())
->toArray(); ->toArray();
} }
@ -95,7 +92,10 @@ class Entry extends Model
$key = $this->normalizeAttributeKey($key); $key = $this->normalizeAttributeKey($key);
if ((! $this->objects->get($key)) && $value) { if ((! $this->objects->get($key)) && $value) {
$this->objects->put($key,Factory::create($key,[$value])); $o = new Attribute($key,[]);
$o->value = $value;
$this->objects->put($key,$o);
} elseif ($this->objects->get($key)) { } elseif ($this->objects->get($key)) {
$this->objects->get($key)->value = $this->attributes[$key]; $this->objects->get($key)->value = $this->attributes[$key];
@ -265,12 +265,8 @@ class Entry extends Model
*/ */
public function getObject(string $key): Attribute|null public function getObject(string $key): Attribute|null
{ {
return match ($key) { return $this->objects
'rdn' => $this->getRDNObject(), ->get($this->normalizeAttributeKey($key));
default => $this->objects
->get($this->normalizeAttributeKey($key))
};
} }
public function getObjects(): Collection public function getObjects(): Collection
@ -293,14 +289,6 @@ class Entry extends Model
->filter(fn($a)=>(! $this->getVisibleAttributes()->contains(fn($b)=>($a->name === $b->name)))); ->filter(fn($a)=>(! $this->getVisibleAttributes()->contains(fn($b)=>($a->name === $b->name))));
} }
private function getRDNObject(): Attribute\RDN
{
$o = new Attribute\RDN('dn',[' ']);
$o->setBase($this->rdnbase); // @todo for an existing object, return the base.
return $o;
}
/** /**
* Return this list of user attributes * Return this list of user attributes
* *
@ -425,12 +413,4 @@ class Entry extends Model
return $this; return $this;
} }
public function setRDNBase(string $bdn): void
{
if ($this->exists)
throw new InvalidUsage('Cannot set RDN base on existing entries');
$this->rdnbase = $bdn;
}
} }

View File

@ -48,20 +48,8 @@ return [
'base_dn' => env('LDAP_BASE_DN', 'dc=local,dc=com'), 'base_dn' => env('LDAP_BASE_DN', 'dc=local,dc=com'),
'timeout' => env('LDAP_TIMEOUT', 5), 'timeout' => env('LDAP_TIMEOUT', 5),
'use_ssl' => env('LDAP_SSL', true), 'use_ssl' => env('LDAP_SSL', true),
'use_tls' => env('LDAP_TLS', false),
'name' => env('LDAP_NAME','LDAPS Server'),
],
'openldaptls' => [
'hosts' => [env('LDAP_HOST', '127.0.0.1')],
'username' => env('LDAP_USERNAME', 'cn=user,dc=local,dc=com'),
'password' => env('LDAP_PASSWORD', 'secret'),
'port' => env('LDAP_PORT', 389),
'base_dn' => env('LDAP_BASE_DN', 'dc=local,dc=com'),
'timeout' => env('LDAP_TIMEOUT', 5),
'use_ssl' => env('LDAP_SSL', false),
'use_tls' => env('LDAP_TLS', true), 'use_tls' => env('LDAP_TLS', true),
'name' => env('LDAP_NAME','LDAP-TLS Server'), 'name' => env('LDAP_NAME','LDAPS Server'),
], ],
'opendj' => [ 'opendj' => [

View File

@ -1,17 +1,5 @@
FROM dunglas/frankenphp:php8.4-alpine FROM dunglas/frankenphp:php8.4-alpine
ARG BUILD_VERSION=2.0.0-dev
ARG BUILD_REVISION=00000000
LABEL org.opencontainers.image.vendor="Deon George"
LABEL org.opencontainers.image.licenses=GPLv2
LABEL org.opencontainers.image.source=https://github.com/leenooks/phpldapadmin
LABEL org.opencontainers.image.title=phpLDAPadmin
LABEL org.opencontainers.image.description="An LDAP Administration Tool"
LABEL org.opencontainers.image.url=https://phpldapadmin.org
LABEL org.opencontainers.image.version=${BUILD_VERSION}
LABEL org.opencontainers.image.revision=${BUILD_REVISION}
# Base # Base
RUN apk add --no-cache bash RUN apk add --no-cache bash

View File

@ -304,7 +304,3 @@ div#objectClass .input-group-delete {
right: 10px; right: 10px;
height: 5px; height: 5px;
} }
.input-group-text {
background-color: #fafafa;
}

View File

@ -4,7 +4,7 @@
<div class="col-12 col-sm-10 col-md-8"> <div class="col-12 col-sm-10 col-md-8">
<div class="row"> <div class="row">
<div class="col-12 bg-light text-dark p-2"> <div class="col-12 bg-light text-dark p-2">
<strong><abbr title="{{ $o->description }}">{{ $o->name }}</abbr></strong> <strong><abbr title="{{ $o->description }}" data-attr-name="{{ $o->name_lc }}" data-attr-required="{{ $o->required_by->intersect($oc)->join('|') }}" data-oc="{{ $oc->count() ? $o->required_by->keys()->intersect($oc)->join('|') : $o->used_in->keys()->join('|') }}">{{ $o->name }}</abbr></strong>
<!-- Attribute Hints --> <!-- Attribute Hints -->
<span class="float-end small"> <span class="float-end small">
@foreach($o->hints as $name => $description) @foreach($o->hints as $name => $description)

View File

@ -1,25 +0,0 @@
<!-- $o=RDN::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach($o->values as $value)
@if($edit)
<div class="input-group has-validation mb-3">
<x-form.select
name="rdn"
:options="[]"
/>
<span class="input-group-text">=</span>
<input type="text" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'border-focus'=>$o->values->contains($value)]) name="rdn" placeholder="rdn">
<label class="input-group-text" for="inputGroupSelect02">,{{ $o->base }}</label>
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
</div>
</div>
@else
{{ $value }}
@endif
@endforeach
</x-attribute.layout>

View File

@ -75,6 +75,10 @@
if (added_oc.sort().join('|') == newadded.sort().join('|')) if (added_oc.sort().join('|') == newadded.sort().join('|'))
return; return;
var attrs = $('[data-attr-name]').map(function() {
return $(this).data('attrName');
});
// Find out what was selected, and add them // Find out what was selected, and add them
newadded.forEach(function (item) { newadded.forEach(function (item) {
if (added_oc.indexOf(item) !== -1) if (added_oc.indexOf(item) !== -1)
@ -147,6 +151,7 @@
url: '{{ url('api/schema/objectclass/attrs') }}/'+item, url: '{{ url('api/schema/objectclass/attrs') }}/'+item,
cache: false cache: false
}); });
}); });
// Loop through added_oc, and remove anything not in newadded // Loop through added_oc, and remove anything not in newadded

View File

@ -52,7 +52,7 @@
theme: 'bootstrap-5', theme: 'bootstrap-5',
dropdownAutoWidth: true, dropdownAutoWidth: true,
width: 'style', width: 'style',
allowClear: {{ $allowclear ?? 'false' }}, allowClear: {{ $allowclear ?? 'true' }},
placeholder: '{{ $placeholder ?? '' }}', placeholder: '{{ $placeholder ?? '' }}',
multiple: {{ $multiple ?? 'false' }}, multiple: {{ $multiple ?? 'false' }},
@isset($addvalues) @isset($addvalues)

View File

@ -5,15 +5,10 @@
@endsection @endsection
@section('main-content') @section('main-content')
<div class="row">
<p class="alert alert-danger text-center">
This is a tech preview of what is to come, and it is by no means complete.
</p>
</div>
<div class="row"> <div class="row">
<div class="offset-1 col-10"> <div class="offset-1 col-10">
<div class="main-card mb-3 card"> <div class="main-card mb-3 card">
<form id="create-form" action="{{ url('entry/add') }}" method="POST" enctype="multipart/form-data"> <form id="import-form" action="{{ url('entry/add') }}" method="POST" enctype="multipart/form-data">
@csrf @csrf
<input type="hidden" name="frame" value="create"> <input type="hidden" name="frame" value="create">
<input type="hidden" name="dn" value="{{ Crypt::encryptString($dn) }}"> <input type="hidden" name="dn" value="{{ Crypt::encryptString($dn) }}">
@ -23,67 +18,38 @@
@lang('Create New Entry') @lang('Create New Entry')
</div> </div>
@switch($step) @switch($step)
@case(1) @case(1)
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-12 col-sm-6"> <div class="col-12 col-sm-6">
<x-form.select <x-form.select
name="objectclass" name="objectclass"
:label="__('Select a Structural ObjectClass...')" :label="__('Select a Structural Object Class...')"
:options="($oc=config('server')->schema('objectclasses')) :options="($oc=config('server')->schema('objectclasses'))
->filter(fn($item)=>$item->isStructural()) ->filter(fn($item)=>$item->isStructural())
->sortBy(fn($item)=>$item->name_lc) ->sortBy(fn($item)=>$item->name_lc)
->map(fn($item,$key)=>['id'=>$key,'value'=>$item->name])" ->map(fn($item,$key)=>['id'=>$key,'value'=>$item->name])"
multiple="false" allowclear="false"
/> multiple="false"
</div> />
</div> </div>
</div> </div>
@break </div>
@break
@case(2) @case(2)
<div class="card-body"> <div class="card-body">
<x-attribute-type :edit="true" :o="$o->getObject('rdn')"/> <div class="row">
<div class="col-12 col-sm-6">
@foreach ($o->getVisibleAttributes() as $ao) <p class="alert alert-danger">
<x-attribute-type :edit="true" :o="$ao"/> Not ready yet :)
@endforeach </p>
<div id="newattrs"></div>
<!-- Add new attributes -->
<div class="row">
<div class="col-12 col-sm-1 col-md-2"></div>
<div class="col-12 col-sm-10 col-md-8">
<div class="d-none" id="newattr-select">
@if($o->getMissingAttributes()->count())
<div class="row">
<div class="col-12 bg-dark text-light p-2">
<i class="fas fa-plus-circle"></i> Add New Attribute
</div>
</div>
<div class="row">
<div class="col-12 pt-2">
<x-form.select id="newattr" label="Select from..." :options="$o->getMissingAttributes()->sortBy('name')->map(fn($item)=>['id'=>$item->name,'value'=>$item->name_lc])"/>
</div>
</div>
@endif
</div>
</div>
<div class="col-2"></div>
</div>
<div class="row pt-3">
<div class="col-12 offset-sm-2 col-sm-4 col-lg-2">
<x-form.reset form="dn-add"/>
<x-form.submit action="Create" form="dn-add"/>
</div>
</div> </div>
</div> </div>
@break; </div>
@break;
@endswitch @endswitch
<div class="card-footer"> <div class="card-footer">
@ -96,68 +62,3 @@
</div> </div>
</div> </div>
@endsection @endsection
@section('page-scripts')
<script type="text/javascript">
var dn = '{{ $o->getDNSecure() }}';
var oc = {!! $o->getObject('objectclass')->values !!};
function editmode() {
$('#dn-edit input[name="dn"]').val(dn);
$('button[id=entry-edit]').addClass('active').removeClass('btn-outline-dark').addClass('btn-outline-light');
// Find all input items and turn off readonly
$('input.form-control').each(function() {
// Except for objectClass - @todo show an "X" instead
if ($(this)[0].name.match(/^objectclass/))
return;
$(this).attr('readonly',false);
});
// Our password type
$('div#userPassword .form-select').each(function() {
$(this).prop('disabled',false);
})
$('.row.d-none').removeClass('d-none');
$('.addable.d-none').removeClass('d-none');
$('.deletable.d-none').removeClass('d-none');
@if($o->getMissingAttributes()->count())
$('#newattr-select.d-none').removeClass('d-none');
@endif
}
$(document).ready(function() {
$('#newattr').on('change',function(item) {
$.ajax({
type: 'POST',
beforeSend: function() {},
success: function(data) {
$('#newattrs').append(data);
},
error: function(e) {
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/attr/add') }}/'+item.target.value,
data: {
objectclasses: oc,
},
cache: false
});
// Remove the option from the list
$(this).find('[value="'+item.target.value+'"]').remove()
// If there are no more options
if ($(this).find("option").length === 1)
$('#newattr-select').remove();
});
editmode();
});
</script>
@append

View File

@ -31,7 +31,7 @@
<input type="hidden" name="dn" value=""> <input type="hidden" name="dn" value="">
@foreach ($o->getVisibleAttributes() as $ao) @foreach ($o->getVisibleAttributes() as $ao)
<x-attribute-type :edit="true" :o="$ao"/> <x-attribute-type :edit="true" :o="$ao" :oc="collect($o->objectclass)"/>
@endforeach @endforeach
<div id="newattrs"></div> <div id="newattrs"></div>