diff --git a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php
index de9fe0c3f..c6d9ff087 100644
--- a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php
+++ b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php
@@ -36,6 +36,15 @@ interface ServerRepositoryInterface extends BaseRepositoryInterface
      */
     public function create($id, $overrides = [], $start = false);
 
+    /**
+     * Set an access token and associated permissions for a server.
+     *
+     * @param string $key
+     * @param array  $permissions
+     * @return \Psr\Http\Message\ResponseInterface
+     */
+    public function setSubuserKey($key, array $permissions);
+
     /**
      * Update server details on the daemon.
      *
diff --git a/app/Contracts/Repository/PermissionRepositoryInterface.php b/app/Contracts/Repository/PermissionRepositoryInterface.php
new file mode 100644
index 000000000..f84c16f7f
--- /dev/null
+++ b/app/Contracts/Repository/PermissionRepositoryInterface.php
@@ -0,0 +1,29 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Contracts\Repository;
+
+interface PermissionRepositoryInterface extends RepositoryInterface
+{
+}
diff --git a/app/Facades/Version.php b/app/Contracts/Repository/SubuserRepositoryInterface.php
similarity index 79%
rename from app/Facades/Version.php
rename to app/Contracts/Repository/SubuserRepositoryInterface.php
index e9475d2a6..766fc1b35 100644
--- a/app/Facades/Version.php
+++ b/app/Contracts/Repository/SubuserRepositoryInterface.php
@@ -1,5 +1,5 @@
 <?php
-/**
+/*
  * Pterodactyl - Panel
  * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
  *
@@ -22,19 +22,8 @@
  * SOFTWARE.
  */
 
-namespace Pterodactyl\Facades;
+namespace Pterodactyl\Contracts\Repository;
 
-use Illuminate\Support\Facades\Facade;
-
-class Version extends Facade
+interface SubuserRepositoryInterface extends RepositoryInterface
 {
-    /**
-     * Returns the facade accessor class.
-     *
-     * @return strig
-     */
-    protected static function getFacadeAccessor()
-    {
-        return '\Pterodactyl\Services\VersionService';
-    }
 }
diff --git a/app/Exceptions/Service/Helper/CdnVersionFetchingException.php b/app/Exceptions/Service/Helper/CdnVersionFetchingException.php
new file mode 100644
index 000000000..d96fe25de
--- /dev/null
+++ b/app/Exceptions/Service/Helper/CdnVersionFetchingException.php
@@ -0,0 +1,29 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Exceptions\Service\Helper;
+
+class CdnVersionFetchingException extends \Exception
+{
+}
diff --git a/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php b/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php
new file mode 100644
index 000000000..0c14f8034
--- /dev/null
+++ b/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php
@@ -0,0 +1,31 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Exceptions\Service\Subuser;
+
+use Pterodactyl\Exceptions\DisplayException;
+
+class ServerSubuserExistsException extends DisplayException
+{
+}
diff --git a/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php b/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php
new file mode 100644
index 000000000..ad551b19f
--- /dev/null
+++ b/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php
@@ -0,0 +1,31 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Exceptions\Service\Subuser;
+
+use Pterodactyl\Exceptions\DisplayException;
+
+class UserIsServerOwnerException extends DisplayException
+{
+}
diff --git a/app/Http/Controllers/Admin/BaseController.php b/app/Http/Controllers/Admin/BaseController.php
index 14665c32a..2858cdaf3 100644
--- a/app/Http/Controllers/Admin/BaseController.php
+++ b/app/Http/Controllers/Admin/BaseController.php
@@ -28,6 +28,7 @@ use Krucas\Settings\Settings;
 use Prologue\Alerts\AlertsMessageBag;
 use Pterodactyl\Http\Controllers\Controller;
 use Pterodactyl\Http\Requests\Admin\BaseFormRequest;
+use Pterodactyl\Services\Helpers\SoftwareVersionService;
 
 class BaseController extends Controller
 {
@@ -41,10 +42,26 @@ class BaseController extends Controller
      */
     protected $settings;
 
-    public function __construct(AlertsMessageBag $alert, Settings $settings)
-    {
+    /**
+     * @var \Pterodactyl\Services\Helpers\SoftwareVersionService
+     */
+    protected $version;
+
+    /**
+     * BaseController constructor.
+     *
+     * @param \Prologue\Alerts\AlertsMessageBag                    $alert
+     * @param \Krucas\Settings\Settings                            $settings
+     * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $version
+     */
+    public function __construct(
+        AlertsMessageBag $alert,
+        Settings $settings,
+        SoftwareVersionService $version
+    ) {
         $this->alert = $alert;
         $this->settings = $settings;
+        $this->version = $version;
     }
 
     /**
@@ -54,7 +71,7 @@ class BaseController extends Controller
      */
     public function getIndex()
     {
-        return view('admin.index');
+        return view('admin.index', ['version' => $this->version]);
     }
 
     /**
diff --git a/app/Models/Permission.php b/app/Models/Permission.php
index 086586cd7..3587e7fc3 100644
--- a/app/Models/Permission.php
+++ b/app/Models/Permission.php
@@ -25,9 +25,13 @@
 namespace Pterodactyl\Models;
 
 use Illuminate\Database\Eloquent\Model;
+use Sofa\Eloquence\Contracts\CleansAttributes;
+use Sofa\Eloquence\Eloquence;
 
-class Permission extends Model
+class Permission extends Model implements CleansAttributes
 {
+    use Eloquence;
+
     /**
      * Should timestamps be used on this model.
      *
@@ -118,12 +122,12 @@ class Permission extends Model
     /**
      * Return a collection of permissions available.
      *
-     * @param array $single
-     * @return \Illuminate\Support\Collection|array
+     * @param bool $array
+     * @return array|\Illuminate\Support\Collection
      */
-    public static function listPermissions($single = false)
+    public static function getPermissions($array = false)
     {
-        if ($single) {
+        if ($array) {
             return collect(self::$permissions)->mapWithKeys(function ($item) {
                 return $item;
             })->all();
diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php
index a2eff9c9a..276f97b98 100644
--- a/app/Models/Subuser.php
+++ b/app/Models/Subuser.php
@@ -26,10 +26,14 @@ namespace Pterodactyl\Models;
 
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Notifications\Notifiable;
+use Sofa\Eloquence\Contracts\CleansAttributes;
+use Sofa\Eloquence\Contracts\Validable as ValidableContract;
+use Sofa\Eloquence\Eloquence;
+use Sofa\Eloquence\Validable;
 
-class Subuser extends Model
+class Subuser extends Model implements CleansAttributes, ValidableContract
 {
-    use Notifiable;
+    use Eloquence, Notifiable, Validable;
 
     /**
      * The table associated with the model.
@@ -62,6 +66,24 @@ class Subuser extends Model
         'server_id' => 'integer',
     ];
 
+    /**
+     * @var array
+     */
+    protected static $applicationRules = [
+        'user_id' => 'required',
+        'server_id' => 'required',
+        'daemonSecret' => 'required',
+    ];
+
+    /**
+     * @var array
+     */
+    protected static $dataIntegrityRules = [
+        'user_id' => 'numeric|exists:users,id',
+        'server_id' => 'numeric|exists:servers,id',
+        'daemonSecret' => 'string',
+    ];
+
     /**
      * Gets the server associated with a subuser.
      *
diff --git a/app/Repositories/Daemon/ServerRepository.php b/app/Repositories/Daemon/ServerRepository.php
index c1cbee1c3..8de958b5e 100644
--- a/app/Repositories/Daemon/ServerRepository.php
+++ b/app/Repositories/Daemon/ServerRepository.php
@@ -84,6 +84,20 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa
         ]);
     }
 
+    /**
+     * {@inheritdoc}
+     */
+    public function setSubuserKey($key, array $permissions)
+    {
+        return $this->getHttpClient()->request('PATCH', '/server', [
+            'json' => [
+                'keys' => [
+                    $key => $permissions,
+                ],
+            ],
+        ]);
+    }
+
     /**
      * {@inheritdoc}
      */
diff --git a/app/Repositories/Eloquent/PermissionRepository.php b/app/Repositories/Eloquent/PermissionRepository.php
new file mode 100644
index 000000000..7fb7b56f6
--- /dev/null
+++ b/app/Repositories/Eloquent/PermissionRepository.php
@@ -0,0 +1,39 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Repositories\Eloquent;
+
+use Pterodactyl\Models\Permission;
+use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface;
+
+class PermissionRepository extends EloquentRepository implements PermissionRepositoryInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function model()
+    {
+        return Permission::class;
+    }
+}
diff --git a/app/Repositories/Eloquent/SubuserRepository.php b/app/Repositories/Eloquent/SubuserRepository.php
new file mode 100644
index 000000000..cda8864ec
--- /dev/null
+++ b/app/Repositories/Eloquent/SubuserRepository.php
@@ -0,0 +1,39 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Repositories\Eloquent;
+
+use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
+use Pterodactyl\Models\Subuser;
+
+class SubuserRepository extends EloquentRepository implements SubuserRepositoryInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function model()
+    {
+        return Subuser::class;
+    }
+}
diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php
new file mode 100644
index 000000000..87f84a401
--- /dev/null
+++ b/app/Services/Helpers/SoftwareVersionService.php
@@ -0,0 +1,149 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Services\Helpers;
+
+use Exception;
+use GuzzleHttp\Client;
+use Illuminate\Contracts\Cache\Repository as CacheRepository;
+use Illuminate\Contracts\Config\Repository as ConfigRepository;
+use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException;
+
+class SoftwareVersionService
+{
+    const VERSION_CACHE_KEY = 'pterodactyl:versions';
+
+    /**
+     * @var \Illuminate\Contracts\Cache\Repository
+     */
+    protected $cache;
+
+    /**
+     * @var \GuzzleHttp\Client
+     */
+    protected $client;
+
+    /**
+     * @var \Illuminate\Contracts\Config\Repository
+     */
+    protected $config;
+
+    /**
+     * SoftwareVersionService constructor.
+     *
+     * @param \Illuminate\Contracts\Cache\Repository  $cache
+     * @param \GuzzleHttp\Client                      $client
+     * @param \Illuminate\Contracts\Config\Repository $config
+     */
+    public function __construct(
+        CacheRepository $cache,
+        Client $client,
+        ConfigRepository $config
+    ) {
+        $this->cache = $cache;
+        $this->client = $client;
+        $this->config = $config;
+
+        $this->cacheVersionData();
+    }
+
+    /**
+     * Get the latest version of the panel from the CDN servers.
+     *
+     * @return string
+     */
+    public function getPanel()
+    {
+        return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'panel', 'error');
+    }
+
+    /**
+     * Get the latest version of the daemon from the CDN servers.
+     *
+     * @return string
+     */
+    public function getDaemon()
+    {
+        return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'daemon', 'error');
+    }
+
+    /**
+     * Get the URL to the discord server.
+     *
+     * @return string
+     */
+    public function getDiscord()
+    {
+        return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'discord', 'https://pterodactyl.io/discord');
+    }
+
+    /**
+     * Determine if the current version of the panel is the latest.
+     *
+     * @return bool
+     */
+    public function isLatestPanel()
+    {
+        if ($this->config->get('app.version') === 'canary') {
+            return true;
+        }
+
+        return version_compare($this->config->get('app.version'), $this->getPanel()) >= 0;
+    }
+
+    /**
+     * Determine if a passed daemon version string is the latest.
+     *
+     * @param string $version
+     * @return bool
+     */
+    public function isLatestDaemon($version)
+    {
+        if ($version === '0.0.0-canary') {
+            return true;
+        }
+
+        return version_compare($version, $this->getDaemon()) >= 0;
+    }
+
+    /**
+     * Keeps the versioning cache up-to-date with the latest results from the CDN.
+     */
+    protected function cacheVersionData()
+    {
+        $this->cache->remember(self::VERSION_CACHE_KEY, $this->config->get('pterodactyl.cdn.cache_time'), function () {
+            try {
+                $response = $this->client->request('GET', $this->config->get('pterodactyl.cdn.url'));
+
+                if ($response->getStatusCode() === 200) {
+                    return json_decode($response->getBody());
+                }
+
+                throw new CdnVersionFetchingException;
+            } catch (Exception $exception) {
+                return (object) [];
+            }
+        });
+    }
+}
diff --git a/app/Services/Old/APILogService.php b/app/Services/Old/APILogService.php
deleted file mode 100644
index d44670411..000000000
--- a/app/Services/Old/APILogService.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-/**
- * Pterodactyl - Panel
- * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-namespace Pterodactyl\Services;
-
-use Log;
-use Illuminate\Http\Request;
-use Pterodactyl\Models\APILog;
-
-class APILogService
-{
-    /**
-     * Log an API Request.
-     *
-     * @param \Illuminate\Http\Request $request
-     * @param null|string              $error
-     * @param bool                     $authorized
-     */
-    public static function log(Request $request, $error = null, $authorized = false)
-    {
-        if ($request->bearerToken() && ! empty($request->bearerToken())) {
-            list($public, $hashed) = explode('.', $request->bearerToken());
-        } else {
-            $public = null;
-        }
-
-        try {
-            $log = APILog::create([
-                'authorized' => $authorized,
-                'error' => $error,
-                'key' => $public,
-                'method' => $request->method(),
-                'route' => $request->fullUrl(),
-                'content' => (empty($request->getContent())) ? null : $request->getContent(),
-                'user_agent' => $request->header('User-Agent'),
-                'request_ip' => $request->ip(),
-            ]);
-            $log->save();
-        } catch (\Exception $ex) {
-            // Simply log it and move on.
-            Log::error($ex);
-        }
-    }
-}
diff --git a/app/Services/Old/VersionService.php b/app/Services/Old/VersionService.php
deleted file mode 100644
index 7134b31f0..000000000
--- a/app/Services/Old/VersionService.php
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-/**
- * Pterodactyl - Panel
- * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-namespace Pterodactyl\Services;
-
-use Cache;
-use GuzzleHttp\Client;
-
-class VersionService
-{
-    /**
-     * The cached CDN response.
-     *
-     * @var object
-     */
-    protected static $versions;
-
-    /**
-     * Version constructor.
-     */
-    public function __construct()
-    {
-        self::$versions = Cache::remember('versions', config('pterodactyl.cdn.cache'), function () {
-            $client = new Client();
-
-            try {
-                $response = $client->request('GET', config('pterodactyl.cdn.url'));
-
-                if ($response->getStatusCode() === 200) {
-                    return json_decode($response->getBody());
-                } else {
-                    throw new \Exception('Invalid response code.');
-                }
-            } catch (\Exception $ex) {
-                // Failed request, just return errored version.
-                return (object) [
-                    'panel' => 'error',
-                    'daemon' => 'error',
-                    'discord' => 'https://pterodactyl.io/discord',
-                ];
-            }
-        });
-    }
-
-    /**
-     * Return current panel version from CDN.
-     *
-     * @return string
-     */
-    public static function getPanel()
-    {
-        return self::$versions->panel;
-    }
-
-    /**
-     * Return current daemon version from CDN.
-     *
-     * @return string
-     */
-    public static function getDaemon()
-    {
-        return self::$versions->daemon;
-    }
-
-    /**
-     * Return Discord link from CDN.
-     *
-     * @return string
-     */
-    public static function getDiscord()
-    {
-        return self::$versions->discord;
-    }
-
-    /**
-     * Return current panel version.
-     *
-     * @return null|string
-     */
-    public function getCurrentPanel()
-    {
-        return config('app.version');
-    }
-
-    /**
-     * Determine if panel is latest version.
-     *
-     * @return bool
-     */
-    public static function isLatestPanel()
-    {
-        if (config('app.version') === 'canary') {
-            return true;
-        }
-
-        return version_compare(config('app.version'), self::$versions->panel) >= 0;
-    }
-
-    /**
-     * Determine if daemon is latest version.
-     *
-     * @return bool
-     */
-    public static function isLatestDaemon($daemon)
-    {
-        if ($daemon === '0.0.0-canary') {
-            return true;
-        }
-
-        return version_compare($daemon, self::$versions->daemon) >= 0;
-    }
-}
diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php
new file mode 100644
index 000000000..39c38f18d
--- /dev/null
+++ b/app/Services/Subusers/SubuserCreationService.php
@@ -0,0 +1,188 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Services\Subusers;
+
+use GuzzleHttp\Exception\RequestException;
+use Illuminate\Database\ConnectionInterface;
+use Illuminate\Log\Writer;
+use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
+use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface;
+use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
+use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
+use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
+use Pterodactyl\Exceptions\DisplayException;
+use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException;
+use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException;
+use Pterodactyl\Models\Permission;
+use Pterodactyl\Models\Server;
+use Pterodactyl\Services\Users\CreationService;
+
+class SubuserCreationService
+{
+    const CORE_DAEMON_PERMISSIONS = [
+        's:get',
+        's:console',
+    ];
+
+    const DAEMON_SECRET_BYTES = 18;
+
+    /**
+     * @var \Illuminate\Database\ConnectionInterface
+     */
+    protected $connection;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
+     */
+    protected $daemonRepository;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface
+     */
+    protected $permissionRepository;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
+     */
+    protected $subuserRepository;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
+     */
+    protected $serverRepository;
+
+    /**
+     * @var \Pterodactyl\Services\Users\CreationService
+     */
+    protected $userCreationService;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
+     */
+    protected $userRepository;
+
+    /**
+     * @var \Illuminate\Log\Writer
+     */
+    protected $writer;
+
+    public function __construct(
+        ConnectionInterface $connection,
+        CreationService $userCreationService,
+        DaemonServerRepositoryInterface $daemonRepository,
+        PermissionRepositoryInterface $permissionRepository,
+        ServerRepositoryInterface $serverRepository,
+        SubuserRepositoryInterface $subuserRepository,
+        UserRepositoryInterface $userRepository,
+        Writer $writer
+    ) {
+        $this->connection = $connection;
+        $this->daemonRepository = $daemonRepository;
+        $this->permissionRepository = $permissionRepository;
+        $this->subuserRepository = $subuserRepository;
+        $this->serverRepository = $serverRepository;
+        $this->userRepository = $userRepository;
+        $this->userCreationService = $userCreationService;
+        $this->writer = $writer;
+    }
+
+    /**
+     * @param int|\Pterodactyl\Models\Server $server
+     * @param string                         $email
+     * @param array                          $permissions
+     * @return \Pterodactyl\Models\Subuser
+     *
+     * @throws \Exception
+     * @throws \Pterodactyl\Exceptions\DisplayException
+     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
+     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
+     * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException
+     * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException
+     */
+    public function handle($server, $email, array $permissions)
+    {
+        if (! $server instanceof Server) {
+            $server = $this->serverRepository->find($server);
+        }
+
+        $user = $this->userRepository->findWhere([['email', '=', $email]]);
+        if (is_null($user)) {
+            $user = $this->userCreationService->handle([
+                'email' => $email,
+                'username' => substr(strtok($email, '@'), 0, 8),
+                'name_first' => 'Server',
+                'name_last' => 'Subuser',
+                'root_admin' => false,
+            ]);
+        } else {
+            if ($server->owner_id === $user->id) {
+                throw new UserIsServerOwnerException(trans('admin/exceptions.subusers.user_is_owner'));
+            }
+
+            $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]);
+            if ($subuserCount !== 0) {
+                throw new ServerSubuserExistsException(trans('admin/exceptions.subusers.subuser_exists'));
+            }
+        }
+
+        $this->connection->beginTransaction();
+        $subuser = $this->subuserRepository->create([
+            'user_id' => $user->id,
+            'server_id' => $server->id,
+            'daemonSecret' => bin2hex(random_bytes(self::DAEMON_SECRET_BYTES)),
+        ]);
+
+        $permissionMappings = Permission::getPermissions(true);
+        $daemonPermissions = self::CORE_DAEMON_PERMISSIONS;
+
+        foreach ($permissions as $permission) {
+            if (array_key_exists($permission, $permissionMappings)) {
+                if (! is_null($permissionMappings[$permission])) {
+                    array_push($daemonPermissions, $permissionMappings[$permission]);
+                }
+
+                $this->permissionRepository->create([
+                    'subuser_id' => $subuser->id,
+                    'permission' => $permission,
+                ]);
+            }
+        }
+
+        try {
+            $this->daemonRepository->setNode($server->node_id)->setAccessServer($server->uuid)
+                ->setSubuserKey($subuser->daemonSecret, $daemonPermissions);
+            $this->connection->commit();
+
+            return $subuser;
+        } catch (RequestException $exception) {
+            $response = $exception->getResponse();
+            $this->writer->warning($exception);
+
+            throw new DisplayException(trans('admin/exceptions.daemon_connection_failed', [
+                'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(),
+            ]));
+        }
+    }
+}
diff --git a/config/cache.php b/config/cache.php
index fb619fb54..6109216c7 100644
--- a/config/cache.php
+++ b/config/cache.php
@@ -80,5 +80,5 @@ return [
     |
     */
 
-    'prefix' => 'laravel',
+    'prefix' => 'pterodactyl',
 ];
diff --git a/config/pterodactyl.php b/config/pterodactyl.php
index 32b239bd1..a21283d1d 100644
--- a/config/pterodactyl.php
+++ b/config/pterodactyl.php
@@ -119,7 +119,7 @@ return [
     | if panel is up to date.
     */
     'cdn' => [
-        'cache' => 60,
+        'cache_time' => 60,
         'url' => 'https://cdn.pterodactyl.io/releases/latest.json',
     ],
 
diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php
index a6fbc0691..398915ac7 100644
--- a/database/factories/ModelFactory.php
+++ b/database/factories/ModelFactory.php
@@ -145,3 +145,12 @@ $factory->define(Pterodactyl\Models\Pack::class, function (Faker\Generator $fake
         'locked' => 0,
     ];
 });
+
+$factory->define(Pterodactyl\Models\Subuser::class, function (Faker\Generator $faker) {
+    return [
+        'id' => $faker->unique()->randomNumber(),
+        'user_id' => $faker->randomNumber(),
+        'server_id' => $faker->randomNumber(),
+        'daemonSecret' => $faker->unique()->uuid,
+    ];
+});
diff --git a/resources/lang/en/admin/exceptions.php b/resources/lang/en/admin/exceptions.php
index 4ff0c4b4f..21bd812b2 100644
--- a/resources/lang/en/admin/exceptions.php
+++ b/resources/lang/en/admin/exceptions.php
@@ -54,4 +54,8 @@ return [
         'zip_extraction' => 'An exception was encountered while attempting to extract the archive provided onto the server.',
         'invalid_archive_exception' => 'The pack archive provided appears to be missing a required archive.tar.gz or import.json file in the base directory.',
     ],
+    'subusers' => [
+        'user_is_owner' => 'You cannot add the server owner as a subuser for this server.',
+        'subuser_exists' => 'A user with that email address is already assigned as a subuser for this server.',
+    ],
 ];
diff --git a/resources/themes/pterodactyl/admin/index.blade.php b/resources/themes/pterodactyl/admin/index.blade.php
index 76f0ed3f6..e2884ba0e 100644
--- a/resources/themes/pterodactyl/admin/index.blade.php
+++ b/resources/themes/pterodactyl/admin/index.blade.php
@@ -35,7 +35,7 @@
 <div class="row">
     <div class="col-xs-12">
         <div class="box
-            @if(Version::isLatestPanel())
+            @if($version->isLatestPanel())
                 box-success
             @else
                 box-danger
@@ -45,10 +45,10 @@
                 <h3 class="box-title">System Information</h3>
             </div>
             <div class="box-body">
-                @if (Version::isLatestPanel())
-                    You are running Pterodactyl Panel version <code>{{ Version::getCurrentPanel() }}</code>. Your panel is up-to-date!
+                @if ($version->isLatestPanel())
+                    You are running Pterodactyl Panel version <code>{{ config('app.version') }}</code>. Your panel is up-to-date!
                 @else
-                    Your panel is <strong>not up-to-date!</strong> The latest version is <a href="https://github.com/Pterodactyl/Panel/releases/v{{ Version::getPanel() }}" target="_blank"><code>{{ Version::getPanel() }}</code></a> and you are currently running version <code>{{ Version::getCurrentPanel() }}</code>.
+                    Your panel is <strong>not up-to-date!</strong> The latest version is <a href="https://github.com/Pterodactyl/Panel/releases/v{{ $version->getPanel() }}" target="_blank"><code>{{ $version->getPanel() }}</code></a> and you are currently running version <code>{{ config('app.version') }}</code>.
                 @endif
             </div>
         </div>
@@ -56,7 +56,7 @@
 </div>
 <div class="row">
     <div class="col-xs-6 col-sm-3 text-center">
-        <a href="{{ Version::getDiscord() }}"><button class="btn btn-warning" style="width:100%;"><i class="fa fa-fw fa-support"></i> Get Help <small>(via Discord)</small></button></a>
+        <a href="{{ $version->getDiscord() }}"><button class="btn btn-warning" style="width:100%;"><i class="fa fa-fw fa-support"></i> Get Help <small>(via Discord)</small></button></a>
     </div>
     <div class="col-xs-6 col-sm-3 text-center">
         <a href="https://docs.pterodactyl.io"><button class="btn btn-primary" style="width:100%;"><i class="fa fa-fw fa-link"></i> Documentation</button></a>
diff --git a/tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php b/tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php
new file mode 100644
index 000000000..a3039694b
--- /dev/null
+++ b/tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php
@@ -0,0 +1,183 @@
+<?php
+/**
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Tests\Unit\Services\Helpers;
+
+use Closure;
+use GuzzleHttp\Client;
+use Mockery as m;
+use Pterodactyl\Services\Helpers\SoftwareVersionService;
+use Tests\TestCase;
+use Illuminate\Contracts\Cache\Repository as CacheRepository;
+use Illuminate\Contracts\Config\Repository as ConfigRepository;
+
+class SoftwareVersionServiceTest extends TestCase
+{
+    /**
+     * @var \Illuminate\Contracts\Cache\Repository
+     */
+    protected $cache;
+
+    /**
+     * @var \GuzzleHttp\Client
+     */
+    protected $client;
+
+    /**
+     * @var \Illuminate\Contracts\Config\Repository
+     */
+    protected $config;
+
+    /**
+     * @var object
+     */
+    protected static $response = [
+        'panel' => '0.2.0',
+        'daemon' => '0.1.0',
+        'discord' => 'https://pterodactyl.io/discord',
+    ];
+
+    /**
+     * @var \Pterodactyl\Services\Helpers\SoftwareVersionService
+     */
+    protected $service;
+
+    /**
+     * Setup tests
+     */
+    public function setUp()
+    {
+        parent::setUp();
+
+        self::$response = (object) self::$response;
+
+        $this->cache = m::mock(CacheRepository::class);
+        $this->client = m::mock(Client::class);
+        $this->config = m::mock(ConfigRepository::class);
+
+        $this->config->shouldReceive('get')->with('pterodactyl.cdn.cache_time')->once()->andReturn(60);
+
+        $this->cache->shouldReceive('remember')->with(SoftwareVersionService::VERSION_CACHE_KEY, 60, Closure::class)->once()->andReturnNull();
+
+        $this->service = m::mock(SoftwareVersionService::class, [$this->cache, $this->client, $this->config])->makePartial();
+    }
+
+    /**
+     * Test that the panel version is returned.
+     */
+    public function testPanelVersionIsReturned()
+    {
+        $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn(self::$response);
+        $this->assertEquals(self::$response->panel, $this->service->getPanel());
+    }
+
+    /**
+     * Test that the panel version is returned as error.
+     */
+    public function testPanelVersionIsReturnedAsErrorIfNoKeyIsFound()
+    {
+        $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn((object) []);
+        $this->assertEquals('error', $this->service->getPanel());
+    }
+
+    /**
+     * Test that the daemon version is returned.
+     */
+    public function testDaemonVersionIsReturned()
+    {
+        $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn(self::$response);
+        $this->assertEquals(self::$response->daemon, $this->service->getDaemon());
+    }
+
+    /**
+     * Test that the daemon version is returned as an error.
+     */
+    public function testDaemonVersionIsReturnedAsErrorIfNoKeyIsFound()
+    {
+        $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn((object) []);
+        $this->assertEquals('error', $this->service->getDaemon());
+    }
+
+    /**
+     * Test that the discord URL is returned.
+     */
+    public function testDiscordUrlIsReturned()
+    {
+        $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn(self::$response);
+        $this->assertEquals(self::$response->discord, $this->service->getDiscord());
+    }
+
+    /**
+     * Test that the correct boolean value is returned by the helper for each version passed.
+     *
+     * @dataProvider panelVersionProvider
+     */
+    public function testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion($version, $response)
+    {
+        $this->config->shouldReceive('get')->with('app.version')->andReturn($version);
+        $this->service->shouldReceive('getPanel')->withNoArgs()->andReturn(self::$response->panel);
+
+        $this->assertEquals($response, $this->service->isLatestPanel());
+    }
+
+    /**
+     * Test that the correct boolean value is returned.
+     *
+     * @dataProvider daemonVersionProvider
+     */
+    public function testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion($version, $response)
+    {
+        $this->service->shouldReceive('getDaemon')->withNoArgs()->andReturn(self::$response->daemon);
+
+        $this->assertEquals($response, $this->service->isLatestDaemon($version));
+    }
+
+    /**
+     * Provide data for testing boolean response on panel version.
+     *
+     * @return array
+     */
+    public function panelVersionProvider()
+    {
+        return [
+            [self::$response['panel'], true],
+            ['0.0.1', false],
+            ['canary', true],
+        ];
+    }
+
+    /**
+     * Provide data for testing booklean response for daemon version.
+     *
+     * @return array
+     */
+    public function daemonVersionProvider()
+    {
+        return [
+            [self::$response['daemon'], true],
+            ['0.0.1', false],
+            ['0.0.0-canary', true],
+        ];
+    }
+}
diff --git a/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php b/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php
new file mode 100644
index 000000000..a7492cb34
--- /dev/null
+++ b/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php
@@ -0,0 +1,260 @@
+<?php
+/**
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Tests\Unit\Services\Subusers;
+
+use Illuminate\Database\ConnectionInterface;
+use Illuminate\Log\Writer;
+use Mockery as m;
+use phpmock\phpunit\PHPMock;
+use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
+use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface;
+use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
+use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
+use Pterodactyl\Exceptions\DisplayException;
+use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException;
+use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException;
+use Pterodactyl\Models\Server;
+use Pterodactyl\Models\Subuser;
+use Pterodactyl\Models\User;
+use Pterodactyl\Services\Subusers\SubuserCreationService;
+use Pterodactyl\Services\Users\CreationService;
+use Tests\TestCase;
+use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
+
+class SubuserCreationServiceTest extends TestCase
+{
+    use PHPMock;
+
+    /**
+     * @var \Illuminate\Database\ConnectionInterface
+     */
+    protected $connection;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
+     */
+    protected $daemonRepository;
+
+    /**
+     * @var \Pterodactyl\Models\Permission
+     */
+    protected $permission;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface
+     */
+    protected $permissionRepository;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
+     */
+    protected $subuserRepository;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
+     */
+    protected $serverRepository;
+
+    /**
+     * @var \Pterodactyl\Services\Subusers\SubuserCreationService
+     */
+    protected $service;
+
+    /**
+     * @var \Pterodactyl\Services\Users\CreationService
+     */
+    protected $userCreationService;
+
+    /**
+     * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
+     */
+    protected $userRepository;
+
+    /**
+     * @var \Illuminate\Log\Writer
+     */
+    protected $writer;
+
+    /**
+     * Setup tests.
+     */
+    public function setUp()
+    {
+        parent::setUp();
+
+        $this->getFunctionMock('\\Pterodactyl\\Services\\Subusers', 'bin2hex')->expects($this->any())->willReturn('bin2hex');
+
+        $this->connection = m::mock(ConnectionInterface::class);
+        $this->daemonRepository = m::mock(DaemonServerRepositoryInterface::class);
+        $this->permission = m::mock('overload:Pterodactyl\Models\Permission');
+        $this->permissionRepository = m::mock(PermissionRepositoryInterface::class);
+        $this->subuserRepository = m::mock(SubuserRepositoryInterface::class);
+        $this->serverRepository = m::mock(ServerRepositoryInterface::class);
+        $this->userCreationService = m::mock(CreationService::class);
+        $this->userRepository = m::mock(UserRepositoryInterface::class);
+        $this->writer = m::mock(Writer::class);
+
+        $this->service = new SubuserCreationService(
+            $this->connection,
+            $this->userCreationService,
+            $this->daemonRepository,
+            $this->permissionRepository,
+            $this->serverRepository,
+            $this->subuserRepository,
+            $this->userRepository,
+            $this->writer
+        );
+    }
+
+    /**
+     * Test that a user without an existing account can be added as a subuser.
+     */
+    public function testAccountIsCreatedForNewUser()
+    {
+        $permissions = ['test-1' => 'test:1', 'test-2' => null];
+        $server = factory(Server::class)->make();
+        $user = factory(User::class)->make();
+        $subuser = factory(Subuser::class)->make(['user_id' => $user->id, 'server_id' => $server->id]);
+
+        $this->userRepository->shouldReceive('findWhere')->with([['email', '=', $user->email]])->once()->andReturnNull();
+        $this->userCreationService->shouldReceive('handle')->with([
+            'email' => $user->email,
+            'username' => substr(strtok($user->email, '@'), 0, 8),
+            'name_first' => 'Server',
+            'name_last' => 'Subuser',
+            'root_admin' => false,
+        ])->once()->andReturn($user);
+
+        $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
+        $this->subuserRepository->shouldReceive('create')->with([
+            'user_id' => $user->id,
+            'server_id' => $server->id,
+            'daemonSecret' => 'bin2hex',
+        ])->once()->andReturn($subuser);
+
+        $this->permission->shouldReceive('getPermissions')->with(true)->once()
+            ->andReturn($permissions);
+
+        foreach(array_keys($permissions) as $permission) {
+            $this->permissionRepository->shouldReceive('create')
+                ->with(['subuser_id' => $subuser->id, 'permission' => $permission])
+                ->once()->andReturnNull();
+        }
+
+        $this->daemonRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf()
+            ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf()
+            ->shouldReceive('setSubuserKey')->with($subuser->daemonSecret, ['s:get', 's:console', 'test:1'])->once()->andReturnSelf();
+        $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
+
+        $response = $this->service->handle($server, $user->email, array_keys($permissions));
+
+        $this->assertInstanceOf(Subuser::class, $response);
+        $this->assertSame($subuser, $response);
+    }
+
+    /**
+     * Test that an existing user can be added as a subuser.
+     */
+    public function testExistingUserCanBeAddedAsASubuser()
+    {
+        $permissions = ['test-1' => 'test:1', 'test-2' => null];
+        $server = factory(Server::class)->make();
+        $user = factory(User::class)->make();
+        $subuser = factory(Subuser::class)->make(['user_id' => $user->id, 'server_id' => $server->id]);
+
+        $this->userRepository->shouldReceive('findWhere')->with([['email', '=', $user->email]])->once()->andReturn($user);
+        $this->subuserRepository->shouldReceive('findCountWhere')->with([
+            ['user_id', '=', $user->id],
+            ['server_id', '=', $server->id],
+        ])->once()->andReturn(0);
+
+        $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
+        $this->subuserRepository->shouldReceive('create')->with([
+            'user_id' => $user->id,
+            'server_id' => $server->id,
+            'daemonSecret' => 'bin2hex',
+        ])->once()->andReturn($subuser);
+
+        $this->permission->shouldReceive('getPermissions')->with(true)->once()
+            ->andReturn($permissions);
+
+        foreach(array_keys($permissions) as $permission) {
+            $this->permissionRepository->shouldReceive('create')
+                ->with(['subuser_id' => $subuser->id, 'permission' => $permission])
+                ->once()->andReturnNull();
+        }
+
+        $this->daemonRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf()
+            ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf()
+            ->shouldReceive('setSubuserKey')->with($subuser->daemonSecret, ['s:get', 's:console', 'test:1'])->once()->andReturnSelf();
+        $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
+
+        $response = $this->service->handle($server, $user->email, array_keys($permissions));
+
+        $this->assertInstanceOf(Subuser::class, $response);
+        $this->assertSame($subuser, $response);
+    }
+
+    /**
+     * Test that an exception gets thrown if the subuser is actually the server owner
+     */
+    public function testExceptionIsThrownIfUserIsServerOwner()
+    {
+        $user = factory(User::class)->make();
+        $server = factory(Server::class)->make(['owner_id' => $user->id]);
+
+        $this->userRepository->shouldReceive('findWhere')->with([['email', '=', $user->email]])->once()->andReturn($user);
+
+        try {
+            $this->service->handle($server, $user->email, []);
+        } catch (DisplayException $exception) {
+            $this->assertInstanceOf(UserIsServerOwnerException::class, $exception);
+            $this->assertEquals(trans('admin/exceptions.subusers.user_is_owner'), $exception->getMessage());
+        }
+    }
+
+    /**
+     * Test that an exception is thrown if the user is already added as a subuser.
+     */
+    public function testExceptionIsThrownIfUserIsAlreadyASubuser()
+    {
+        $user = factory(User::class)->make();
+        $server = factory(Server::class)->make();
+
+        $this->userRepository->shouldReceive('findWhere')->with([['email', '=', $user->email]])->once()->andReturn($user);
+        $this->subuserRepository->shouldReceive('findCountWhere')->with([
+            ['user_id', '=', $user->id],
+            ['server_id', '=', $server->id],
+        ])->once()->andReturn(1);
+
+        try {
+            $this->service->handle($server, $user->email, []);
+        } catch (DisplayException $exception) {
+            $this->assertInstanceOf(ServerSubuserExistsException::class, $exception);
+            $this->assertEquals(trans('admin/exceptions.subusers.subuser_exists'), $exception->getMessage());
+        }
+
+    }
+}