Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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("OAuth2LoginStrategy::getLogin", ['provider' => $provider , 'provided_tenant' => $provided_tenant]);
Comment thread
smarcet marked this conversation as resolved.
Outdated
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) {
Comment thread
smarcet marked this conversation as resolved.
Outdated
$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