diff --git a/app/Http/Controllers/Api/OAuth2/OAuth2StreamChatSSOApiController.php b/app/Http/Controllers/Api/OAuth2/OAuth2StreamChatSSOApiController.php new file mode 100644 index 00000000..9eaf8b8d --- /dev/null +++ b/app/Http/Controllers/Api/OAuth2/OAuth2StreamChatSSOApiController.php @@ -0,0 +1,66 @@ +service = $service; + } + + /** + * @param string $forum_slug + * @return \Illuminate\Http\JsonResponse|mixed + */ + public function getUserProfile(string $forum_slug){ + try{ + $profile = $this->service->getUserProfile($forum_slug); + return $this->ok($profile->serialize()); + } + catch (ValidationException $ex) { + Log::warning($ex); + return $this->error412([$ex->getMessage()]); + } + catch(EntityNotFoundException $ex) + { + Log::warning($ex); + return $this->error404(['message'=> $ex->getMessage()]); + } + catch (\Exception $ex) { + Log::error($ex); + return $this->error500($ex); + } + } +} \ No newline at end of file diff --git a/app/Http/routes.php b/app/Http/routes.php index 8ec96b4a..c0d4d509 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -401,5 +401,11 @@ Route::group( Route::get('profile', 'OAuth2RocketChatSSOApiController@getUserProfile'); }); }); + + Route::group(['prefix' => 'stream-chat'], function () { + Route::group(['prefix' => '{forum_slug}'], function () { + Route::get('profile', 'OAuth2StreamChatSSOApiController@getUserProfile'); + }); + }); }); }); \ No newline at end of file diff --git a/app/Models/Repositories/IStreamChatSSOProfileRepository.php b/app/Models/Repositories/IStreamChatSSOProfileRepository.php new file mode 100644 index 00000000..0fe860b9 --- /dev/null +++ b/app/Models/Repositories/IStreamChatSSOProfileRepository.php @@ -0,0 +1,23 @@ +forum_slug; + } + + /** + * @param string $forum_slug + */ + public function setForumSlug(string $forum_slug): void + { + $this->forum_slug = $forum_slug; + } + + /** + * @return string + */ + public function getApiKey(): string + { + return $this->api_key; + } + + /** + * @param string $api_key + */ + public function setApiKey(string $api_key): void + { + $this->api_key = $api_key; + } + + /** + * @return string + */ + public function getApiSecret(): string + { + return $this->api_secret; + } + + /** + * @param string $api_secret + */ + public function setApiSecret(string $api_secret): void + { + $this->api_secret = $api_secret; + } +} \ No newline at end of file diff --git a/app/Models/SSO/StreamChat/StreamChatUserProfile.php b/app/Models/SSO/StreamChat/StreamChatUserProfile.php new file mode 100644 index 00000000..03a941f5 --- /dev/null +++ b/app/Models/SSO/StreamChat/StreamChatUserProfile.php @@ -0,0 +1,77 @@ +user_id = $user_id; + $this->user_name = $user_name; + $this->user_image = $user_image; + $this->token = $token; + $this->api_key = $api_key; + } + + /** + * @return array + */ + public function serialize(){ + $data = [ + "id" => $this->user_id, + "name" => $this->user_name, + "image" => $this->user_image, + "token" => $this->token, + "api_key" => $this->api_key + ]; + return $data; + } + +} \ No newline at end of file diff --git a/app/Repositories/DoctrineStreamChatSSOProfileRepository.php b/app/Repositories/DoctrineStreamChatSSOProfileRepository.php new file mode 100644 index 00000000..d354c868 --- /dev/null +++ b/app/Repositories/DoctrineStreamChatSSOProfileRepository.php @@ -0,0 +1,42 @@ +findOneBy([ + 'forum_slug' => trim($forum_slug) + ]); + } +} \ No newline at end of file diff --git a/app/Repositories/RepositoriesProvider.php b/app/Repositories/RepositoriesProvider.php index 23252c90..8a4eb856 100644 --- a/app/Repositories/RepositoriesProvider.php +++ b/app/Repositories/RepositoriesProvider.php @@ -23,8 +23,10 @@ use App\libs\Auth\Repositories\IWhiteListedIPRepository; use App\libs\OAuth2\Repositories\IOAuth2TrailExceptionRepository; use App\Models\Repositories\IDisqusSSOProfileRepository; use App\Models\Repositories\IRocketChatSSOProfileRepository; +use App\Models\Repositories\IStreamChatSSOProfileRepository; use App\Models\SSO\DisqusSSOProfile; use App\Models\SSO\RocketChatSSOProfile; +use App\Models\SSO\StreamChat\StreamChatSSOProfile; use App\Repositories\IServerConfigurationRepository; use App\Repositories\IServerExtensionRepository; use Auth\Group; @@ -242,6 +244,13 @@ final class RepositoriesProvider extends ServiceProvider } ); + App::singleton( + IStreamChatSSOProfileRepository::class, + function(){ + return EntityManager::getRepository(StreamChatSSOProfile::class); + } + ); + } public function provides() @@ -266,6 +275,7 @@ final class RepositoriesProvider extends ServiceProvider ISpamEstimatorFeedRepository::class, IDisqusSSOProfileRepository::class, IRocketChatSSOProfileRepository::class, + IStreamChatSSOProfileRepository::class, ]; } } \ No newline at end of file diff --git a/app/Services/Auth/IStreamChatSSOService.php b/app/Services/Auth/IStreamChatSSOService.php new file mode 100644 index 00000000..9a1346c2 --- /dev/null +++ b/app/Services/Auth/IStreamChatSSOService.php @@ -0,0 +1,28 @@ +repository = $repository; + $this->user_repository = $user_repository; + $this->resource_server_context = $resource_server_context; + parent::__construct($tx_service); + } + + /** + * @inheritDoc + */ + public function getUserProfile(string $forum_slug): ?StreamChatUserProfile + { + return $this->tx_service->transaction(function() use($forum_slug){ + + Log::debug("StreamChatService::getUserProfile"); + $current_user_id = $this->resource_server_context->getCurrentUserId(); + $access_token = $this->resource_server_context->getCurrentAccessToken(); + if(empty($access_token)){ + throw new ValidationException("Access Token is empty."); + } + Log::debug(sprintf("RocketChatSSOService::getUserProfile current_user_id %s", $current_user_id)); + if (is_null($current_user_id)) { + throw new ValidationException('me is no set!.'); + } + + $current_user = $this->user_repository->getById($current_user_id); + if(is_null($current_user)) throw new EntityNotFoundException(); + + if(!$current_user instanceof User) throw new EntityNotFoundException(); + $sso_profile = $this->repository->getByForumSlug($forum_slug); + if(is_null($sso_profile)){ + throw new EntityNotFoundException("Forum not found"); + } + + $client = new StreamChatClient($sso_profile->getApiKey(), $sso_profile->getApiSecret()); + $token = $client->createToken($current_user->getIdentifier()); + + $chat_user = $client->updateUser([ + 'id' => strval($current_user->getId()), + 'role' => $current_user->isSuperAdmin()? 'admin' : 'user', + 'name' => $current_user->getFullName(), + 'image' => $current_user->getPic(), + ]); + + return new StreamChatUserProfile + ( + strval($current_user->getId()), + $current_user->getFullName(), + $current_user->getPic(), + $token, + $sso_profile->getApiKey() + ); + }); + } +} \ No newline at end of file diff --git a/app/Services/ServicesProvider.php b/app/Services/ServicesProvider.php index 15e65535..16282c6a 100644 --- a/app/Services/ServicesProvider.php +++ b/app/Services/ServicesProvider.php @@ -21,6 +21,8 @@ use App\Services\Auth\IGroupService; use App\Services\Auth\IRocketChatSSOService; use App\Services\Auth\IUserService; use App\Services\Auth\RocketChatSSOService; +use App\Services\Auth\StreamChatSSOService; +use App\Services\Auth\IStreamChatSSOService; use App\Services\Auth\UserService; use Illuminate\Support\ServiceProvider; use Services\SecurityPolicies\AuthorizationCodeRedeemPolicy; @@ -92,6 +94,7 @@ final class ServicesProvider extends ServiceProvider App::singleton(IDisqusSSOService::class, DisqusSSOService::class); App::singleton(IRocketChatSSOService::class, RocketChatSSOService::class); App::singleton(IRocketChatAPI::class, RocketChatAPI::class); + App::singleton(IStreamChatSSOService::class, StreamChatSSOService::class); } public function provides() @@ -111,6 +114,7 @@ final class ServicesProvider extends ServiceProvider IDisqusSSOService::class, IRocketChatSSOService::class, IRocketChatAPI::class, + IStreamChatSSOService::class, ]; } } \ No newline at end of file diff --git a/app/libs/Auth/Models/IGroupSlugs.php b/app/libs/Auth/Models/IGroupSlugs.php index a61f0b71..35cd1f1d 100644 --- a/app/libs/Auth/Models/IGroupSlugs.php +++ b/app/libs/Auth/Models/IGroupSlugs.php @@ -18,8 +18,8 @@ */ interface IGroupSlugs { - public const SuperAdminGroup = 'super-admins'; + public const AdminGroup = 'administrators'; public const OAuth2ServerAdminGroup = 'oauth2-server-admins'; public const OAuth2SystemScopeAdminsGroup = 'oauth2-system-scope-admins'; public const OpenIdServerAdminsGroup = 'openid-server-admins'; diff --git a/composer.json b/composer.json index d71f5454..4473def2 100644 --- a/composer.json +++ b/composer.json @@ -16,15 +16,16 @@ "ext-json": "*", "ext-pdo": "*", "beberlei/DoctrineExtensions": "1.1.5", + "doctrine/orm": "2.6.4", + "doctrine/persistence": "1.1.1", "ezyang/htmlpurifier": "v4.12.0", "fideloper/proxy": "^4.0", + "get-stream/stream-chat": "^1.1", "glenscott/url-normalizer": "1.4.0", "greggilbert/recaptcha": "2.1.1", "guzzlehttp/guzzle": "6.3.3", "ircmaxell/random-lib": "1.1.0", "jenssegers/agent": "2.6.3", - "doctrine/orm": "2.6.4", - "doctrine/persistence": "1.1.1", "laravel-doctrine/extensions": "1.0.14", "laravel-doctrine/migrations": "1.2.0", "laravel-doctrine/orm": "1.4.11", @@ -36,9 +37,9 @@ "s-ichikawa/laravel-sendgrid-driver": "2.1.0", "smarcet/jose4php": "1.0.17", "sokil/php-isocodes": "^3.0", + "vladimir-yuldashev/laravel-queue-rabbitmq": "v7.5.0", "zendframework/zend-crypt": "3.3.0", - "zendframework/zend-math": "3.1.1", - "vladimir-yuldashev/laravel-queue-rabbitmq": "v7.5.0" + "zendframework/zend-math": "3.1.1" }, "require-dev": { "filp/whoops": "^2.0", diff --git a/composer.lock b/composer.lock index bf13d5fe..6af84719 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "18249b8a524f89ffc27a99bf8a1161f0", + "content-hash": "0320ba04c31a757080ddb018434ada90", "packages": [ { "name": "beberlei/doctrineextensions", @@ -1516,6 +1516,106 @@ ], "time": "2020-02-22T01:51:47+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "feb0e820b8436873675fd3aca04f3728eb2185cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/feb0e820b8436873675fd3aca04f3728eb2185cb", + "reference": "feb0e820b8436873675fd3aca04f3728eb2185cb", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <=9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "time": "2020-03-25T18:49:23+00:00" + }, + { + "name": "get-stream/stream-chat", + "version": "1.1.7", + "source": { + "type": "git", + "url": "https://github.com/GetStream/stream-chat-php.git", + "reference": "22b9fd53a63a69bc2046c35376670ebd1ca939cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GetStream/stream-chat-php/zipball/22b9fd53a63a69bc2046c35376670ebd1ca939cb", + "reference": "22b9fd53a63a69bc2046c35376670ebd1ca939cb", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^v5.0.0", + "guzzlehttp/guzzle": "^6.3.3", + "php": ">=5.7" + }, + "require-dev": { + "phpunit/phpunit": "^8.2.1", + "ramsey/uuid": "^3.8.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "GetStream\\StreamChat": "lib/" + }, + "psr-4": { + "GetStream\\Unit\\": "tests/unit/", + "GetStream\\Integration\\": "tests/integration/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "authors": [ + { + "name": "Tommaso Barbugli", + "email": "support@getstream.io" + } + ], + "description": "A PHP client for Stream Chat (https://getstream.io/chat/)", + "homepage": "https://getstream.io/chat/", + "keywords": [ + "api", + "chat", + "chat-sdk", + "stream" + ], + "time": "2020-07-01T18:03:44+00:00" + }, { "name": "glenscott/url-normalizer", "version": "1.4.0", diff --git a/database/migrations/Version20200715195145.php b/database/migrations/Version20200715195145.php new file mode 100644 index 00000000..2e57a874 --- /dev/null +++ b/database/migrations/Version20200715195145.php @@ -0,0 +1,53 @@ +hasTable("sso_stream_chat_profile")) { + $builder->create("sso_stream_chat_profile", function (Table $table) { + $table->increments('id'); + $table->timestamps(); + $table->string("forum_slug")->setNotnull(true); + $table->string("api_key")->setNotnull(true); + $table->string("api_secret")->setNotnull(true); + $table->unique("forum_slug"); + }); + } + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $builder = new Builder($schema); + + $builder->dropIfExists("sso_stream_chat_profile"); + } +} diff --git a/database/migrations/Version20200715195155.php b/database/migrations/Version20200715195155.php new file mode 100644 index 00000000..c8348d8e --- /dev/null +++ b/database/migrations/Version20200715195155.php @@ -0,0 +1,47 @@ + 'sso-stream-chat', + 'active' => true, + 'route' => '/api/v1/sso/stream-chat/{forum_slug}/profile', + 'http_method' => 'GET', + 'scopes' => [ + \App\libs\OAuth2\IUserScopes::SSO + ], + ], + ]); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + + } +} diff --git a/database/seeds/ApiEndpointSeeder.php b/database/seeds/ApiEndpointSeeder.php index 38282536..4064f286 100644 --- a/database/seeds/ApiEndpointSeeder.php +++ b/database/seeds/ApiEndpointSeeder.php @@ -122,6 +122,15 @@ class ApiEndpointSeeder extends Seeder \App\libs\OAuth2\IUserScopes::SSO ], ], + [ + 'name' => 'sso-stream-chat', + 'active' => true, + 'route' => '/api/v1/sso/stream-chat/{forum_slug}/profile', + 'http_method' => 'GET', + 'scopes' => [ + \App\libs\OAuth2\IUserScopes::SSO + ], + ], ]); } diff --git a/database/seeds/TestSeeder.php b/database/seeds/TestSeeder.php index be8ed20e..a35a7996 100644 --- a/database/seeds/TestSeeder.php +++ b/database/seeds/TestSeeder.php @@ -1728,21 +1728,28 @@ PPK; $api = $api_repository->findOneBy(['name' => 'sso']); $api_scope_payloads = [ - array( + [ 'name' => 'sso-disqus', 'active' => true, 'api' => $api, 'route' => '/api/v1/sso/disqus/{forum_slug}/profile', 'http_method' => 'GET' - ), - - array( + ], + [ 'name' => 'sso-rocket-chat', 'active' => true, 'api' => $api, 'route' => '/api/v1/sso/rocket-chat/{forum_slug}/profile', 'http_method' => 'GET' - ), + ], + [ + 'name' => 'sso-stream-chat', + 'active' => true, + 'api' => $api, + 'route' => '/api/v1/sso/stream-chat/{forum_slug}/profile', + 'http_method' => 'GET', + + ], ]; foreach($api_scope_payloads as $payload) { diff --git a/tests/OAuthSSOApiControllerTest.php b/tests/OAuthSSOApiControllerTest.php index 4727a177..92164e2a 100644 --- a/tests/OAuthSSOApiControllerTest.php +++ b/tests/OAuthSSOApiControllerTest.php @@ -18,6 +18,7 @@ use Illuminate\Support\Facades\DB; use App\Models\SSO\DisqusSSOProfile; use App\Models\Utils\BaseEntity; use App\Models\SSO\RocketChatSSOProfile; +use App\Models\SSO\StreamChat\StreamChatSSOProfile; /** * Class OAuthSSOApiControllerTest */ @@ -38,6 +39,11 @@ final class OAuthSSOApiControllerTest extends OAuth2ProtectedApiTest */ static $rocket_chat_repository; + /** + * @var ObjectRepository + */ + static $stream_chat_repository; + /** * @var DisqusSSOProfile */ @@ -48,14 +54,22 @@ final class OAuthSSOApiControllerTest extends OAuth2ProtectedApiTest */ static $rocket_chat_profile; + /** + * @var StreamChatSSOProfile + */ + static $stream_chat_profile; + + public function setUp() { parent::setUp(); DB::table("sso_disqus_profile")->delete(); DB::table("sso_rocket_chat_profile")->delete(); + DB::table("sso_stream_chat_profile")->delete(); self::$disqus_repository = EntityManager::getRepository(DisqusSSOProfile::class); self::$rocket_chat_repository = EntityManager::getRepository(RocketChatSSOProfile::class); + self::$stream_chat_repository = EntityManager::getRepository(StreamChatSSOProfile::class); self::$disqus_profile = new DisqusSSOProfile(); self::$disqus_profile->setForumSlug("poc_disqus"); @@ -67,14 +81,20 @@ final class OAuthSSOApiControllerTest extends OAuth2ProtectedApiTest self::$rocket_chat_profile->setBaseUrl("https://rocket-chat.dev.fnopen.com"); self::$rocket_chat_profile->setServiceName("fnid"); + self::$stream_chat_profile = new StreamChatSSOProfile(); + self::$stream_chat_profile->setForumSlug("poc_stream_chat"); + self::$stream_chat_profile->setApiKey(env("STREAM_CHAT_API_KEY") ?? ''); + self::$stream_chat_profile->setApiSecret(env("STREAM_CHAT_API_SECRET") ?? ''); + self::$em = Registry::getManager(BaseEntity::EntityManager); if (!self::$em ->isOpen()) { self::$em = Registry::resetManager(BaseEntity::EntityManager); } + self::$em->persist(self::$disqus_profile); self::$em->persist(self::$rocket_chat_profile); + self::$em->persist(self::$stream_chat_profile); self::$em->flush(); - } protected function tearDown() @@ -143,6 +163,38 @@ final class OAuthSSOApiControllerTest extends OAuth2ProtectedApiTest $content = $response->getContent(); } + /** + public function testStreamChatGetUserProfileOK(){ + + $params = [ + "forum_slug" => self::$stream_chat_profile->getForumSlug() + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action + ( + "GET", + "Api\\OAuth2\\OAuth2StreamChatSSOApiController@getUserProfile", + $params, + [], + [], + [], + $headers + ); + + $this->assertResponseStatus(200); + $content = $response->getContent(); + $profile = json_decode($content, true); + $this->assertTrue(isset($profile['id'])); + $this->assertTrue(isset($profile['token'])); + $this->assertTrue(isset($profile['api_key'])); + } + **/ + protected function getScopes() { $scope = array( diff --git a/update_doctrine.sh b/update_doctrine.sh index 1c5c28d8..8884132e 100755 --- a/update_doctrine.sh +++ b/update_doctrine.sh @@ -5,4 +5,5 @@ php artisan doctrine:clear:metadata:cache php artisan doctrine:clear:query:cache php artisan doctrine:clear:result:cache php artisan route:clear -php artisan route:cache \ No newline at end of file +php artisan route:cache +php artisan config:clear \ No newline at end of file