Skip to content

Commit f1fcb10

Browse files
committed
5124: Experiments
1 parent b82bbe5 commit f1fcb10

8 files changed

Lines changed: 310 additions & 2 deletions

File tree

composer.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@
5959
"drupal/viewsreference": "^2.0@beta",
6060
"drupal/xls_serialization": "^2.0",
6161
"drush/drush": "^12.2",
62-
"jjj/chosen": "2.2.1"
62+
"jjj/chosen": "2.2.1",
63+
"os2loop/os2loop_login_hack": "^1.0"
6364
},
6465
"require-dev": {
6566
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
@@ -111,6 +112,16 @@
111112
}
112113
}
113114
},
115+
"os2loop/os2loop_login_hack": {
116+
"type": "path",
117+
"url": "web/profiles/custom/os2loop/modules/os2loop_login_hack",
118+
"options": {
119+
"symlink": false,
120+
"versions": {
121+
"os2loop/os2loop_login_hack": "1.0-dev"
122+
}
123+
}
124+
},
114125
"drupal/views_flag_refresh": {
115126
"type": "package",
116127
"package": {

composer.lock

Lines changed: 88 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
composer.lock
2+
vendor/
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "os2loop/os2loop_login_hack",
3+
"description": "drupal/os2loop_login_hack",
4+
"license": "GPL-2.0+",
5+
"type": "os2loop-custom-module",
6+
"authors": [
7+
{
8+
"name": "Mikkel Ricky",
9+
"email": "rimi@aarhus.dk"
10+
}
11+
],
12+
"require": {
13+
"firebase/php-jwt": "^6.11"
14+
}
15+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: 'os2loop_login_hack'
2+
type: module
3+
description: 'os2loop_login_hack'
4+
package: Custom
5+
core_version_requirement: ^10 || ^11
6+
dependencies:
7+
- drupal:user
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
os2loop_login_hack.start:
2+
path: '/os2loop-login-hack/start'
3+
defaults:
4+
_title: 'Start login hack'
5+
_controller: '\Drupal\os2loop_login_hack\Controller\Os2loopLoginHackController::start'
6+
methods: [POST]
7+
requirements:
8+
_role: 'anonymous'
9+
10+
os2loop_login_hack.authenticate:
11+
path: '/os2loop-login-hack/authenticate'
12+
defaults:
13+
_title: 'Authenticate'
14+
_controller: '\Drupal\os2loop_login_hack\Controller\Os2loopLoginHackController::authenticate'
15+
methods: [GET]
16+
requirements:
17+
_role: 'anonymous'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
services:
2+
logger.channel.os2loop_login_hack:
3+
parent: logger.channel_base
4+
arguments: ['os2loop_login_hack']
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Drupal\os2loop_login_hack\Controller;
6+
7+
use Drupal\Component\Datetime\TimeInterface;
8+
use Drupal\Core\Controller\ControllerBase;
9+
use Drupal\Core\Entity\EntityTypeManagerInterface;
10+
use Drupal\Core\Routing\TrustedRedirectResponse;
11+
use Drupal\Core\Url;
12+
use Drupal\user\Entity\User;
13+
use Drupal\user\UserStorageInterface;
14+
use Firebase\JWT\JWT;
15+
use Firebase\JWT\Key;
16+
use Psr\Log\LoggerInterface;
17+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
18+
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
19+
use Symfony\Component\HttpFoundation\JsonResponse;
20+
use Symfony\Component\HttpFoundation\Request;
21+
use Symfony\Component\HttpFoundation\Response;
22+
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
23+
24+
/**
25+
* Returns responses for os2loop_login_hack routes.
26+
*/
27+
final class Os2loopLoginHackController extends ControllerBase {
28+
private const JWT_KEY = 'os2loop_login_hack';
29+
30+
/**
31+
* The user storage.
32+
*/
33+
private readonly UserStorageInterface $userStorage;
34+
35+
/**
36+
* Constructor.
37+
*/
38+
public function __construct(
39+
EntityTypeManagerInterface $entityTypeManager,
40+
private readonly TimeInterface $time,
41+
#[Autowire(service: 'logger.channel.os2loop_login_hack')]
42+
private readonly LoggerInterface $logger,
43+
) {
44+
$this->userStorage = $entityTypeManager->getStorage('user');
45+
}
46+
47+
/**
48+
* Start user authentication.
49+
*/
50+
public function start(Request $request): Response {
51+
try {
52+
$data = json_decode($request->getContent(), associative: TRUE, flags: JSON_THROW_ON_ERROR);
53+
$username = $data['username'] ?? NULL;
54+
if (empty($username)) {
55+
throw new BadRequestHttpException('Missing username');
56+
}
57+
58+
$user = $this->loadUser($username);
59+
if (empty($user)) {
60+
// Don't disclose whether or not the user exists.
61+
throw new BadRequestHttpException();
62+
}
63+
64+
// Check that we can get userinfo.
65+
$userinfo = $this->getUserinfo($user);
66+
if (empty($userinfo)) {
67+
throw new BadRequestHttpException();
68+
}
69+
70+
// https://github.com/firebase/php-jwt?tab=readme-ov-file#example
71+
$payload = [
72+
// Issued at.
73+
'iat' => $this->time->getRequestTime(),
74+
// Expire af 60 seconds.
75+
'exp' => $this->time->getRequestTime() + 60,
76+
'username' => $username,
77+
];
78+
$jwt = JWT::encode($payload, self::JWT_KEY, 'HS256');
79+
80+
$url = Url::fromRoute('os2loop_login_hack.authenticate', [
81+
'username' => $username,
82+
'jwt' => $jwt,
83+
])->setAbsolute()->toString(TRUE)->getGeneratedUrl();
84+
85+
return new JsonResponse([
86+
'authenticate_url' => $url,
87+
'jwt' => $jwt,
88+
]);
89+
}
90+
catch (\Exception $exception) {
91+
$this->logger->error('start: @message', ['@message' => $exception->getMessage(), $exception]);
92+
throw new BadRequestException($exception->getMessage());
93+
}
94+
}
95+
96+
/**
97+
* Authenticate user.
98+
*/
99+
public function authenticate(Request $request): Response {
100+
try {
101+
$username = $request->get('username');
102+
$jwt = $request->get('jwt');
103+
if (empty($username) || empty($jwt)) {
104+
throw new BadRequestHttpException();
105+
}
106+
107+
$payload = (array) JWT::decode($jwt, new Key(self::JWT_KEY, 'HS256'));
108+
$username = $payload['username'] ?? NULL;
109+
if (empty($username)) {
110+
throw new BadRequestHttpException();
111+
}
112+
113+
$user = $this->loadUser($username);
114+
if (empty($user)) {
115+
// Don't disclose whether or not the user exists.
116+
throw new BadRequestHttpException();
117+
}
118+
119+
$this->updateUser($user);
120+
121+
user_login_finalize($user);
122+
123+
$url = Url::fromRoute('<front>')->setAbsolute()->toString(TRUE)->getGeneratedUrl();
124+
$this->messenger()->addStatus($this->t('Welcome @user.', ['@user' => $user->getDisplayName()]));
125+
126+
return new TrustedRedirectResponse($url);
127+
}
128+
catch (\Exception $exception) {
129+
$this->logger->error('start: @message', ['@message' => $exception->getMessage(), $exception]);
130+
throw new BadRequestException($exception->getMessage());
131+
}
132+
}
133+
134+
/**
135+
* Load user by username.
136+
*
137+
* @param string $username
138+
* The username.
139+
*
140+
* @return \Drupal\user\Entity\User|null
141+
* The user if any.
142+
*/
143+
private function loadUser(string $username): ?User {
144+
$users = $this->userStorage->loadByProperties(['name' => $username]);
145+
146+
return reset($users) ?: NULL;
147+
}
148+
149+
/**
150+
* Update user with info from IdP.
151+
*/
152+
private function updateUser(User $user): User {
153+
// $userinfo = $this->getUserinfo($user);
154+
// @todo Update user.
155+
return $user;
156+
}
157+
158+
/**
159+
* Get user info from userinfo endpoint.
160+
*/
161+
private function getUserinfo(): array {
162+
return [];
163+
}
164+
165+
}

0 commit comments

Comments
 (0)