support login via LDAP/OAuth #38

Closed
opened 2026-02-04 16:18:32 +03:00 by OVERLORD · 10 comments
Owner

Originally created by @fibis on GitHub (Jan 1, 2016).

Originally assigned to: @ssddanbrown on GitHub.

Originally created by @fibis on GitHub (Jan 1, 2016). Originally assigned to: @ssddanbrown on GitHub.
Author
Owner

@mbugeia commented on GitHub (Jan 2, 2016):

+1 for LDAP
Bonus point, if you add reverse proxy auth compatibility via HTTP basic authentification (for SSO) I will probably make a Yunohost package.

@mbugeia commented on GitHub (Jan 2, 2016): +1 for LDAP Bonus point, if you add reverse proxy auth compatibility via HTTP basic authentification (for SSO) I will probably make a Yunohost package.
Author
Owner

@tjuberg commented on GitHub (Jan 3, 2016):

+1 for LDAP as well.

@tjuberg commented on GitHub (Jan 3, 2016): +1 for LDAP as well.
Author
Owner

@ssddanbrown commented on GitHub (Jan 3, 2016):

Just setting up a local raspberry pi LDAP server now so I can get familiar with LDAP and implement it properly 😄

@ssddanbrown commented on GitHub (Jan 3, 2016): Just setting up a local raspberry pi LDAP server now so I can get familiar with LDAP and implement it properly :smile:
Author
Owner

@ssddanbrown commented on GitHub (Jan 31, 2016):

As of release 0.7 (commit 148e172fe8) LDAP user auth is now part of BookStack. It's only basic at the moment, and experimental since it has only been tested by me. Setup/Usage instructions are in the readme.

Since login is functional in some capacity I will close this issue. For any extra LDAP features or bugs new ,more focused, issues should be created.

@ssddanbrown commented on GitHub (Jan 31, 2016): As of release 0.7 (commit 148e172fe812a3acdd4d8e172c58e283835577ad) LDAP user auth is now part of BookStack. It's only basic at the moment, and experimental since it has only been tested by me. Setup/Usage instructions are in the readme. Since login is functional in some capacity I will close this issue. For any extra LDAP features or bugs new ,more focused, issues should be created.
Author
Owner

@ssddanbrown commented on GitHub (May 19, 2016):

@MVprobr Thanks for letting me know. In regards to oAuth, What exactly is it that you want implemented in BookStack?

  • Some kind of generic oAuth implementation?
  • A specific oAuth provider to login with?
  • Do you want BookStack to act as a oAuth provider?

Sorry for the questions, I'm not too familiar with the actual use cases when it comes to oAuth.

@ssddanbrown commented on GitHub (May 19, 2016): @MVprobr Thanks for letting me know. In regards to oAuth, What exactly is it that you want implemented in BookStack? - Some kind of generic oAuth implementation? - A specific oAuth provider to login with? - Do you want BookStack to act as a oAuth provider? Sorry for the questions, I'm not too familiar with the actual use cases when it comes to oAuth.
Author
Owner

@younes0 commented on GitHub (May 30, 2016):

I need to setup a specific oauth2 provider in my case. This should be easy to implement.
I will also try to make the admin options for it.

@younes0 commented on GitHub (May 30, 2016): I need to setup a specific oauth2 provider in my case. This should be easy to implement. I will also try to make the admin options for it.
Author
Owner

@fibis commented on GitHub (Jul 29, 2016):

To login into BookStack with a oauth2 provider is also one of my needs. +1

@fibis commented on GitHub (Jul 29, 2016): To login into BookStack with a oauth2 provider is also one of my needs. +1
Author
Owner

@dealproc commented on GitHub (Nov 30, 2018):

@younes0 did you ever get sorted with this? I'd like to experiment with it against identityserver4 and possibly use this system.

@dealproc commented on GitHub (Nov 30, 2018): @younes0 did you ever get sorted with this? I'd like to experiment with it against identityserver4 and possibly use this system.
Author
Owner

@younes0 commented on GitHub (Nov 30, 2018):

@dealproc I've setup Socialite on my bookstack instance, and I handle user creation from my main app.

Socialite:

namespace BookStack\Providers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use BookStack\User;
use BookStack\Icra\IcramProvider;


class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        // Custom validation methods
        \Validator::extend('is_image', function($attribute, $value, $parameters, $validator) {
            $imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp'];
            return in_array($value->getMimeType(), $imageMimes);
        });

        $socialite = $this->app->make('Laravel\Socialite\Contracts\Factory');

        $socialite->extend('icram', function ($app) use ($socialite) {
            $config = $app['config']['services.icram'];
            return $socialite->buildProvider(IcramProvider::class, $config);
        });

    }
}

<?php

namespace BookStack\Icra;

use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;
use Laravel\Socialite\Two\User;

class IcramProvider extends AbstractProvider implements ProviderInterface
{
    /**
     * {@inheritdoc}
     */
    protected function getAuthUrl($state)
    {
        return $this->buildAuthUrlFromBase(env('ICRAM_URL').'/oauth/authorize', $state);
    }

    /**
     * {@inheritdoc}
     */
    protected function getTokenUrl()
    {
        return env('ICRAM_URL').'/oauth/token';
    }

    /**
     * {@inheritdoc}
     */
    public function getAccessToken($code)
    {
        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
            'headers' => ['Authorization' => 'Basic ' . base64_encode($this->clientId . ':' . $this->clientSecret)],
            'body'    => $this->getTokenFields($code),
        ]);

        return $this->parseAccessToken($response->getBody());
    }

    /**
     * {@inheritdoc}
     */
    protected function getTokenFields($code)
    {
        return array_add(
            parent::getTokenFields($code), 'grant_type', 'authorization_code'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function getUserByToken($token)
    {
        $response = $this->getHttpClient()->get(env('ICRAM_URL').'/api/me', [
            'headers' => [
                'Authorization' => 'Bearer ' . $token,
            ],
        ]);

        return json_decode($response->getBody(), true);
    }

    /**
     * {@inheritdoc}
     */
    protected function mapUserToObject(array $user)
    {
        return (new User)->setRaw($user)->map([
            'id'       => $user['id'],
            'nickname' => $user['username'],
            'name'     => $user['firstname'].' '.$user['lastname'],
            'avatar'   => null,
        ]);
    }

}

User Creation:

<?php

namespace Icram\Models;

use Yeb\Laravel\ExtendedModel;
use Carbon\Carbon;

class WikiUser extends ExtendedModel
{
    protected $connection = 'wiki';

    public static $unguarded = false;

    public $table = 'users';
    
    protected $guarded = [''];

    /**
     * Define the relationship with the user's roles.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function roles()
    {
        return $this->belongsToMany(WikiRole::class, 'role_user', 'user_id', 'role_id');
    }

    public function getUser()
    {
        return User::where('email', $this->email)->first();
    }

    public static function firstOrCreateFromUser(User $user, $delete = true)
    {
        if ($model = $user->getWikiUser()) {
            if ($delete) {
                $model->delete();
            
            } else {
                return $model;
            }
        } 

        $model = static::create(static::getDefaults($user));

        // group
        $roleName = $user->is_admin ? 'admin' : 'viewer';
        $role = WikiRole::where('name', $roleName)->first();
        
        $model->roles()->attach($role);
        $model->save();
        
        $con = \DB::connection('wiki');

        // social account
        $con->table('social_accounts')->insert([
            'user_id'   => $model->id,
            'driver'    => 'icram',
            'driver_id' => $user->id,
            'avatar'    => '',
        ]);

        // image
        $imageId = $con->table('images')->insertGetId([
            'name'       => $user->id.'.jpg',
            'url'        => $user->photoUrl,
            'path'       => '/photos/'.$user->id.'.jpg',
            'type'       => 'jpeg',
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now(),
            'created_by' => $model->id,
            'updated_by' => $model->id,
        ]);

        $model->update(['image_id' => $imageId]);
        
        return $model;
    }

    public function syncInfos()
    {
        $this->update(static::getDefaults($this->getUser()));

        return $this;
    }

    static protected function getDefaults($user)
    {
        return [
            'name'             => $user->fullname, 
            'email'            => $user->email,
            'email_confirmed'  => true,
            'password'         => $user->password ?: str_random(),
            'external_auth_id' => '',
            'image_url'        => $user->photoUrl, 
        ];
    }
}

<?php

namespace Icram\Models;

use Yeb\Laravel\ExtendedModel;

class WikiRole extends ExtendedModel
{
    protected $connection = 'wiki';

    public static $unguarded = false;

    public $table = 'roles';
    
    protected $guarded = [''];

    /**
     * Define the relationship with the group's users.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function users()
    {
        return $this->belongsToMany(WikiUser::class, 'role_user');
    }
}

// usage from main app
/*
 * Create/Update related when User is created/updated
 *
 * @return void
 */
public static function boot()
{
    parent::boot();

    // created
    static::created(function($model) {
        $model->profile()->create([]);
    });

    // updated
    static::updated(function($model) {
        // wiki
        $wikiUser = $model->getWikiUser();

        if ($wikiUser) {
            $wikiUser->syncInfos();
        
        } else if ($model->isComplete()) {
            WikiUser::firstOrCreateFromUser($model);
        }
    });

    // deleted
    static::deleting(function($model) {
           if ($wikiUser = WikiUser::where('email', $model->email)->first()) {
            $wikiUser->delete();
        }
    });
}
@younes0 commented on GitHub (Nov 30, 2018): @dealproc I've setup Socialite on my bookstack instance, and I handle user creation from my main app. Socialite: ```php namespace BookStack\Providers; use Illuminate\Support\Facades\Auth; use Illuminate\Support\ServiceProvider; use BookStack\User; use BookStack\Icra\IcramProvider; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { // Custom validation methods \Validator::extend('is_image', function($attribute, $value, $parameters, $validator) { $imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp']; return in_array($value->getMimeType(), $imageMimes); }); $socialite = $this->app->make('Laravel\Socialite\Contracts\Factory'); $socialite->extend('icram', function ($app) use ($socialite) { $config = $app['config']['services.icram']; return $socialite->buildProvider(IcramProvider::class, $config); }); } } <?php namespace BookStack\Icra; use Laravel\Socialite\Two\AbstractProvider; use Laravel\Socialite\Two\ProviderInterface; use Laravel\Socialite\Two\User; class IcramProvider extends AbstractProvider implements ProviderInterface { /** * {@inheritdoc} */ protected function getAuthUrl($state) { return $this->buildAuthUrlFromBase(env('ICRAM_URL').'/oauth/authorize', $state); } /** * {@inheritdoc} */ protected function getTokenUrl() { return env('ICRAM_URL').'/oauth/token'; } /** * {@inheritdoc} */ public function getAccessToken($code) { $response = $this->getHttpClient()->post($this->getTokenUrl(), [ 'headers' => ['Authorization' => 'Basic ' . base64_encode($this->clientId . ':' . $this->clientSecret)], 'body' => $this->getTokenFields($code), ]); return $this->parseAccessToken($response->getBody()); } /** * {@inheritdoc} */ protected function getTokenFields($code) { return array_add( parent::getTokenFields($code), 'grant_type', 'authorization_code' ); } /** * {@inheritdoc} */ protected function getUserByToken($token) { $response = $this->getHttpClient()->get(env('ICRAM_URL').'/api/me', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, ], ]); return json_decode($response->getBody(), true); } /** * {@inheritdoc} */ protected function mapUserToObject(array $user) { return (new User)->setRaw($user)->map([ 'id' => $user['id'], 'nickname' => $user['username'], 'name' => $user['firstname'].' '.$user['lastname'], 'avatar' => null, ]); } } ``` User Creation: ```php <?php namespace Icram\Models; use Yeb\Laravel\ExtendedModel; use Carbon\Carbon; class WikiUser extends ExtendedModel { protected $connection = 'wiki'; public static $unguarded = false; public $table = 'users'; protected $guarded = ['']; /** * Define the relationship with the user's roles. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function roles() { return $this->belongsToMany(WikiRole::class, 'role_user', 'user_id', 'role_id'); } public function getUser() { return User::where('email', $this->email)->first(); } public static function firstOrCreateFromUser(User $user, $delete = true) { if ($model = $user->getWikiUser()) { if ($delete) { $model->delete(); } else { return $model; } } $model = static::create(static::getDefaults($user)); // group $roleName = $user->is_admin ? 'admin' : 'viewer'; $role = WikiRole::where('name', $roleName)->first(); $model->roles()->attach($role); $model->save(); $con = \DB::connection('wiki'); // social account $con->table('social_accounts')->insert([ 'user_id' => $model->id, 'driver' => 'icram', 'driver_id' => $user->id, 'avatar' => '', ]); // image $imageId = $con->table('images')->insertGetId([ 'name' => $user->id.'.jpg', 'url' => $user->photoUrl, 'path' => '/photos/'.$user->id.'.jpg', 'type' => 'jpeg', 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), 'created_by' => $model->id, 'updated_by' => $model->id, ]); $model->update(['image_id' => $imageId]); return $model; } public function syncInfos() { $this->update(static::getDefaults($this->getUser())); return $this; } static protected function getDefaults($user) { return [ 'name' => $user->fullname, 'email' => $user->email, 'email_confirmed' => true, 'password' => $user->password ?: str_random(), 'external_auth_id' => '', 'image_url' => $user->photoUrl, ]; } } <?php namespace Icram\Models; use Yeb\Laravel\ExtendedModel; class WikiRole extends ExtendedModel { protected $connection = 'wiki'; public static $unguarded = false; public $table = 'roles'; protected $guarded = ['']; /** * Define the relationship with the group's users. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function users() { return $this->belongsToMany(WikiUser::class, 'role_user'); } } // usage from main app /* * Create/Update related when User is created/updated * * @return void */ public static function boot() { parent::boot(); // created static::created(function($model) { $model->profile()->create([]); }); // updated static::updated(function($model) { // wiki $wikiUser = $model->getWikiUser(); if ($wikiUser) { $wikiUser->syncInfos(); } else if ($model->isComplete()) { WikiUser::firstOrCreateFromUser($model); } }); // deleted static::deleting(function($model) { if ($wikiUser = WikiUser::where('email', $model->email)->first()) { $wikiUser->delete(); } }); }
Author
Owner

@dealproc commented on GitHub (Nov 30, 2018):

I have no idea of how to make that dance. Will have to bookmark this and come back to it once i get a little read through and see if its possible. my hope was to have a user directed to this, and let them create their user within the system as a "user", and then assign them into what categories they'd need thereafter. I like the idea of this system as you're publishing eBooks per topic/product/whatever, which is what intrigued me about it.

@dealproc commented on GitHub (Nov 30, 2018): I have no idea of how to make that dance. Will have to bookmark this and come back to it once i get a little read through and see if its possible. my hope was to have a user directed to this, and let them create their user within the system as a "user", and then assign them into what categories they'd need thereafter. I like the idea of this system as you're publishing eBooks per topic/product/whatever, which is what intrigued me about it.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/BookStack#38