Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.64% covered (warning)
87.64%
78 / 89
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SessionController
87.64% covered (warning)
87.64%
78 / 89
20.00% covered (danger)
20.00%
1 / 5
30.59
0.00% covered (danger)
0.00%
0 / 1
 putSessionAdmin
n/a
0 / 0
n/a
0 / 0
5
 putSessionLogin
91.67% covered (success)
91.67%
22 / 24
0.00% covered (danger)
0.00%
0 / 1
5.01
 putSessionPerson
n/a
0 / 0
n/a
0 / 0
1
 registerDependantSessions
93.75% covered (success)
93.75%
30 / 32
0.00% covered (danger)
0.00%
0 / 1
9.02
 getWorkspace
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getSession
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
4
 deleteSession
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3/** @noinspection PhpUnhandledExceptionInspection */
4declare(strict_types=1);
5
6// TODO unit tests !
7
8use Slim\Exception\HttpBadRequestException;
9use Slim\Exception\HttpException;
10use Slim\Exception\HttpUnauthorizedException;
11use Slim\Http\Response;
12use Slim\Http\ServerRequest as Request;
13
14class SessionController extends Controller {
15  protected static array $_workspaces = [];
16
17  /**
18   * @codeCoverageIgnore
19   */
20  public static function putSessionAdmin(Request $request, Response $response): Response {
21    usleep(500000); // 0.5s delay to slow down brute force attack
22
23    $body = RequestBodyParser::getElementsFromRequest($request, [
24      "name" => 'REQUIRED',
25      "password" => 'REQUIRED'
26    ]);
27
28    $attempts = CacheService::getFailedLogins($body['name']);
29    if ($attempts >= 5) {
30      throw new HttpError("Too many login attempts", 429);
31    }
32
33    $token = self::adminDAO()->createAdminToken($body['name'], $body['password']);
34
35    if (is_a($token, FailedLogin::class)) {
36      CacheService::addFailedLogin($body['name']);
37      throw new HttpError("No login with this password.", 400);
38    }
39
40    $admin = self::adminDAO()->getAdmin($token);
41    $workspaces = self::adminDAO()->getWorkspaces($token);
42    $accessSet = AccessSet::createFromAdminToken($admin, ...$workspaces);
43
44    if (!$accessSet->hasAccessType(AccessObjectType::WORKSPACE_ADMIN) and !$accessSet->hasAccessType(AccessObjectType::SUPER_ADMIN)) {
45      throw new HttpException($request, "You don't have any workspaces and are not allowed to create some.", 204);
46    }
47
48    return $response->withJson($accessSet);
49  }
50
51  public static function putSessionLogin(Request $request, Response $response): Response {
52    $body = RequestBodyParser::getElementsFromRequest($request, [
53      "name" => 'REQUIRED',
54      "password" => ''
55    ]);
56
57    $attempts = CacheService::getFailedLogins($body['name']);
58    if ($attempts >= 5) {
59      throw new HttpError("Too many login attempts", 429);
60    }
61
62    $loginSession = self::sessionDAO()->getOrCreateLoginSession($body['name'], $body['password']);
63
64    if (!is_a($loginSession, LoginSession::class)) {
65      if ($loginSession === FailedLogin::wrongPasswordProtectedLogin) {
66        CacheService::addFailedLogin($body['name']);
67      }
68      $userName = htmlspecialchars($body['name']);
69      throw new HttpBadRequestException($request, "No Login for `$userName` with this password.");
70    }
71
72    if (!$loginSession->getLogin()->isCodeRequired()) {
73      $personSession = self::sessionDAO()->createOrUpdatePersonSession($loginSession, '');
74
75      CacheService::removeAuthentication($personSession);
76
77      $testsOfPerson = self::sessionDAO()->getTestsOfPerson($personSession);
78      $groupMonitors = self::sessionDAO()->getGroupMonitors($personSession);
79      $sysChecks = self::sessionDAO()->getSysChecksOfPerson($personSession);
80      $accessSet = AccessSet::createFromPersonSession($personSession, ...$testsOfPerson, ...$groupMonitors, ...$sysChecks);
81
82      self::registerDependantSessions($loginSession);
83      CacheService::storeAuthentication($personSession);
84
85    } else {
86      $accessSet = AccessSet::createFromLoginSession($loginSession);
87    }
88
89    return $response->withJson($accessSet);
90  }
91
92  /**
93   * @codeCoverageIgnore
94   */
95  public static function putSessionPerson(Request $request, Response $response): Response {
96    $body = RequestBodyParser::getElementsFromRequest($request, [
97      'code' => ''
98    ]);
99
100    $loginSession = self::sessionDAO()->getLoginSessionByToken(self::authToken($request)->getToken());
101
102    $personSession = self::sessionDAO()->createOrUpdatePersonSession($loginSession, $body['code']);
103    CacheService::removeAuthentication($personSession);
104    $testsOfPerson = self::sessionDAO()->getTestsOfPerson($personSession);
105    CacheService::storeAuthentication($personSession);
106    return $response->withJson(AccessSet::createFromPersonSession($personSession, ...$testsOfPerson));
107  }
108
109  private static function registerDependantSessions(LoginSession $login): void {
110    $members = self::sessionDAO()->getDependantSessions($login);
111
112    $workspace = self::getWorkspace($login->getLogin()->getWorkspaceId());
113    $bookletFiles = [];
114
115    foreach ($members as $member) {
116      /* @var $member LoginSession */
117
118      if (Mode::hasCapability($member->getLogin()->getMode(), 'alwaysNewSession')) {
119        continue;
120      }
121
122      if (!Mode::hasCapability($member->getLogin()->getMode(), 'monitorable')) {
123        continue;
124      }
125
126      if (!$member->getToken()) {
127        $member = SessionController::sessionDAO()->createLoginSession($member->getLogin());
128      }
129
130      foreach ($member->getLogin()->getBooklets() as $code => $booklets) {
131        $memberPersonSession = SessionController::sessionDAO()->createOrUpdatePersonSession(
132          $member,
133          $code,
134          true,
135          false
136        );
137
138        foreach ($booklets as $bookletId) {
139          if (!isset($bookletFiles[$bookletId])) {
140            $bookletFile = $workspace->getFileById('Booklet', $bookletId);
141            $bookletFiles[$bookletId] = $bookletFile;
142          } else {
143            $bookletFile = $bookletFiles[$bookletId];
144          }
145          /* @var $bookletFile XMLFileBooklet */
146
147          $test = self::testDAO()->getTestByPerson($memberPersonSession->getPerson()->getId(), $bookletId);
148          if (!$test) {
149            $test = self::testDAO()->createTest(
150              $memberPersonSession->getPerson()->getId(),
151              $bookletId,
152              $bookletFile->getLabel()
153            );
154          }
155
156          $sessionMessage = SessionChangeMessage::session($test->id, $memberPersonSession);
157          $sessionMessage->setTestState([], $bookletId);
158          BroadcastService::sessionChange($sessionMessage);
159        }
160      }
161    }
162  }
163
164  private static function getWorkspace(int $workspaceId): Workspace {
165    if (!isset(self::$_workspaces[$workspaceId])) {
166      self::$_workspaces[$workspaceId] = new Workspace($workspaceId);
167    }
168
169    return self::$_workspaces[$workspaceId];
170  }
171
172  public static function getSession(Request $request, Response $response): Response {
173    $authToken = self::authToken($request);
174
175    if ($authToken->getType() == "login") {
176      $loginSession = self::sessionDAO()->getLoginSessionByToken($authToken->getToken());
177      return $response->withJson(AccessSet::createFromLoginSession($loginSession));
178    }
179
180    if ($authToken->getType() == "person") {
181      $personSession = self::sessionDAO()->getPersonSessionByToken($authToken->getToken());
182      $testsOfPerson = self::sessionDAO()->getTestsOfPerson($personSession);
183      $workspace = self::workspaceDAO($personSession->getLoginSession()->getLogin()->getWorkspaceId());
184      $workspaceData = new WorkspaceData(
185        $workspace->getWorkspaceId(),
186        $workspace->getWorkspaceName(),
187        'R'
188      );
189      $groupMonitors = self::sessionDAO()->getGroupMonitors($personSession);
190      $sysChecks = self::sessionDAO()->getSysChecksOfPerson($personSession);
191
192      $accessSet = AccessSet::createFromPersonSession($personSession, $workspaceData, ...$testsOfPerson, ...$groupMonitors, ...$sysChecks);
193      return $response->withJson($accessSet);
194    }
195
196    if ($authToken->getType() == "admin") {
197      $admin = self::adminDAO()->getAdmin($authToken->getToken());
198      $workspaces = self::adminDAO()->getWorkspaces($authToken->getToken());
199      $accessSet = AccessSet::createFromAdminToken($admin, ...$workspaces);
200      self::adminDAO()->refreshAdminToken($authToken->getToken());
201      return $response->withJson($accessSet);
202    }
203
204    throw new HttpUnauthorizedException($request);
205  }
206
207  public static function deleteSession(Request $request, Response $response): Response {
208    $authToken = self::authToken($request);
209
210    if ($authToken->getType() == "person") {
211      self::sessionDAO()->deletePersonToken($authToken);
212    }
213
214    if ($authToken->getType() == "admin") {
215      self::adminDAO()->deleteAdminSession($authToken);
216    }
217
218    // nothing to do for login-sessions; they have constant token as they are only the first step of 2f-auth
219    return $response->withStatus(205);
220  }
221
222}