Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support login via LDAP/OAuth #39

Closed
fibis opened this issue Jan 1, 2016 · 10 comments
Closed

support login via LDAP/OAuth #39

fibis opened this issue Jan 1, 2016 · 10 comments
Assignees

Comments

@fibis
Copy link

fibis commented Jan 1, 2016

No description provided.

@mbugeia
Copy link

mbugeia commented 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.

@TJuberg
Copy link

TJuberg commented Jan 3, 2016

+1 for LDAP as well.

@ssddanbrown
Copy link
Member

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

@ssddanbrown ssddanbrown self-assigned this Jan 13, 2016
@ssddanbrown
Copy link
Member

As of release 0.7 (commit 148e172) 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
Copy link
Member

@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.

@younes0
Copy link

younes0 commented 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.

@fibis
Copy link
Author

fibis commented Jul 29, 2016

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

@dealproc
Copy link

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

@younes0
Copy link

younes0 commented 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();
        }
    });
}

@dealproc
Copy link

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 up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

7 participants
@TJuberg @younes0 @dealproc @mbugeia @fibis @ssddanbrown and others