DirectoryTree / LdapRecord-Laravel

Multi-domain LDAP Authentication & Management for Laravel.
https://ldaprecord.com/docs/laravel/v3
MIT License
504 stars 54 forks source link

[Feature] Make Route Binding with LDAP objects possible #611

Closed lukas-staab closed 8 months ago

lukas-staab commented 10 months ago

Short explanation of my use case: I would like some users allow to modify (open)LDAP Entries, like adding Users to groups, creating new OUs, creating Groups in OUs and so on. For a nice User interface I need to authorize the ACLs (who is allowed to edit what) inside Laravel to disable buttons, guard routes and so on. My (open)Ldap knowlede is pretty fresh, and I am not aware if i can simulate if an action (like edit) is allowed without doing it. If there is a more LDAP way to do it with the ldaprecord already i would love to know. Maybe its also possible with the db sync, but i did not get it running with Entries besides users (unsure if possible)

Here my suggestion how to implement it:

Adding the Methods resolveRouteBinding and resolveRouteBindingQuery to the LdapRecordModel like in Eloquent https://github.com/laravel/framework/blob/38b897f5d3c97cca88078b6a37878d812aeb79de/src/Illuminate/Database/Eloquent/Model.php#L2017 would enable referencing and binding route params to the models. The following code would be possible then:

// web.php 
Route::get('/{community}/members/', 'CommunityController@edit')
  ->can('edit', 'community')

// Controller.php
public function edit(Request $r, Community $community){
  // now we have direct the Model Object, not only the dn or ou name as a string
  $community->setAttribute(...)
}

// Community.php Model
public class Community extends OrganizationalUnit {

   public function getRouteKeyName() : string {
        return 'ou';
    }

}

//RouteServiceProvider.php 
public function boot(){
  $this->model('community', Community::class);
  // it would be nice if we would not need to manually register the Policies
}

This alone is nice, but for me the feature above, which i wanted to use, were Policies which (seems to me) need this binding. Like

class CommunityPolicy {
    public function edit(User $user, Community $community){
          $ldapUser = $user->ldap(); // See below
          // returns true if user is in the moderators group for this community - so editing would be allowed 
          return $community->moderatorGroup()->members()->exists($ldapUser) 
    }
}

Note here: i have setup'ed LdapRecord with DB Login, so the User given in the Policy function is the eloquent one, sadly, so i had to transform it in it's Ldap Equivalent.

There is for sure a more elegant way to do that, i did it like:

public function ldap() : \App\Ldap\User
    {
        return \App\Ldap\User::findByOrFail('uid', $this->username);
    }

With the Policy, we can also secure the route if wanted:

// web.php
Route::get('/{community}/members/', 'CommunityController@edit')
  ->can('edit', 'community')

There is one major assumtion within this feature: the param the route gets ({community} here) has to be somewhat unique, at least in some subspaces of the ldap Tree (DNs are a bit discouraged because there are not very good looking inside URLs). This feature would probably fit best, and be most usefull, with usernames.

I would start a PR, but wanted a second opinion on it first :) Maybe I am missing something super obvious.

Edit: ah damn, probably would have been better in the laravel repo ... (feel free to move)

stevebauman commented 10 months ago

Hi @lukas-staab!

I'm not sure if this is something we can place in the core LdapRecord repository, as the UrlRoutable interface that is used for route model binding is not inside the illuminate/support repository, but the core illuminate/framework repository:

https://github.com/laravel/framework/blob/10.x/src/Illuminate/Contracts/Routing/UrlRoutable.php

If we require this, it will download the whole framework, even for those not using Laravel.

I'd just suggest setting up route binding in your RouteServiceProvider for your models manually:

use App\Ldap\Community;

Route::bind('community', function (string $value) {
    return Community::findByOrFail('ou', $value);
});

This provides full customizability, rather than implementing an opinionated route key name and also inserting more Laravel specific code into the core repository.

Let me know your thoughts! 👍

stevebauman commented 8 months ago

Closing due to inactivity.