FOSSology  4.4.0
Open Source License Compliance by Open Source Software
AuthHelper.php
Go to the documentation of this file.
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2018, 2021 Siemens AG
4  SPDX-FileCopyrightText: © 2021-2022 Orange
5  Contributors: Piotr Pszczola, Bartlomiej Drozdz
6 
7  SPDX-License-Identifier: GPL-2.0-only
8 */
9 
18 namespace Fossology\UI\Api\Helper;
19 
20 use Firebase\JWT\CachedKeySet;
21 use Firebase\JWT\JWT;
22 use Firebase\JWT\Key;
27 use GuzzleHttp\Client;
28 use GuzzleHttp\Psr7\HttpFactory;
29 use Symfony\Component\Cache\Adapter\FilesystemAdapter;
30 use Symfony\Component\HttpFoundation\Session\Session;
31 use UnexpectedValueException;
32 
38 {
43  private $session;
48  private $userDao;
53  private $dbHelper;
54 
62  public function __construct(UserDao $userDao, Session $session,
63  DbHelper $dbhelper)
64  {
65  $this->userDao = $userDao;
66  $this->session = $session;
67  $this->dbHelper = $dbhelper;
68  if (!$this->session->isStarted()) {
69  $this->session->setName('Login');
70  $this->session->start();
71  }
72  JWT::$leeway = 30; // Set 30 seconds of leeway
73  }
74 
85  public function checkUsernameAndPassword($userName, $password)
86  {
87  $authPlugin = $GLOBALS["container"]->get("helper.restHelper")->getPlugin('auth');
88  return $authPlugin->checkUsernameAndPassword($userName, $password);
89  }
90 
101  public function verifyAuthToken($authHeader, &$userId, &$tokenScope)
102  {
103  global $SysConf;
104  $jwtTokenMatch = null;
105  $headerValid = preg_match(
106  "/^bearer (([a-zA-Z0-9\-\_\+\/\=]+)\.([a-zA-Z0-9\-\_\+\/\=]+)\.([a-zA-Z0-9\-\_\+\/\=]+))$/i",
107  $authHeader, $jwtTokenMatch);
108  if (! $headerValid) {
109  throw new HttpBadRequestException(
110  "Authorization header is malformed or empty.");
111  }
112  $jwtToken = $jwtTokenMatch[1];
113  $jwtTokenPayload = $jwtTokenMatch[3];
114  $jwtTokenPayloadDecoded = JWT::jsonDecode(
115  JWT::urlsafeB64Decode($jwtTokenPayload));
116 
117  $restToken = Auth::getRestTokenType();
118  if (($restToken & Auth::TOKEN_OAUTH) == Auth::TOKEN_OAUTH &&
119  property_exists($jwtTokenPayloadDecoded, 'iss') &&
120  $jwtTokenPayloadDecoded->{'iss'} == $SysConf['SYSCONFIG']['OidcIssuer']
121  ) {
122  $this->validateOauthLogin(
123  $jwtToken,
124  $userId,
125  $tokenScope
126  );
127  } else if (($restToken & Auth::TOKEN_TOKEN) == Auth::TOKEN_TOKEN &&
128  ! property_exists($jwtTokenPayloadDecoded, 'iss')
129  ) {
130  $this->validateTokenLogin(
131  $jwtToken,
132  $jwtTokenPayloadDecoded,
133  $userId,
134  $tokenScope
135  );
136  } else {
137  throw new HttpForbiddenException("Invalid token type sent.");
138  }
139 
140  $isUserActive = $this->userDao->isUserIdActive($userId);
141  if (!$isUserActive) {
142  throw new HttpForbiddenException("User inactive.");
143  }
144  }
145 
152  private function isDateExpired($date)
153  {
154  if (empty($date)) { // oauth clients do not have expiry
155  return false;
156  }
157  return strtotime("today") > strtotime($date);
158  }
159 
167  public function isTokenActive($valuesFromDb, $tokenId)
168  {
169  if ($valuesFromDb['active'] == "f") {
170  throw new HttpForbiddenException("Token expired.");
171  } elseif ($this->isDateExpired($valuesFromDb['expire_on']) &&
172  $valuesFromDb['active'] == "t") {
173  $this->dbHelper->invalidateToken($tokenId);
174  throw new HttpForbiddenException("Token expired.");
175  }
176  }
177 
182  public function getSession()
183  {
184  return $this->session;
185  }
186 
195  public function updateUserSession($userId, $scope, $groupName = null)
196  {
197  $authPlugin = $GLOBALS["container"]->get("helper.restHelper")->getPlugin('auth');
198  $user = $this->userDao->getUserByPk($userId);
199  $row = $this->userDao->getUserAndDefaultGroupByUserName($user["user_name"]);
200  if ($groupName !== null) {
201  $row['group_fk'] = $this->userDao->getGroupIdByName($groupName);
202  $row['group_name'] = $groupName;
203  }
204  $authPlugin->updateSession($row);
205  $this->getSession()->set('token_scope', $scope);
206  }
207 
218  public function generateJwtToken($expire, $created, $jti, $scope, $key)
219  {
220  $scope = array_search($scope, RestHelper::SCOPE_DB_MAP);
221  $newJwtToken = [
222  "exp" => strtotime($expire . " +1 day -1 second"), // To allow day level granularity
223  "nbf" => strtotime($created),
224  "jti" => base64_encode($jti),
225  "scope" => $scope
226  ];
227  return JWT::encode($newJwtToken, $key, 'HS256');
228  }
229 
236  public function getMaxTokenValidity()
237  {
238  return $this->dbHelper->getMaxTokenValidity();
239  }
240 
249  public function userHasGroupAccess($userId, $groupName)
250  {
251  $this->isGroupExisting($groupName);
252  $groupMap = $this->userDao->getUserGroupMap($userId);
253  $userHasGroupAccess = in_array($groupName, $groupMap, true);
254 
255  if (!$userHasGroupAccess) {
256  throw new HttpForbiddenException(
257  "User has no access to " . $groupName . " group");
258  }
259  }
260 
268  public function isGroupExisting($groupName)
269  {
270  if (empty($this->userDao->getGroupIdByName($groupName))) {
271  throw new HttpForbiddenException(
272  "Provided group:" . $groupName . " does not exist");
273  }
274  }
275 
290  private function validateOauthLogin($jwtToken, &$userId, &$tokenScope)
291  {
292  global $SysConf;
293  $jwks = $this->loadJwks();
294  try {
295  try {
296  $jwtTokenDecoded = JWT::decode(
297  $jwtToken,
298  $jwks
299  );
300  } catch (\Exception $e) {
301  throw new \UnexpectedValueException("JWKS: " . $e->getMessage());
302  }
303  $clientId = $jwtTokenDecoded->{$SysConf['SYSCONFIG']['OidcClientIdClaim']};
304  $tokenId = $this->dbHelper->getTokenIdFromClientId($clientId);
305  $dbRows = $this->dbHelper->getTokenKey($tokenId);
306 
307  if (empty($dbRows)) {
308  throw new \UnexpectedValueException("Invalid token sent.", 403);
309  }
310  $this->isTokenActive($dbRows, $tokenId);
311  $userId = $dbRows['user_fk'];
312  $tokenScope = $dbRows['token_scope'];
313  if ($tokenScope == "w") {
314  $tokenScope = "write";
315  } elseif ($tokenScope == "r") {
316  $tokenScope = "read";
317  }
318  } catch (\UnexpectedValueException $e) {
319  throw new HttpForbiddenException($e->getMessage(), $e);
320  }
321  }
322 
333  public static function loadJwks()
334  {
335  global $SysConf;
336  $cacheDir = array_key_exists('CACHEDIR', $GLOBALS) ? $GLOBALS['CACHEDIR'] : null;
337  $cacheDuration = 60 * 60 * 24; // 24 hours
338  $algInject = $SysConf['SYSCONFIG']['OidcJwkAlgInject'];
339  if (empty($algInject)) {
340  $algInject = null;
341  }
342 
343  $proxy = [];
344 
345  if (
346  array_key_exists('http_proxy', $SysConf['FOSSOLOGY']) &&
347  !empty($SysConf['FOSSOLOGY']['http_proxy'])
348  ) {
349  $proxy['http'] = $SysConf['FOSSOLOGY']['http_proxy'];
350  }
351  if (
352  array_key_exists('https_proxy', $SysConf['FOSSOLOGY']) &&
353  !empty($SysConf['FOSSOLOGY']['https_proxy'])
354  ) {
355  $proxy['https'] = $SysConf['FOSSOLOGY']['https_proxy'];
356  }
357  if (
358  array_key_exists('no_proxy', $SysConf['FOSSOLOGY']) &&
359  !empty($SysConf['FOSSOLOGY']['no_proxy'])
360  ) {
361  $proxy['no'] = explode(',', $SysConf['FOSSOLOGY']['no_proxy']);
362  }
363 
364  $version = $SysConf['BUILD']['VERSION'];
365  $headers = ['User-Agent' => "fossology/$version"];
366 
367  $guzzleClient = new Client([
368  'http_errors' => false,
369  'proxy' => $proxy,
370  'headers' => $headers
371  ]);
372 
373  $httpFactory = new HttpFactory();
374 
375  $cacheItemPool = new FilesystemAdapter('rest', $cacheDuration, $cacheDir);
376 
377  return new CachedKeySet(
378  $SysConf['SYSCONFIG']['OidcJwksURL'],
379  $guzzleClient,
380  $httpFactory,
381  $cacheItemPool,
382  $cacheDuration,
383  true,
384  $algInject
385  );
386  }
387 
403  private function validateTokenLogin($jwtToken, $jwtTokenPayloadDecoded,
404  &$userId, &$tokenScope)
405  {
406  $jwtJti = $jwtTokenPayloadDecoded->{'jti'};
407  $jwtJti = base64_decode($jwtJti, true);
408  list ($tokenId, $userId) = explode(".", $jwtJti);
409 
410  $dbRows = $this->dbHelper->getTokenKey($tokenId);
411  if (empty($dbRows)) {
412  throw new HttpForbiddenException("Invalid token sent.");
413  }
414  $this->isTokenActive($dbRows, $tokenId);
415  try {
416  $jwtTokenDecoded = JWT::decode($jwtToken,
417  new Key($dbRows["token_key"], 'HS256'));
418  $tokenScope = $jwtTokenDecoded->{'scope'};
419  } catch (\UnexpectedValueException $e) {
420  throw new HttpForbiddenException($e->getMessage(), $e);
421  }
422  }
423 }
Contains the constants and helpers for authentication of user.
Definition: Auth.php:24
static getRestTokenType()
Definition: Auth.php:110
Fossology exception.
Definition: Exception.php:15
Provides helper methods for REST api.
Definition: AuthHelper.php:38
isGroupExisting($groupName)
Verify if given Group name exists.
Definition: AuthHelper.php:268
static loadJwks()
Load the JWK array.
Definition: AuthHelper.php:333
updateUserSession($userId, $scope, $groupName=null)
Update the session using updateSession().
Definition: AuthHelper.php:195
validateOauthLogin($jwtToken, &$userId, &$tokenScope)
Validate OAuth token.
Definition: AuthHelper.php:290
userHasGroupAccess($userId, $groupName)
Verify if given User Id has access to given Group name.
Definition: AuthHelper.php:249
verifyAuthToken($authHeader, &$userId, &$tokenScope)
Definition: AuthHelper.php:101
__construct(UserDao $userDao, Session $session, DbHelper $dbhelper)
Definition: AuthHelper.php:62
isTokenActive($valuesFromDb, $tokenId)
Definition: AuthHelper.php:167
generateJwtToken($expire, $created, $jti, $scope, $key)
Definition: AuthHelper.php:218
validateTokenLogin($jwtToken, $jwtTokenPayloadDecoded, &$userId, &$tokenScope)
Validate JWT token from FOSSology.
Definition: AuthHelper.php:403
checkUsernameAndPassword($userName, $password)
Check the username and password against the database.
Definition: AuthHelper.php:85
Provides helper methods to access database for REST api.
Definition: DbHelper.php:38
REST api helper classes.