Compare commits
7 Commits
9ef5d5ea63
...
aab7f01f52
Author | SHA1 | Date | |
---|---|---|---|
aab7f01f52 | |||
061513dafe | |||
13e645dde0 | |||
1f1db14ae9 | |||
b2335e26f2 | |||
d61685a5b2 | |||
3a4b0bfe05 |
@ -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.
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -442,6 +442,16 @@ final class ObjectClass extends Base
|
|||||||
return $this->type;
|
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
|
* Determine if an array is listed in the may_force attrs
|
||||||
*/
|
*/
|
||||||
|
@ -59,14 +59,14 @@ final class Server
|
|||||||
* Gets the root DN of the specified LDAPServer, or throws an exception if it
|
* Gets the root DN of the specified LDAPServer, or throws an exception if it
|
||||||
* can't find it.
|
* can't find it.
|
||||||
*
|
*
|
||||||
* @param null $connection Return a collection of baseDNs
|
* @param string|null $connection Return a collection of baseDNs
|
||||||
* @param bool $objects Return a collection of Entry Models
|
* @param bool $objects Return a collection of Entry Models
|
||||||
* @return Collection
|
* @return Collection
|
||||||
* @throws ObjectNotFoundException
|
* @throws ObjectNotFoundException
|
||||||
* @testedin GetBaseDNTest::testBaseDNExists();
|
* @testedin GetBaseDNTest::testBaseDNExists();
|
||||||
* @todo Need to allow for the scenario if the baseDN is not readable by ACLs
|
* @todo Need to allow for the scenario if the baseDN is not readable by ACLs
|
||||||
*/
|
*/
|
||||||
public static function baseDNs(string $connection='default',bool $objects=TRUE): Collection
|
public static function baseDNs(string $connection=NULL,bool $objects=TRUE): Collection
|
||||||
{
|
{
|
||||||
$cachetime = Carbon::now()
|
$cachetime = Carbon::now()
|
||||||
->addSeconds(Config::get('ldap.cache.time'));
|
->addSeconds(Config::get('ldap.cache.time'));
|
||||||
@ -360,9 +360,13 @@ final class Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to get the schema DN from the specified entry.
|
// Try to get the schema DN from the specified entry.
|
||||||
$schema_dn = $this->schemaDN('default');
|
$schema_dn = $this->schemaDN($this->connection);
|
||||||
$schema = $this->fetch($schema_dn);
|
$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) {
|
switch ($item) {
|
||||||
case 'attributetypes':
|
case 'attributetypes':
|
||||||
Log::debug('Attribute Types');
|
Log::debug('Attribute Types');
|
||||||
|
@ -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'),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
@ -68,6 +68,28 @@ class HomeController extends Controller
|
|||||||
->with('page_actions',$page_actions);
|
->with('page_actions',$page_actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a new attribute view
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param string $id
|
||||||
|
* @return \Closure|\Illuminate\Contracts\View\View|string
|
||||||
|
*/
|
||||||
|
public function entry_attr_add(Request $request,string $id)
|
||||||
|
{
|
||||||
|
$xx = new \stdClass();
|
||||||
|
$xx->index = 0;
|
||||||
|
|
||||||
|
$x = $request->noheader
|
||||||
|
? (string)view(sprintf('components.attribute.widget.%s',$id))
|
||||||
|
->with('o',new Attribute($id,[]))
|
||||||
|
->with('value',$request->value)
|
||||||
|
->with('loop',$xx)
|
||||||
|
: (new AttributeType(new Attribute($id,[]),TRUE,collect($request->oc ?: [])))->render();
|
||||||
|
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
public function entry_export(Request $request,string $id)
|
public function entry_export(Request $request,string $id)
|
||||||
{
|
{
|
||||||
$dn = Crypt::decryptString($id);
|
$dn = Crypt::decryptString($id);
|
||||||
@ -84,10 +106,33 @@ class HomeController extends Controller
|
|||||||
->with('result',new LDIFExport($result));
|
->with('result',new LDIFExport($result));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entry_newattr(string $id)
|
/**
|
||||||
|
* Render an available list of objectclasses for an Entry
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function entry_objectclass_add(string $id)
|
||||||
{
|
{
|
||||||
$x = new AttributeType(new Attribute($id,[]),TRUE);
|
$dn = Crypt::decryptString($id);
|
||||||
return $x->render();
|
$o = config('server')->fetch($dn);
|
||||||
|
|
||||||
|
$ocs = $o->getObject('objectclass')
|
||||||
|
->structural
|
||||||
|
->map(fn($item)=>$item->getParents())
|
||||||
|
->flatten()
|
||||||
|
->merge(
|
||||||
|
config('server')->schema('objectclasses')
|
||||||
|
->filter(fn($item)=>$item->isAuxiliary())
|
||||||
|
)
|
||||||
|
->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)
|
public function entry_password_check(Request $request)
|
||||||
@ -126,6 +171,7 @@ class HomeController extends Controller
|
|||||||
$o->{$key} = array_filter($value,fn($item)=>! is_null($item));
|
$o->{$key} = array_filter($value,fn($item)=>! is_null($item));
|
||||||
|
|
||||||
// We need to process and encrypt the password
|
// We need to process and encrypt the password
|
||||||
|
if ($request->userpassword) {
|
||||||
$passwords = [];
|
$passwords = [];
|
||||||
foreach ($request->userpassword as $key => $value) {
|
foreach ($request->userpassword as $key => $value) {
|
||||||
// If the password is still the MD5 of the old password, then it hasnt changed
|
// If the password is still the MD5 of the old password, then it hasnt changed
|
||||||
@ -140,6 +186,7 @@ class HomeController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$o->userpassword = $passwords;
|
$o->userpassword = $passwords;
|
||||||
|
}
|
||||||
|
|
||||||
if (! $o->getDirty())
|
if (! $o->getDirty())
|
||||||
return back()
|
return back()
|
||||||
@ -294,10 +341,8 @@ class HomeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function schema_frame(Request $request)
|
public function schema_frame(Request $request)
|
||||||
{
|
{
|
||||||
$s = config('server');
|
|
||||||
|
|
||||||
// If an invalid key, we'll 404
|
// If an invalid key, we'll 404
|
||||||
if ($request->type && $request->key && ($s->schema($request->type)->has($request->key) === FALSE))
|
if ($request->type && $request->key && (! config('server')->schema($request->type)->has($request->key)))
|
||||||
abort(404);
|
abort(404);
|
||||||
|
|
||||||
return view('frames.schema')
|
return view('frames.schema')
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
@ -290,7 +286,7 @@ class Entry extends Model
|
|||||||
public function getMissingAttributes(): Collection
|
public function getMissingAttributes(): Collection
|
||||||
{
|
{
|
||||||
return $this->getAvailableAttributes()
|
return $this->getAvailableAttributes()
|
||||||
->diff($this->getVisibleAttributes());
|
->filter(fn($a)=>(! $this->getVisibleAttributes()->contains(fn($b)=>($a->name === $b->name))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'default' => env('LDAP_CONNECTION', 'default'),
|
'default' => env('LDAP_CONNECTION', 'openldap'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -28,7 +28,7 @@ return [
|
|||||||
|
|
||||||
'connections' => [
|
'connections' => [
|
||||||
|
|
||||||
'default' => [
|
'openldap' => [
|
||||||
'hosts' => [env('LDAP_HOST', '127.0.0.1')],
|
'hosts' => [env('LDAP_HOST', '127.0.0.1')],
|
||||||
'username' => env('LDAP_USERNAME', 'cn=user,dc=local,dc=com'),
|
'username' => env('LDAP_USERNAME', 'cn=user,dc=local,dc=com'),
|
||||||
'password' => env('LDAP_PASSWORD', 'secret'),
|
'password' => env('LDAP_PASSWORD', 'secret'),
|
||||||
@ -40,6 +40,18 @@ return [
|
|||||||
'name' => env('LDAP_NAME','LDAP Server'),
|
'name' => env('LDAP_NAME','LDAP Server'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'opendj' => [
|
||||||
|
'hosts' => ['opendj'],
|
||||||
|
'username' => 'cn=Directory Manager',
|
||||||
|
'password' => 'password',
|
||||||
|
'port' => 1389,
|
||||||
|
'base_dn' => 'dc=example,dc=com',
|
||||||
|
'timeout' => env('LDAP_TIMEOUT', 5),
|
||||||
|
'use_ssl' => env('LDAP_SSL', false),
|
||||||
|
'use_tls' => env('LDAP_TLS', false),
|
||||||
|
'name' => 'OpenDJ Server',
|
||||||
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
13
public/css/fixes.css
vendored
13
public/css/fixes.css
vendored
@ -291,3 +291,16 @@ select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__
|
|||||||
line-height: 1.0;
|
line-height: 1.0;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select2-container--bootstrap-5 .select2-dropdown .select2-results__options .select2-results__option[role=group] .select2-results__group {
|
||||||
|
color: #212529;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#objectclass .input-group-delete {
|
||||||
|
position: relative;
|
||||||
|
float: inline-end;
|
||||||
|
bottom: 30px;
|
||||||
|
right: 10px;
|
||||||
|
height: 5px;
|
||||||
|
}
|
@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="float-end">
|
<div class="float-end">
|
||||||
<button class="btn btn-primary btn-lg">Login</button>
|
<button class="btn btn-lg btn-primary">Login</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
@if ((isset($page_actions) && $page_actions->contains('edit')) || old())
|
@if ((isset($page_actions) && $page_actions->contains('edit')) || old())
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<span class="nav-link pt-0 pb-1">
|
<span class="nav-link pt-0 pb-1">
|
||||||
<button id="entry-edit" class="p-2 m-0 border-0 btn-transition btn btn-outline-dark w-100 text-start">
|
<button id="entry-edit" class="p-2 m-0 border-0 btn btn-transition btn-outline-dark w-100 text-start">
|
||||||
<i class="fas fa-fw fa-edit me-2"></i> @lang('Edit')
|
<i class="fas fa-fw fa-edit me-2"></i> @lang('Edit')
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
@if (isset($page_actions) && $page_actions->contains('export'))
|
@if (isset($page_actions) && $page_actions->contains('export'))
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link pt-0 pb-1">
|
<a class="nav-link pt-0 pb-1">
|
||||||
<button type="button" class="p-2 m-0 border-0 btn-transition btn btn-outline-dark w-100 text-start" data-bs-toggle="modal" data-bs-target="#entry_export-modal">
|
<button type="button" class="p-2 m-0 border-0 btn btn-transition btn-outline-dark w-100 text-start" data-bs-toggle="modal" data-bs-target="#entry_export-modal">
|
||||||
<i class="fas fa-fw fa-file-export me-2"></i> @lang('Export')
|
<i class="fas fa-fw fa-file-export me-2"></i> @lang('Export')
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
@ -50,7 +50,7 @@
|
|||||||
@if (isset($page_actions) && $page_actions->contains('copy'))
|
@if (isset($page_actions) && $page_actions->contains('copy'))
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link pt-0 pb-1">
|
<a class="nav-link pt-0 pb-1">
|
||||||
<button class="p-2 m-0 border-0 btn-transition btn btn-outline-dark w-100 text-start">
|
<button class="p-2 m-0 border-0 btn btn-transition btn-outline-dark w-100 text-start">
|
||||||
<i class="fas fa-fw fa-truck-moving me-2"></i> @lang('Copy or Move')
|
<i class="fas fa-fw fa-truck-moving me-2"></i> @lang('Copy or Move')
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="app-header__menu">
|
<div class="app-header__menu">
|
||||||
<span>
|
<span>
|
||||||
<button type="button" class="btn-icon btn-icon-only btn btn-primary btn-sm mobile-toggle-header-nav">
|
<button type="button" class="btn-icon btn-icon-only btn btn-sm btn-primary mobile-toggle-header-nav">
|
||||||
<span class="btn-icon-wrapper">
|
<span class="btn-icon-wrapper">
|
||||||
<i class="fas fa-ellipsis-v fa-w-6"></i>
|
<i class="fas fa-ellipsis-v fa-w-6"></i>
|
||||||
</span>
|
</span>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="app-header__menu">
|
<div class="app-header__menu">
|
||||||
<span>
|
<span>
|
||||||
<button type="button" class="btn-icon btn-icon-only btn btn-primary btn-sm mobile-toggle-header-nav">
|
<button type="button" class="btn-icon btn-icon-only btn btn-sm btn-primary mobile-toggle-header-nav">
|
||||||
<span class="btn-icon-wrapper">
|
<span class="btn-icon-wrapper">
|
||||||
<i class="fas fa-ellipsis-v fa-w-6"></i>
|
<i class="fas fa-ellipsis-v fa-w-6"></i>
|
||||||
</span>
|
</span>
|
||||||
|
@ -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)
|
||||||
|
@ -2,14 +2,7 @@
|
|||||||
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||||
@foreach (old($o->name_lc,$o->values) as $value)
|
@foreach (old($o->name_lc,$o->values) as $value)
|
||||||
@if ($edit && ($value === NULL || (! $o->isStructural($value))))
|
@if ($edit && ($value === NULL || (! $o->isStructural($value))))
|
||||||
<div class="input-group has-validation">
|
<x-attribute.widget.objectclass :o="$o" :edit="$edit" :new="$new" :loop="$loop" :value="$value"/>
|
||||||
<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="{{ ! is_null($x=Arr::get($o->values,$loop->index)) ? $x : '['.__('NEW').']' }}" @readonly(true)>
|
|
||||||
<div class="invalid-feedback pb-2">
|
|
||||||
@if($e)
|
|
||||||
{{ join('|',$e) }}
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@else
|
@else
|
||||||
{{ $value }}
|
{{ $value }}
|
||||||
@if ($o->isStructural($value))
|
@if ($o->isStructural($value))
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
<span id="objectclass_{{$value}}">
|
||||||
|
<div class="input-group has-validation">
|
||||||
|
<!-- @todo Have an "x" to remove the entry, we need an event to process the removal, removing any attribute values along the way -->
|
||||||
|
<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)>
|
||||||
|
<div class="invalid-feedback pb-2">
|
||||||
|
@if($e)
|
||||||
|
{{ join('|',$e) }}
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="input-group-delete"><i class="fas fa-fw fa-xmark"></i></span>
|
||||||
|
</span>
|
@ -1,13 +1,181 @@
|
|||||||
|
@php($clone=FALSE)
|
||||||
@if($o->is_rdn)
|
@if($o->is_rdn)
|
||||||
<span class="btn btn-sm btn-outline-focus mt-3"><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</span>
|
<span class="btn btn-sm btn-outline-focus mt-3"><i class="fas fa-fw fa-exchange"></i> @lang('Rename')</span>
|
||||||
@elseif($edit && $o->can_addvalues)
|
@elseif($edit && $o->can_addvalues)
|
||||||
<span class="p-0 m-0">
|
<span class="p-0 m-0">
|
||||||
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</span>
|
@switch(get_class($o))
|
||||||
</span>
|
@case('App\Classes\LDAP\Attribute\Binary\JpegPhoto')
|
||||||
@endif
|
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}"><i class="fas fa-fw fa-plus"></i> @lang('Upload JpegPhoto')</span>
|
||||||
|
|
||||||
|
@break
|
||||||
|
|
||||||
|
@case('App\Classes\LDAP\Attribute\ObjectClass')
|
||||||
|
<button 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')</button>
|
||||||
|
|
||||||
|
<!-- NEW OBJECT CLASS -->
|
||||||
|
<div class="modal fade" id="new_objectclass-modal" tabindex="-1" aria-labelledby="new_objectclass-label" aria-hidden="true" data-bs-backdrop="static">
|
||||||
|
<div class="modal-dialog modal-lg modal-fullscreen-lg-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="new_objectclass-label">New Object Class</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<x-form.select id="newoc" label="Select from..."/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-sm btn-primary" data-bs-dismiss="modal">Next</button>
|
||||||
|
{{--
|
||||||
|
<button type="button" class="btn btn-sm btn-primary" data-bs-dismiss="modal"><i class="fas fa-fw fa-spinner fa-spin d-none"></i> Next</button>
|
||||||
|
--}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@section('page-scripts')
|
@section('page-scripts')
|
||||||
@if($edit && $o->can_addvalues)
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
var added_oc = []; // Object classes being added to this entry
|
||||||
|
|
||||||
|
// Show our ObjectClass modal so that we can add more objectclasses
|
||||||
|
$('#new_objectclass-modal').on('shown.bs.modal',function() {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
success: function(data) {
|
||||||
|
$('select#newoc').select2({
|
||||||
|
dropdownParent: $('#new_objectclass-modal'),
|
||||||
|
theme: 'bootstrap-5',
|
||||||
|
allowClear: true,
|
||||||
|
multiple: true,
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(e) {
|
||||||
|
if (e.status != 412)
|
||||||
|
alert('That didnt work? Please try again....');
|
||||||
|
},
|
||||||
|
url: '{{ url('entry/objectclass/add') }}/'+dn,
|
||||||
|
cache: false
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
// When the ObjectClass modal is closed, process what was selected
|
||||||
|
$('#new_objectclass-modal').on('hide.bs.modal',function() {
|
||||||
|
var c = {{ $o->values->count() }}; // @todo do we need this?
|
||||||
|
var newadded = $('select#newoc').val();
|
||||||
|
|
||||||
|
// If nothing selected, we dont have anything to do
|
||||||
|
if (added_oc.sort().join('|') == newadded.sort().join('|'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var attrs = $('[data-attr-name]').map(function() {
|
||||||
|
return $(this).data('attrName');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find out what was selected, and add them
|
||||||
|
newadded.forEach(function (item) {
|
||||||
|
if (added_oc.indexOf(item) !== -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add attribute to the page
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
beforeSend: function() {},
|
||||||
|
success: function(data) {
|
||||||
|
$('#{{ $o->name_lc }}').append(data);
|
||||||
|
},
|
||||||
|
error: function(e) {
|
||||||
|
if (e.status != 412)
|
||||||
|
alert('That didnt work? Please try again....');
|
||||||
|
},
|
||||||
|
url: '{{ url('entry/attr/add',[$o->name_lc]) }}',
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Loop through added_oc, and remove anything not in newadded
|
||||||
|
added_oc.forEach(function(item) {
|
||||||
|
if (newadded.indexOf(item) === -1) {
|
||||||
|
$('span#objectclass_'+item).empty();
|
||||||
|
|
||||||
|
// @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
|
||||||
|
console.log('Remove additional attributes of:'+item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
added_oc = newadded;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@append
|
||||||
|
@break
|
||||||
|
|
||||||
|
@case('App\Classes\LDAP\Attribute')
|
||||||
|
@default
|
||||||
|
@php($clone=TRUE)
|
||||||
|
<span @class(['btn','btn-sm','btn-outline-primary','mt-3','addable','d-none'=>(! $new)]) id="{{ $o->name_lc }}"><i class="fas fa-fw fa-plus"></i> @lang('Add Value')</span>
|
||||||
|
|
||||||
|
@section('page-scripts')
|
||||||
|
@if($clone && $edit && $o->can_addvalues)
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// Create a new entry when Add Value clicked
|
// Create a new entry when Add Value clicked
|
||||||
@ -20,3 +188,6 @@
|
|||||||
</script>
|
</script>
|
||||||
@endif
|
@endif
|
||||||
@append
|
@append
|
||||||
|
@endswitch
|
||||||
|
</span>
|
||||||
|
@endif
|
@ -3,7 +3,7 @@
|
|||||||
<input type="hidden" id="{{ $id ?? $name }}_disabled" name="{{ $name }}" value="" disabled>
|
<input type="hidden" id="{{ $id ?? $name }}_disabled" name="{{ $name }}" value="" disabled>
|
||||||
@endisset
|
@endisset
|
||||||
<select style="width: 80%" class="form-select @isset($name)@error((! empty($old)) ? $old : $name) is-invalid @enderror @endisset" id="{{ $id ?? $name }}" @isset($name)name="{{ $name }}"@endisset @required(isset($required) && $required) @disabled(isset($disabled) && $disabled)>
|
<select style="width: 80%" class="form-select @isset($name)@error((! empty($old)) ? $old : $name) is-invalid @enderror @endisset" id="{{ $id ?? $name }}" @isset($name)name="{{ $name }}"@endisset @required(isset($required) && $required) @disabled(isset($disabled) && $disabled)>
|
||||||
@if(empty($value) || isset($addnew) || isset($choose))
|
@if((empty($value) && ! empty($options)) || isset($addnew) || isset($choose))
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
@isset($addnew)
|
@isset($addnew)
|
||||||
<option value="new">{{ $addnew ?: 'Add New' }}</option>
|
<option value="new">{{ $addnew ?: 'Add New' }}</option>
|
||||||
|
@ -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>
|
||||||
@ -126,8 +126,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
<button type="button" class="btn btn-primary btn-sm" id="entry_export-download">Download</button>
|
<button type="button" class="btn btn-sm btn-primary" id="entry_export-download">Download</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -161,8 +161,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
<button type="button" class="btn btn-primary btn-sm" id="userpassword_check-submit"><i class="fas fa-fw fa-spinner fa-spin d-none"></i> Check</button>
|
<button type="button" class="btn btn-sm btn-primary" id="userpassword_check-submit"><i class="fas fa-fw fa-spinner fa-spin d-none"></i> Check</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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');
|
||||||
@ -193,6 +194,10 @@
|
|||||||
|
|
||||||
// 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() {
|
||||||
|
// Except for objectClass - @todo show an "X" instead
|
||||||
|
if ($(this)[0].name.match(/^objectclass/))
|
||||||
|
return;
|
||||||
|
|
||||||
$(this).attr('readonly',false);
|
$(this).attr('readonly',false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -213,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);
|
||||||
@ -222,7 +227,10 @@
|
|||||||
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/newattr') }}/'+item.target.value,
|
url: '{{ url('entry/attr/add') }}/'+item.target.value,
|
||||||
|
data: {
|
||||||
|
objectclasses: oc,
|
||||||
|
},
|
||||||
cache: false
|
cache: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
@ -38,10 +38,11 @@ Route::group(['prefix'=>'user'],function() {
|
|||||||
Route::get('image',[HomeController::class,'user_image']);
|
Route::get('image',[HomeController::class,'user_image']);
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::post('entry/update/commit',[HomeController::class,'entry_update']);
|
|
||||||
Route::post('entry/update/pending',[HomeController::class,'entry_pending_update']);
|
|
||||||
Route::get('entry/newattr/{id}',[HomeController::class,'entry_newattr']);
|
|
||||||
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::post('entry/attr/add/{id}',[HomeController::class,'entry_attr_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/pending',[HomeController::class,'entry_pending_update']);
|
||||||
|
|
||||||
Route::post('import/process/{type}',[HomeController::class,'import']);
|
Route::post('import/process/{type}',[HomeController::class,'import']);
|
@ -1,9 +0,0 @@
|
|||||||
# At the moment we want to override osixia/ldap to enable anonymous reads
|
|
||||||
dn: olcDatabase={1}{{ LDAP_BACKEND }},cn=config
|
|
||||||
changetype: modify
|
|
||||||
delete: olcAccess
|
|
||||||
-
|
|
||||||
add: olcAccess
|
|
||||||
olcAccess: to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break
|
|
||||||
olcAccess: to attrs=userPassword,shadowLastChange by self read by dn="cn=admin,{{ LDAP_BASE_DN }}" write by anonymous read by * read
|
|
||||||
olcAccess: to * by self read by dn="cn=admin,{{ LDAP_BASE_DN }}" write by * read
|
|
Loading…
x
Reference in New Issue
Block a user