From 65285264c70bc910380a07be72f60148c6adc1a5 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 19 Nov 2020 00:08:29 +0200 Subject: [PATCH 1/9] Fixed minor spec bug --- src/Appwrite/Specification/Format/OpenAPI3.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Specification/Format/OpenAPI3.php b/src/Appwrite/Specification/Format/OpenAPI3.php index 30db36bdb7..dee4a5b1c6 100644 --- a/src/Appwrite/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/Specification/Format/OpenAPI3.php @@ -280,7 +280,7 @@ class OpenAPI3 extends Format 'x-example' => $node['x-example'] ?? null, ]; - if(!\is_null($node['default'])) { + if(isset($node['default'])) { $body['content'][$consumes[0]]['schema']['properties'][$name]['default'] = $node['default']; } From a08ce93b31aa98a2bc004669d39b22131bb428db Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 19 Nov 2020 00:08:45 +0200 Subject: [PATCH 2/9] Added a new mode constant --- app/init.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/init.php b/app/init.php index a6aff0b490..7f31739bd9 100644 --- a/app/init.php +++ b/app/init.php @@ -34,6 +34,7 @@ const APP_DOMAIN = 'appwrite.io'; const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address const APP_EMAIL_SECURITY = 'security@localhost.test'; // Default security email address const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s'; +const APP_MODE_DEFAULT = 'default'; const APP_MODE_ADMIN = 'admin'; const APP_PAGING_LIMIT = 12; const APP_CACHE_BUSTER = 140; @@ -463,7 +464,7 @@ App::setResource('projectDB', function($register, $project) { App::setResource('mode', function($request) { /** @var Utopia\Swoole\Request $request */ - return $request->getParam('mode', $request->getHeader('x-appwrite-mode', 'default')); + return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); }, ['request']); App::setResource('geodb', function($register) { From 43904a7c7467981fde02176f775cbe66573c5252 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 19 Nov 2020 00:08:54 +0200 Subject: [PATCH 3/9] Changed unique type --- src/Appwrite/Auth/Auth.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 0aebac7c51..89a0e90751 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -49,9 +49,9 @@ class Auth /** * User Unique ID. * - * @var int + * @var string */ - public static $unique = 0; + public static $unique = ''; /** * User Secret Key. @@ -75,7 +75,7 @@ class Auth /** * Encode Session. * - * @param int $id + * @param string $id * @param string $secret * * @return string From f44bb22f76e03ace6247375b2d48dcd378d318ba Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 19 Nov 2020 00:24:28 +0200 Subject: [PATCH 4/9] Updated Authorization and added unit test --- .../Database/Validator/Authorization.php | 30 +++++-- .../Database/Validator/AuthorizationTest.php | 78 +++++++++++++++++++ 2 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 tests/unit/Database/Validator/AuthorizationTest.php diff --git a/src/Appwrite/Database/Validator/Authorization.php b/src/Appwrite/Database/Validator/Authorization.php index 03fd40a306..65fe548e75 100644 --- a/src/Appwrite/Database/Validator/Authorization.php +++ b/src/Appwrite/Database/Validator/Authorization.php @@ -10,7 +10,7 @@ class Authorization extends Validator /** * @var array */ - static $roles = ['*']; + static $roles = ['*' => true]; /** * @var Document @@ -77,7 +77,7 @@ class Authorization extends Validator foreach ($permissions[$this->action] as $permission) { $permission = \str_replace(':{self}', ':'.$this->document->getId(), $permission); - if (\in_array($permission, self::getRoles())) { + if (\array_key_exists($permission, self::$roles)) { return true; } } @@ -92,17 +92,35 @@ class Authorization extends Validator * * @return void */ - public static function setRole($role): void + public static function setRole(string $role): void { - self::$roles[] = $role; + self::$roles[$role] = true; } /** * @return array */ - public static function getRoles() + public static function getRoles(): array { - return self::$roles; + return \array_keys(self::$roles); + } + + /** + * @return void + */ + public static function cleanRoles(): void + { + self::$roles = []; + } + + /** + * @param string $role + * + * @return bool + */ + public static function isRole(string $role): bool + { + return (\array_key_exists($role, self::$roles)); } /** diff --git a/tests/unit/Database/Validator/AuthorizationTest.php b/tests/unit/Database/Validator/AuthorizationTest.php new file mode 100644 index 0000000000..43a456b86c --- /dev/null +++ b/tests/unit/Database/Validator/AuthorizationTest.php @@ -0,0 +1,78 @@ +document = new Document([ + '$id' => uniqid(), + '$collection' => uniqid(), + '$permissions' => [ + 'read' => ['user:123', 'team:123'], + 'write' => ['*'], + ], + ]); + $this->object = new Authorization($this->document, 'read'); + } + + public function tearDown(): void + { + } + + public function testValues() + { + $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + + Authorization::setRole('user:456'); + Authorization::setRole('user:123'); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::cleanRoles(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + + Authorization::setRole('team:123'); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::cleanRoles(); + Authorization::disable(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::reset(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + + Authorization::setDefaultStatus(false); + Authorization::disable(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::reset(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::enable(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + + } +} \ No newline at end of file From 76c575e989e502dbfb77cd47f1da05daa62daf27 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 19 Nov 2020 00:55:02 +0200 Subject: [PATCH 5/9] Fixed authorization --- app/controllers/general.php | 2 -- tests/unit/Database/Validator/AuthorizationTest.php | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index c921816206..13b4ff0f61 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -36,8 +36,6 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo /** @var bool $mode */ /** @var array $clients */ - Authorization::$roles = ['*']; - $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); if (\in_array($localeParam, Config::getParam('locale-codes'))) { diff --git a/tests/unit/Database/Validator/AuthorizationTest.php b/tests/unit/Database/Validator/AuthorizationTest.php index 43a456b86c..e2e9ceac51 100644 --- a/tests/unit/Database/Validator/AuthorizationTest.php +++ b/tests/unit/Database/Validator/AuthorizationTest.php @@ -42,6 +42,11 @@ class AuthorizationTest extends TestCase Authorization::setRole('user:456'); Authorization::setRole('user:123'); + $this->assertEquals(Authorization::isRole('user:456'), true); + $this->assertEquals(Authorization::isRole('user:457'), false); + $this->assertEquals(Authorization::isRole(''), false); + $this->assertEquals(Authorization::isRole('*'), true); + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); Authorization::cleanRoles(); From 4f0903db7950efc7b0a990705f7ea88b8877ac57 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 19 Nov 2020 08:56:14 +0200 Subject: [PATCH 6/9] Added validation check --- app/controllers/general.php | 2 +- app/http.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 13b4ff0f61..f2e3dfe837 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -35,7 +35,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo /** @var Appwrite\Event\Event $deletes */ /** @var bool $mode */ /** @var array $clients */ - + $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); if (\in_array($localeParam, Config::getParam('locale-codes'))) { diff --git a/app/http.php b/app/http.php index b018d37a12..afb9411ec5 100644 --- a/app/http.php +++ b/app/http.php @@ -2,6 +2,7 @@ require_once __DIR__.'/../vendor/autoload.php'; +use Appwrite\Database\Validator\Authorization; use Utopia\Swoole\Files; use Utopia\Swoole\Request; use Appwrite\Utopia\Response; @@ -94,6 +95,9 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo $app = new App('America/New_York'); try { + Authorization::cleanRoles(); + Authorization::setRole('*'); + $app->run($request, $response); } catch (\Throwable $th) { Console::error('[Error] Type: '.get_class($th)); From 3828a8deaefd3e51624d4a46c99a9a2c27fcc3fb Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 19 Nov 2020 11:00:24 +0200 Subject: [PATCH 7/9] Fixed Doctor SMTP check --- app/tasks/doctor.php | 1 + app/views/install/compose.phtml | 5 +++++ docker-compose.yml | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/app/tasks/doctor.php b/app/tasks/doctor.php index fc949d45c7..70a7546b2a 100644 --- a/app/tasks/doctor.php +++ b/app/tasks/doctor.php @@ -138,6 +138,7 @@ $cli Console::success('SMTP................connected 👍'); } catch (\Throwable $th) { Console::error('SMTP.............disconnected 👎'); + var_dump($th); } $host = App::getEnv('_APP_STATSD_HOST', 'telegraf'); diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 6ec157ce7e..a97cd7882f 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -67,6 +67,11 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS + - _APP_SMTP_HOST + - _APP_SMTP_PORT + - _APP_SMTP_SECURE + - _APP_SMTP_USERNAME + - _APP_SMTP_PASSWORD - _APP_INFLUXDB_HOST - _APP_INFLUXDB_PORT - _APP_STORAGE_LIMIT diff --git a/docker-compose.yml b/docker-compose.yml index 55cd64fdd6..72d791fa98 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -82,6 +82,11 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS + - _APP_SMTP_HOST + - _APP_SMTP_PORT + - _APP_SMTP_SECURE + - _APP_SMTP_USERNAME + - _APP_SMTP_PASSWORD - _APP_INFLUXDB_HOST - _APP_INFLUXDB_PORT - _APP_STORAGE_LIMIT From 9169bdeeec3198bef707b899809e908896a776f6 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 20 Nov 2020 08:48:15 +0200 Subject: [PATCH 8/9] Better detection of server and console modes --- app/controllers/api/teams.php | 30 ++++++++++++++++------------ app/init.php | 3 +-- src/Appwrite/Auth/Auth.php | 37 ++++++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index bdf05bd54a..32d77e5e1d 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -33,14 +33,16 @@ App::post('/v1/teams') ->label('sdk.response.model', Response::MODEL_TEAM) ->param('name', null, new Text(128), 'Team name. Max length: 128 chars.') ->param('roles', ['owner'], new ArrayList(new Key()), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.', true) - ->action(function ($name, $roles, $response, $user, $projectDB, $mode) { + ->action(function ($name, $roles, $response, $user, $projectDB) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ - /** @var bool $mode */ Authorization::disable(); + $isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); + $team = $projectDB->createDocument([ '$collection' => Database::SYSTEM_COLLECTION_TEAMS, '$permissions' => [ @@ -48,7 +50,7 @@ App::post('/v1/teams') 'write' => ['team:{self}/owner'], ], 'name' => $name, - 'sum' => ($mode !== APP_MODE_ADMIN && $user->getId()) ? 1 : 0, + 'sum' => ($isPreviliggedUser || $isAppUser) ? 0 : 1, 'dateCreated' => \time(), ]); @@ -58,7 +60,7 @@ App::post('/v1/teams') throw new Exception('Failed saving team to DB', 500); } - if ($mode !== APP_MODE_ADMIN && $user->getId()) { // Don't add user on server mode + if (!$isPreviliggedUser && !$isAppUser) { // Don't add user on server mode $membership = new Document([ '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, '$permissions' => [ @@ -88,7 +90,7 @@ App::post('/v1/teams') ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($team, Response::MODEL_TEAM) ; - }, ['response', 'user', 'projectDB', 'mode']); + }, ['response', 'user', 'projectDB']); App::get('/v1/teams') ->desc('List Teams') @@ -246,14 +248,16 @@ App::post('/v1/teams/:teamId/memberships') ->param('name', '', new Text(128), 'New team member name. Max length: 128 chars.', true) ->param('roles', [], new ArrayList(new Key()), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.') ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add our own built-in confirm page - ->action(function ($teamId, $email, $name, $roles, $url, $response, $project, $user, $projectDB, $locale, $audits, $mails, $mode) { + ->action(function ($teamId, $email, $name, $roles, $url, $response, $project, $user, $projectDB, $locale, $audits, $mails) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $mails */ - /** @var bool $mode */ + + $isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); $name = (empty($name)) ? $email : $name; $team = $projectDB->getDocument($teamId); @@ -323,7 +327,7 @@ App::post('/v1/teams/:teamId/memberships') } } - if (!$isOwner && APP_MODE_ADMIN !== $mode && $user->getId()) { // Not owner, not admin, not app (server) + if (!$isOwner && !$isPreviliggedUser && !$isAppUser) { // Not owner, not admin, not app (server) throw new Exception('User is not allowed to send invitations for this team', 401); } @@ -339,12 +343,12 @@ App::post('/v1/teams/:teamId/memberships') 'teamId' => $team->getId(), 'roles' => $roles, 'invited' => \time(), - 'joined' => (APP_MODE_ADMIN === $mode || !$user->getId()) ? \time() : 0, - 'confirm' => (APP_MODE_ADMIN === $mode || !$user->getId()), + 'joined' => ($isPreviliggedUser || $isAppUser) ? \time() : 0, + 'confirm' => ($isPreviliggedUser || $isAppUser), 'secret' => Auth::hash($secret), ]); - if (APP_MODE_ADMIN === $mode || !$user->getId()) { // Allow admin to create membership + if ($isPreviliggedUser || $isAppUser) { // Allow admin to create membership Authorization::disable(); $membership = $projectDB->createDocument($membership->getArrayCopy()); @@ -395,7 +399,7 @@ App::post('/v1/teams/:teamId/memberships') ->setParam('{{text-cta}}', '#ffffff') ; - if (APP_MODE_ADMIN !== $mode && $user->getId()) { // No need in comfirmation when in admin or app mode + if (!$isPreviliggedUser && !$isAppUser) { // No need in comfirmation when in admin or app mode $mails ->setParam('event', 'teams.membership.create') ->setParam('from', ($project->getId() === 'console') ? '' : \sprintf($locale->getText('account.emails.team'), $project->getAttribute('name'))) @@ -420,7 +424,7 @@ App::post('/v1/teams/:teamId/memberships') 'name' => $name, ])), Response::MODEL_MEMBERSHIP) ; - }, ['response', 'project', 'user', 'projectDB', 'locale', 'audits', 'mails', 'mode']); + }, ['response', 'project', 'user', 'projectDB', 'locale', 'audits', 'mails']); App::get('/v1/teams/:teamId/memberships') ->desc('Get Team Memberships') diff --git a/app/init.php b/app/init.php index 7f31739bd9..3e64c87562 100644 --- a/app/init.php +++ b/app/init.php @@ -382,8 +382,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response $session = Auth::decodeSession( $request->getCookie(Auth::$cookieName, // Get sessions - $request->getCookie(Auth::$cookieName.'_legacy', // Get fallback session from old clients (no SameSite support) - $request->getHeader('x-appwrite-key', '')))); // Get API Key + $request->getCookie(Auth::$cookieName.'_legacy', '')));// Get fallback session from old clients (no SameSite support) // Get fallback session from clients who block 3rd-party cookies $response->addHeader('X-Debug-Fallback', 'false'); diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 89a0e90751..88eb11299d 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -23,7 +23,6 @@ class Auth const USER_ROLE_OWNER = 4; const USER_ROLE_APP = 5; const USER_ROLE_SYSTEM = 6; - const USER_ROLE_ALL = '*'; /** * Token Types. @@ -206,4 +205,40 @@ class Auth return false; } + + /** + * Is Previligged User? + * + * @param array $roles + * + * @return bool + */ + public static function isPreviliggedUser(array $roles): bool + { + if( + array_key_exists('role:'.self::USER_ROLE_OWNER, $roles) || + array_key_exists('role:'.self::USER_ROLE_DEVELOPER, $roles) || + array_key_exists('role:'.self::USER_ROLE_ADMIN, $roles) + ) { + return true; + } + + return false; + } + + /** + * Is App User? + * + * @param array $roles + * + * @return bool + */ + public static function isAppUser(array $roles): bool + { + if(array_key_exists('role:'.self::USER_ROLE_APP, $roles)) { + return true; + } + + return false; + } } From e2ba702dc9f2deb1eb5fe9c3faeddd826a35015a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 20 Nov 2020 08:48:25 +0200 Subject: [PATCH 9/9] More tests :) --- tests/unit/Auth/AuthTest.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index f56c1a3f37..5860d1efb9 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -118,4 +118,38 @@ class AuthTest extends TestCase $this->assertEquals(Auth::tokenVerify($tokens3, Auth::TOKEN_TYPE_LOGIN, $secret), false); $this->assertEquals(Auth::tokenVerify($tokens3, Auth::TOKEN_TYPE_LOGIN, 'false-secret'), false); } + + public function testIsPreviliggedUser() + { + $this->assertEquals(false, Auth::isPreviliggedUser([])); + $this->assertEquals(false, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_GUEST => true])); + $this->assertEquals(false, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_MEMBER => true])); + $this->assertEquals(true, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_ADMIN => true])); + $this->assertEquals(true, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_DEVELOPER => true])); + $this->assertEquals(true, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_OWNER => true])); + $this->assertEquals(false, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_APP => true])); + $this->assertEquals(false, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_SYSTEM => true])); + + $this->assertEquals(false, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_APP => true, 'role:'.Auth::USER_ROLE_APP => true])); + $this->assertEquals(false, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_APP => true, 'role:'.Auth::USER_ROLE_GUEST => true])); + $this->assertEquals(true, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_OWNER => true, 'role:'.Auth::USER_ROLE_GUEST => true])); + $this->assertEquals(true, Auth::isPreviliggedUser(['role:'.Auth::USER_ROLE_OWNER => true, 'role:'.Auth::USER_ROLE_ADMIN => true, 'role:'.Auth::USER_ROLE_DEVELOPER => true])); + } + + public function testIsAppUser() + { + $this->assertEquals(false, Auth::isAppUser([])); + $this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_GUEST => true])); + $this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_MEMBER => true])); + $this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_ADMIN => true])); + $this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_DEVELOPER => true])); + $this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_OWNER => true])); + $this->assertEquals(true, Auth::isAppUser(['role:'.Auth::USER_ROLE_APP => true])); + $this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_SYSTEM => true])); + + $this->assertEquals(true, Auth::isAppUser(['role:'.Auth::USER_ROLE_APP => true, 'role:'.Auth::USER_ROLE_APP => true])); + $this->assertEquals(true, Auth::isAppUser(['role:'.Auth::USER_ROLE_APP => true, 'role:'.Auth::USER_ROLE_GUEST => true])); + $this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_OWNER => true, 'role:'.Auth::USER_ROLE_GUEST => true])); + $this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_OWNER => true, 'role:'.Auth::USER_ROLE_ADMIN => true, 'role:'.Auth::USER_ROLE_DEVELOPER => true])); + } }