diff --git a/.gitignore b/.gitignore index c655a186..ffd7efd9 100644 --- a/.gitignore +++ b/.gitignore @@ -51,5 +51,5 @@ model.sql /.env.local /.phpunit.cache/ docker-compose/mysql/model/*.sql -public/assets/css/*.css.map -public/assets/*.js.map \ No newline at end of file +public/assets/*.map +public/assets/css/*.map \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 568a6506..92d107fd 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -15,6 +15,7 @@ use App\libs\Utils\TextUtils; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Validator; @@ -127,6 +128,11 @@ public function boot() return true; }); + + Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { + // custom tenants for AUTH0 providers + $event->extendSocialite('lfid', \SocialiteProviders\Auth0\Provider::class); + }); } /** diff --git a/app/Strategies/DisplayResponseUserAgentStrategy.php b/app/Strategies/DisplayResponseUserAgentStrategy.php index 27c296df..832d5bcb 100644 --- a/app/Strategies/DisplayResponseUserAgentStrategy.php +++ b/app/Strategies/DisplayResponseUserAgentStrategy.php @@ -13,6 +13,7 @@ **/ use App\libs\Auth\SocialLoginProviders; +use Illuminate\Support\Facades\Log; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Redirect; @@ -40,11 +41,12 @@ public function getConsentResponse(array $data = []) public function getLoginResponse(array $data = []) { $provider = $data["provider"] ?? null; - + $provided_tenant = $data["tenant"] ?? ''; + Log::debug("DisplayResponseUserAgentStrategy::getLoginResponse", ['provider' => $provider , 'provided_tenant' => $provided_tenant]); if(!empty($provider)) { return redirect()->route('social_login', ['provider' => $provider]); } - $data['supported_providers'] = SocialLoginProviders::buildSupportedProviders(); + $data['supported_providers'] = SocialLoginProviders::buildSupportedProviders($provided_tenant); return Response::view("auth.login", $data, 200); } diff --git a/app/Strategies/OAuth2LoginStrategy.php b/app/Strategies/OAuth2LoginStrategy.php index bf592d16..ba4a234d 100644 --- a/app/Strategies/OAuth2LoginStrategy.php +++ b/app/Strategies/OAuth2LoginStrategy.php @@ -15,9 +15,11 @@ use App\libs\OAuth2\Strategies\ILoginHintProcessStrategy; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; +use OAuth2\Endpoints\AuthorizationEndpoint; use OAuth2\Factories\OAuth2AuthorizationRequestFactory; use OAuth2\OAuth2Message; use OAuth2\Requests\OAuth2AuthenticationRequest; +use OAuth2\Requests\OAuth2AuthorizationRequest; use OAuth2\Services\IMementoOAuth2SerializerService; use Services\IUserActionService; use Utils\IPHelper; @@ -52,12 +54,12 @@ public function __construct ) { parent::__construct($user_action_service, $auth_service, $login_hint_process_strategy); - $this->memento_service = $memento_service; + $this->memento_service = $memento_service; } public function getLogin() { - Log::debug(sprintf("OAuth2LoginStrategy::getLogin")); + Log::debug("OAuth2LoginStrategy::getLogin"); if (!Auth::guest()) return Redirect::action("UserController@getProfile"); @@ -70,10 +72,13 @@ public function getLogin() ) ); + Log::debug("OAuth2LoginStrategy::getLogin", ['auth_request' => (string)$auth_request ]); + $response_strategy = DisplayResponseStrategyFactory::build($auth_request->getDisplay()); return $response_strategy->getLoginResponse([ - 'provider' => $auth_request instanceof OAuth2AuthenticationRequest ? $auth_request->getProvider() : null + 'provider' => $auth_request instanceof OAuth2AuthenticationRequest ? $auth_request->getProvider() : null, + 'tenant' => $auth_request instanceof OAuth2AuthorizationRequest ? $auth_request->getTenant() : null ]); } diff --git a/app/Strategies/OTP/OTPChannelEmailStrategy.php b/app/Strategies/OTP/OTPChannelEmailStrategy.php index 348a3455..f3e1e36d 100644 --- a/app/Strategies/OTP/OTPChannelEmailStrategy.php +++ b/app/Strategies/OTP/OTPChannelEmailStrategy.php @@ -61,13 +61,13 @@ public function send(IOTPTypeBuilderStrategy $typeBuilderStrategy, OAuth2OTP $ot try{ $reset_password_link = null; $user = $this->user_repository->getByEmailOrName($otp->getUserName()); - if($user instanceof User && !$user->hasPasswordSet()){ + if($user instanceof User && !$user->hasPasswordSet() && $user->isEmailVerified()){ // create a password reset request Log::debug ( sprintf ( - "OTPChannelEmailStrategy::send user %s has no password set", + "OTPChannelEmailStrategy::send user %s has no password set, trying to generate a reset link ...", $user->getId() ) ); diff --git a/app/libs/Auth/SocialLoginProviders.php b/app/libs/Auth/SocialLoginProviders.php index 2d631fac..b88bd0a1 100644 --- a/app/libs/Auth/SocialLoginProviders.php +++ b/app/libs/Auth/SocialLoginProviders.php @@ -1,6 +1,4 @@ $provided_tenant]); $res = []; - foreach(self::ValidProviders as $provider){ - if(self::isEnabledProvider($provider)) + $tenant = trim(Request::get('tenant', $provided_tenant)); + $allowed_3rd_party_providers = self::toList( + Config::get("tenants.$tenant.allowed_3rd_party_providers", '') + ); + + Log::debug("SocialLoginProviders::buildSupportedProviders", ["tenant" => $tenant, "allowed_3rd_party_providers" => $allowed_3rd_party_providers]); + foreach (self::ValidProviders as $provider) { + Log::debug("SocialLoginProviders::buildSupportedProviders", ["tenant" => $tenant, "provider" => $provider]); + + if (!self::isEnabledProvider($provider)) { + Log::warning("SocialLoginProviders::buildSupportedProviders provider is not enabled.", ["tenant" => $tenant, "provider" => $provider]); + continue; + } + + // check if the 3rd party provider has defined some exclusive tenants ... + $tenants = self::toList( + Config::get("services.$provider.tenants", '') + ); + + // If no tenant param was provided, any enabled provider is allowed. + if ($tenant === '' && count($tenants) == 0) { $res[$provider] = ucfirst($provider); + continue; + } + Log::debug(sprintf("SocialLoginProviders::buildSupportedProviders provider %s is enabled", $provider)); + // 1. check if we have exclusive tenants defined at provider level + if (count($tenants) > 0 && !in_array($tenant, $tenants)) { + // tenant is not defined on the exclusive collection of the provider + Log::warning + ( + sprintf + ( + "SocialLoginProviders::buildSupportedProviders provider %s is not enabled for tenant %s", + $provider, + $tenant + ), + ["tenants" => $tenants] + ); + continue; + } + // 2. check if the tenant has that provider enabled + if (!count($tenants) && !in_array($provider, $allowed_3rd_party_providers)) { + Log::warning + ( + sprintf + ( + "SocialLoginProviders::buildSupportedProviders provider %s is not enabled for tenant %s", + $provider, + $tenant + ), + ["allowed_3rd_party_providers" => $allowed_3rd_party_providers] + ); + continue; + } + + Log::debug(sprintf("SocialLoginProviders::buildSupportedProviders provider %s is added", $provider)); + $res[$provider] = ucfirst($provider); } + return $res; } + + private static function toList($value): array + { + if (is_array($value)) { + return array_values(array_filter(array_map('trim', $value), static fn($v) => $v !== '')); + } + if (is_string($value)) { + if ($value === '') return []; + return array_values(array_filter(array_map('trim', explode(',', $value)), static fn($v) => $v !== '')); + } + return []; + } + + /** + * @param string $provider + * @return bool + */ + public static function isEnabledProvider(string $provider): bool + { + return !empty(Config::get("services." . $provider . ".client_id", null)) && + !empty(Config::get("services." . $provider . ".client_secret", null)); + } + } \ No newline at end of file diff --git a/app/libs/OAuth2/Discovery/DiscoveryDocumentBuilder.php b/app/libs/OAuth2/Discovery/DiscoveryDocumentBuilder.php index fe0d9c98..7d6d11e1 100644 --- a/app/libs/OAuth2/Discovery/DiscoveryDocumentBuilder.php +++ b/app/libs/OAuth2/Discovery/DiscoveryDocumentBuilder.php @@ -261,9 +261,9 @@ public function addUserInfoEncryptionEncSupported($enc) * @return $this */ public function addAvailableThirdPartyIdentityProviders(){ - foreach(SocialLoginProviders::ValidProviders as $provider) - if(SocialLoginProviders::isEnabledProvider($provider)) - $this->addArrayValue("third_party_identity_providers", $provider); + $providers = SocialLoginProviders::buildSupportedProviders(); + foreach($providers as $provider => $value) + $this->addArrayValue("third_party_identity_providers", $provider); return $this; } diff --git a/app/libs/OAuth2/OAuth2Protocol.php b/app/libs/OAuth2/OAuth2Protocol.php index 9bb0c7a1..2c064dd5 100644 --- a/app/libs/OAuth2/OAuth2Protocol.php +++ b/app/libs/OAuth2/OAuth2Protocol.php @@ -147,6 +147,11 @@ final class OAuth2Protocol implements IOAuth2Protocol self::OAuth2Protocol_ResponseMode_Direct ); + /** + * custom param + */ + const Tenant = 'tenant'; + /** * http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes * diff --git a/app/libs/OAuth2/Requests/OAuth2AuthorizationRequest.php b/app/libs/OAuth2/Requests/OAuth2AuthorizationRequest.php index f85e6f72..97f68372 100644 --- a/app/libs/OAuth2/Requests/OAuth2AuthorizationRequest.php +++ b/app/libs/OAuth2/Requests/OAuth2AuthorizationRequest.php @@ -232,4 +232,8 @@ public function getCodeChallenge():?string{ public function getCodeChallengeMethod():?string{ return $this->getParam(OAuth2Protocol::PKCE_CodeChallengeMethod); } + + public function getTenant():?string{ + return $this->getParam(OAuth2Protocol::Tenant); + } } diff --git a/composer.json b/composer.json index 9c015091..a09d6eb1 100644 --- a/composer.json +++ b/composer.json @@ -19,35 +19,29 @@ ], "require": { "php": "^8.3", - "ext-pdo": "*", "ext-json": "*", "ext-openssl": "*", - "firebase/php-jwt": "6.11.1", - "laravel/framework": "12.0", - "laravel/helpers": "^1.7.0", - "laravel/tinker": "2.10.1", - "laravel-doctrine/orm": "3.1.1", - "laravel-doctrine/extensions": "2.0.1", - "laravel-doctrine/migrations": "3.4.0", + "ext-pdo": "*", "beberlei/doctrineextensions": "1.5.0", - "laravel/socialite": "^5.21.0", - "socialiteproviders/apple": "^5.6.1", - "socialiteproviders/facebook": "^4.1.0", - "socialiteproviders/google": "^4.1.0", - "socialiteproviders/linkedin": "^5.0.0", - "socialiteproviders/manager": "^4.8.1", - "socialiteproviders/okta": "^4.5.0", "behat/transliterator": "1.5.0", "ezyang/htmlpurifier": "v4.17.0", + "firebase/php-jwt": "6.11.1", "get-stream/stream-chat": "^3.10.0", "glenscott/url-normalizer": "1.4.0", + "greggilbert/recaptcha": "dev-master", "guzzlehttp/guzzle": "7.9.3", "guzzlehttp/uri-template": "^1.0", "ircmaxell/random-lib": "1.2.0", "jenssegers/agent": "2.6.3", - "greggilbert/recaptcha": "dev-master", "laminas/laminas-crypt": "3.11.0", "laminas/laminas-math": "3.7.0", + "laravel-doctrine/extensions": "2.0.1", + "laravel-doctrine/migrations": "3.4.0", + "laravel-doctrine/orm": "3.1.1", + "laravel/framework": "12.0", + "laravel/helpers": "^1.7.0", + "laravel/socialite": "^5.21.0", + "laravel/tinker": "2.10.1", "league/flysystem": "3.25.1", "league/flysystem-aws-s3-v3": "3.8.0", "php-opencloud/openstack": "3.10.0", @@ -55,6 +49,14 @@ "predis/predis": "v2.2.2", "s-ichikawa/laravel-sendgrid-driver": "^4.0", "smarcet/jose4php": "2.0.0", + "socialiteproviders/apple": "^5.6.1", + "socialiteproviders/auth0": "^4.2", + "socialiteproviders/facebook": "^4.1.0", + "socialiteproviders/google": "^4.1.0", + "socialiteproviders/linkedin": "^5.0.0", + "socialiteproviders/manager": "^4.8.1", + "socialiteproviders/okta": "^4.5.0", + "socialiteproviders/zoho": "^4.1", "sokil/php-isocodes": "^3.0", "vladimir-yuldashev/laravel-queue-rabbitmq": "v14.2.0" }, diff --git a/composer.lock b/composer.lock index a40e3d3e..797901e1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6efeae436ebd865b45a619f4c7974929", + "content-hash": "2f7638c2df61a21a21b66914a63054d7", "packages": [ { "name": "aws/aws-crt-php", @@ -6721,6 +6721,56 @@ }, "time": "2023-12-06T14:43:17+00:00" }, + { + "name": "socialiteproviders/auth0", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Auth0.git", + "reference": "2f280a2f90d050b391e938b6ccaaa5bbec5ac699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Auth0/zipball/2f280a2f90d050b391e938b6ccaaa5bbec5ac699", + "reference": "2f280a2f90d050b391e938b6ccaaa5bbec5ac699", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0", + "socialiteproviders/manager": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "SocialiteProviders\\Auth0\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chase Coney", + "email": "chase.coney@gmail.com" + } + ], + "description": "Auth0 OAuth2 Provider for Laravel Socialite", + "keywords": [ + "auth0", + "laravel", + "oauth", + "provider", + "socialite" + ], + "support": { + "docs": "https://socialiteproviders.com/auth0", + "issues": "https://github.com/socialiteproviders/providers/issues", + "source": "https://github.com/socialiteproviders/providers" + }, + "time": "2024-05-20T23:48:59+00:00" + }, { "name": "socialiteproviders/facebook", "version": "4.1.0", @@ -6977,6 +7027,47 @@ }, "time": "2024-11-07T21:57:40+00:00" }, + { + "name": "socialiteproviders/zoho", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Zoho.git", + "reference": "7edf769faf6ee851a3c698486dc1f42edba09ed9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Zoho/zipball/7edf769faf6ee851a3c698486dc1f42edba09ed9", + "reference": "7edf769faf6ee851a3c698486dc1f42edba09ed9", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.2 || ^8.0", + "socialiteproviders/manager": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "SocialiteProviders\\Zoho\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "glowlogix", + "email": "support@glowlogix.com" + } + ], + "description": "Zoho OAuth2 Provider for Laravel Socialite", + "support": { + "source": "https://github.com/SocialiteProviders/Zoho/tree/4.1.0" + }, + "time": "2020-12-01T23:10:59+00:00" + }, { "name": "sokil/php-isocodes", "version": "3.3.15", @@ -12434,10 +12525,10 @@ "prefer-lowest": false, "platform": { "php": "^8.3", - "ext-pdo": "*", "ext-json": "*", - "ext-openssl": "*" + "ext-openssl": "*", + "ext-pdo": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/services.php b/config/services.php index 36108e6b..ee3926dc 100644 --- a/config/services.php +++ b/config/services.php @@ -1,6 +1,15 @@ [ + 'client_id' => env('LFID_CLIENT_ID'), + 'client_secret' => env('LFID_CLIENT_SECRET'), + 'redirect' => env('LFID_REDIRECT_URI'), + 'base_url' => env('LFID_BASE_URL'), + 'tenants' => env('LFID_TENANTS','lf'), + ] +]; -return [ +return array_merge([ /* |-------------------------------------------------------------------------- @@ -66,4 +75,4 @@ 'base_url' => env("OKTA_BASE_URL"), 'redirect' => env('OKTA_REDIRECT_URI') ], -]; +], $custom_auth0_tenants); diff --git a/config/tenants.php b/config/tenants.php new file mode 100644 index 00000000..98662496 --- /dev/null +++ b/config/tenants.php @@ -0,0 +1,7 @@ + [ + 'allowed_3rd_party_providers' => env('LFID_ALLOWED_3RD_PARTY_PROVIDERS', '') + ], +]; \ No newline at end of file