diff --git a/bin/console b/bin/console index 6cff909481..3b63e8e717 100755 --- a/bin/console +++ b/bin/console @@ -35,6 +35,7 @@ use Alchemy\Phrasea\Command\Compile\Configuration; use Alchemy\Phrasea\Command\RecordAdd; use Alchemy\Phrasea\Command\RescanTechnicalDatas; use Alchemy\Phrasea\CLI; +use Alchemy\Phrasea\Command\User\UserApplicationsCommand; use Alchemy\Phrasea\Command\Plugin\AddPlugin; use Alchemy\Phrasea\Command\Plugin\RemovePlugin; use Alchemy\Phrasea\Command\CheckConfig; @@ -116,6 +117,8 @@ $cli->command(new \module_console_fieldsDelete('fields:delete')); $cli->command(new \module_console_fieldsRename('fields:rename')); $cli->command(new \module_console_fieldsMerge('fields:merge')); +$cli->command(new UserApplicationsCommand('user:applications')); + $cli->command(new CreateCollection('collection:create')); $cli->command(new UnPublishCollectionCommand('collection:unpublish')); $cli->command(new PublishCollectionCommand('collection:publish')); diff --git a/lib/Alchemy/Phrasea/Command/User/UserApplicationsCommand.php b/lib/Alchemy/Phrasea/Command/User/UserApplicationsCommand.php new file mode 100644 index 0000000000..cbef2cb103 --- /dev/null +++ b/lib/Alchemy/Phrasea/Command/User/UserApplicationsCommand.php @@ -0,0 +1,328 @@ +setDescription('List, Create, Edit, Delete application in Phraseanet (experimental)') + ->addOption('list', null, InputOption::VALUE_NONE, 'List all applications or user applications if --user_id is set') + ->addOption('create', null, InputOption::VALUE_NONE, 'Create application for user in Phraseanet') + ->addOption('edit', null, InputOption::VALUE_NONE, 'Edit application in Phraseanet work only if app_id is set') + ->addOption('delete', null, InputOption::VALUE_NONE, 'Delete application in Phraseanet, require an app_id') + ->addOption('user_id', 'u', InputOption::VALUE_REQUIRED, 'The Id of user owner of application (user_id), required to Create, Edit and Delete.') + ->addOption('app_id', 'a', InputOption::VALUE_REQUIRED, 'The application ID, required for Edit and Delete') + ->addOption('name', null, InputOption::VALUE_REQUIRED, 'The desired name for application, required for Create and Edit.') + ->addOption('type', 't', InputOption::VALUE_OPTIONAL, 'The kind of application, Desktop or Web.',ApiApplication::WEB_TYPE) + ->addOption('description', 'd', InputOption::VALUE_REQUIRED, 'The desired description for application.') + ->addOption('website', 'w', InputOption::VALUE_OPTIONAL, 'The desired url, eg: -w "https://www.alchemy.fr".') + ->addOption('callback', 'c', InputOption::VALUE_OPTIONAL, 'The desired endpoint for callback, required for web kind eg: -c "https://www.alchemy.fr/callback"') + ->addOption('webhook_url', null, InputOption::VALUE_REQUIRED, 'The webhook url') + ->addOption('active', null, InputOption::VALUE_OPTIONAL, 'Activate or deactivate the app, values true or false', 'true') + ->addOption('generate_token', null, InputOption::VALUE_NONE, 'Generate or regenerate the access token') + ->addOption('password_oauth2_gt', null, InputOption::VALUE_OPTIONAL, 'Activate or deactivate password OAuth2 grant type , values true or false', 'false') + ->addOption('jsonformat', null, InputOption::VALUE_NONE, 'Output in json format') + + ->setHelp(''); + + return $this; + } + + protected function doExecute(InputInterface $input, OutputInterface $output) + { + $userId = $input->getOption('user_id'); + $appId = $input->getOption('app_id'); + $name = $input->getOption('name'); + $type = $input->getOption('type'); + $description = $input->getOption('description'); + $website = $input->getOption('website'); + $urlCallback = $input->getOption('callback'); + $webhookUrl = $input->getOption('webhook_url'); + $active = $input->getOption('active'); + $generateToken = $input->getOption('generate_token'); + $passwordOauth2Gt = $input->getOption('password_oauth2_gt'); + $create = $input->getOption('create'); + $edit = $input->getOption('edit'); + $delete = $input->getOption('delete'); + $list = $input->getOption('list'); + $jsonformat = $input->getOption('jsonformat'); + + $applicationManipulator = $this->container['manipulator.api-application']; + $apiOauthTokenManipulator = $this->container['manipulator.api-oauth-token']; + $accountRepository = $this->container['repo.api-accounts']; + $apiApllicationConverter = $this->container['converter.api-application']; + $userRepository = $this->container['repo.users']; + $apiOauthRepository = $this->container['repo.api-oauth-tokens']; + + if ($create) { + if (null === $user = $userRepository->find($userId)) { + $output->writeln('User not found'); + return 0; + } + + if (!$name) { + $output->writeln('Name of application must be provide with option --name.'); + return 0; + } + + if (!$description) { + $output->writeln('Desciption of application must be provide.'); + + return 0; + } + + try { + $application = $applicationManipulator + ->create( + $name, + $type, + $description, + $website, + $user, + $urlCallback + ); + + $apiAccountManipulator = $this->container['manipulator.api-account']; + $apiAccountManipulator->create($application, $user, V2::VERSION); + + $account = $accountRepository->findByUserAndApplication($user, $application); + + if ($generateToken) { + $apiOauthTokenManipulator->create($account); + } + + if ($passwordOauth2Gt) { + if (in_array($passwordOauth2Gt, ['true', 'false'])) { + $application->setGrantPassword(($passwordOauth2Gt == 'true') ? true : false); + $applicationManipulator->update($application); + } else { + $output->writeln(' Value of option --password_oauth2_gt should be "true" or "false"'); + + return 0; + } + } + + if ($webhookUrl) { + $applicationManipulator->setWebhookUrl($application, $webhookUrl); + $applicationManipulator->update($application); + } + + if ($active) { + if (in_array($active, ['true', 'false'])) { + $application->setActivated(($active == 'true') ? true : false); + $applicationManipulator->update($application); + } else { + $output->writeln('Value of option --active should be "true" or "false"'); + + return 0; + } + } else { + $application->setActivated(true); + $applicationManipulator->update($application); + } + + $this->showApllicationInformation($apiOauthRepository, $account, $application, $jsonformat, $output); + } catch (\Exception $e) { + $output->writeln('Create an application for user failed : '.$e->getMessage().''); + } + } elseif ($edit) { + if (!$appId) { + $output->writeln('ID of the application must be provided with option --app_id to edit the application.'); + + return 0; + } + + $application = $apiApllicationConverter->convert($appId); + $account = $accountRepository->findByUserAndApplication($application->getCreator(), $application); + + if (!$account) { + $output->writeln('ApiAccount not found!'); + + return 0; + } + + if ($name) { + $application->setName($name); + } + if ($type) { + $applicationManipulator->setType($application, $type); + if ($type == ApiApplication::DESKTOP_TYPE) { + $applicationManipulator->setRedirectUri($application, ApiApplication::NATIVE_APP_REDIRECT_URI); + } + } + if ($description) { + $application->setDescription($description); + } + if ($website) { + $applicationManipulator->setWebsiteUrl($application, $website); + } + if ($urlCallback) { + $applicationManipulator->setRedirectUri($application, $urlCallback); + } + if ($generateToken) { + if (null !== $devToken = $apiOauthRepository->findDeveloperToken($account)) { + $apiOauthTokenManipulator->renew($devToken); + } else { + $apiOauthTokenManipulator->create($account); + } + } + if ($passwordOauth2Gt) { + if (in_array($passwordOauth2Gt, ['true', 'false'])) { + $application->setGrantPassword(($passwordOauth2Gt == 'true') ? true : false); + } else { + $output->writeln(' Value of option --password_oauth2_gt should be "true" or "false"'); + + return 0; + } + } + if ($webhookUrl) { + $applicationManipulator->setWebhookUrl($application, $webhookUrl); + } + + if ($active) { + if (in_array($active, ['true', 'false'])) { + $application->setActivated(($active == 'true') ? true : false); + } else { + $output->writeln('Value of option --active should be "true" or "false"'); + + return 0; + } + } + + $applicationManipulator->update($application); + + $this->showApllicationInformation($apiOauthRepository, $account, $application, $jsonformat, $output); + } elseif ($list) { + if ($userId) { + if (null === $user = $userRepository->find($userId)) { + $output->writeln('User not found'); + + return 0; + } + + $accounts = $accountRepository->findByUser($user); + } else { + $accounts = $accountRepository->findAll(); + } + + $applicationList = []; + + foreach ($accounts as $account) { + $application = $account->getApplication(); + $token = $apiOauthRepository->findDeveloperToken($account); + + $applicationList[] = [ + $application->getId(), + $account->getUser()->getId(), + $application->getName(), + $application->getClientId(), + $application->getRedirectUri(), + ($token) ? $token->getOauthToken() : '-', + $application->isPasswordGranted() ? "true": "false" + ]; + } + + $applicationTable = $this->getHelperSet()->get('table'); + $headers = ['app_id', 'user_id', 'name', 'client_id', 'callback_url', 'generated token', 'grant_password status']; + + if ($jsonformat ) { + foreach ($applicationList as $appList) { + $appInfo[] = array_combine($headers, $appList); + } + + echo json_encode($appInfo); + } else { + $applicationTable = $this->getHelperSet()->get('table'); + $applicationTable + ->setHeaders($headers) + ->setRows($applicationList) + ->render($output) + ; + } + } elseif ($delete) { + if (!$appId) { + $output->writeln('ID of the application must be provided with option --app_id to delete the app.'); + + return 0; + } + + $application = $apiApllicationConverter->convert($appId); + + if (is_null($application->getCreator())) { + /** @var DialogHelper $dialog */ + $dialog = $this->getHelperSet()->get('dialog'); + + $continue = $dialog->askConfirmation($output, "It's a special phraseanet application, do you want really to delete it? (N/y)", false); + + if (!$continue) { + $output->writeln("See you later !"); + + return 0; + } + } + + $applicationManipulator->delete($application); + + $output->writeln("Application ID $appId deleted successfully !"); + } + + return 0; + } + + private function showApllicationInformation($apiOauthRepository, ApiAccount $account, ApiApplication $application, $jsonformat, $output) + { + $token = $account ? $apiOauthRepository->findDeveloperToken($account) : null; + + $applicationCreated = [ + $application->getClientSecret(), + $application->getClientId(), + $this->container["conf"]->get("servername") . "api/oauthv2/authorize", + $this->container["conf"]->get("servername") . "api/oauthv2/token", + ($token) ? $token->getOauthToken() : '-', + $application->isPasswordGranted() ? "true": "false" + ]; + + $headers = ['client secret', 'client ID', 'Authorize endpoint url', 'Access endpoint', 'generated token', 'grant_password status']; + if ($jsonformat ) { + $createdAppInfo = array_combine($headers, $applicationCreated); + echo json_encode($createdAppInfo); + } else { + $table = $this->getHelperSet()->get('table'); + $table + ->setHeaders($headers) + ->setRows([$applicationCreated]) + ->render($output) + ; + } + } +} diff --git a/package.json b/package.json index 4a9f380dbe..d7d63b3742 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "normalize-css": "^2.1.0", "npm": "^6.0.0", "npm-modernizr": "^2.8.3", - "phraseanet-production-client": "0.34.141-d", + "phraseanet-production-client": "0.34.142-d", "requirejs": "^2.3.5", "tinymce": "^4.0.28", "underscore": "^1.8.3", diff --git a/yarn.lock b/yarn.lock index bc67e69c04..66dfaf5868 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7577,10 +7577,10 @@ phraseanet-common@^0.4.5-d: js-cookie "^2.1.0" pym.js "^1.3.1" -phraseanet-production-client@0.34.141-d: - version "0.34.141-d" - resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.141-d.tgz#25a137975af873c17837eaa0180ffe500c936f7f" - integrity sha512-cYRaVtJPh5poYqOruoBwapM7HMp5Bv9NvW9HkEqPYr5p2nfZMzgEjjpyOQHnNkaEI+6w2kDk8KGNYd37HnOTmw== +phraseanet-production-client@0.34.142-d: + version "0.34.142-d" + resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.142-d.tgz#9b66d9722e316c9af0ec35c9f497932b70c28c2e" + integrity sha512-U4gholdgWgjas16yerS+XqfrTcAhOCMMwXSbj8sapCdHUxy1GnSSelKcx/OTp+IPU/0oWpjT0I6M27azxhA/Sw== dependencies: "@mapbox/mapbox-gl-language" "^0.9.2" "@turf/turf" "^5.1.6"