Compare commits

...

3 Commits

Author SHA1 Message Date
3cc577136c 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 29s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m24s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
2025-03-12 22:52:14 +11:00
b3307835f4 Fix password hash select list, was not being editable when choosing edit mode 2025-03-12 22:52:14 +11:00
babe1f6fc5 Improved handling for Kerberous attributes - closes #154 2025-03-12 22:52:14 +11:00
20 changed files with 293 additions and 12 deletions

View File

@ -3,7 +3,7 @@ run-name: ${{ gitea.actor }} Building Docker Image 🐳
on: [push] on: [push]
env: env:
DOCKER_HOST: tcp://127.0.0.1:2375 DOCKER_HOST: tcp://127.0.0.1:2375
ASSETS: 509b1a1 ASSETS: c2780a3
jobs: jobs:
test: test:

View File

@ -279,7 +279,11 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
*/ */
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{ {
return view('components.attribute') $view = view()->exists($x='components.attribute.'.$this->name_lc)
? view($x)
: view('components.attribute');
return $view
->with('o',$this) ->with('o',$this)
->with('edit',$edit) ->with('edit',$edit)
->with('old',$old) ->with('old',$old)

View File

@ -27,6 +27,8 @@ class Factory
'entrydn' => Internal\DN::class, 'entrydn' => Internal\DN::class,
'entryuuid' => Internal\UUID::class, 'entryuuid' => Internal\UUID::class,
'etag' => Internal\Etag::class, 'etag' => Internal\Etag::class,
'krbprincipalkey' => KrbPrincipalKey::class,
'krbticketflags' => KrbTicketFlags::class,
'gidnumber' => GidNumber::class, 'gidnumber' => GidNumber::class,
'hassubordinates' => Internal\HasSubordinates::class, 'hassubordinates' => Internal\HasSubordinates::class,
'jpegphoto' => Binary\JpegPhoto::class, 'jpegphoto' => Binary\JpegPhoto::class,

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;
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(int $key): ?string
{
$pw = Arr::get($this->oldValues,$key);
return $pw
? str_repeat('*',16)
: NULL;
}
public function render_item_new(int $key): ?string
{
$pw = Arr::get($this->values,$key);
return $pw
? str_repeat('*',16)
: NULL;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use App\Classes\LDAP\Attribute;
use Illuminate\Support\Collection;
/**
* Represents an attribute whose value is a Kerberos Ticket Flag
* See RFC4120
*/
class KrbTicketFlags extends Attribute
{
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());
}
}

17
package-lock.json generated
View File

@ -10,6 +10,7 @@
"animate-sass": "^0.8.2", "animate-sass": "^0.8.2",
"axios": "^1.3.4", "axios": "^1.3.4",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"bootstrap-icons": "^1.11.3",
"jquery": "^3.6.3", "jquery": "^3.6.3",
"jquery-ui": "^1.13.2", "jquery-ui": "^1.13.2",
"jquery.fancytree": "^2.38.3", "jquery.fancytree": "^2.38.3",
@ -3053,6 +3054,22 @@
"@popperjs/core": "^2.11.8" "@popperjs/core": "^2.11.8"
} }
}, },
"node_modules/bootstrap-icons": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT"
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",

View File

@ -15,6 +15,7 @@
"animate-sass": "^0.8.2", "animate-sass": "^0.8.2",
"axios": "^1.3.4", "axios": "^1.3.4",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"bootstrap-icons": "^1.11.3",
"jquery": "^3.6.3", "jquery": "^3.6.3",
"jquery-ui": "^1.13.2", "jquery-ui": "^1.13.2",
"jquery.fancytree": "^2.38.3", "jquery.fancytree": "^2.38.3",

View File

@ -7,3 +7,6 @@
// Select2 // Select2
@import "select2/dist/css/select2"; @import "select2/dist/css/select2";
@import "select2-bootstrap-5-theme/dist/select2-bootstrap-5-theme"; @import "select2-bootstrap-5-theme/dist/select2-bootstrap-5-theme";
// Bootstrap icons
@import "bootstrap-icons"

View File

@ -17,4 +17,6 @@
<x-attribute :o="$o" :edit="true" :new="$new ?? FALSE"/> <x-attribute :o="$o" :edit="true" :new="$new ?? FALSE"/>
</div> </div>
</div> </div>
@yield($o->name_lc.'-scripts')

View File

@ -0,0 +1,8 @@
<!-- $o=Attribute::class -->
<x-attribute.layout :edit="false" :new="false" :detail="true" :o="$o">
@foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->values) as $value)
<div class="input-group">
<input type="text" @class(['form-control','mb-1']) name="{{ $o->name_lc }}[]" value="{{ \Carbon\Carbon::createFromTimestamp(strtotime($value))->format(config('pla.datetime_format','Y-m-d H:i:s')) }}" @disabled(true)>
</div>
@endforeach
</x-attribute.layout>

View File

@ -0,0 +1 @@
@include('components.attribute.krblastfailedauth')

View File

@ -0,0 +1 @@
@include('components.attribute.krblastfailedauth')

View File

@ -0,0 +1,8 @@
<!-- $o=Attribute::class -->
<x-attribute.layout :edit="false" :new="false" :detail="true" :o="$o">
@foreach(old($o->name_lc,($new ?? FALSE) ? [NULL] : $o->values) as $value)
<div class="input-group">
<input type="text" @class(['form-control','mb-1']) name="{{ $o->name_lc }}[]" value="{{ $value }}" @disabled(true)>
</div>
@endforeach
</x-attribute.layout>

View File

@ -0,0 +1 @@
@include('components.attribute.krblastfailedauth')

View File

@ -0,0 +1,18 @@
<!-- $o=Password::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">
<input type="password" @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="{{ md5($value) }}" @readonly(true)>
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
</div>
</div>
@else
{{ str_repeat('*',16) }}
@endif
@endforeach
</x-attribute.layout>

View File

@ -0,0 +1,109 @@
<!-- $o=KrbTicketFlags::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach(($o->values->count() ? $o->values : ($new ? [0] : NULL)) as $value)
@if($edit)
<div id="32"></div>
<div id="16"></div>
<div class="input-group has-validation mb-3">
<input type="hidden" @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 }}" @readonly(true)>
<div class="invalid-feedback pb-2">
@if($e)
{{ join('|',$e) }}
@endif
</div>
</div>
@else
{{ $value }}
@endif
@endforeach
</x-attribute.layout>
@section($o->name_lc.'-scripts')
<script type="text/javascript">
var value = {{ $value ?? 0 }};
var label = {!! $helper !!};
function tooltip(bit) {
if (bit === undefined)
return;
return label[bit] ? label[bit] : 'Bit '+bit;
}
function binary(s=31,e=0) {
var result = '';
for (let x=s;x>=e;x--) {
var bit = (value&Math.pow(2,x));
result += '<i id="b'+x+'" style="margin-left:-1px;" class="fs-4 bi bi-'+(bit ? '1' : '0')+'-square'+(bit ? '-fill' : '')+'" data-bs-toggle="tooltip" data-bs-placement="bottom" title="'+tooltip(x)+'"></i>';
}
return result;
}
function krbticketflags() {
$('div#32').append(binary(31,16));
$('div#16').append(binary(15,0));
$('attribute#krbTicketFlags').find('i')
.on('click',function() {
var item = $(this);
if ($('form#dn-edit').attr('readonly'))
return;
var key = Number(item.attr('id').substring(1));
if (item.data('old') === undefined)
item.data('old',null);
item.toggleClass('text-success');
// has the item changed?
if (item.data('old') === null) {
// It was set to 1
if (item.hasClass('bi-1-square-fill')) {
item.data('old',1);
item.removeClass('bi-1-square-fill').addClass('bi-0-square-fill');
value -= Math.pow(2,key);
// It was set to 0
} else if (item.hasClass('bi-0-square')) {
item.data('old',0);
item.removeClass('bi-0-square').addClass('bi-1-square-fill');
value += Math.pow(2,key);
}
} else {
if (item.data('old') === 0) {
item.removeClass('bi-1-square-fill').addClass('bi-0-square');
value -= Math.pow(2,key);
} else {
item.removeClass('bi-0-square-fill').addClass('bi-1-square-fill');
value += Math.pow(2,key);
}
item.data('old',null);
}
$('attribute#krbTicketFlags').find('input').val(value);
});
}
// When returning to a Entry after an update, jquery hasnt loaded yet, so make sure we defer this to after the page has run
if (window.$ === undefined) {
document.addEventListener('DOMContentLoaded',() => krbticketflags());
} else {
krbticketflags();
$('attribute#krbTicketFlags').find('i')
.tooltip();
}
</script>
@endsection

View File

@ -1,5 +1,5 @@
<div class="row pt-2"> <div class="row pt-2">
<div @class(['col-1','d-none'=>(! $edit)])></div> <div @class(['col-1','d-none'=>(! $edit) && (! ($detail ?? true))])></div>
<div class="col-10 p-2"> <div class="col-10 p-2">
<attribute id="{{ $o->name }}"> <attribute id="{{ $o->name }}">
{{ $slot }} {{ $slot }}
@ -7,4 +7,6 @@
<x-attribute.widget.options :o="$o" :edit="$edit" :new="$new"/> <x-attribute.widget.options :o="$o" :edit="$edit" :new="$new"/>
</div> </div>
</div> </div>
@yield($o->name_lc.'-scripts')

View File

@ -220,7 +220,7 @@
<!-- All other attributes --> <!-- All other attributes -->
@default @default
@php($clone=TRUE) @php($clone=TRUE)
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-addnew"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</span> <button @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name }}-addnew"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</button>
@section('page-scripts') @section('page-scripts')
@if($clone && $edit && $o->can_addvalues) @if($clone && $edit && $o->can_addvalues)

View File

@ -85,7 +85,7 @@
}); });
// Our password type // Our password type
$('div#userPassword .form-select').each(function() { $('attribute#userPassword .form-select').each(function() {
$(this).prop('disabled',false); $(this).prop('disabled',false);
}) })

View File

@ -23,7 +23,7 @@
<div class="tab-content"> <div class="tab-content">
<!-- All Attributes --> <!-- All Attributes -->
<div class="tab-pane active" id="attributes" role="tabpanel"> <div class="tab-pane active" id="attributes" role="tabpanel">
<form id="dn-edit" method="POST" class="needs-validation" action="{{ url('entry/update/pending') }}" novalidate> <form id="dn-edit" method="POST" class="needs-validation" action="{{ url('entry/update/pending') }}" novalidate readonly>
@csrf @csrf
<input type="hidden" name="dn" value=""> <input type="hidden" name="dn" value="">
@ -156,9 +156,12 @@
function editmode() { function editmode() {
$('#dn-edit input[name="dn"]').val(dn); $('#dn-edit input[name="dn"]').val(dn);
$('form#dn-edit').attr('readonly',false);
$('button[id=entry-edit]') $('button[id=entry-edit]')
.removeClass('btn-outline-dark') .removeClass('btn-outline-dark')
.addClass('btn-dark'); .addClass('btn-dark')
.addClass('opacity-100')
.attr('disabled',true);
// Find all input items and turn off readonly // Find all input items and turn off readonly
$('input.form-control').each(function() { $('input.form-control').each(function() {
@ -170,13 +173,13 @@
}); });
// Our password type // Our password type
$('div#userPassword .form-select').each(function() { $('attribute#userPassword .form-select').each(function() {
$(this).prop('disabled',false); $(this).prop('disabled',false);
}) })
$('.row.d-none').removeClass('d-none'); $('.row.d-none').removeClass('d-none');
$('.addable.d-none').removeClass('d-none'); $('button.addable.d-none').removeClass('d-none');
$('.deletable.d-none').removeClass('d-none'); $('button.deletable.d-none').removeClass('d-none');
@if($o->getMissingAttributes()->count()) @if($o->getMissingAttributes()->count())
$('#newattr-select.d-none').removeClass('d-none'); $('#newattr-select.d-none').removeClass('d-none');