Skip to content

Commit f13ba45

Browse files
smarcetCopilot
andauthored
Feature/auth0 3rd party provider (#93)
* fix: emit OTP do not attemp to create password reset link if user is not verified * chore: add auth0 socialite provider feat: include auth0 as valid social provider * chore: add tenant logic chore: define custom provider for LFID * chore: add tenant to auth request (OIDC) * chore: fix on empty tenant * Update app/Strategies/DisplayResponseUserAgentStrategy.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update app/libs/Auth/SocialLoginProviders.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent f62cb34 commit f13ba45

File tree

13 files changed

+258
-50
lines changed

13 files changed

+258
-50
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@ model.sql
5151
/.env.local
5252
/.phpunit.cache/
5353
docker-compose/mysql/model/*.sql
54-
public/assets/css/*.css.map
55-
public/assets/*.js.map
54+
public/assets/*.map
55+
public/assets/css/*.map

app/Providers/AppServiceProvider.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use App\libs\Utils\TextUtils;
1616
use Illuminate\Support\Facades\App;
1717
use Illuminate\Support\Facades\Config;
18+
use Illuminate\Support\Facades\Event;
1819
use Illuminate\Support\Facades\Log;
1920
use Illuminate\Support\ServiceProvider;
2021
use Illuminate\Support\Facades\Validator;
@@ -127,6 +128,11 @@ public function boot()
127128

128129
return true;
129130
});
131+
132+
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
133+
// custom tenants for AUTH0 providers
134+
$event->extendSocialite('lfid', \SocialiteProviders\Auth0\Provider::class);
135+
});
130136
}
131137

132138
/**

app/Strategies/DisplayResponseUserAgentStrategy.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
**/
1414

1515
use App\libs\Auth\SocialLoginProviders;
16+
use Illuminate\Support\Facades\Log;
1617
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
1718
use Illuminate\Support\Facades\Response;
1819
use Illuminate\Support\Facades\Redirect;
@@ -40,11 +41,12 @@ public function getConsentResponse(array $data = [])
4041
public function getLoginResponse(array $data = [])
4142
{
4243
$provider = $data["provider"] ?? null;
43-
44+
$provided_tenant = $data["tenant"] ?? '';
45+
Log::debug("DisplayResponseUserAgentStrategy::getLoginResponse", ['provider' => $provider , 'provided_tenant' => $provided_tenant]);
4446
if(!empty($provider)) {
4547
return redirect()->route('social_login', ['provider' => $provider]);
4648
}
47-
$data['supported_providers'] = SocialLoginProviders::buildSupportedProviders();
49+
$data['supported_providers'] = SocialLoginProviders::buildSupportedProviders($provided_tenant);
4850
return Response::view("auth.login", $data, 200);
4951
}
5052

app/Strategies/OAuth2LoginStrategy.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
use App\libs\OAuth2\Strategies\ILoginHintProcessStrategy;
1616
use Illuminate\Support\Facades\Auth;
1717
use Illuminate\Support\Facades\Log;
18+
use OAuth2\Endpoints\AuthorizationEndpoint;
1819
use OAuth2\Factories\OAuth2AuthorizationRequestFactory;
1920
use OAuth2\OAuth2Message;
2021
use OAuth2\Requests\OAuth2AuthenticationRequest;
22+
use OAuth2\Requests\OAuth2AuthorizationRequest;
2123
use OAuth2\Services\IMementoOAuth2SerializerService;
2224
use Services\IUserActionService;
2325
use Utils\IPHelper;
@@ -52,12 +54,12 @@ public function __construct
5254
)
5355
{
5456
parent::__construct($user_action_service, $auth_service, $login_hint_process_strategy);
55-
$this->memento_service = $memento_service;
57+
$this->memento_service = $memento_service;
5658
}
5759

5860
public function getLogin()
5961
{
60-
Log::debug(sprintf("OAuth2LoginStrategy::getLogin"));
62+
Log::debug("OAuth2LoginStrategy::getLogin");
6163

6264
if (!Auth::guest())
6365
return Redirect::action("UserController@getProfile");
@@ -70,10 +72,13 @@ public function getLogin()
7072
)
7173
);
7274

75+
Log::debug("OAuth2LoginStrategy::getLogin", ['auth_request' => (string)$auth_request ]);
76+
7377
$response_strategy = DisplayResponseStrategyFactory::build($auth_request->getDisplay());
7478

7579
return $response_strategy->getLoginResponse([
76-
'provider' => $auth_request instanceof OAuth2AuthenticationRequest ? $auth_request->getProvider() : null
80+
'provider' => $auth_request instanceof OAuth2AuthenticationRequest ? $auth_request->getProvider() : null,
81+
'tenant' => $auth_request instanceof OAuth2AuthorizationRequest ? $auth_request->getTenant() : null
7782
]);
7883
}
7984

app/Strategies/OTP/OTPChannelEmailStrategy.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ public function send(IOTPTypeBuilderStrategy $typeBuilderStrategy, OAuth2OTP $ot
6161
try{
6262
$reset_password_link = null;
6363
$user = $this->user_repository->getByEmailOrName($otp->getUserName());
64-
if($user instanceof User && !$user->hasPasswordSet()){
64+
if($user instanceof User && !$user->hasPasswordSet() && $user->isEmailVerified()){
6565
// create a password reset request
6666
Log::debug
6767
(
6868
sprintf
6969
(
70-
"OTPChannelEmailStrategy::send user %s has no password set",
70+
"OTPChannelEmailStrategy::send user %s has no password set, trying to generate a reset link ...",
7171
$user->getId()
7272
)
7373
);
Lines changed: 93 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
<?php namespace App\libs\Auth;
2-
use Illuminate\Support\Facades\Config;
3-
42
/**
53
* Copyright 2021 OpenStack Foundation
64
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,6 +12,10 @@
1412
* limitations under the License.
1513
**/
1614

15+
use Illuminate\Support\Facades\Config;
16+
use Illuminate\Support\Facades\Log;
17+
use Illuminate\Support\Facades\Request;
18+
1719
/**
1820
* Class SocialLoginProviders
1921
* @package App\libs\Auth
@@ -25,41 +27,116 @@ final class SocialLoginProviders
2527
const LinkedIn = "linkedin";
2628
const Google = "google";
2729
const OKTA = 'okta';
30+
const LFID = 'lfid';
2831

2932
const ValidProviders = [
3033
self::Facebook,
3134
self::LinkedIn,
3235
self::Apple,
3336
//self::Google
3437
self::OKTA,
38+
self::LFID,
3539
];
3640

3741
/**
3842
* @param string $provider
3943
* @return bool
4044
*/
41-
public static function isSupportedProvider(string $provider):bool{
45+
public static function isSupportedProvider(string $provider): bool
46+
{
4247
return in_array($provider, self::ValidProviders);
4348
}
4449

4550
/**
46-
* @param string $provider
47-
* @return bool
48-
*/
49-
public static function isEnabledProvider(string $provider):bool{
50-
return !empty(Config::get("services.".$provider.".client_id", null)) &&
51-
!empty(Config::get("services.".$provider.".client_secret", null));
52-
}
53-
54-
/**
55-
* @return string[]
51+
* @param string $provided_tenant
52+
* @return array
5653
*/
57-
public static function buildSupportedProviders():array{
54+
public static function buildSupportedProviders(string $provided_tenant = ''): array
55+
{
56+
Log::debug("SocialLoginProviders::buildSupportedProviders", ["provided_tenant" => $provided_tenant]);
5857
$res = [];
59-
foreach(self::ValidProviders as $provider){
60-
if(self::isEnabledProvider($provider))
58+
$tenant = trim(Request::get('tenant', $provided_tenant));
59+
$allowed_3rd_party_providers = self::toList(
60+
Config::get("tenants.$tenant.allowed_3rd_party_providers", '')
61+
);
62+
63+
Log::debug("SocialLoginProviders::buildSupportedProviders", ["tenant" => $tenant, "allowed_3rd_party_providers" => $allowed_3rd_party_providers]);
64+
foreach (self::ValidProviders as $provider) {
65+
Log::debug("SocialLoginProviders::buildSupportedProviders", ["tenant" => $tenant, "provider" => $provider]);
66+
67+
if (!self::isEnabledProvider($provider)) {
68+
Log::warning("SocialLoginProviders::buildSupportedProviders provider is not enabled.", ["tenant" => $tenant, "provider" => $provider]);
69+
continue;
70+
}
71+
72+
// check if the 3rd party provider has defined some exclusive tenants ...
73+
$tenants = self::toList(
74+
Config::get("services.$provider.tenants", '')
75+
);
76+
77+
// If no tenant param was provided, any enabled provider is allowed.
78+
if ($tenant === '' && count($tenants) == 0) {
6179
$res[$provider] = ucfirst($provider);
80+
continue;
81+
}
82+
Log::debug(sprintf("SocialLoginProviders::buildSupportedProviders provider %s is enabled", $provider));
83+
// 1. check if we have exclusive tenants defined at provider level
84+
if (count($tenants) > 0 && !in_array($tenant, $tenants)) {
85+
// tenant is not defined on the exclusive collection of the provider
86+
Log::warning
87+
(
88+
sprintf
89+
(
90+
"SocialLoginProviders::buildSupportedProviders provider %s is not enabled for tenant %s",
91+
$provider,
92+
$tenant
93+
),
94+
["tenants" => $tenants]
95+
);
96+
continue;
97+
}
98+
// 2. check if the tenant has that provider enabled
99+
if (!count($tenants) && !in_array($provider, $allowed_3rd_party_providers)) {
100+
Log::warning
101+
(
102+
sprintf
103+
(
104+
"SocialLoginProviders::buildSupportedProviders provider %s is not enabled for tenant %s",
105+
$provider,
106+
$tenant
107+
),
108+
["allowed_3rd_party_providers" => $allowed_3rd_party_providers]
109+
);
110+
continue;
111+
}
112+
113+
Log::debug(sprintf("SocialLoginProviders::buildSupportedProviders provider %s is added", $provider));
114+
$res[$provider] = ucfirst($provider);
62115
}
116+
63117
return $res;
64118
}
119+
120+
private static function toList($value): array
121+
{
122+
if (is_array($value)) {
123+
return array_values(array_filter(array_map('trim', $value), static fn($v) => $v !== ''));
124+
}
125+
if (is_string($value)) {
126+
if ($value === '') return [];
127+
return array_values(array_filter(array_map('trim', explode(',', $value)), static fn($v) => $v !== ''));
128+
}
129+
return [];
130+
}
131+
132+
/**
133+
* @param string $provider
134+
* @return bool
135+
*/
136+
public static function isEnabledProvider(string $provider): bool
137+
{
138+
return !empty(Config::get("services." . $provider . ".client_id", null)) &&
139+
!empty(Config::get("services." . $provider . ".client_secret", null));
140+
}
141+
65142
}

app/libs/OAuth2/Discovery/DiscoveryDocumentBuilder.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,9 @@ public function addUserInfoEncryptionEncSupported($enc)
261261
* @return $this
262262
*/
263263
public function addAvailableThirdPartyIdentityProviders(){
264-
foreach(SocialLoginProviders::ValidProviders as $provider)
265-
if(SocialLoginProviders::isEnabledProvider($provider))
266-
$this->addArrayValue("third_party_identity_providers", $provider);
264+
$providers = SocialLoginProviders::buildSupportedProviders();
265+
foreach($providers as $provider => $value)
266+
$this->addArrayValue("third_party_identity_providers", $provider);
267267
return $this;
268268
}
269269

app/libs/OAuth2/OAuth2Protocol.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ final class OAuth2Protocol implements IOAuth2Protocol
147147
self::OAuth2Protocol_ResponseMode_Direct
148148
);
149149

150+
/**
151+
* custom param
152+
*/
153+
const Tenant = 'tenant';
154+
150155
/**
151156
* http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
152157
*

app/libs/OAuth2/Requests/OAuth2AuthorizationRequest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,8 @@ public function getCodeChallenge():?string{
232232
public function getCodeChallengeMethod():?string{
233233
return $this->getParam(OAuth2Protocol::PKCE_CodeChallengeMethod);
234234
}
235+
236+
public function getTenant():?string{
237+
return $this->getParam(OAuth2Protocol::Tenant);
238+
}
235239
}

composer.json

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,42 +19,44 @@
1919
],
2020
"require": {
2121
"php": "^8.3",
22-
"ext-pdo": "*",
2322
"ext-json": "*",
2423
"ext-openssl": "*",
25-
"firebase/php-jwt": "6.11.1",
26-
"laravel/framework": "12.0",
27-
"laravel/helpers": "^1.7.0",
28-
"laravel/tinker": "2.10.1",
29-
"laravel-doctrine/orm": "3.1.1",
30-
"laravel-doctrine/extensions": "2.0.1",
31-
"laravel-doctrine/migrations": "3.4.0",
24+
"ext-pdo": "*",
3225
"beberlei/doctrineextensions": "1.5.0",
33-
"laravel/socialite": "^5.21.0",
34-
"socialiteproviders/apple": "^5.6.1",
35-
"socialiteproviders/facebook": "^4.1.0",
36-
"socialiteproviders/google": "^4.1.0",
37-
"socialiteproviders/linkedin": "^5.0.0",
38-
"socialiteproviders/manager": "^4.8.1",
39-
"socialiteproviders/okta": "^4.5.0",
4026
"behat/transliterator": "1.5.0",
4127
"ezyang/htmlpurifier": "v4.17.0",
28+
"firebase/php-jwt": "6.11.1",
4229
"get-stream/stream-chat": "^3.10.0",
4330
"glenscott/url-normalizer": "1.4.0",
31+
"greggilbert/recaptcha": "dev-master",
4432
"guzzlehttp/guzzle": "7.9.3",
4533
"guzzlehttp/uri-template": "^1.0",
4634
"ircmaxell/random-lib": "1.2.0",
4735
"jenssegers/agent": "2.6.3",
48-
"greggilbert/recaptcha": "dev-master",
4936
"laminas/laminas-crypt": "3.11.0",
5037
"laminas/laminas-math": "3.7.0",
38+
"laravel-doctrine/extensions": "2.0.1",
39+
"laravel-doctrine/migrations": "3.4.0",
40+
"laravel-doctrine/orm": "3.1.1",
41+
"laravel/framework": "12.0",
42+
"laravel/helpers": "^1.7.0",
43+
"laravel/socialite": "^5.21.0",
44+
"laravel/tinker": "2.10.1",
5145
"league/flysystem": "3.25.1",
5246
"league/flysystem-aws-s3-v3": "3.8.0",
5347
"php-opencloud/openstack": "3.10.0",
5448
"phpseclib/phpseclib": "^3.0.43",
5549
"predis/predis": "v2.2.2",
5650
"s-ichikawa/laravel-sendgrid-driver": "^4.0",
5751
"smarcet/jose4php": "2.0.0",
52+
"socialiteproviders/apple": "^5.6.1",
53+
"socialiteproviders/auth0": "^4.2",
54+
"socialiteproviders/facebook": "^4.1.0",
55+
"socialiteproviders/google": "^4.1.0",
56+
"socialiteproviders/linkedin": "^5.0.0",
57+
"socialiteproviders/manager": "^4.8.1",
58+
"socialiteproviders/okta": "^4.5.0",
59+
"socialiteproviders/zoho": "^4.1",
5860
"sokil/php-isocodes": "^3.0",
5961
"vladimir-yuldashev/laravel-queue-rabbitmq": "v14.2.0"
6062
},

0 commit comments

Comments
 (0)