CVE-2023-3394 (published 2023-06-23T19:15Z)

Session Fixation in GitHub repository fossbilling/fossbilling prior to 0.5.1.

CVSS v3 base score: 5.4, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-384
cwe_name: Session Fixation
description: Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.
extended_description: 
            Such a scenario is commonly observed when:
            
               A web application authenticates a user without first invalidating the existing session, thereby continuing to use the session already associated with the user.
               An attacker is able to force a known session identifier on a user so that, once the user authenticates, the attacker has access to the authenticated session.
               The application or container uses predictable session identifiers.
	    
	    In the generic exploit of session fixation vulnerabilities, an attacker creates a new session on a web application and records the associated session identifier. The attacker then causes the victim to associate, and possibly authenticate, against the server using that session identifier, giving the attacker access to the user's account through the active session.
         
         

Fix information:
repo_url: https://github.com/fossbilling/fossbilling
commit_details: repo_url: https://github.com/fossbilling/fossbilling
author: Belle Aerni
author_date: 2023-06-19 04:14:29-07:00
msg: Replace Box_Session and improve session handling (#1332)

* Replace Box_Session and improve session handling

* Improve weights, capitalize class name correctly

* Make the tests pass, update the SQL structure

* Tweak to the fingerprint class, fix more tests

* Regenerate session IDs when logging in

* Improve readability

* Update ServiceTest.php

* Tweak weights and added Huntr badge to the readme

* Improve readability, add PHPDocs, and updated weight

* Update SECURITY.md
repository: repo_url: https://github.com/fossbilling/fossbilling
repo_name: FOSSBilling/FOSSBilling
description: Empower your hosting business with FOSSBilling, the free and open-source solution for efficient billing and client management.
repo_language: PHP
owner: fossbilling
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -22,6 +22,8 @@
 [![CodeFactor](https://www.codefactor.io/repository/github/FOSSBilling/FOSSBilling/badge)](https://www.codefactor.io/repository/github/fossbilling/fossbilling)
 [![Financial Contributors](https://opencollective.com/FOSSBilling/tiers/badge.svg?color=brightgreen)](https://opencollective.com/fossbilling)
 [![Crowdin](https://badges.crowdin.net/e/c70c78b4ab1e71424ce53dcf6bca9b12/localized.svg)](https://fossbilling.crowdin.com/FOSSBilling)
+[![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev/repos/fossbilling/fossbilling/)
+
 </div>
 
 > **Warning**

programming_language: Markdown
filename: SECURITY.md
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,8 @@
 
 | Version | Supported          |
 | ------- | ------------------ |
-| 0.4.x   | :white_check_mark: |
+| 0.5.x   | :white_check_mark: |
+| 0.4.x   | :x: |
 | 0.3.x   | :x: |
 | 0.2.x   | :x: |
 | 0.1.x   | :x: |

programming_language: Markdown
filename: cspell.json
change_type: ModificationType.MODIFY
diff: @@ -226,7 +226,6 @@
         "gugl",
         "cclogin",
         "CURDATE",
-        "BOXCLR",
         "vatnum",
         "caid",
         "ECB's",
@@ -338,7 +337,9 @@
         "autoloader",
         "qwertyuiopasdfghjklzxccvbnm",
         "QWERTYUIOPASDFGHJKLZXCVBNM",
-        "subkeys"
+        "subkeys",
+        "httponly",
+        "samesite"
     ],
     "ignorePaths": [
         "tests/**",

programming_language: R
filename: di.php
change_type: ModificationType.MODIFY
diff: @@ -224,15 +224,20 @@ $di['events_manager'] = function () use ($di) {
  *
  * @param void
  *
- * @return \Box_Session
+ * @return \FOSSBilling\Session
  */
 $di['session'] = function () use ($di) {
     $handler = new PdoSessionHandler($di['pdo']);
-    $mode = (isset($di['config']['security']['mode'])) ? $di['config']['security']['mode'] : 'strict';
-    $lifespan = (isset($di['config']['security']['cookie_lifespan'])) ? $di['config']['security']['cookie_lifespan'] : 7200;
-    $secure = (isset($di['config']['security']['force_https'])) ? $di['config']['security']['force_https'] : true;
 
-    return new Box_Session($handler, $mode, $lifespan, $secure);
+    $mode = $di['config']['security']['mode'] ?? 'strict';
+    $lifespan = $di['config']['security']['cookie_lifespan'] ?? 7200;
+    $secure = $di['config']['security']['force_https'] ?? true;
+
+    $session = new \FOSSBilling\Session($handler, $mode, $lifespan, $secure);
+    $session->setDi($di);
+    $session->setupSession();
+
+    return $session;
 };
 
 /*

programming_language: PHP
filename: content.sql
change_type: ModificationType.MODIFY
diff: @@ -316,7 +316,7 @@ LOCK TABLES `setting` WRITE;
 
 INSERT INTO `setting` (`id`, `param`, `value`, `public`, `category`, `hash`, `created_at`, `updated_at`)
 VALUES
-	(1,'last_patch','33',0,NULL,NULL,'2022-12-01 12:00:00','2022-12-01 12:00:00'),
+	(1,'last_patch','34',0,NULL,NULL,'2022-12-01 12:00:00','2022-12-01 12:00:00'),
 	(2,'company_name','Company Name',0,NULL,NULL,'2022-12-01 12:00:00','2022-12-01 12:00:00'),
 	(3,'company_email','company@email.com',0,NULL,NULL,'2022-12-01 12:00:00','2022-12-01 12:00:00'),
 	(4,'company_signature','FOSSBilling.org - Client Management, Invoice and Support Software',0,NULL,NULL,'2022-12-01 12:00:00','2022-12-01 12:00:00'),

programming_language: SQL
filename: structure.sql
change_type: ModificationType.MODIFY
diff: @@ -1145,6 +1145,7 @@ CREATE TABLE `session` (
   `id` varchar(32) NOT NULL DEFAULT '',
   `modified_at` int(11) DEFAULT NULL,
   `content` text,
+  `fingerprint` text,
   UNIQUE KEY `unique_id` (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;

programming_language: SQL
filename: Session.php
change_type: ModificationType.DELETE
diff: @@ -1,88 +0,0 @@
-<?php
-/**
- * Copyright 2022-2023 FOSSBilling
- * Copyright 2011-2021 BoxBilling, Inc.
- * SPDX-License-Identifier: Apache-2.0
- *
- * @copyright FOSSBilling (https://www.fossbilling.org)
- * @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
- */
-
-class Box_Session
-{
-    public function setRandomId()
-    {
-        $id = random_bytes(64);
-        session_id($id);
-    }
-
-
-    public function __construct($handler, $securityMode = 'regular', $cookieLifespan = 7200, $secure = true)
-    {
-        if (!headers_sent()) {
-            session_set_save_handler(
-                array($handler, 'open'),
-                array($handler, 'close'),
-                array($handler, 'read'),
-                array($handler, 'write'),
-                array($handler, 'destroy'),
-                array($handler, 'gc')
-            );
-        }
-        if (php_sapi_name() !== 'cli') {
-            $currentCookieParams = session_get_cookie_params();
-            $currentCookieParams["httponly"] = true;
-            $currentCookieParams["lifetime"] = $cookieLifespan;
-            $currentCookieParams["secure"] = $secure;
-
-            if ($securityMode == 'strict') {
-                session_set_cookie_params([
-                    'lifetime' => $currentCookieParams["lifetime"],
-                    'path' => $currentCookieParams["path"],
-                    'domain' => $currentCookieParams["domain"],
-                    'secure' => $currentCookieParams["secure"],
-                    'httponly' => $currentCookieParams["httponly"],
-                    'samesite' => 'Strict'
-                ]);
-                // TODO: Adjust the DB to support 64 character long session IDs
-                // Currently adjusting it causing issues within this file: https://github.com/FOSSBilling/FOSSBilling/blob/main/src/library/PdoSessionHandler.php
-                //$this->setRandomId();
-            } else {
-                session_set_cookie_params(
-                    $currentCookieParams["lifetime"],
-                    $currentCookieParams["path"],
-                    $currentCookieParams["domain"],
-                    $currentCookieParams["secure"],
-                    $currentCookieParams["httponly"]
-                );
-            }
-
-            session_start();
-        }
-    }
-
-    public function getId()
-    {
-        return session_id();
-    }
-
-    public function delete($key)
-    {
-        unset($_SESSION[$key]);
-    }
-
-    public function get($key)
-    {
-        return isset($_SESSION[$key]) ? $_SESSION[$key] : null;
-    }
-
-    public function set($key, $value)
-    {
-        $_SESSION[$key] = $value;
-    }
-
-    public function destroy()
-    {
-        session_destroy();
-    }
-}

programming_language: unknown
filename: Fingerprint.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,163 @@
+<?php
+declare(strict_types=1);
+/**
+ * Copyright 2022-2023 FOSSBilling
+ * Copyright 2011-2021 BoxBilling, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * @copyright FOSSBilling (https://www.fossbilling.org)
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
+ */
+
+namespace FOSSBilling;
+
+class Fingerprint
+{
+    private array $fingerprintProperties;
+
+    public function __construct()
+    {
+        $agentDetails = $this->extractAgentInfo();
+
+        /**
+         * Sets up the fingerprint info for the existing request.
+         * 'weight' is used to weigh specific parameters.
+         *      Example: The agent string has a weight of 2, one failure from it equal as 2 failures of other properties.
+         *      By doing this, we can prevent minor changes such as a browser update from requiring the user to re-authenticate.
+         *      But it does mean that if the that property and one-or-two other ones fail, the user will need to re-authenticate.
+         */
+        $this->fingerprintProperties = [
+            'agentString' => [
+                'source' => $agentDetails['userAgent'],
+                'weight' => 2,
+            ],
+            'browser' => [
+                'source' => $agentDetails['browser'],
+                'weight' => 100, // Always fail if this doesn't match.
+            ],
+            'browserVersion' => [
+                'source' => $agentDetails['browserVersion'],
+                'weight' => 1,
+            ],
+            'os' => [
+                'source' => $agentDetails['os'],
+                'weight' => 100, // Always fail if this doesn't match.
+            ],
+            'ip' => [
+                'source' => $_SERVER['REMOTE_ADDR'] ?? '',
+                'weight' => 3,
+            ],
+            'referrer' => [
+                'source' => $_SERVER['HTTP_REFERER'] ?? '',
+                'weight' => 1,
+            ],
+            'forwardedFor' => [
+                'source' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '',
+                'weight' => 3,
+            ],
+            'language' => [
+                'source' => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '',
+                'weight' => 2,
+            ],
+            'encoding' => [
+                'source' => $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '',
+                'weight' => 1,
+            ],
+            'upgradeRequests' => [
+                'source' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'] ?? '',
+                'weight' => 1,
+            ],
+        ];
+    }
+
+    /**
+     * Generates a fingerprint for the device that made the request to the server
+     */
+    public function fingerprint(): array
+    {
+        $fingerprint = [];
+
+        foreach ($this->fingerprintProperties as $name => $properties) {
+            if (!empty($properties['source'])) {
+                $fingerprint[$name] = hash('md5', $properties['source']);
+            }
+        }
+
+        return $fingerprint;
+    }
+
+    /**
+     * Compares a provided fingerprint against one generated for the device that made the request to the server.
+     * This function creates a baseline "score" with the total of properties in the fingerprint. The final score must be at least half of the baseline.
+     *      - Each property can define a weight. For example, if the IP address doesn't match and the weight is set to 3, 3 will be selected from the total.
+     *          - This means with a total of 9 properties, the IP address being wrong would effectively be weighted as 3 properties and only two more differing properties will make it fail the check.
+     *      - If any property is found in one of the fingerprints and not the other, the baseline is incremented and the final score is decreased by it's weight.
+     */
+    public function checkFingerprint(array $fingerprint): bool
+    {
+        $itemCount = 0;
+        $scoreSubtract = 0;
+
+        foreach ($this->fingerprintProperties as $name => $properties) {
+            $exitsInFingerprint = array_key_exists($name, $fingerprint);
+            $exitsInCurrentFingerprint = !empty($properties['source']);
+
+            if ((!$exitsInFingerprint && $exitsInCurrentFingerprint) || ($exitsInFingerprint && !$exitsInCurrentFingerprint)) {
+                //The property exists in one fingerprint and not the other, so we increment the total count and deduct from the score.
+                $itemCount++;
+                $scoreSubtract += $properties['weight'];
+            } elseif (!$exitsInFingerprint && !$exitsInCurrentFingerprint) {
+                // Do nothing in this case, as the property isn't in either fingerprint.
+            } else {
+                $itemCount++;
+                $hashedData = hash('md5', $properties['source']);
+
+                if ($fingerprint[$name] !== $hashedData) {
+                    $scoreSubtract += $properties['weight'];
+                }
+            }
+        }
+
+        // Remove the total score from the total number of items. The final score must be at least half the number of properties in order for the fingerprint to be considered valid.
+        $finalScore = $itemCount - $scoreSubtract;
+        return $finalScore >= ($itemCount / 2);
+    }
+
+    private function extractAgentInfo(): array
+    {
+        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
+
+        // Extract the browser name
+        if (preg_match('/(?:Chrome|CriOS)\/([0-9\.]+)/', $userAgent, $matches)) {
+            $browser = 'Chrome';
+            $version = $matches[1];
+        } elseif (preg_match('/Firefox\/([0-9\.]+)/', $userAgent, $matches)) {
+            $browser = 'Firefox';
+            $version = $matches[1];
+        } elseif (preg_match('/Safari\/([0-9\.]+)/', $userAgent, $matches)) {
+            $browser = 'Safari';
+            $version = $matches[1];
+        } else {
+            $browser = 'Unknown';
+            $version = 'Unknown';
+        }
+
+        // Extract the operating system
+        if (preg_match('/Windows NT ([0-9\.]+)/', $userAgent, $matches)) {
+            $os = 'Windows NT ' . $matches[1];
+        } elseif (preg_match('/Mac OS X ([0-9_]+)/', $userAgent, $matches)) {
+            $os = 'Mac OS X';
+        } elseif (preg_match('/Linux/', $userAgent)) {
+            $os = 'Linux';
+        } else {
+            $os = 'Unknown';
+        }
+
+        return [
+            'browser' => $browser,
+            'browserVersion' => $version,
+            'os' => $os,
+            'userAgent' => $userAgent,
+        ];
+    }
+}

programming_language: PHP
filename: Session.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,155 @@
+<?php
+declare(strict_types=1);
+/**
+ * Copyright 2022-2023 FOSSBilling
+ * Copyright 2011-2021 BoxBilling, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * @copyright FOSSBilling (https://www.fossbilling.org)
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
+ */
+
+namespace FOSSBilling;
+
+class Session implements \FOSSBilling\InjectionAwareInterface
+{
+    private ?\Pimple\Container $di;
+    private \PdoSessionHandler $handler;
+    private string $securityMode;
+    private int $cookieLifespan;
+    private bool $secure;
+
+    public function setDi(\Pimple\Container|null $di): void
+    {
+        $this->di = $di;
+    }
+
+    public function getDi(): ?\Pimple\Container
+    {
+        return $this->di;
+    }
+
+    public function __construct(\PdoSessionHandler $handler, string $securityMode = 'regular', int $cookieLifespan = 7200, bool $secure = true)
+    {
+        $this->handler = $handler;
+        $this->securityMode = $securityMode;
+        $this->cookieLifespan = $cookieLifespan;
+        $this->secure = $secure;
+    }
+
+    public function setupSession()
+    {
+        if (php_sapi_name() === 'cli') {
+            return;
+        }
+
+        $this->canUseSession();
+
+        if (!headers_sent()) {
+            session_set_save_handler(
+                [$this->handler, 'open'],
+                [$this->handler, 'close'],
+                [$this->handler, 'read'],
+                [$this->handler, 'write'],
+                [$this->handler, 'destroy'],
+                [$this->handler, 'gc']
+            );
+        }
+
+        $currentCookieParams = session_get_cookie_params();
+        $currentCookieParams["httponly"] = true;
+        $currentCookieParams["lifetime"] = $this->cookieLifespan;
+        $currentCookieParams["secure"] = $this->secure;
+
+        $cookieParams = [
+            'lifetime' => $currentCookieParams["lifetime"],
+            'path' => $currentCookieParams["path"],
+            'domain' => $currentCookieParams["domain"],
+            'secure' => $currentCookieParams["secure"],
+            'httponly' => $currentCookieParams["httponly"]
+        ];
+
+        if ($this->securityMode == 'strict') {
+            $cookieParams['samesite'] = 'Strict';
+        }
+
+        session_set_cookie_params($cookieParams);
+        session_start();
+
+        $this->updateFingerprint();
+    }
+
+    public function getId(): string
+    {
+        return session_id();
+    }
+
+    public function delete(string $key): void
+    {
+        unset($_SESSION[$key]);
+    }
+
+    public function get(string $key): mixed
+    {
+        return $_SESSION[$key] ?? null;
+    }
+
+    public function set(string $key, mixed $value): void
+    {
+        $_SESSION[$key] = $value;
+    }
+
+    public function destroy(string $type = ''): bool
+    {
+        switch ($type) {
+            case 'admin':
+                $this->delete('admin');
+                break;
+            case 'client':
+                $this->delete('client');
+                $this->delete('client_id');
+                break;
+        }
+
+        return session_destroy();
+    }
+
+    /**
+     * Checks both the fingerprint and age of the current session to see if it can be used.
+     * If the session can't be used, it's destroyed from the database, forcing a new one to be created.
+     */
+    private function canUseSession():void
+    {
+        if (empty($_COOKIE['PHPSESSID'])) {
+            return;
+        }
+
+        $sessionID = $_COOKIE['PHPSESSID'];
+        $maxAge = time() - $this->di['config']['security']['cookie_lifespan'];
+
+        $fingerprint = new \FOSSBilling\Fingerprint;
+        $session = $this->di['db']->findOne('session', 'id = :id', [':id' => $sessionID]);
+
+        if (empty($session->fingerprint)) {
+            return;
+        }
+
+        if (!$fingerprint->checkFingerprint(json_decode($session->fingerprint, true)) || $session->modified_at <= $maxAge) {
+            $this->di['db']->trash($session);
+            unset($_COOKIE['PHPSESSID']);
+        }
+    }
+
+    /**
+     * Depending on the specifics, this will either set or update the fingerprint associated with the current session.
+     */
+    private function updateFingerprint():void
+    {
+        $sessionID = $_COOKIE['PHPSESSID'] ?? session_id();
+        $session = $this->di['db']->findOne('session', 'id = :id', [':id' => $sessionID]);
+
+        $fingerprint = new \FOSSBilling\Fingerprint;
+        $session->fingerprint = json_encode($fingerprint->fingerprint());
+        $this->di['db']->store($session);
+    }
+}

programming_language: PHP
filename: UpdatePatcher.php
change_type: ModificationType.MODIFY
diff: @@ -276,7 +276,12 @@ class UpdatePatcher implements InjectionAwareInterface
                     __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'FileCache.php' => 'unlink',
                 ];
                 $this->executeFileActions($fileActions);
-            }
+            },
+            34 => function() {
+                // Adds the new "fingerprint" to the session table, to allow us to fingerprint devices and help prevent against attacks such as session hijacking.
+                $q = "ALTER TABLE session ADD fingerprint TEXT;";
+                $this->executeSql($q);
+            },
         ];
         ksort($patches, SORT_NATURAL);
 

programming_language: PHP
filename: Guest.php
change_type: ModificationType.MODIFY
diff: @@ -136,14 +136,9 @@ class Guest extends \Api_Abstract
             throw new \Box_Exception('Please check your login details.', [], 401);
         }
 
-        if (isset($data['remember'])) {
-            $email = $data['email'];
-            $cookie_time = (3600 * 24 * 30); // 30 days
-            setcookie('BOXCLR', 'e=' . base64_encode($email) . '&p=' . base64_encode($client->pass), time() + $cookie_time, '/');
-        }
-
         $this->di['events_manager']->fire(['event' => 'onAfterClientLogin', 'params' => ['id' => $client->id, 'ip' => $this->ip]]);
 
+        session_regenerate_id();
         $result = $service->toSessionArray($client);
         $this->di['session']->set('client_id', $client->id);
 

programming_language: PHP
filename: Service.php
change_type: ModificationType.MODIFY
diff: @@ -70,6 +70,9 @@ class Service
         $ss = $this->di['mod_service']('system');
         $ss->setParamValue('last_cron_exec', date('Y-m-d H:i:s'), $create);
 
+        $this->clearOldSessions();
+        $this->di['logger']->setChannel('cron')->info('Cleared outdated sessions from the database');
+
         $this->di['events_manager']->fire(['event' => 'onAfterAdminCronRun']);
 
         $this->di['logger']->setChannel('cron')->info('Finished executing cron jobs');
@@ -111,4 +114,15 @@ class Service
 
         return $t1 < $t2;
     }
+
+    private function clearOldSessions()
+    {
+        $sessions = $this->di['db']->findAll('session');
+        foreach ($sessions as $session){
+            $maxAge = time() - $this->di['config']['security']['cookie_lifespan'];
+            if($session->modified_at <= $maxAge){
+                $this->di['db']->trash($session);
+            }
+        }
+    }
 }

programming_language: PHP
filename: Admin.php
change_type: ModificationType.MODIFY
diff: @@ -52,7 +52,7 @@ class Admin extends \Api_Abstract
     public function logout()
     {
         unset($_COOKIE['BOXADMR']);
-        $this->di['session']->delete('admin');
+        $this->di['session']->destroy('admin');
         $this->di['logger']->info('Admin logged out');
 
         return true;

programming_language: PHP
filename: Service.php
change_type: ModificationType.MODIFY
diff: @@ -217,11 +217,7 @@ class Service implements InjectionAwareInterface
 
     public function logoutClient()
     {
-        if ($_COOKIE) { // testing env fix
-            setcookie('BOXCLR', '', time() - 3600, '/');
-        }
-        $this->di['session']->delete('client');
-        $this->di['session']->delete('client_id');
+        $this->di['session']->destroy('client');
         $this->di['logger']->info('Logged out');
 
         return true;

programming_language: PHP
filename: Service.php
change_type: ModificationType.MODIFY
diff: @@ -50,6 +50,7 @@ class Service implements InjectionAwareInterface
             'role' => $model->role,
         ];
 
+        session_regenerate_id();
         $this->di['session']->set('admin', $result);
 
         $this->di['logger']->info(sprintf('Staff member %s logged in', $model->id));
@@ -498,8 +499,6 @@ class Service implements InjectionAwareInterface
         $newId = $this->di['db']->store($admin);
 
         $this->di['logger']->info('Main administrator %s account created', $admin->email);
-        $this->_sendMail($admin, $data['password']);
-
         $data['remember'] = true;
 
         return $newId;
@@ -649,32 +648,6 @@ class Service implements InjectionAwareInterface
         return true;
     }
 
-    protected function _sendMail($admin, $admin_pass)
-    {
-        $admin_name = $admin->name;
-        $admin_email = $admin->email;
-
-        $client_url = $this->di['url']->link('/');
-        $admin_url = $this->di['url']->adminLink('/');
-
-        $content = "Hello, $admin_name. " . PHP_EOL;
-        $content .= 'You have successfully installed FOSSBilling at ' . BB_URL . PHP_EOL;
-        $content .= 'Access the client area at: ' . $client_url . PHP_EOL;
-        $content .= 'Access the admin area at: ' . $admin_url . ' with login details:' . PHP_EOL;
-        $content .= 'Email: ' . $admin_email . PHP_EOL;
-        $content .= 'Password: ' . $admin_pass . PHP_EOL . PHP_EOL;
-
-        $content .= 'Read the FOSSBilling documentation to get started https://fossbilling.org/docs' . PHP_EOL;
-        $content .= 'Thank you for using FOSSBilling.' . PHP_EOL;
-
-        $subject = sprintf('FOSSBilling is ready at "%s"', BB_URL);
-
-        $systemService = $this->di['mod_service']('system');
-        $from = $systemService->getParamValue('company_email');
-        $emailService = $this->di['mod_service']('Email');
-        $emailService->sendMail($admin_email, $from, $subject, $content);
-    }
-
     public function authorizeAdmin($email, $plainTextPassword)
     {
         $model = $this->di['db']->findOne('Admin', 'email = ? AND status = ?', [$email, \Model_Admin::STATUS_ACTIVE]);

programming_language: PHP
filename: DiTest.php
change_type: ModificationType.MODIFY
diff: @@ -21,7 +21,7 @@ class DiTest extends PHPUnit\Framework\TestCase
         $this->assertInstanceOf('Box_Url', $di['url']);
         $this->assertInstanceOf('Box_EventManager', $di['events_manager']);
 
-        $this->assertInstanceOf('\Box_Session', $di['session']);
+        $this->assertInstanceOf('\FOSSBilling\Session', $di['session']);
         $this->assertInstanceOf('Box_Authorization', $di['auth']);
         $this->assertInstanceOf('Twig\Environment', $di['twig']);
         $this->assertInstanceOf('\FOSSBilling\Tools', $di['tools']);

programming_language: PHP
filename: Box_SessionTest.php
change_type: ModificationType.DELETE
diff: @@ -1,35 +0,0 @@
-<?php
-/**
- * @group Core
- */
-class Box_SessionTest extends PHPUnit\Framework\TestCase
-{
-    public function testSession()
-    {
-        $mock = $this->getMockBuilder("\Box_Session")
-            ->disableOriginalConstructor()
-            ->getMock();
-
-        $mock->expects($this->atLeastOnce())
-            ->method("getId")
-            ->will($this->returnValue("rrcpqo7tkjh14d2vmf0car64k7"));
-
-        $mock->expects($this->atLeastOnce())
-            ->method("get")
-            ->will($this->returnValue("testValue"));
-
-        $mock->expects($this->atLeastOnce())
-            ->method("delete")
-            ->will($this->returnValue(true));
-
-        $this->assertEquals($mock->getId(), 'rrcpqo7tkjh14d2vmf0car64k7', 'Session ID is not equal');
-
-        $this->assertEquals($mock->get('testKey'), 'testValue', 'The value is not equal to the one which was set');
-
-        $this->assertEquals($mock->delete('testKey'), true);
-
-        $this->assertEquals($mock->set('testKey', 'testValue'), null, 'The value is not equal to the one which was set');
-
-        $this->assertEquals($mock->destroy(), null, 'Session destroy did not return true');
-    }
-}
\ No newline at end of file

programming_language: unknown
filename: ServiceTest.php
change_type: ModificationType.MODIFY
diff: @@ -51,7 +51,7 @@ class ServiceTest extends \BBTestCase
             ->method('findOne')
             ->will($this->returnValue($model));
 
-        $sessionMock = $this->getMockBuilder("\Box_Session")
+        $sessionMock = $this->getMockBuilder("\FOSSBilling\Session")
             ->disableOriginalConstructor()
             ->getMock();
         $sessionMock->expects($this->atLeastOnce())
@@ -111,7 +111,7 @@ class ServiceTest extends \BBTestCase
             ->method('store')
             ->will($this->returnValue(rand(1, 100)));
 
-        $sessionMock = $this->getMockBuilder("\Box_Session")
+        $sessionMock = $this->getMockBuilder("\FOSSBilling\Session")
             ->disableOriginalConstructor()
             ->getMock();
         $sessionMock->expects($this->atLeastOnce())

programming_language: PHP
filename: AdminTest.php
change_type: ModificationType.MODIFY
diff: @@ -109,7 +109,7 @@ class AdminTest extends \BBTestCase
         $serviceMock->expects($this->atLeastOnce())->
         method('toSessionArray')->will($this->returnValue($sessionArray));
 
-        $sessionMock = $this->getMockBuilder('\Box_Session')->disableOriginalConstructor()->getMock();
+        $sessionMock = $this->getMockBuilder('\FOSSBilling\Session')->disableOriginalConstructor()->getMock();
         $sessionMock->expects($this->atLeastOnce())->
         method('set');
 

programming_language: PHP
filename: GuestTest.php
change_type: ModificationType.MODIFY
diff: @@ -188,7 +188,7 @@ class GuestTest extends \BBTestCase {
         $eventMock->expects($this->atLeastOnce())->
             method('fire');
 
-        $sessionMock = $this->getMockBuilder('\Box_Session')
+        $sessionMock = $this->getMockBuilder('\FOSSBilling\Session')
             ->disableOriginalConstructor()
             ->getMock();
 

programming_language: PHP
filename: ServiceTest.php
change_type: ModificationType.MODIFY
diff: @@ -61,6 +61,11 @@ class ServiceTest extends \BBTestCase {
         $eventsMock = $this->getMockBuilder('\Box_EventManager')->getMock();
         $eventsMock->expects($this->atLeastOnce())
             ->method('fire');
+        
+        $dbMock = $this->getMockBuilder('Box_Database')->getMock();
+        $dbMock->expects($this->atLeastOnce())
+            ->method('findAll')
+            ->will($this->returnValue([]));
 
         $di = new \Pimple\Container();
         $di['logger'] = new \Box_Log();
@@ -68,6 +73,7 @@ class ServiceTest extends \BBTestCase {
         $di['api_system'] = $apiSystem;
         $di['mod_service'] = $di->protect(function() use($systemServiceMock) {return $systemServiceMock;});
         $serviceMock->setDi($di);
+        $di['db'] = $dbMock;
 
         $result = $serviceMock->runCrons();
         $this->assertTrue($result);

programming_language: PHP
filename: AdminTest.php
change_type: ModificationType.MODIFY
diff: @@ -41,7 +41,7 @@ class AdminTest extends \BBTestCase
 
     public function testLogout()
     {
-        $sessionMock = $this->getMockBuilder('\Box_Session')
+        $sessionMock = $this->getMockBuilder('\FOSSBilling\Session')
             ->disableOriginalConstructor()
             ->getMock();
 

programming_language: PHP
filename: ServiceTest.php
change_type: ModificationType.MODIFY
diff: @@ -378,12 +378,12 @@ class ServiceTest extends \BBTestCase
 
     public function testLogoutClient()
     {
-        $sessionMock = $this->getMockBuilder("\Box_Session")
+        $sessionMock = $this->getMockBuilder("\FOSSBilling\Session")
             ->disableOriginalConstructor()
             ->getMock();
 
         $sessionMock->expects($this->atLeastOnce())
-            ->method("delete");
+            ->method("destroy");
 
         $di            = new \Pimple\Container();
         $di['logger']  = new \Box_Log();

programming_language: PHP
filename: ServiceTest.php
change_type: ModificationType.MODIFY
diff: @@ -41,7 +41,7 @@ class ServiceTest extends \BBTestCase
             ->method('findOne')
             ->will($this->returnValue($admin));
 
-        $sessionMock = $this->getMockBuilder('\Box_Session')
+        $sessionMock = $this->getMockBuilder('\FOSSBilling\Session')
             ->disableOriginalConstructor()
             ->getMock();
         $sessionMock->expects($this->atLeastOnce())
@@ -1174,21 +1174,6 @@ class ServiceTest extends \BBTestCase
         $logMock = $this->getMockBuilder('\Box_Log')->getMock();
 
         $systemService = $this->getMockBuilder('\Box\Mod\System\Service')->getMock();
-        $systemService->expects($this->atLeastOnce())
-            ->method('getParamValue');
-
-        $emailServiceMock = $this->getMockBuilder('\Box\Mod\Email\Service')->getMock();
-        $emailServiceMock->expects($this->atLeastOnce())
-            ->method('sendMail');
-
-        $urlMock = $this->getMockBuilder('\Box_Url')->getMock();
-        $urlMock->expects($this->atLeastOnce())
-            ->method('link')
-            ->willReturn('');
-        $urlMock->expects($this->atLeastOnce())
-            ->method('adminLink')
-            ->willReturn('');
-
 
         $passwordMock = $this->getMockBuilder('\Box_Password')->getMock();
         $passwordMock->expects($this->atLeastOnce())
@@ -1198,15 +1183,11 @@ class ServiceTest extends \BBTestCase
         $di                = new \Pimple\Container();
         $di['logger']      = $logMock;
         $di['db']          = $dbMock;
-        $di['mod_service'] = $di->protect(function ($serviceName) use ($systemService, $emailServiceMock) {
+        $di['mod_service'] = $di->protect(function ($serviceName) use ($systemService) {
             if ('system' == $serviceName) {
                 return $systemService;
             }
-            if ('Email' == $serviceName) {
-                return $emailServiceMock;
-            }
         });
-        $di['url']         = $urlMock;
         $di['password']    = $passwordMock;
 
         $service = new \Box\Mod\Staff\Service();

programming_language: PHP
filename: ServiceTest.php
change_type: ModificationType.MODIFY
diff: @@ -334,7 +334,7 @@ class ServiceTest extends \BBTestCase {
     {
         $di = new \Pimple\Container();
 
-        $sessionMock = $this->getMockBuilder('\Box_Session')->disableOriginalConstructor()->getMock();
+        $sessionMock = $this->getMockBuilder('\FOSSBilling\Session')->disableOriginalConstructor()->getMock();
         $sessionMock->expects($this->atLeastOnce())
             ->method('get')
             ->with('pending_messages')
@@ -351,7 +351,7 @@ class ServiceTest extends \BBTestCase {
     {
         $di = new \Pimple\Container();
 
-        $sessionMock = $this->getMockBuilder('\Box_Session')->disableOriginalConstructor()->getMock();
+        $sessionMock = $this->getMockBuilder('\FOSSBilling\Session')->disableOriginalConstructor()->getMock();
         $sessionMock->expects($this->atLeastOnce())
             ->method('get')
             ->with('pending_messages')
@@ -375,7 +375,7 @@ class ServiceTest extends \BBTestCase {
 
         $di = new \Pimple\Container();
 
-        $sessionMock = $this->getMockBuilder('\Box_Session')->disableOriginalConstructor()->getMock();
+        $sessionMock = $this->getMockBuilder('\FOSSBilling\Session')->disableOriginalConstructor()->getMock();
         $sessionMock->expects($this->atLeastOnce())
             ->method('set')
             ->with('pending_messages');
@@ -393,7 +393,7 @@ class ServiceTest extends \BBTestCase {
     {
         $di = new \Pimple\Container();
 
-        $sessionMock = $this->getMockBuilder('\Box_Session')->disableOriginalConstructor()->getMock();
+        $sessionMock = $this->getMockBuilder('\FOSSBilling\Session')->disableOriginalConstructor()->getMock();
         $sessionMock->expects($this->atLeastOnce())
             ->method('delete')
             ->with('pending_messages');

programming_language: PHP