Compare commits

..

7 Commits

Author SHA1 Message Date
aab7f01f52 More work on work on adding/removing objectclasses to an entry, still need to automatically remove attrs from removed objectclasses
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 1m26s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 3m28s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s
2025-02-04 08:56:12 +11:00
061513dafe Fix for 'Couldnt figure out a password hash for {SSHA}' fixes #216 2025-02-04 08:56:12 +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
16 changed files with 133 additions and 68 deletions

View File

@ -35,8 +35,8 @@ The update to v2 is progressing well - here is a list of work to do and done:
- [X] Validate password is correct - [X] Validate password is correct
- [ ] JpegPhoto Create/Delete - [ ] JpegPhoto Create/Delete
- [X] JpegPhoto Display - [X] JpegPhoto Display
- [ ] ObjectClass Add/Remove - [X] ObjectClass Add/Remove
- [ ] Add additional required attributes (for ObjectClass Addition) - [X] Add additional required attributes (for ObjectClass Addition)
- [ ] Remove existing required attributes (for ObjectClass Removal) - [ ] Remove existing required attributes (for ObjectClass Removal)
- [X] Add additional values to Attributes that support multiple values - [X] Add additional values to Attributes that support multiple values
- [X] Delete extra values for Attributes that support multiple values - [X] Delete extra values for Attributes that support multiple values
@ -52,6 +52,7 @@ The update to v2 is progressing well - here is a list of work to do and done:
Support is known for these LDAP servers: Support is known for these LDAP servers:
- [X] OpenLDAP - [X] OpenLDAP
- [X] OpenDJ
- [ ] Microsoft Active Directory - [ ] Microsoft Active Directory
If there is an LDAP server that you have that you would like to have supported, please open an issue to request it. If there is an LDAP server that you have that you would like to have supported, please open an issue to request it.

View File

@ -33,9 +33,6 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
// Is this attribute the RDN? // Is this attribute the RDN?
protected bool $is_rdn = FALSE; protected bool $is_rdn = FALSE;
// Objectclasses that require this attribute
protected Collection $required_by;
// MIN/MAX number of values // MIN/MAX number of values
protected int $min_values_count = 0; protected int $min_values_count = 0;
protected int $max_values_count = 0; protected int $max_values_count = 0;
@ -102,7 +99,6 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
$this->name = $name; $this->name = $name;
$this->values = collect($values); $this->values = collect($values);
$this->lang_tags = collect(); $this->lang_tags = collect();
$this->required_by = collect();
$this->oldValues = collect($values); $this->oldValues = collect($values);
// No need to load our schema for internal attributes // No need to load our schema for internal attributes
@ -149,6 +145,10 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
'old_values' => $this->oldValues, 'old_values' => $this->oldValues,
// Attribute values // Attribute values
'values' => $this->values, 'values' => $this->values,
// Required by Object Classes
'required_by' => $this->schema->required_by_object_classes,
// Used in Object Classes
'used_in' => $this->schema->used_in_object_classes,
default => throw new \Exception('Unknown key:' . $key), default => throw new \Exception('Unknown key:' . $key),
}; };
@ -296,19 +296,6 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
return Arr::get($this->values,$key); return Arr::get($this->values,$key);
} }
/**
* Set the objectclasses that require this attribute
*
* @param Collection $oc
* @return Collection
*/
public function required_by(Collection $oc): Collection
{
return $this->required_by = ($this->schema
? $oc->intersect($this->schema->required_by_object_classes)
: collect());
}
/** /**
* If this attribute has RFC3866 Language Tags, this will enable those values to be captured * If this attribute has RFC3866 Language Tags, this will enable those values to be captured
* *

View File

@ -10,4 +10,9 @@ final class SHA extends Base
{ {
return sprintf('{%s}%s',self::key,base64_encode(hash('sha1',$password,true))); 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

@ -16,4 +16,9 @@ final class SSHA extends Base
{ {
return sprintf('{%s}%s',self::key,$this->salted_hash($password,'sha1',self::salt,$salt)); 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

@ -549,17 +549,6 @@ final class AttributeType extends Base {
$this->aliases->forget($x); $this->aliases->forget($x);
} }
/**
* Given a list of object classes, determine if this is a required attribute
*
* @param Collection $oc List of objectclasses to compare.
* @return Collection
*/
public function required_by(Collection $oc): Collection
{
return $oc->diff($this->required_by_object_classes);
}
/** /**
* Sets this attribute's list of aliases. * Sets this attribute's list of aliases.
* *

View File

@ -82,4 +82,21 @@ class APIController extends Controller
abort(404); abort(404);
} }
} }
}
/**
* Return the required and additional attributes for an object class
*
* @param Request $request
* @param string $objectclass
* @return array
*/
public function schema_objectclass_attrs(Request $request,string $objectclass): array
{
$oc = config('server')->schema('objectclasses',$objectclass);
return [
'must' => $oc->getMustAttrs()->pluck('name'),
'may' => $oc->getMayAttrs()->pluck('name'),
];
}
}

View File

@ -85,7 +85,7 @@ class HomeController extends Controller
->with('o',new Attribute($id,[])) ->with('o',new Attribute($id,[]))
->with('value',$request->value) ->with('value',$request->value)
->with('loop',$xx) ->with('loop',$xx)
: (new AttributeType(new Attribute($id,[]),TRUE))->render(); : (new AttributeType(new Attribute($id,[]),TRUE,collect($request->oc ?: [])))->render();
return $x; return $x;
} }

View File

@ -186,12 +186,8 @@ class Entry extends Model
if (preg_match('/^'.$attribute.'=/i',$this->dn)) if (preg_match('/^'.$attribute.'=/i',$this->dn))
$o->setRDN(); $o->setRDN();
// Set required flag
$o->required_by(collect($this->getAttribute('objectclass')));
// Store our original value to know if this attribute has changed // Store our original value to know if this attribute has changed
if ($x=Arr::get($this->original,$attribute)) $o->oldValues(Arr::get($this->original,$attribute,[]));
$o->oldValues($x);
$result->put($attribute,$o); $result->put($attribute,$o);
} }

View File

@ -4,21 +4,24 @@ namespace App\View\Components;
use Closure; use Closure;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use Illuminate\View\Component; use Illuminate\View\Component;
use App\Classes\LDAP\Attribute as LDAPAttribute; use App\Classes\LDAP\Attribute as LDAPAttribute;
class AttributeType extends Component class AttributeType extends Component
{ {
public Collection $oc;
public LDAPAttribute $o; public LDAPAttribute $o;
public bool $new; public bool $new;
/** /**
* Create a new component instance. * Create a new component instance.
*/ */
public function __construct(LDAPAttribute $o,bool $new=FALSE) public function __construct(LDAPAttribute $o,bool $new=FALSE,Collection $oc=NULL)
{ {
$this->o = $o; $this->o = $o;
$this->oc = $oc;
$this->new = $new; $this->new = $new;
} }
@ -29,6 +32,7 @@ class AttributeType extends Component
{ {
return view('components.attribute-type') return view('components.attribute-type')
->with('o',$this->o) ->with('o',$this->o)
->with('oc',$this->oc)
->with('new',$this->new); ->with('new',$this->new);
} }
} }

View File

@ -295,4 +295,12 @@ select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__
.select2-container--bootstrap-5 .select2-dropdown .select2-results__options .select2-results__option[role=group] .select2-results__group { .select2-container--bootstrap-5 .select2-dropdown .select2-results__options .select2-results__option[role=group] .select2-results__group {
color: #212529; color: #212529;
font-weight: 800; font-weight: 800;
}
div#objectclass .input-group-delete {
position: relative;
float: inline-end;
bottom: 30px;
right: 10px;
height: 5px;
} }

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,19 +1,12 @@
<div class="input-group has-validation"> <span id="objectclass_{{$value}}">
<!-- @todo Have an "x" to remove the entry, we need an event to process the removal, removing any attribute values along the way --> <div class="input-group has-validation">
<input type="text" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>$o->values->contains($value)]) name="{{ $o->name_lc }}[]" value="{{ $value }}" placeholder="{{ Arr::get($o->values,$loop->index,'['.__('NEW').']') }}" @readonly(true)> <!-- @todo Have an "x" to remove the entry, we need an event to process the removal, removing any attribute values along the way -->
<div class="invalid-feedback pb-2"> <input type="text" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$loop->index)),'mb-1','border-focus'=>$o->values->contains($value)]) name="{{ $o->name_lc }}[]" value="{{ $value }}" placeholder="{{ Arr::get($o->values,$loop->index,'['.__('NEW').']') }}" @readonly(true)>
@if($e) <div class="invalid-feedback pb-2">
{{ join('|',$e) }} @if($e)
@endif {{ join('|',$e) }}
@endif
</div>
</div> </div>
</div> <span class="input-group-delete"><i class="fas fa-fw fa-xmark"></i></span>
<span class="input-group-delete"><i class="fas fa-fw fa-xmark"></i></span> </span>
<style>
.input-group-delete {
float: right;
position: relative;
top: -32px;
right: 10px;
}
</style>

View File

@ -68,42 +68,97 @@
var newadded = $('select#newoc').val(); var newadded = $('select#newoc').val();
// If nothing selected, we dont have anything to do // If nothing selected, we dont have anything to do
if (! newadded.length) 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)
return; return;
// Add attribute to the page
$.ajax({ $.ajax({
type: 'GET', type: 'POST',
beforeSend: function() {}, beforeSend: function() {},
success: function(data) { success: function(data) {
console.log(data);
$('#{{ $o->name_lc }}').append(data); $('#{{ $o->name_lc }}').append(data);
console.log('set to:'+item);
}, },
error: function(e) { error: function(e) {
if (e.status != 412) if (e.status != 412)
alert('That didnt work? Please try again....'); alert('That didnt work? Please try again....');
}, },
url: '{{ url('entry/attr/add',[$o->name_lc]) }}', url: '{{ url('entry/attr/add',[$o->name_lc]) }}',
data: {noheader: true,value: item,loop: c++}, // @todo can we omit loop and c data: {
noheader: true,
value: item,
objectclasses: oc,
loop: c++, // @todo can we omit loop and c
},
cache: false
});
$.ajax({
type: 'POST',
beforeSend: function() {},
success: function(data) {
// Render any must attributes
if (data.must.length) {
data.must.forEach(function(item) {
// Add attribute to the page
$.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,
data: {
value: item,
objectclasses: oc,
loop: c++, // @todo can we omit loop and c
},
cache: false
});
})
}
// Add attributes to "Add new Attribute" that are now available
if (data.may.length) {
var newattr = $('select#newattr');
// @todo It might be nice to resort this options
data.may.forEach(function(item) {
newattr.append(new Option(item,item,false,false));
});
}
},
error: function(e) {
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('api/schema/objectclass/attrs') }}/'+item,
cache: false cache: false
}); });
// @todo add any required attributes that are defined required as a result of adding this OC
// @todo Add attributes to "Add new Attribute" that are now available
}); });
// Loop through added_oc, and remove anything not in newadded // Loop through added_oc, and remove anything not in newadded
added_oc.forEach(function(item) { added_oc.forEach(function(item) {
if (newadded.indexOf(item) === -1) { if (newadded.indexOf(item) === -1) {
$('input[value="'+item+'"]').parent().empty(); $('span#objectclass_'+item).empty();
// @todo remove any required attributes that are no longer defined as a result of removing this OC // @todo remove any required attributes that are no longer defined as a result of removing this OC
console.log('Remove required attributes of:'+item);
// @todo Remove attributes from "Add new Attribute" that are no longer available // @todo Remove attributes from "Add new Attribute" that are no longer available
console.log('Remove additional attributes of:'+item);
} }
}); });

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>
@ -173,6 +173,7 @@
@section('page-scripts') @section('page-scripts')
<script type="text/javascript"> <script type="text/javascript">
var dn = '{{ $o->getDNSecure() }}'; var dn = '{{ $o->getDNSecure() }}';
var oc = {!! $o->getObject('objectclass')->values !!};
function download(filename,text) { function download(filename,text) {
var element = document.createElement('a'); var element = document.createElement('a');
@ -217,7 +218,7 @@
$(document).ready(function() { $(document).ready(function() {
$('#newattr').on('change',function(item) { $('#newattr').on('change',function(item) {
$.ajax({ $.ajax({
type: 'GET', type: 'POST',
beforeSend: function() {}, beforeSend: function() {},
success: function(data) { success: function(data) {
$('#newattrs').append(data); $('#newattrs').append(data);
@ -227,6 +228,9 @@
alert('That didnt work? Please try again....'); alert('That didnt work? Please try again....');
}, },
url: '{{ url('entry/attr/add') }}/'+item.target.value, url: '{{ url('entry/attr/add') }}/'+item.target.value,
data: {
objectclasses: oc,
},
cache: false cache: false
}); });

View File

@ -19,6 +19,7 @@ Route::group([],function() {
Route::get('bases',[APIController::class,'bases']); Route::get('bases',[APIController::class,'bases']);
Route::get('children',[APIController::class,'children']); Route::get('children',[APIController::class,'children']);
Route::post('schema/view',[APIController::class,'schema_view']); Route::post('schema/view',[APIController::class,'schema_view']);
Route::post('schema/objectclass/attrs/{id}',[APIController::class,'schema_objectclass_attrs']);
}); });
Route::group(['middleware'=>'auth:api','prefix'=>'user'],function() { Route::group(['middleware'=>'auth:api','prefix'=>'user'],function() {

View File

@ -40,7 +40,7 @@ Route::group(['prefix'=>'user'],function() {
Route::get('entry/export/{id}',[HomeController::class,'entry_export']); Route::get('entry/export/{id}',[HomeController::class,'entry_export']);
Route::post('entry/password/check/',[HomeController::class,'entry_password_check']); Route::post('entry/password/check/',[HomeController::class,'entry_password_check']);
Route::get('entry/attr/add/{id}',[HomeController::class,'entry_attr_add']); Route::post('entry/attr/add/{id}',[HomeController::class,'entry_attr_add']);
Route::post('entry/objectclass/add/{id}',[HomeController::class,'entry_objectclass_add']); Route::post('entry/objectclass/add/{id}',[HomeController::class,'entry_objectclass_add']);
Route::post('entry/update/commit',[HomeController::class,'entry_update']); Route::post('entry/update/commit',[HomeController::class,'entry_update']);
Route::post('entry/update/pending',[HomeController::class,'entry_pending_update']); Route::post('entry/update/pending',[HomeController::class,'entry_pending_update']);