Skip to content

Commit

Permalink
Merge pull request #3 from wondeltd/MYL-1109-saml-support
Browse files Browse the repository at this point in the history
MYL-1109 - Initial SAML Support
  • Loading branch information
LarsaSolidor authored May 1, 2024
2 parents b198eb1 + a8dd570 commit d4b9add
Show file tree
Hide file tree
Showing 28 changed files with 802 additions and 390 deletions.
24 changes: 24 additions & 0 deletions app/Enums/ClaimTypes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Enums;

enum ClaimTypes: string
{
case EmailAddress = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress';
case MyLoginID = 'http://schemas.xmlsoap.org/identity/claims/myloginid';
case FirstName = 'http://schemas.xmlsoap.org/identity/claims/firstname';
case LastName = 'http://schemas.xmlsoap.org/identity/claims/lastname';
case DateOfBirth = 'http://schemas.xmlsoap.org/identity/claims/dateofbirth';
case Type = 'http://schemas.xmlsoap.org/identity/claims/type';
case LastOnline = 'http://schemas.xmlsoap.org/identity/claims/lastonline';
case ServiceProvidersGoogleID = 'http://schemas.xmlsoap.org/identity/claims/serviceproviders.google.id';
case ServiceProvidersMicrosoftID = 'http://schemas.xmlsoap.org/identity/claims/serviceproviders.microsoft.id';
case ServiceProvidersWondeID = 'http://schemas.xmlsoap.org/identity/claims/serviceproviders.wonde.id';
case OrganisationMyLoginID = 'http://schemas.xmlsoap.org/identity/claims/organisation.myloginid';
case OrganisationWondeID = 'http://schemas.xmlsoap.org/identity/claims/organisation.wondeid';
case OrganisationName = 'http://schemas.xmlsoap.org/identity/claims/organisation.name';
case OrganisationPhaseOfEducation = 'http://schemas.xmlsoap.org/identity/claims/organisation.phaseofeducation';
case OrganisationUrn = 'http://schemas.xmlsoap.org/identity/claims/organisation.urn';
case OrganisationLACode = 'http://schemas.xmlsoap.org/identity/claims/organisation.lacode';
case OrganisationEstablishmentNumber = 'http://schemas.xmlsoap.org/identity/claims/organisation.establishmentnumber';
}
9 changes: 9 additions & 0 deletions app/Enums/SSOProtocol.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Enums;

enum SSOProtocol: string
{
case OAuth = 'o_auth';
case SAML = 'saml';
}
43 changes: 22 additions & 21 deletions app/Http/Controllers/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Controllers;

use App\Enums\SSOProtocol;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
Expand All @@ -22,57 +23,57 @@ public function loginPage(): View

public function redirect()
{

$query = http_build_query([
'client_id' => config('services.mylogin.client_id'),
'redirect_uri' => config('services.mylogin.redirect_uri'),
'response_type' => 'code',
]);

$url = config('services.mylogin.url') . "/oauth/authorize?" . $query;
$url = config('services.mylogin.url').'/oauth/authorize?'.$query;

return redirect($url);
}

public function callback(Request $request)
{
$response = Http::withHeaders([
'Authorization' => 'Basic ' . base64_encode(config('services.mylogin.client_id') . ':' . config('services.mylogin.client_secret'))
'Authorization' => 'Basic '.base64_encode(config('services.mylogin.client_id').':'.config('services.mylogin.client_secret')),
])
->post(config('services.mylogin.url') . '/oauth/token', [
'grant_type' => 'authorization_code',
'code' => $request->code,
'redirect_uri' => config('services.mylogin.redirect_uri')
]);
->post(config('services.mylogin.url').'/oauth/token', [
'grant_type' => 'authorization_code',
'code' => $request->code,
'redirect_uri' => config('services.mylogin.redirect_uri'),
]);

$details = $response->json();

if (! $response->ok() || empty($details['access_token'])) {
return to_route("login");
return to_route('login');
}

$userResponse = Http::withHeaders([
'Authorization' => 'Bearer ' . $details['access_token']
'Authorization' => 'Bearer '.$details['access_token'],
])
->get(config('services.mylogin.url') . '/api/user')
->json();
->get(config('services.mylogin.url').'/api/user')
->json();

if ($userResponse) {
$user = User::where('email', $userResponse['data']['email'])->first();

if (! $user) {
$user = User::create([
'name' => $userResponse['data']['first_name'] . ' ' . $userResponse['data']['last_name'],
'email' => $userResponse['data']['email'],
'password' => Hash::make(Str::random(48))
]);
}
$user = User::firstOrCreate([
'mylogin_id' => $userResponse['data']['id'],
], [
'name' => $userResponse['data']['first_name'].' '.$userResponse['data']['last_name'],
'email' => $userResponse['data']['email'],
'password' => Hash::make(Str::random(48)),
]);

$user->update([
'access_token' => $details['access_token'],
'refresh_token' => $details['refresh_token'],
]);

auth()->login($user);
session()->replace(['last_login_protocol' => SSOProtocol::OAuth->value]);
}

return to_route('dashboard');
Expand All @@ -86,6 +87,6 @@ public function logout(Request $request): RedirectResponse

$request->session()->regenerateToken();

return redirect()->away(config('services.mylogin.url') . '/oauth/logout?client_id=' . config('services.mylogin.client_id'));
return redirect()->away(config('services.mylogin.url').'/oauth/logout?client_id='.config('services.mylogin.client_id'));
}
}
43 changes: 33 additions & 10 deletions app/Http/Controllers/DashboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,48 @@

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Enums\SSOProtocol;
use Illuminate\Support\Facades\Http;
use Illuminate\View\View;

class DashboardController extends Controller
{
public function dashboard(): View
{
$userResponse = Http::withHeaders([
'Authorization' => 'Bearer ' . auth()->user()->access_token
])
->get(config('services.mylogin.url') . '/api/user')
->json();

$userData = match (session()->get('last_login_protocol')) {
SSOProtocol::SAML->value => $this->getSAMLUserDetails(),
SSOProtocol::OAuth->value => $this->getOAuthUserDetails(),
default => abort(404),
};

return view('dashboard', [
'userResponse' => $userResponse
'userData' => $userData,
]);
}

public function getOAuthUserDetails(): array
{
$response = Http::withHeaders([
'Authorization' => 'Bearer '.auth()->user()->access_token,
])
->get(config('services.mylogin.url').'/api/user')
->json();

return [
'name' => "{$response['data']['first_name']} {$response['data']['last_name']}",
'method' => SSOProtocol::OAuth,
'content' => $response,
];
}

private function getSAMLUserDetails(): array
{
$user = auth()->user();

return [
'name' => $user->name,
'method' => SSOProtocol::SAML,
'content' => $user->last_saml_assertion,
];
}
}
6 changes: 6 additions & 0 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
];

/**
Expand Down
3 changes: 3 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ class User extends Authenticatable
* @var array<int, string>
*/
protected $fillable = [
'mylogin_id',
'name',
'email',
'password',
'access_token',
'refresh_token',
'last_saml_assertion',
];

/**
Expand All @@ -43,5 +45,6 @@ class User extends Authenticatable
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'last_saml_assertion' => 'json',
];
}
23 changes: 22 additions & 1 deletion app/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

namespace App\Providers;

use App\Enums\ClaimTypes;
use App\Enums\SSOProtocol;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Slides\Saml2\Events\SignedIn;

class EventServiceProvider extends ServiceProvider
{
Expand All @@ -25,7 +31,22 @@ class EventServiceProvider extends ServiceProvider
*/
public function boot(): void
{
//
Event::listen(SignedIn::class, function (SignedIn $event) {
$samlUser = $event->getSaml2User();

$user = User::updateOrCreate([
'mylogin_id' => $samlUser->getAttribute(ClaimTypes::MyLoginID->value)[0],
], [
'name' => "{$samlUser->getAttribute(ClaimTypes::FirstName->value)[0]} {$samlUser->getAttribute(ClaimTypes::LastName->value)[0]}",
'email' => $samlUser->getAttribute(ClaimTypes::EmailAddress->value)[0],
'password' => Hash::make(Str::random()),
'last_saml_assertion' => $samlUser->getAttributes(),
]);

auth()->login($user);

session()->replace(['last_login_protocol' => SSOProtocol::SAML->value]);
});
}

/**
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"license": "MIT",
"require": {
"php": "^8.1",
"24slides/laravel-saml2": "^2.4",
"guzzlehttp/guzzle": "^7.7",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.2",
Expand Down
Loading

0 comments on commit d4b9add

Please sign in to comment.