Skip to content

Commit c7a93fd

Browse files
committed
Merge #16 Telekom bearer token: Handling V31
2 parents bb47dc2 + df50e71 commit c7a93fd

File tree

9 files changed

+1285
-0
lines changed

9 files changed

+1285
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace OCA\UserOIDC\MagentaBearer;
4+
5+
use Exception;
6+
7+
class InvalidTokenException extends Exception {
8+
}

lib/MagentaBearer/MBackend.php

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
6+
*
7+
* @author Roeland Jago Douma <roeland@famdouma.nl>
8+
*
9+
* @license GNU AGPL version 3 or any later version
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Affero General Public License as
13+
* published by the Free Software Foundation, either version 3 of the
14+
* License, or (at your option) any later version.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU Affero General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Affero General Public License
22+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23+
*
24+
*/
25+
26+
namespace OCA\UserOIDC\MagentaBearer;
27+
28+
use OCA\UserOIDC\AppInfo\Application;
29+
use OCA\UserOIDC\Db\Provider;
30+
use OCA\UserOIDC\Db\ProviderMapper;
31+
use OCA\UserOIDC\Db\UserMapper;
32+
use OCA\UserOIDC\Event\TokenValidatedEvent;
33+
use OCA\UserOIDC\Service\DiscoveryService;
34+
use OCA\UserOIDC\Service\ProviderService;
35+
use OCA\UserOIDC\Service\ProvisioningDeniedException;
36+
use OCA\UserOIDC\Service\ProvisioningEventService;
37+
use OCA\UserOIDC\User\AbstractOidcBackend;
38+
use OCP\EventDispatcher\IEventDispatcher;
39+
use OCP\IConfig;
40+
use OCP\IRequest;
41+
use OCP\ISession;
42+
use OCP\IURLGenerator;
43+
use OCP\IUserManager;
44+
use OCP\Security\ICrypto;
45+
use Psr\Log\LoggerInterface;
46+
47+
class MBackend extends AbstractOidcBackend {
48+
49+
/**
50+
* @var TokenService
51+
*/
52+
protected $mtokenService;
53+
54+
/**
55+
* @var ProvisioningEventService
56+
*/
57+
protected $provisioningService;
58+
59+
/**
60+
* @var ICrypto
61+
*/
62+
protected $crypto;
63+
64+
public function __construct(IConfig $config,
65+
UserMapper $userMapper,
66+
LoggerInterface $logger,
67+
IRequest $request,
68+
ISession $session,
69+
IURLGenerator $urlGenerator,
70+
IEventDispatcher $eventDispatcher,
71+
DiscoveryService $discoveryService,
72+
ProviderMapper $providerMapper,
73+
ProviderService $providerService,
74+
IUserManager $userManager,
75+
ICrypto $crypto,
76+
TokenService $mtokenService,
77+
ProvisioningEventService $provisioningService,
78+
) {
79+
parent::__construct($config, $userMapper, $logger, $request, $session,
80+
$urlGenerator, $eventDispatcher, $discoveryService,
81+
$providerMapper, $providerService, $userManager);
82+
83+
$this->mtokenService = $mtokenService;
84+
$this->provisioningService = $provisioningService;
85+
$this->crypto = $crypto;
86+
}
87+
88+
public function getBackendName(): string {
89+
return Application::APP_ID . '\\MagentaBearer';
90+
}
91+
92+
/**
93+
* Backend is activated if header bearer token is detected.
94+
*
95+
* @return bool ture if bearer header found
96+
*/
97+
public function isSessionActive(): bool {
98+
// if this returns true, getCurrentUserId is called
99+
// not sure if we should rather to the validation in here as otherwise it might fail for other backends or bave other side effects
100+
$headerToken = $this->request->getHeader(Application::OIDC_API_REQ_HEADER);
101+
// session is active if we have a bearer token (API request) OR if we logged in via user_oidc (we have a provider ID in the session)
102+
return (preg_match('/^\s*bearer\s+/i', $headerToken) != false);
103+
}
104+
105+
/**
106+
* Return the id of the current user
107+
* @return string
108+
*/
109+
public function getCurrentUserId(): string {
110+
// get the bearer token from headers
111+
$headerToken = $this->request->getHeader(Application::OIDC_API_REQ_HEADER);
112+
$headerToken = preg_replace('/^bearer\s+/i', '', $headerToken);
113+
if ($headerToken === '') {
114+
$this->logger->debug('No Bearer token');
115+
return '';
116+
}
117+
118+
$providers = $this->providerMapper->getProviders();
119+
if (count($providers) === 0) {
120+
$this->logger->debug('no OIDC providers');
121+
return '';
122+
}
123+
124+
// we implement only Telekom behavior (which includes auto-provisioning)
125+
// so we neglect switches from the upstream Nexrcloud oidc handling
126+
127+
// try to validate with all providers
128+
foreach ($providers as $provider) {
129+
if ($this->providerService->getSetting($provider->getId(), ProviderService::SETTING_CHECK_BEARER, '0') === '1') {
130+
try {
131+
$sharedSecret = $this->crypto->decrypt($provider->getBearerSecret());
132+
$bearerToken = $this->mtokenService->decryptToken($headerToken, $sharedSecret);
133+
$this->mtokenService->verifySignature($bearerToken, $sharedSecret);
134+
$payload = $this->mtokenService->decode($bearerToken);
135+
$this->mtokenService->verifyClaims($payload, ['http://auth.magentacloud.de']);
136+
} catch (InvalidTokenException $eToken) {
137+
// there is
138+
$this->logger->debug('Invalid token:' . $eToken->getMessage() . '. Trying another provider.');
139+
continue;
140+
} catch (SignatureException $eSignature) {
141+
// only the key seems not to fit, so try the next provider
142+
$this->logger->debug($eSignature->getMessage() . '. Trying another provider.');
143+
continue;
144+
} catch (\Throwable $e) {
145+
// there is
146+
$this->logger->debug('General non matching provider problem:' . $e->getMessage());
147+
continue;
148+
}
149+
150+
$uidAttribute = $this->providerService->getSetting($provider->getId(), ProviderService::SETTING_MAPPING_UID, 'sub');
151+
$userId = $payload->{$uidAttribute};
152+
if ($userId === null) {
153+
$this->logger->debug('No extractable user id, check mapping!');
154+
return '';
155+
}
156+
157+
// check bearercache here, not skipping validation for security reasons
158+
159+
// Telekom bearer does not support refersh_token, so the pupose of TokenValidatedEvent is not given,
160+
// but could produce trouble if not send with the field, apart from performance aspects.
161+
//
162+
// $discovery = $this->discoveryService->obtainDiscovery($provider);
163+
// $this->eventDispatcher->dispatchTyped(new TokenValidatedEvent(['token' => $payload], $provider, $discovery));
164+
165+
try {
166+
$this->provisioningService->provisionUser($userId, $provider->getId(), $payload);
167+
$this->checkFirstLogin($userId); // create the folders same as on web login
168+
return $userId;
169+
} catch (ProvisioningDeniedException $denied) {
170+
$this->logger->error('Bearer token access denied: ' . $denied->getMessage());
171+
return '';
172+
}
173+
}
174+
}
175+
176+
$this->logger->debug('Could not find provider for token');
177+
return '';
178+
}
179+
180+
/**
181+
* FIXXME: send proper error status from BAckend errors
182+
*
183+
* This function sets an https status code here (early in the failing backend operation)
184+
* to pass on bearer errors cleanly with correct status code and a readable reason
185+
*
186+
* For this, there is a "tricky" setting of a header needed to make it working in all
187+
* known situations, see
188+
* https://stackoverflow.com/questions/3258634/php-how-to-send-http-response-code
189+
*/
190+
// protected function sendHttpStatus(int $httpStatusCode, string $httpStatusMsg) {
191+
// $phpSapiName = substr(php_sapi_name(), 0, 3);
192+
// if ($phpSapiName == 'cgi' || $phpSapiName == 'fpm') {
193+
// header('Status: ' . $httpStatusCode . ' ' . $httpStatusMsg);
194+
// } else {
195+
// $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0';
196+
// header($protocol . ' ' . $httpStatusCode . ' ' . $httpStatusMsg);
197+
// }
198+
// }
199+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
namespace OCA\UserOIDC\MagentaBearer;
4+
5+
class SignatureException extends InvalidTokenException {
6+
}

0 commit comments

Comments
 (0)