Skip to content

Commit 94bef0b

Browse files
committed
5124: Making progress
1 parent 8391b71 commit 94bef0b

15 files changed

Lines changed: 335 additions & 57 deletions

web/profiles/custom/os2loop/modules/os2loop_login_hack/.gitignore renamed to web/profiles/custom/os2loop/modules/os2loop_cura_login/.gitignore

File renamed without changes.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Cura login
2+
3+
Use `https://os2loop.example.com/os2loop-cura-login/start` as `linkURL` (or is it `formPostUrl`?) in the Cura link
4+
configuration.
5+
6+
## Example
7+
8+
``` shell
9+
curl "http://$(docker compose port nginx 8080)/os2loop-cura-login/start"
10+
```
11+
12+
13+
``` shell
14+
drush os2loop-cura-login:get-login-url --help
15+
```
16+
17+
``` shell
18+
drush --uri='http://nginx:8080' os2loop-cura-login:get-login-url test@example.com --secret=$(drush config:get --format string os2loop_cura_login.settings signing_secret --include-overridden) --algorithm=$(drush config:get --format string os2loop_cura_login.settings signing_algorithm --include-overridden)
19+
```
20+
21+
22+
## Development and debugging
23+
24+
``` php
25+
# settings.local.php
26+
$config['os2loop_cura_login.settings']['log_level'] = \Drupal\Core\Logger\RfcLogLevel::DEBUG;
27+
```

web/profiles/custom/os2loop/modules/os2loop_login_hack/composer.json renamed to web/profiles/custom/os2loop/modules/os2loop_cura_login/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "os2loop/os2loop_login_hack",
3-
"description": "drupal/os2loop_login_hack",
2+
"name": "os2loop/os2loop_cura_login",
3+
"description": "drupal/os2loop_cura_login",
44
"license": "GPL-2.0+",
55
"type": "os2loop-custom-module",
66
"authors": [
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Schema for the configuration files of the OS2Loop Cura login module.
2+
os2loop_cura_login.settings:
3+
type: config_object
4+
label: 'OS2Loop Cura login settings'
5+
mapping:
6+
example:
7+
type: string
8+
label: 'Example'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: 'OS2Loop Cura login'
2+
type: module
3+
description: 'OS2Loop Cura login'
4+
package: 'OS2Loop'
5+
core_version_requirement: ^10 || ^11
6+
dependencies:
7+
- drupal:user
8+
9+
configure: os2loop_cura_login.settings
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
os2loop_cura_login.settings:
2+
title: 'OS2Loop Cura login settings'
3+
route_name: os2loop_cura_login.settings
4+
description: 'Configure OS2Loop Cura login settings'
5+
parent: os2loop.group.admin
6+
weight: 100
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
os2loop_cura_login.start:
2+
path: '/os2loop-cura-login/start'
3+
defaults:
4+
_title: 'Start login hack'
5+
_controller: '\Drupal\os2loop_cura_login\Controller\Os2loopCuraLoginController::start'
6+
methods: [GET, POST]
7+
requirements:
8+
_role: 'anonymous'
9+
10+
os2loop_cura_login.authenticate:
11+
path: '/os2loop-login-hack/authenticate'
12+
defaults:
13+
_title: 'Authenticate'
14+
_controller: '\Drupal\os2loop_cura_login\Controller\Os2loopCuraLoginController::authenticate'
15+
methods: [GET]
16+
requirements:
17+
_role: 'anonymous'
18+
19+
os2loop_cura_login.settings:
20+
path: '/admin/config/os2loop/os2loop_cura_login/settings'
21+
defaults:
22+
_form: '\Drupal\os2loop_cura_login\Form\SettingsForm'
23+
_title: 'OS2Loop Cura login settings'
24+
requirements:
25+
_permission: 'administer site settings'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
services:
2+
logger.channel.os2loop_cura_login:
3+
parent: logger.channel_base
4+
arguments: ['os2loop_cura_login']

web/profiles/custom/os2loop/modules/os2loop_login_hack/src/Controller/Os2loopLoginHackController.php renamed to web/profiles/custom/os2loop/modules/os2loop_cura_login/src/Controller/Os2loopCuraLoginController.php

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22

33
declare(strict_types=1);
44

5-
namespace Drupal\os2loop_login_hack\Controller;
5+
namespace Drupal\os2loop_cura_login\Controller;
66

77
use Drupal\Component\Datetime\TimeInterface;
8+
use Drupal\Core\Config\ImmutableConfig;
89
use Drupal\Core\Controller\ControllerBase;
9-
use Drupal\Core\Entity\EntityTypeManagerInterface;
10+
use Drupal\Core\Logger\RfcLogLevel;
1011
use Drupal\Core\Routing\TrustedRedirectResponse;
1112
use Drupal\Core\Url;
1213
use Drupal\user\Entity\User;
1314
use Drupal\user\UserStorageInterface;
1415
use Firebase\JWT\JWT;
1516
use Firebase\JWT\Key;
1617
use Psr\Log\LoggerInterface;
18+
use Psr\Log\LoggerTrait;
19+
use Psr\Log\LogLevel;
1720
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1821
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
1922
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -22,45 +25,55 @@
2225
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
2326

2427
/**
25-
* Returns responses for os2loop_login_hack routes.
28+
* Returns responses for os2loop_cura_login routes.
2629
*/
27-
final class Os2loopLoginHackController extends ControllerBase {
28-
private const JWT_KEY = 'os2loop_login_hack';
30+
final class Os2loopCuraLoginController extends ControllerBase {
31+
use LoggerTrait;
32+
33+
private const JWT_KEY = 'os2loop_cura_login';
2934

3035
/**
3136
* The user storage.
3237
*/
3338
private readonly UserStorageInterface $userStorage;
3439

40+
/**
41+
* The module config.
42+
*/
43+
private readonly ImmutableConfig $config;
44+
3545
/**
3646
* Constructor.
3747
*/
3848
public function __construct(
39-
EntityTypeManagerInterface $entityTypeManager,
4049
private readonly TimeInterface $time,
41-
#[Autowire(service: 'logger.channel.os2loop_login_hack')]
50+
#[Autowire(service: 'logger.channel.os2loop_cura_login')]
4251
private readonly LoggerInterface $logger,
4352
) {
44-
$this->userStorage = $entityTypeManager->getStorage('user');
53+
$this->userStorage = $this->entityTypeManager()->getStorage('user');
54+
$this->config = $this->config('os2loop_cura_login.settings');
4555
}
4656

4757
/**
4858
* Start user authentication.
4959
*/
5060
public function start(Request $request): Response {
5161
try {
52-
$this->logger->info('Request: @request', [
62+
$this->info('Request: @request', [
5363
'@request' => json_encode([
5464
'method' => $request->getMethod(),
5565
'query' => $request->query->all(),
5666
'content' => (string) $request->getContent(),
5767
]),
5868
]);
5969

60-
return new Response('https://example.com/cura-login');
70+
$jwt = Request::METHOD_POST === $request->getMethod()
71+
? $request->getContent()
72+
: $request->query->getString($this->config->get('token_param_name') ?? 'token');
73+
74+
$payload = (array) JWT::decode($jwt, new Key($this->config->get('signing_secret'), $this->config->get('signing_algorithm')));
6175

62-
$data = json_decode($request->getContent(), associative: TRUE, flags: JSON_THROW_ON_ERROR);
63-
$username = $data['username'] ?? NULL;
76+
$username = $payload['username'] ?? NULL;
6477
if (empty($username)) {
6578
throw new BadRequestHttpException('Missing username');
6679
}
@@ -87,7 +100,7 @@ public function start(Request $request): Response {
87100
];
88101
$jwt = JWT::encode($payload, self::JWT_KEY, 'HS256');
89102

90-
$url = Url::fromRoute('os2loop_login_hack.authenticate', [
103+
$url = Url::fromRoute('os2loop_cura_login.authenticate', [
91104
'username' => $username,
92105
'jwt' => $jwt,
93106
])->setAbsolute()->toString(TRUE)->getGeneratedUrl();
@@ -98,7 +111,7 @@ public function start(Request $request): Response {
98111
]);
99112
}
100113
catch (\Exception $exception) {
101-
$this->logger->error('start: @message', ['@message' => $exception->getMessage(), $exception]);
114+
$this->error('start: @message', ['@message' => $exception->getMessage(), $exception]);
102115
throw new BadRequestException($exception->getMessage());
103116
}
104117
}
@@ -136,7 +149,7 @@ public function authenticate(Request $request): Response {
136149
return new TrustedRedirectResponse($url);
137150
}
138151
catch (\Exception $exception) {
139-
$this->logger->error('start: @message', ['@message' => $exception->getMessage(), $exception]);
152+
$this->error('start: @message', ['@message' => $exception->getMessage(), $exception]);
140153
throw new BadRequestException($exception->getMessage());
141154
}
142155
}
@@ -174,4 +187,23 @@ private function getUserinfo(User $user): array {
174187
];
175188
}
176189

190+
public function log($level, \Stringable|string $message, array $context = []): void
191+
{
192+
// Lifted from LoggerChannel
193+
$levels = [
194+
LogLevel::EMERGENCY => RfcLogLevel::EMERGENCY,
195+
LogLevel::ALERT => RfcLogLevel::ALERT,
196+
LogLevel::CRITICAL => RfcLogLevel::CRITICAL,
197+
LogLevel::ERROR => RfcLogLevel::ERROR,
198+
LogLevel::WARNING => RfcLogLevel::WARNING,
199+
LogLevel::NOTICE => RfcLogLevel::NOTICE,
200+
LogLevel::INFO => RfcLogLevel::INFO,
201+
LogLevel::DEBUG => RfcLogLevel::DEBUG,
202+
];
203+
$rfcLogLevel = $levels[$level] ?? RfcLogLevel::ERROR;
204+
if ((int)$this->config->get('log_level') >= $rfcLogLevel) {
205+
$this->logger->log($level, $message, $context);
206+
}
207+
}
208+
177209
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace Drupal\os2loop_cura_login\Drush\Commands;
4+
5+
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
6+
use Drupal\Component\Datetime\TimeInterface;
7+
use Drupal\Core\DependencyInjection\AutowireTrait;
8+
use Drupal\Core\Url;
9+
use Drupal\Core\Utility\Token;
10+
use Drush\Attributes as CLI;
11+
use Drush\Commands\DrushCommands;
12+
use Firebase\JWT\JWT;
13+
use Http\Client\HttpClient;
14+
use Psr\Http\Client\ClientInterface;
15+
use Symfony\Component\DependencyInjection\ContainerInterface;
16+
use Symfony\Component\HttpFoundation\Request;
17+
18+
/**
19+
* A Drush commandfile.
20+
*/
21+
final class Os2loopCuraLoginCommands extends DrushCommands
22+
{
23+
use AutowireTrait;
24+
25+
/**
26+
* Constructs an Os2loopCuraLoginCommands object.
27+
*/
28+
public function __construct(
29+
private readonly TimeInterface $time,
30+
private readonly ClientInterface $httpClient,
31+
) {
32+
parent::__construct();
33+
}
34+
35+
/**
36+
* Command description here.
37+
*/
38+
#[CLI\Command(name: 'os2loop-cura-login:get-login-url')]
39+
#[CLI\Argument(name: 'username', description: 'The username.')]
40+
#[CLI\Option(name: 'post', description: 'Use POST to get the login URL')]
41+
#[CLI\Usage(name: 'os2loop-cura-login:get-login-url test@example.com', description: 'Get login URL')]
42+
public function commandName(
43+
$username,
44+
$options = [
45+
'get' => null,
46+
'secret' => null,
47+
'algorithm' => 'HS256',
48+
]
49+
) {
50+
// https://github.com/firebase/php-jwt?tab=readme-ov-file#example
51+
$payload = [
52+
// Issued at.
53+
'iat' => $this->time->getRequestTime(),
54+
// Expire af 60 seconds.
55+
'exp' => $this->time->getRequestTime() + 60,
56+
'username' => $username,
57+
];
58+
$jwt = JWT::encode($payload, $options['secret'], $options['algorithm']);
59+
60+
$routeParameters = [];
61+
$requestOptions = [];
62+
if ($name = $options['get']) {
63+
$method = Request::METHOD_GET;
64+
$routeParameters[$name] = $jwt;
65+
} else {
66+
$method = Request::METHOD_POST;
67+
$requestOptions['body'] = $jwt;
68+
}
69+
$url = Url::fromRoute('os2loop_cura_login.start', $routeParameters)->setAbsolute()->toString(true)->getGeneratedUrl();
70+
$this->io()->writeln($method === Request::METHOD_POST
71+
? sprintf('POST\'ing to %s', $url)
72+
: sprintf('GET\'ing %s', $url),
73+
);
74+
$request = $this->httpClient->request($method, $url, $requestOptions);
75+
76+
header('content-type: text/plain');
77+
echo var_export([
78+
$url,
79+
$request->getStatusCode(),
80+
$request->getBody()->getContents(),
81+
], true);
82+
die(__FILE__ . ':' . __LINE__ . ':' . __METHOD__);
83+
}
84+
85+
}

0 commit comments

Comments
 (0)