diff --git a/README.txt b/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..a21da700f593c4ca1b846f314f7cc96003224f08 --- /dev/null +++ b/README.txt @@ -0,0 +1,98 @@ +SOCIAL AUTH GOOGLE MODULE + +CONTENTS OF THIS FILE +--------------------- + + * Introduction + * Requirements + * Recommended modules + * Installation + * Configuration + * How it works + * Support requests + +INTRODUCTION +------------ + +Social Auth Digitalocean Module is a Digitalocean Authentication integration for Drupal. + +REQUIREMENTS +------------ + +This module requires the following modules: + + * Social Auth (https://drupal.org/project/social_auth) + * Social API (https://drupal.org/project/social_api) + + +RECOMMENDED MODULES +------------------- + + * Composer Manager (https://www.drupal.org/project/composer_manager): + This module will help to install the League PHP library and Digitalocean base library for the league, + which are the libraries required to make user authentication. + +INSTALLATION +------------ + + * Download Digitalocean Base Library for TheLeague OAuth2 PHP library + (https://github.com/thephpleague/oauth2-digitalocean). We recommend to use + Composer Manager module to install the library. + + * Install the dependencies: Social API and Social Auth. + + * Install as you would normally install a contributed Drupal module. See: + https://drupal.org/documentation/install/modules-themes/modules-8 + for further information. + +CONFIGURATION +------------- + + * Add your Digitalocean project OAuth information in + Configuration » User Authentication » Digitalocean. + + * Place a Social Auth Digitalocean block in Structure » Block Layout. + + * If you already have a Social Auth Login block in the site, rebuild the cache. + + +HOW IT WORKS +------------ + +User can click on the Digitalocean logo on the Social Auth Login block +You can also add a button or link anywhere on the site that points +to /user/login/digitalocean, so theming and customizing the button or link +is very flexible. + +When the user opens the /user/login/digitalocean link, it automatically takes +user to Digitalocean Accounts for authentication. Digitalocean then returns the user to +Drupal site. If we have an existing Drupal user with the same email address +provided by Digitalocean, that user is logged in. Otherwise a new Drupal user is +created. + +SUPPORT REQUESTS +---------------- + +Before posting a support request, carefully read the installation +instructions provided in module documentation page. + +Before posting a support request, check Composer Manager status report at +admin/reports/composer-manager. This status page will show the Digitalocean The League OAuth2 Base +PHP library version if Drupal can detect it. + +Before posting a support request, check Recent log entries at +admin/reports/dblog + +Once you have done this, you can post a support request at module issue queue: +https://www.drupal.org/node/2841076 + +When posting a support request, please inform what does the status report say +at admin/reports/composer-manager and if you were able to see any errors in +Recent log entries. + +MAINTAINERS +----------- + +Current maintainers: + * Getulio Sánchez (gvso) - https://www.drupal.org/u/gvso + * Himanshu Dixit (himanshu-dixit) - https://www.drupal.org/u/himanshu-dixit diff --git a/composer.json b/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..f489b13bbc1457b78f6915f0882c0f9a866a3d7a --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "drupal/social_auth_digitalocean", + "type": "drupal-module", + "description": "Social Auth integration for Digitalocean.", + "keywords": ["Drupal"], + "license": "GPL-2.0+", + "homepage": "https://www.drupal.org/project/social_auth_digitalocean", + "minimum-stability": "dev", + "support": { + "issues": "https://www.drupal.org/project/issues/social_auth_digitalocean", + "source": "http://cgit.drupalcode.org/social_auth_digitalocean" + }, + "require": { + "chrishemmings/oauth2-digitalocean": "^2.0" + }, + "authors": [ + { + "name": "Getulio Valentin Sánchez (gvso)", + "email": "valentin2507@gmail.com" + }, + { + "name": "Himanshu Dixit (himanshu-dixit)", + "email": "hudixt@gmail.com" + } + ] +} diff --git a/config/schema/social_auth_digitalocean.schema.yml b/config/schema/social_auth_digitalocean.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..fea19ce69d8a4286cea9431a27d2cb255462a5e6 --- /dev/null +++ b/config/schema/social_auth_digitalocean.schema.yml @@ -0,0 +1,10 @@ +social_auth_digitalocean.settings: + type: config_object + label: 'Social Auth Digitalocean settings' + mapping: + client_id: + type: string + label: 'Client ID' + client_secret: + type: string + label: 'Client Secret' diff --git a/img/digitalocean_logo.svg b/img/digitalocean_logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..9e94ed5e8adeff57463125ac422b80d84e6a0216 --- /dev/null +++ b/img/digitalocean_logo.svg @@ -0,0 +1,33 @@ + +image/svg+xml \ No newline at end of file diff --git a/social_auth_digitalocean.info.yml b/social_auth_digitalocean.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..802ddf1a4e4a93754ec19d46e4623decc7ff9b92 --- /dev/null +++ b/social_auth_digitalocean.info.yml @@ -0,0 +1,8 @@ +name: Social Auth Digitalocean +type: module +description: 'Social Auth integration for Digitalocean.' +package: Social +core: 8.x +configure: social_auth_digitalocean.settings_form +dependencies: + - social_auth diff --git a/social_auth_digitalocean.install b/social_auth_digitalocean.install new file mode 100644 index 0000000000000000000000000000000000000000..9569bb951946e9d4f780f9d2f2dd46148a8f44ba --- /dev/null +++ b/social_auth_digitalocean.install @@ -0,0 +1,45 @@ +install(['social_api']); + + if ($phase == 'install') { + $requirements = SocialApiImplementerInstaller::checkLibrary('social_auth_digitalocean', 'Social Auth Digitalocean', 'chrishemmings/oauth2-digitalocean', 2.0, 3.0); + } + + return $requirements; + +} + +/** + * Implements hook_install(). + */ +function social_auth_digitalocean_install() { + SocialAuthController::setLoginButtonSettings('social_auth_digitalocean', 'social_auth_digitalocean.redirect_to_digitalocean', 'img/digitalocean_logo.svg'); +} + +/** + * Implements hook_uninstall(). + */ +function social_auth_digitalocean_uninstall() { + SocialAuthController::deleteLoginButtonSettings('social_auth_digitalocean'); +} diff --git a/social_auth_digitalocean.links.task.yml b/social_auth_digitalocean.links.task.yml new file mode 100644 index 0000000000000000000000000000000000000000..b7e4259c23472b75665da5d03f839748bbb772d5 --- /dev/null +++ b/social_auth_digitalocean.links.task.yml @@ -0,0 +1,4 @@ +social_auth_digitalocean.settings.tab: + title: 'Digitalocean' + route_name: social_auth_digitalocean.settings_form + base_route: social_auth.integrations diff --git a/social_auth_digitalocean.module b/social_auth_digitalocean.module new file mode 100644 index 0000000000000000000000000000000000000000..7690d0f4b880148aac1ddbb4960c379fdcf57a76 --- /dev/null +++ b/social_auth_digitalocean.module @@ -0,0 +1,27 @@ +' . t('Configuration instructions') . ''; + $output .= '

'; + $output .= t('Configuration instructions and other useful documentation can be found from Social Auth Digitalocean Handbook.', ['@handbook-url' => 'https://www.drupal.org/node/2764227']); + $output .= '

'; + break; + + } + + return $output; +} diff --git a/social_auth_digitalocean.routing.yml b/social_auth_digitalocean.routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..52735082b9d0279f7f3185c7597192d0fc1fb5b5 --- /dev/null +++ b/social_auth_digitalocean.routing.yml @@ -0,0 +1,25 @@ +social_auth_digitalocean.redirect_to_digitalocean: + path: 'user/login/digitalocean' + defaults: + _controller: '\Drupal\social_auth_digitalocean\Controller\DigitaloceanAuthController::redirectToDigitalocean' + requirements: + _role: 'anonymous' + options: + no_cache: TRUE + +social_auth_digitalocean.callback: + path: 'user/login/digitalocean/callback' + defaults: + _controller: '\Drupal\social_auth_digitalocean\Controller\DigitaloceanAuthController::callback' + requirements: + _role: 'anonymous' + options: + no_cache: TRUE + +social_auth_digitalocean.settings_form: + path: 'admin/config/social-api/social-auth/digitalocean' + defaults: + _title: 'Social Auth Digitalocean settings' + _form: '\Drupal\social_auth_digitalocean\Form\DigitaloceanAuthSettingsForm' + requirements: + _permission: 'administer social api authentication' diff --git a/social_auth_digitalocean.services.yml b/social_auth_digitalocean.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..398c4180fa09677caef324f9a4d494585909f333 --- /dev/null +++ b/social_auth_digitalocean.services.yml @@ -0,0 +1,9 @@ +services: + social_auth_digitalocean.manager: + class: Drupal\social_auth_digitalocean\DigitaloceanAuthManager + arguments: + - '@logger.factory' + - '@event_dispatcher' + - '@entity_field.manager' + - '@url_generator.non_bubbling' + - '@config.factory' diff --git a/src/Controller/DigitaloceanAuthController.php b/src/Controller/DigitaloceanAuthController.php new file mode 100644 index 0000000000000000000000000000000000000000..672872cdf90d46a7355a20fe5b47f7dbef515435 --- /dev/null +++ b/src/Controller/DigitaloceanAuthController.php @@ -0,0 +1,187 @@ +networkManager = $network_manager; + $this->userManager = $user_manager; + $this->digitaloceanManager = $digitalocean_manager; + $this->request = $request; + $this->dataHandler = $social_auth_data_handler; + $this->loggerFactory = $logger_factory; + + // Sets the plugin id. + $this->userManager->setPluginId('social_auth_digitalocean'); + + // Sets the session keys to nullify if user could not logged in. + $this->userManager->setSessionKeysToNullify(['access_token', 'oauth2state']); + $this->setting = $this->config('social_auth_digitalocean.settings'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.network.manager'), + $container->get('social_auth.user_manager'), + $container->get('social_auth_digitalocean.manager'), + $container->get('request_stack'), + $container->get('social_auth.social_auth_data_handler'), + $container->get('logger.factory') + ); + } + + /** + * Response for path 'user/login/digitalocean'. + * + * Redirects the user to Digitalocean for authentication. + */ + public function redirectToDigitalocean() { + /* @var \League\OAuth2\Client\Provider\Digitalocean false $digitalocean */ + $digitalocean = $this->networkManager->createInstance('social_auth_digitalocean')->getSdk(); + + // If digitalocean client could not be obtained. + if (!$digitalocean) { + drupal_set_message($this->t('Social Auth Digitalocean not configured properly. Contact site administrator.'), 'error'); + return $this->redirect('user.login'); + } + + // Digitalocean service was returned, inject it to $digitaloceanManager. + $this->digitaloceanManager->setClient($digitalocean); + + // Generates the URL where the user will be redirected for Digitalocean login. + // If the user did not have email permission granted on previous attempt, + // we use the re-request URL requesting only the email address. + $digitalocean_login_url = $this->digitaloceanManager->getDigitaloceanLoginUrl(); + + $state = $this->digitaloceanManager->getState(); + + $this->dataHandler->set('oauth2state', $state); + + return new TrustedRedirectResponse($digitalocean_login_url); + } + + /** + * Response for path 'user/login/digitalocean/callback'. + * + * Digitalocean returns the user here after user has authenticated in Digitalocean. + */ + public function callback() { + // Checks if user cancel login via Digitalocean. + $error = $this->request->getCurrentRequest()->get('error'); + if ($error == 'access_denied') { + drupal_set_message($this->t('You could not be authenticated.'), 'error'); + return $this->redirect('user.login'); + } + + /* @var \League\OAuth2\Client\Provider\Digitalocean false $digitalocean */ + $digitalocean = $this->networkManager->createInstance('social_auth_digitalocean')->getSdk(); + + // If Digitalocean client could not be obtained. + if (!$digitalocean) { + drupal_set_message($this->t('Social Auth Digitalocean not configured properly. Contact site administrator.'), 'error'); + return $this->redirect('user.login'); + } + + $state = $this->dataHandler->get('oauth2state'); + + // Retreives $_GET['state']. + $retrievedState = $this->request->getCurrentRequest()->query->get('state'); + if (empty($retrievedState) || ($retrievedState !== $state)) { + $this->userManager->nullifySessionKeys(); + drupal_set_message($this->t('Digitalocean login failed. Unvalid oAuth2 State.'), 'error'); + return $this->redirect('user.login'); + } + + // Saves access token to session. + $this->dataHandler->set('access_token', $this->digitaloceanManager->getAccessToken()); + + $this->digitaloceanManager->setClient($digitalocean)->authenticate(); + + // Gets user's info from Digitalocean API. + if (!$digitalocean_profile = $this->digitaloceanManager->getUserInfo()) { + drupal_set_message($this->t('Digitalocean login failed, could not load Digitalocean profile. Contact site administrator.'), 'error'); + return $this->redirect('user.login'); + } + + // If user information could be retrieved. + return $this->userManager->authenticateUser('drupal_user', $digitalocean_profile->getEmail(), $digitalocean_profile->getId(), $this->digitaloceanManager->getAccessToken()->getToken(), '', ' '); + } + +} diff --git a/src/DigitaloceanAuthManager.php b/src/DigitaloceanAuthManager.php new file mode 100644 index 0000000000000000000000000000000000000000..09b13f19335912fe8309b7bd7f9856fe553156cc --- /dev/null +++ b/src/DigitaloceanAuthManager.php @@ -0,0 +1,160 @@ +loggerFactory = $logger_factory; + $this->eventDispatcher = $event_dispatcher; + $this->entityFieldManager = $entity_field_manager; + $this->urlGenerator = $url_generator; + $this->config = $configFactory->getEditable('social_auth_digitalocean.settings'); + } + + /** + * Authenticates the users by using the access token. + * + * @return $this + * The current object. + */ + public function authenticate() { + $this->token = $this->client->getAccessToken('authorization_code', + ['code' => $_GET['code']]); + } + + /** + * Gets the data by using the access token returned. + * + * @return League\OAuth2\Client\Provider\DigitaloceanUser + * User info returned by the Digitalocean. + */ + public function getUserInfo() { + $this->user = $this->client->getResourceOwner($this->token); + return $this->user; + } + + /** + * Returns token generated after authorization. + * + * @return string + * Used for making API calls. + */ + public function getAccessToken() { + return $this->token; + } + + /** + * Returns the Digitalocean login URL where user will be redirected. + * + * @return string + * Absolute Digitalocean login URL where user will be redirected + */ + public function getDigitaloceanLoginUrl() { + $login_url = $this->client->getAuthorizationUrl(); + + // Generate and return the URL where we should redirect the user. + return $login_url; + } + + /** + * Returns the Digitalocean login URL where user will be redirected. + * + * @return string + * Absolute Digitalocean login URL where user will be redirected + */ + public function getState() { + $state = $this->client->getState(); + + // Generate and return the URL where we should redirect the user. + return $state; + } + +} diff --git a/src/Form/DigitaloceanAuthSettingsForm.php b/src/Form/DigitaloceanAuthSettingsForm.php new file mode 100644 index 0000000000000000000000000000000000000000..0c8b148d3c8472c8c93ef8c74c4e5ca1760eeda2 --- /dev/null +++ b/src/Form/DigitaloceanAuthSettingsForm.php @@ -0,0 +1,126 @@ +requestContext = $request_context; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + // Instantiates this class. + return new static( + // Load the services required to construct this class. + $container->get('config.factory'), + $container->get('router.route_provider'), + $container->get('path.validator'), + $container->get('router.request_context') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'social_auth_digitalocean_settings'; + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return array_merge( + parent::getEditableConfigNames(), + ['social_auth_digitalocean.settings'] + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $config = $this->config('social_auth_digitalocean.settings'); + + $form['digitalocean_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Digitalocean Client settings'), + '#open' => TRUE, + '#description' => $this->t('You need to first create a Digitalocean App at @digitalocean-dev', ['@digitalocean-dev' => 'https://cloud.digitalocean.com/settings/api/applications']), + ]; + + $form['digitalocean_settings']['client_id'] = [ + '#type' => 'textfield', + '#required' => TRUE, + '#title' => $this->t('Client ID'), + '#default_value' => $config->get('client_id'), + '#description' => $this->t('Copy the Client ID here.'), + ]; + + $form['digitalocean_settings']['client_secret'] = [ + '#type' => 'textfield', + '#required' => TRUE, + '#title' => $this->t('Client Secret'), + '#default_value' => $config->get('client_secret'), + '#description' => $this->t('Copy the Client Secret here.'), + ]; + + $form['digitalocean_settings']['authorized_redirect_url'] = [ + '#type' => 'textfield', + '#disabled' => TRUE, + '#title' => $this->t('Application Callback URL'), + '#description' => $this->t('Copy this value to Application Callback URL field of your Digitalocean App settings.'), + '#default_value' => $GLOBALS['base_url'] . '/user/login/digitalocean/callback', + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + $this->config('social_auth_digitalocean.settings') + ->set('client_id', $values['client_id']) + ->set('client_secret', $values['client_secret']) + ->save(); + + parent::submitForm($form, $form_state); + } + +} diff --git a/src/Plugin/Network/DigitaloceanAuth.php b/src/Plugin/Network/DigitaloceanAuth.php new file mode 100644 index 0000000000000000000000000000000000000000..2003f4c48eeefc14416d4a50cd2a84491cf9e7db --- /dev/null +++ b/src/Plugin/Network/DigitaloceanAuth.php @@ -0,0 +1,187 @@ +get('social_auth.social_auth_data_handler'), + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager'), + $container->get('config.factory'), + $container->get('logger.factory'), + $container->get('router.request_context'), + $container->get('settings') + ); + } + + /** + * DigitaloceanAuth constructor. + * + * @param \Drupal\social_auth\SocialAuthDataHandler $data_handler + * The data handler. + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration factory object. + * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory + * The logger factory. + * @param \Drupal\Core\Routing\RequestContext $requestContext + * The Request Context Object. + * @param \Drupal\Core\Site\Settings $settings + * The settings factory. + */ + public function __construct(SocialAuthDataHandler $data_handler, + array $configuration, + $plugin_id, + array $plugin_definition, + EntityTypeManagerInterface $entity_type_manager, + ConfigFactoryInterface $config_factory, + LoggerChannelFactoryInterface $logger_factory, + RequestContext $requestContext, + Settings $settings + ) { + + parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $config_factory); + + $this->dataHandler = $data_handler; + $this->loggerFactory = $logger_factory; + $this->requestContext = $requestContext; + $this->siteSettings = $settings; + } + + /** + * Sets the underlying SDK library. + * + * @return \League\OAuth2\Client\Provider\Digitalocean + * The initialized 3rd party library instance. + * + * @throws SocialApiException + * If the SDK library does not exist. + */ + protected function initSdk() { + + $class_name = 'ChrisHemmings\OAuth2\Client\Provider\DigitalOcean'; + if (!class_exists($class_name)) { + throw new SocialApiException(sprintf('The Digitalocean Library for the league oAuth not found. Class: %s.', $class_name)); + } + /* @var \Drupal\social_auth_digitalocean\Settings\DigitaloceanAuthSettings $settings */ + $settings = $this->settings; + // Proxy configuration data for outward proxy. + $proxyUrl = $this->siteSettings->get("http_client_config")["proxy"]["http"]; + if ($this->validateConfig($settings)) { + // All these settings are mandatory. + if ($proxyUrl) { + $league_settings = [ + 'clientId' => $settings->getClientId(), + 'clientSecret' => $settings->getClientSecret(), + 'redirectUri' => $this->requestContext->getCompleteBaseUrl() . '/user/login/digitalocean/callback', + 'proxy' => $proxyUrl, + ]; + } + else { + $league_settings = [ + 'clientId' => $settings->getClientId(), + 'clientSecret' => $settings->getClientSecret(), + 'redirectUri' => $this->requestContext->getCompleteBaseUrl() . '/user/login/digitalocean/callback', + ]; + } + + return new \ChrisHemmings\OAuth2\Client\Provider\DigitalOcean($league_settings); + } + return FALSE; + } + + /** + * Checks that module is configured. + * + * @param \Drupal\social_auth_digitalocean\Settings\DigitaloceanAuthSettings $settings + * The Digitalocean auth settings. + * + * @return bool + * True if module is configured. + * False otherwise. + */ + protected function validateConfig(DigitaloceanAuthSettings $settings) { + $client_id = $settings->getClientId(); + $client_secret = $settings->getClientSecret(); + if (!$client_id || !$client_secret) { + $this->loggerFactory + ->get('social_auth_digitalocean') + ->error('Define Client ID and Client Secret on module settings.'); + return FALSE; + } + + return TRUE; + } + +} diff --git a/src/Plugin/Network/DigitaloceanAuthInterface.php b/src/Plugin/Network/DigitaloceanAuthInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..7eae68d85b0819a35b0d23f79db85925ff311745 --- /dev/null +++ b/src/Plugin/Network/DigitaloceanAuthInterface.php @@ -0,0 +1,10 @@ +clientId) { + $this->clientId = $this->config->get('client_id'); + } + return $this->clientId; + } + + /** + * {@inheritdoc} + */ + public function getClientSecret() { + if (!$this->clientSecret) { + $this->clientSecret = $this->config->get('client_secret'); + } + return $this->clientSecret; + } + +} diff --git a/src/Settings/DigitaloceanAuthSettingsInterface.php b/src/Settings/DigitaloceanAuthSettingsInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a01316e644eaa5646da31f97ad264c3ecb5b4411 --- /dev/null +++ b/src/Settings/DigitaloceanAuthSettingsInterface.php @@ -0,0 +1,26 @@ +