Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@ model.sql
/.env.local
/.phpunit.cache/
docker-compose/mysql/model/*.sql
public/assets/css/*.css.map
public/assets/*.js.map
public/assets/*.map
public/assets/css/*.map
6 changes: 6 additions & 0 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
});
}

/**
Expand Down
6 changes: 4 additions & 2 deletions app/Strategies/DisplayResponseUserAgentStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
11 changes: 8 additions & 3 deletions app/Strategies/OAuth2LoginStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand All @@ -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
]);
}

Expand Down
4 changes: 2 additions & 2 deletions app/Strategies/OTP/OTPChannelEmailStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
);
Expand Down
109 changes: 93 additions & 16 deletions app/libs/Auth/SocialLoginProviders.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<?php namespace App\libs\Auth;
use Illuminate\Support\Facades\Config;

/**
* Copyright 2021 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -14,6 +12,10 @@
* limitations under the License.
**/

use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;

/**
* Class SocialLoginProviders
* @package App\libs\Auth
Expand All @@ -25,41 +27,116 @@ final class SocialLoginProviders
const LinkedIn = "linkedin";
const Google = "google";
const OKTA = 'okta';
const LFID = 'lfid';

const ValidProviders = [
self::Facebook,
self::LinkedIn,
self::Apple,
//self::Google
self::OKTA,
self::LFID,
];

/**
* @param string $provider
* @return bool
*/
public static function isSupportedProvider(string $provider):bool{
public static function isSupportedProvider(string $provider): bool
{
return in_array($provider, self::ValidProviders);
}

/**
* @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));
}

/**
* @return string[]
* @param string $provided_tenant
* @return array
*/
public static function buildSupportedProviders():array{
public static function buildSupportedProviders(string $provided_tenant = ''): array
{
Log::debug("SocialLoginProviders::buildSupportedProviders", ["provided_tenant" => $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)) {
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using '!count($tenants)' is less explicit than 'count($tenants) === 0' or 'empty($tenants)'. For consistency with line 78, consider using 'count($tenants) == 0' or the more idiomatic 'empty($tenants)'.

Suggested change
if (!count($tenants) && !in_array($provider, $allowed_3rd_party_providers)) {
if (count($tenants) == 0 && !in_array($provider, $allowed_3rd_party_providers)) {

Copilot uses AI. Check for mistakes.
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));
}

}
6 changes: 3 additions & 3 deletions app/libs/OAuth2/Discovery/DiscoveryDocumentBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
5 changes: 5 additions & 0 deletions app/libs/OAuth2/OAuth2Protocol.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
4 changes: 4 additions & 0 deletions app/libs/OAuth2/Requests/OAuth2AuthorizationRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
34 changes: 18 additions & 16 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,44 @@
],
"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",
"phpseclib/phpseclib": "^3.0.43",
"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"
},
Expand Down
Loading
Loading