Compare commits
2 Commits
04993ebb27
...
2fae99e24b
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fae99e24b | |||
| b74b563c56 |
5
.env
5
.env
@@ -43,8 +43,11 @@ REDIS_HOST=redis://redis
|
|||||||
MAILER_DSN=null://null
|
MAILER_DSN=null://null
|
||||||
###< symfony/mailer ###
|
###< symfony/mailer ###
|
||||||
|
|
||||||
|
AUTH_METHOD=form_login
|
||||||
|
|
||||||
###> drenso/symfony-oidc-bundle ###
|
###> drenso/symfony-oidc-bundle ###
|
||||||
OIDC_WELL_KNOWN_URL="Enter the .well-known url for the OIDC provider"
|
OIDC_WELL_KNOWN_URL="https://oidc/.well-known"
|
||||||
OIDC_CLIENT_ID="Enter your OIDC client id"
|
OIDC_CLIENT_ID="Enter your OIDC client id"
|
||||||
OIDC_CLIENT_SECRET="Enter your OIDC client secret"
|
OIDC_CLIENT_SECRET="Enter your OIDC client secret"
|
||||||
|
OIDC_BYPASS_FORM_LOGIN=false
|
||||||
###< drenso/symfony-oidc-bundle ###
|
###< drenso/symfony-oidc-bundle ###
|
||||||
|
|||||||
@@ -24,9 +24,15 @@ security:
|
|||||||
logout:
|
logout:
|
||||||
path: /logout
|
path: /logout
|
||||||
provider: app_oidc
|
provider: app_oidc
|
||||||
|
form_login:
|
||||||
|
login_path: app_login
|
||||||
|
check_path: app_login
|
||||||
|
enable_csrf: true
|
||||||
oidc:
|
oidc:
|
||||||
login_path: '/login/oidc'
|
login_path: '/login/oidc'
|
||||||
check_path: '/login/oidc/auth'
|
check_path: '/login/oidc/auth'
|
||||||
|
enable_end_session_listener: true
|
||||||
|
entry_point: form_login
|
||||||
|
|
||||||
# activate different ways to authenticate
|
# activate different ways to authenticate
|
||||||
# https://symfony.com/doc/current/security.html#the-firewall
|
# https://symfony.com/doc/current/security.html#the-firewall
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
security:
|
|
||||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
|
||||||
password_hashers:
|
|
||||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
|
||||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
|
||||||
providers:
|
|
||||||
users_in_memory: { memory: null }
|
|
||||||
app_local:
|
|
||||||
entity:
|
|
||||||
class: App\User\Framework\Entity\User
|
|
||||||
property: email
|
|
||||||
|
|
||||||
app_ldap:
|
|
||||||
id: App\User\Framework\Security\LdapUserProvider
|
|
||||||
|
|
||||||
firewalls:
|
|
||||||
dev:
|
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
|
||||||
security: false
|
|
||||||
main:
|
|
||||||
lazy: true
|
|
||||||
provider: app_ldap
|
|
||||||
entry_point: form_login_ldap
|
|
||||||
form_login_ldap:
|
|
||||||
login_path: app_login
|
|
||||||
check_path: app_login
|
|
||||||
enable_csrf: true
|
|
||||||
service: Symfony\Component\Ldap\Ldap
|
|
||||||
dn_string: '%env(LDAP_DN_STRING)%'
|
|
||||||
form_login:
|
|
||||||
login_path: app_login
|
|
||||||
check_path: app_login
|
|
||||||
enable_csrf: true
|
|
||||||
logout:
|
|
||||||
path: app_logout
|
|
||||||
|
|
||||||
# activate different ways to authenticate
|
|
||||||
# https://symfony.com/doc/current/security.html#the-firewall
|
|
||||||
|
|
||||||
# https://symfony.com/doc/current/security/impersonating_user.html
|
|
||||||
# switch_user: true
|
|
||||||
|
|
||||||
# Easy way to control access for large sections of your site
|
|
||||||
# Note: Only the *first* access control that matches will be used
|
|
||||||
access_control:
|
|
||||||
- { path: ^/reset-password, roles: PUBLIC_ACCESS }
|
|
||||||
- { path: ^/login, roles: PUBLIC_ACCESS }
|
|
||||||
- { path: ^/, roles: ROLE_USER } # Or ROLE_ADMIN, ROLE_SUPER_ADMIN,
|
|
||||||
|
|
||||||
when@test:
|
|
||||||
security:
|
|
||||||
password_hashers:
|
|
||||||
# By default, password hashers are resource intensive and take time. This is
|
|
||||||
# important to generate secure password hashes. In tests however, secure hashes
|
|
||||||
# are not important, waste resources and increase test times. The following
|
|
||||||
# reduces the work factor to the lowest possible values.
|
|
||||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
|
||||||
algorithm: auto
|
|
||||||
cost: 4 # Lowest possible value for bcrypt
|
|
||||||
time_cost: 3 # Lowest possible value for argon
|
|
||||||
memory_cost: 10 # Lowest possible value for argon
|
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
parameters:
|
parameters:
|
||||||
# App
|
# App
|
||||||
app.url: '%env(APP_URL)%'
|
app.url: '%env(APP_URL)%'
|
||||||
|
app.version: '%env(default:app.default.version:APP_VERSION)%'
|
||||||
|
|
||||||
# Debrid Services
|
# Debrid Services
|
||||||
app.debrid.real_debrid.key: '%env(REAL_DEBRID_KEY)%'
|
app.debrid.real_debrid.key: '%env(REAL_DEBRID_KEY)%'
|
||||||
@@ -34,7 +35,14 @@ parameters:
|
|||||||
app.default.version: '0.dev'
|
app.default.version: '0.dev'
|
||||||
app.default.timezone: 'America/Chicago'
|
app.default.timezone: 'America/Chicago'
|
||||||
|
|
||||||
app.version: '%env(default:app.default.version:APP_VERSION)%'
|
# Auth
|
||||||
|
auth.default.method: 'form_login'
|
||||||
|
auth.method: '%env(default:auth.default.method:AUTH_METHOD)%'
|
||||||
|
|
||||||
|
auth.oidc.well_known_url: '%env(OIDC_WELL_KNOWN_URL)%'
|
||||||
|
auth.oidc.client_id: '%env(OIDC_CLIENT_ID)%'
|
||||||
|
auth.oidc.client_secret: '%env(OIDC_CLIENT_SECRET)%'
|
||||||
|
auth.oidc.bypass_form_login: '%env(bool:OIDC_BYPASS_FORM_LOGIN)%'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# default configuration for services in *this* file
|
# default configuration for services in *this* file
|
||||||
|
|||||||
@@ -23,6 +23,21 @@ final class ConfigResolver
|
|||||||
|
|
||||||
#[Autowire(param: 'media.tvshows.path')]
|
#[Autowire(param: 'media.tvshows.path')]
|
||||||
private readonly ?string $tvshowsPath = null,
|
private readonly ?string $tvshowsPath = null,
|
||||||
|
|
||||||
|
#[Autowire(param: 'auth.method')]
|
||||||
|
private readonly ?string $authMethod = null,
|
||||||
|
|
||||||
|
#[Autowire(param: 'auth.oidc.well_known_url')]
|
||||||
|
private readonly ?string $authOidcWellKnownUrl = null,
|
||||||
|
|
||||||
|
#[Autowire(param: 'auth.oidc.client_id')]
|
||||||
|
private readonly ?string $authOidcClientId = null,
|
||||||
|
|
||||||
|
#[Autowire(param: 'auth.oidc.client_secret')]
|
||||||
|
private readonly ?string $authOidcClientSecret = null,
|
||||||
|
|
||||||
|
#[Autowire(param: 'auth.oidc.bypass_form_login')]
|
||||||
|
private ?bool $authOidcBypassFormLogin = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function validate(): bool
|
public function validate(): bool
|
||||||
@@ -46,4 +61,35 @@ final class ConfigResolver
|
|||||||
{
|
{
|
||||||
return $this->messages;
|
return $this->messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function authIs(string $method): bool
|
||||||
|
{
|
||||||
|
if (strtolower($method) === strtolower($this->getAuthMethod())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthMethod(): string
|
||||||
|
{
|
||||||
|
return strtolower($this->authMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bypassFormLogin(): bool
|
||||||
|
{
|
||||||
|
return $this->authOidcBypassFormLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthConfig(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'method' => $this->authMethod,
|
||||||
|
'oidc' => [
|
||||||
|
'well_known_url' => $this->authOidcWellKnownUrl,
|
||||||
|
'client_id' => $this->authOidcClientId,
|
||||||
|
'client_secret' => $this->authOidcClientSecret,
|
||||||
|
'bypass_form_login' => $this->authOidcBypassFormLogin,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,29 +2,30 @@
|
|||||||
|
|
||||||
namespace App\User\Framework\Controller\Web;
|
namespace App\User\Framework\Controller\Web;
|
||||||
|
|
||||||
|
use App\Base\ConfigResolver;
|
||||||
use App\User\Framework\Repository\UserRepository;
|
use App\User\Framework\Repository\UserRepository;
|
||||||
use App\User\Framework\Security\OidcUserProvider;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Drenso\OidcBundle\Exception\OidcException;
|
|
||||||
use Drenso\OidcBundle\OidcClientInterface;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Bundle\SecurityBundle\Security;
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|
||||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||||
|
|
||||||
class LoginController extends AbstractController
|
class LoginController extends AbstractController
|
||||||
{
|
{
|
||||||
#[Route(path: '/login', name: 'app_login')]
|
#[Route(path: '/login', name: 'app_login')]
|
||||||
public function login(AuthenticationUtils $authenticationUtils, UserRepository $userRepository): Response
|
public function login(ConfigResolver $config, AuthenticationUtils $authenticationUtils, UserRepository $userRepository): Response
|
||||||
{
|
{
|
||||||
if ((new ArrayCollection($userRepository->findAll()))->count() === 0) {
|
if ((new ArrayCollection($userRepository->findAll()))->count() === 0) {
|
||||||
return $this->redirectToRoute('app_getting_started');
|
return $this->redirectToRoute('app_getting_started');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($config->authIs('oidc') && $config->bypassFormLogin()) {
|
||||||
|
return $this->redirectToRoute('app_login_oidc');
|
||||||
|
}
|
||||||
|
|
||||||
// get the login error if there is one
|
// get the login error if there is one
|
||||||
$error = $authenticationUtils->getLastAuthenticationError();
|
$error = $authenticationUtils->getLastAuthenticationError();
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ class LoginController extends AbstractController
|
|||||||
$lastUsername = $authenticationUtils->getLastUsername();
|
$lastUsername = $authenticationUtils->getLastUsername();
|
||||||
|
|
||||||
return $this->render('user/login.html.twig', [
|
return $this->render('user/login.html.twig', [
|
||||||
|
'show_oidc_button' => $config->authIs('oidc'),
|
||||||
'last_username' => $lastUsername,
|
'last_username' => $lastUsername,
|
||||||
'error' => $error,
|
'error' => $error,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\User\Framework\Controller\Web;
|
namespace App\User\Framework\Controller\Web;
|
||||||
|
|
||||||
|
use App\Base\ConfigResolver;
|
||||||
use Drenso\OidcBundle\OidcClientInterface;
|
use Drenso\OidcBundle\OidcClientInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Bundle\SecurityBundle\Security;
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
@@ -11,16 +12,29 @@ use Symfony\Component\Routing\Attribute\Route;
|
|||||||
|
|
||||||
class LoginOidcController extends AbstractController
|
class LoginOidcController extends AbstractController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private ConfigResolver $configResolver,
|
||||||
|
) {}
|
||||||
|
|
||||||
#[Route('/login/oidc', name: 'app_login_oidc')]
|
#[Route('/login/oidc', name: 'app_login_oidc')]
|
||||||
public function oidcStart(OidcClientInterface $oidcClient): RedirectResponse
|
public function oidcStart(OidcClientInterface $oidcClient): RedirectResponse
|
||||||
{
|
{
|
||||||
|
if (false === $this->configResolver->authIs('oidc')) {
|
||||||
|
throw new \Exception('You must configure the OIDC environment variables before logging in at this route.');
|
||||||
|
}
|
||||||
|
|
||||||
// Redirect to authorization @ OIDC provider
|
// Redirect to authorization @ OIDC provider
|
||||||
return $oidcClient->generateAuthorizationRedirect();
|
return $oidcClient->generateAuthorizationRedirect(scopes: ['openid', 'profile']);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/login/oidc/auth', name: 'app_login_oidc_auth')]
|
#[Route('/login/oidc/auth', name: 'app_login_oidc_auth')]
|
||||||
public function oidcAuthenticate(): RedirectResponse
|
public function oidcAuthenticate(): RedirectResponse
|
||||||
{
|
{
|
||||||
|
if (false === $this->configResolver->authIs('oidc')) {
|
||||||
|
throw new \Exception('You must configure the OIDC environment variables before logging in at this route.');
|
||||||
|
}
|
||||||
|
|
||||||
throw new \LogicException('This method can be blank - it will be intercepted by the "oidc" key on your firewall.');
|
throw new \LogicException('This method can be blank - it will be intercepted by the "oidc" key on your firewall.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use Drenso\OidcBundle\Exception\OidcException;
|
|||||||
use Drenso\OidcBundle\Model\OidcTokens;
|
use Drenso\OidcBundle\Model\OidcTokens;
|
||||||
use Drenso\OidcBundle\Model\OidcUserData;
|
use Drenso\OidcBundle\Model\OidcUserData;
|
||||||
use Drenso\OidcBundle\Security\UserProvider\OidcUserProviderInterface;
|
use Drenso\OidcBundle\Security\UserProvider\OidcUserProviderInterface;
|
||||||
|
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||||
use Symfony\Component\Security\Core\User\OidcUser;
|
use Symfony\Component\Security\Core\User\OidcUser;
|
||||||
@@ -25,8 +26,9 @@ class OidcUserProvider implements OidcUserProviderInterface
|
|||||||
|
|
||||||
if (null === $user) {
|
if (null === $user) {
|
||||||
$user = new User()
|
$user = new User()
|
||||||
->setEmail($userData->getEmail())
|
->setEmail(!empty($userData->getEmail()) ? $userData->getEmail() : $userData->getSub())
|
||||||
->setName($userData->getFullName())
|
->setName(!empty($userData->getFullName()) ? $userData->getFullName() : $userData->getGivenName())
|
||||||
|
->setPassword('n/a')
|
||||||
;
|
;
|
||||||
$this->userRepository->getEntityManager()->persist($user);
|
$this->userRepository->getEntityManager()->persist($user);
|
||||||
$this->userRepository->getEntityManager()->flush();
|
$this->userRepository->getEntityManager()->flush();
|
||||||
|
|||||||
@@ -52,10 +52,16 @@
|
|||||||
<button type="submit" class="bg-green-600/40 px-1.5 py-1 w-full rounded-md text-gray-50 backdrop-filter backdrop-blur-sm border-2 border-green-500 hover:bg-green-700/40">
|
<button type="submit" class="bg-green-600/40 px-1.5 py-1 w-full rounded-md text-gray-50 backdrop-filter backdrop-blur-sm border-2 border-green-500 hover:bg-green-700/40">
|
||||||
Sign in
|
Sign in
|
||||||
</button>
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if show_oidc_button == "oidc" %}
|
||||||
|
<a href="{{ path('app_login_oidc') }}" class="bg-sky-950/60 px-1.5 py-1 w-full rounded-md text-gray-50 text-center backdrop-filter backdrop-blur-sm border-2 border-gray-950 hover:bg-orange-700/40">
|
||||||
|
Sign in with OIDC
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<a href="{{ path('app_forgot_password_request') }}">Forgot password?</a>
|
<a href="{{ path('app_forgot_password_request') }}">Forgot password?</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user