diff --git a/README-Original.md b/README-Original.md deleted file mode 100644 index 3af3089..0000000 --- a/README-Original.md +++ /dev/null @@ -1,32 +0,0 @@ -# UW Auth - -This module enables Drupal 8 to authenticate users using Shibboleth, and assign -their roles based on group membership settings in Active Directory or in -the UW Groups Web Service. - -Whenever a Shibboleth session is detected, the user is logged in as that user. -Or, if the account doesn't exist, it'll be created automatically. There is no -filtering of what users should be created. User accounts will be given the -same user id, as their NetID. And, their email will be set to NETID@uw.edu. - -The module was designed and tested using Apache with mod_shib2. Shibboleth is -configured to expose the attribute "uwnetid", which will be used as the -visitors username. - -In order to enable the module, go to Manage > Extend > UW Auth. Once enabled, -go to Manage > Configuration > People > UW Auth to configure. - -Finally, if no roles are mapped (or the user isn't assigned to any), they will -be given the role of authenticated user. - -## Recommended Version - -For stable operation, please download or pull a specific tag (release). -Otherwise you are likely to download the latest development code, which may -or may not work right. - -## Federation - -There is no support for federated logins. The module assumes all -users are using UW NetID's. This functionality may change in the future, if the -need arises for it. diff --git a/README.md b/README.md index 109fcb3..894ea0b 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,6 @@ This module enables Drupal 8 to authenticate users using Shibboleth, and assign their roles based on information in other identity management systems. -It is a fork of the module created by the University of Washington, to be found -at [https://github.com/jtyocum/uwauth](https://github.com/jtyocum/uwauth), as -allowed by its GPL-3.0+ license, as provided in the `LICENSE.txt` file. - -The original `README.md file` has been renamed to `README-Original.md`. - ## Operation @@ -57,6 +51,23 @@ Such a mapping may look like this depending on your Shibboleth configuration: ## Installation +To install via Composer, add a repository to your composer.json then install as usual via Composer: + +```` +{ + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/deohs/uwauth" + } + ], + "require": { + "drupal/uwauth": "~2.2" + } +} +```` + + In addition to enabling the module, you need to modify the `.htaccess` at the document root, or its equivalent in your vhost definition, to avoid having URLs needed by the Shibboleth SP being rewritten. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7d8d668 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "drupal/uwauth", + "description": "Provides authentication and role assignment with Shibboleth, and UW Groups or Active Directory.", + "type": "drupal-module", + "homepage": "https://github.com/deohs/uwauth", + "authors": [ + { + "name": "John Yocum (jtyocum)", + "role": "Creator" + }, + { + "name": "Thomas Kiehne (tkiehne)", + "email": "tkiehne@uw.edu", + "homepage": "https://www.drupal.org/u/tkiehne", + "role": "Maintainer" + } + ], + "support": { + "issues": "https://github.com/deohs/uwauth/issues", + "source": "https://github.com/deohs/uwauth" + }, + "license": "GPL-3.0-or-later", + "minimum-stability": "dev" +} diff --git a/config/install/uwauth.settings.yml b/config/install/uwauth.settings.yml index 73429b1..a1a69dc 100644 --- a/config/install/uwauth.settings.yml +++ b/config/install/uwauth.settings.yml @@ -11,7 +11,7 @@ auth: - 'user.login' - 'user.logout' name_id: 'uid' - sp_endoint: '/Shibboleth.sso' + sp_endpoint: '/Shibboleth.sso' mail: valid_domains: - 'uw.edu' diff --git a/config/schema/uwauth.schema.yml b/config/schema/uwauth.schema.yml index 5c8cf65..98f71ec 100644 --- a/config/schema/uwauth.schema.yml +++ b/config/schema/uwauth.schema.yml @@ -27,7 +27,7 @@ uwauth.settings: label: 'Attribute or variable mapped to the Shibboleth NameID' nullable: false translatable: false - sp_endoint: + sp_endpoint: type: 'string' label: 'Path used by the Shibboleth SP endpoint' nullable: false diff --git a/src/Debug.php b/src/Debug.php index 6560434..858b74b 100644 --- a/src/Debug.php +++ b/src/Debug.php @@ -9,6 +9,11 @@ */ class Debug { + /** + * Debug flag. + * + * @var bool + */ protected $verbose; /** diff --git a/src/EventSubscriber/UwAuthSubscriber.php b/src/EventSubscriber/UwAuthSubscriber.php index dd47599..edad35b 100644 --- a/src/EventSubscriber/UwAuthSubscriber.php +++ b/src/EventSubscriber/UwAuthSubscriber.php @@ -87,7 +87,7 @@ class UwAuthSubscriber implements EventSubscriberInterface { /** * A hash of severity level by group sync method. * - * @var array + * @var arraystringstring */ protected $severity; @@ -110,7 +110,7 @@ class UwAuthSubscriber implements EventSubscriberInterface { * The current_route_match service. * @param \Psr\Log\LoggerInterface $logger * The logger.channel.uwauth logger channel. - * @param array $severity + * @param arraystringstring $severity * The severity levels to use for each role sync method. */ public function __construct( @@ -213,7 +213,7 @@ private function fetchAdGroups(AccountInterface $account) { * @param \Drupal\Core\Session\AccountInterface $account * A user object. * - * @return array + * @return arraystring * An array of group names. */ private function fetchGwsGroups(AccountInterface $account) { @@ -239,7 +239,7 @@ private function fetchGwsGroups(AccountInterface $account) { // Extract groups from response. $uwgws_feed = simplexml_load_string(str_replace('xmlns=', 'ns=', $uwgws_response)); $uwgws_entries = $uwgws_feed->xpath("//a[@class='name']"); - $uwgws_groups = array(); + $uwgws_groups = []; foreach ($uwgws_entries as $uwgws_entry) { $uwgws_groups[] = (string) $uwgws_entry[0]; } @@ -290,7 +290,7 @@ protected function getFilteredAttributes() { * {@inheritdoc} */ public static function getSubscribedEvents() { - $events[KernelEvents::REQUEST][] = array('handle', 29); + $events[KernelEvents::REQUEST][] = ['handle', 29]; return $events; } @@ -416,7 +416,7 @@ private function loginUser($username, AttributeBagInterface $attributes) { * @param \Drupal\Core\Session\AccountInterface $account * A user object. * - * @return array + * @return arraystring * An array of role names. */ private function mapGroupsRoles(AccountInterface $account) { @@ -441,14 +441,14 @@ private function mapGroupsRoles(AccountInterface $account) { // Group to Role maps are stored as a multi-line string, containing pipe- // delimited key-value pairs. - $group_role_map = array(); + $group_role_map = []; foreach (preg_split("/((\r?\n)|(\r\n?))/", $this->settings->get('group.map')) as $entry) { $pair = explode('|', $entry); $group_role_map[(string) $pair[0]] = (string) $pair[1]; } // Loop through group list, and extract matching roles. - $mapped_roles = array(); + $mapped_roles = []; foreach ($group_membership as $group) { if (array_key_exists($group, $group_role_map)) { $mapped_roles[] = (string) $group_role_map[$group]; diff --git a/src/Form/UwAuthSettingsForm.php b/src/Form/UwAuthSettingsForm.php index 837ee8c..b86b9de 100644 --- a/src/Form/UwAuthSettingsForm.php +++ b/src/Form/UwAuthSettingsForm.php @@ -127,11 +127,11 @@ protected function buildMailSettings(array $form, FormStateInterface $form_state */ protected function buildLocalSettings(array $form, FormStateInterface $form_state) { $groupSource = $this->settings->get('group.source'); - $form['uwauth_local'] = array( + $form['uwauth_local'] = [ '#type' => 'details', '#title' => $this->t('Local Drupal groups'), '#open' => $groupSource === static::SYNC_LOCAL, - ); + ]; $excludedRoutes = $this->settings->get('auth.excluded_routes'); $excludedRoutes = implode("\n", $excludedRoutes); @@ -157,25 +157,25 @@ protected function buildLocalSettings(array $form, FormStateInterface $form_stat * The modified form array. */ protected function buildUwSettings(array $form, FormStateInterface $form_state) { - $form['uwauth_general'] = array( + $form['uwauth_general'] = [ '#type' => 'details', '#title' => $this->t('General'), '#open' => TRUE, - ); + ]; $groupSource = $this->settings->get('group.source'); - $form['uwauth_general']['source'] = array( + $form['uwauth_general']['source'] = [ '#type' => 'select', '#title' => $this->t('Group Source'), '#description' => $this->t('Choose your group membership source, or none to disable.'), - '#options' => array( + '#options' => [ static::SYNC_GROUPS => $this->t('Groups Web Service'), static::SYNC_AD => $this->t('Active Directory'), static::SYNC_LOCAL => $this->t('Local Drupal groups'), static::SYNC_NONE => $this->t("None, don't login"), - ), + ], '#default_value' => $groupSource, - ); + ]; $form['uwauth_general']['name_id'] = [ '#default_value' => $this->settings->get('auth.name_id'), @@ -204,82 +204,82 @@ protected function buildUwSettings(array $form, FormStateInterface $form_state) '#type' => 'textfield', ]; - $form['uwauth_gws'] = array( + $form['uwauth_gws'] = [ '#type' => 'details', '#title' => $this->t('Groups Web Service'), '#description' => $this->t('If using GWS as your group source, please provide the paths to your UW certificates. For security purposes, the certificates should be stored outside of your website root.'), '#open' => $groupSource === static::SYNC_GROUPS, - ); + ]; - $form['uwauth_gws']['cert'] = array( + $form['uwauth_gws']['cert'] = [ '#type' => 'textfield', '#title' => $this->t('Certificate Path'), '#description' => $this->t('Example: /etc/ssl/drupal_uwca_cert.pem'), '#default_value' => $this->settings->get('gws.cert'), - ); + ]; - $form['uwauth_gws']['key'] = array( + $form['uwauth_gws']['key'] = [ '#type' => 'textfield', '#title' => $this->t('Private Key Path'), '#description' => $this->t('Example: /etc/ssl/drupal_uwca_key.pem'), '#default_value' => $this->settings->get('gws.key'), - ); + ]; - $form['uwauth_gws']['cacert'] = array( + $form['uwauth_gws']['cacert'] = [ '#type' => 'textfield', '#title' => $this->t('CA Certificate Path'), '#description' => $this->t('Example: /etc/ssl/drupal_uwca_ca.pem'), '#default_value' => $this->settings->get('gws.cacert'), - ); + ]; - $form['uwauth_ad'] = array( + $form['uwauth_ad'] = [ '#type' => 'details', '#title' => $this->t('Active Directory'), '#description' => $this->t('If using AD as your group source, please provide the LDAP URI and Base DN for your domain. For anonymous lookups, leave the Bind DN and Bind Password fields blank.'), '#open' => $groupSource === static::SYNC_AD, - ); + ]; - $form['uwauth_ad']['uri'] = array( + $form['uwauth_ad']['uri'] = [ '#type' => 'textfield', '#title' => $this->t('LDAP URI'), '#description' => $this->t('Example: ldap://domaincontroller.example.org'), '#default_value' => $this->settings->get('ad.uri'), - ); + ]; - $form['uwauth_ad']['basedn'] = array( + $form['uwauth_ad']['basedn'] = [ '#type' => 'textfield', '#title' => $this->t('Base DN'), '#description' => $this->t('Example: DC=example,DC=org'), '#default_value' => $this->settings->get('ad.basedn'), - ); + ]; - $form['uwauth_ad']['binddn'] = array( + $form['uwauth_ad']['binddn'] = [ '#type' => 'textfield', '#title' => $this->t('Bind DN'), '#description' => $this->t('Example: CN=drupal,CN=Users,DC=example,DC=org'), '#default_value' => $this->settings->get('ad.binddn'), - ); + ]; - $form['uwauth_ad']['bindpass'] = array( + $form['uwauth_ad']['bindpass'] = [ '#type' => 'password', '#title' => $this->t('Bind Password'), '#description' => $this->t('NOTE: If a bind password has been set, leave this field blank to leave it unchanged.'), - ); + ]; - $form['uwauth_map'] = array( + $form['uwauth_map'] = [ '#type' => 'details', '#title' => $this->t('Group to Role Mapping'), '#description' => $this->t('For group source portability, groups are mapped to roles. Each group can be mapped to a single role.'), '#open' => in_array($groupSource, [static::SYNC_AD, static::SYNC_GROUPS]), - ); + ]; - $form['uwauth_map']['map'] = array( + $form['uwauth_map']['map'] = [ '#type' => 'textarea', '#title' => $this->t('Group Map'), '#description' => $this->t('Note: Each row corresponds to a single group to role mapping. The format is group|role. All roles should be entered as their machine name.'), '#default_value' => $this->settings->get('group.map'), '#rows' => 15, - ); + ]; return $form; } @@ -298,48 +298,48 @@ public function validateForm(array &$form, FormStateInterface $form_state) { $uri = $form_state->getValue('uri'); if (($source == static::SYNC_AD) && ((strlen($uri) == 0) || (strlen($baseDn) == 0))) { - $form_state->setErrorByName('source', t('Active Directory requires both the URI and Base DN to be configured.')); + $form_state->setErrorByName('source', $this->t('Active Directory requires both the URI and Base DN to be configured.')); } if (($source == static::SYNC_GROUPS) && ((strlen($cert) == 0) || (strlen($key) == 0) || (strlen($caCert) == 0))) { - $form_state->setErrorByName('source', t('Groups Web Service requires the Certificate, Key, and CA Certificate to be configured.')); + $form_state->setErrorByName('source', $this->t('Groups Web Service requires the Certificate, Key, and CA Certificate to be configured.')); } if ((strlen($cert) > 0) && preg_match_all("/[^a-zA-Z0-9_\-\/:\\. ]/", $cert)) { - $form_state->setErrorByName('cert', t('The Certificate path contains invalid characters.')); + $form_state->setErrorByName('cert', $this->t('The Certificate path contains invalid characters.')); } elseif ((strlen($cert) > 0) && !is_readable($cert)) { - $form_state->setErrorByName('cert', t('The Certificate file could not be read. Please verify the path is correct.')); + $form_state->setErrorByName('cert', $this->t('The Certificate file could not be read. Please verify the path is correct.')); } if ((strlen($key) > 0) && preg_match_all("/[^a-zA-Z0-9_\-\/:\\. ]/", $key)) { - $form_state->setErrorByName('key', t('The Key path contains invalid characters.')); + $form_state->setErrorByName('key', $this->t('The Key path contains invalid characters.')); } elseif ((strlen($key) > 0) && !is_readable($key)) { - $form_state->setErrorByName('key', t('The Key file could not be read. Please verify the path is correct.')); + $form_state->setErrorByName('key', $this->t('The Key file could not be read. Please verify the path is correct.')); } if ((strlen($caCert) > 0) && preg_match_all("/[^a-zA-Z0-9_\-\/:\\. ]/", $caCert)) { - $form_state->setErrorByName('cacert', t('The CA Certificate path contains invalid characters.')); + $form_state->setErrorByName('cacert', $this->t('The CA Certificate path contains invalid characters.')); } elseif ((strlen($caCert) > 0) && !is_readable($caCert)) { - $form_state->setErrorByName('cacert', t('The CA Certificate file could not be read. Please verify the path is correct.')); + $form_state->setErrorByName('cacert', $this->t('The CA Certificate file could not be read. Please verify the path is correct.')); } if ((strlen($uri) > 0) && (preg_match("/^(ldap:\/\/|ldaps:\/\/)[a-z0-9_\.\-]*[a-z0-9]$/i", $uri) === 0)) { - $form_state->setErrorByName('uri', t('The LDAP URI contains invalid characters or formatting.')); + $form_state->setErrorByName('uri', $this->t('The LDAP URI contains invalid characters or formatting.')); } if ((strlen($baseDn) > 0) && (preg_match("/^(OU=|DC=)[a-z0-9_\-=, ]*[a-z0-9]$/i", $baseDn) === 0)) { - $form_state->setErrorByName('basedn', t('The Base DN contains invalid characters or formatting.')); + $form_state->setErrorByName('basedn', $this->t('The Base DN contains invalid characters or formatting.')); } if ((strlen($bindDn) > 0) && (preg_match("/^CN=[a-z0-9_\-=, ]*[a-z0-9]$/i", $bindDn) === 0)) { - $form_state->setErrorByName('binddn', t('The Bind DN contains invalid characters or formatting.')); + $form_state->setErrorByName('binddn', $this->t('The Bind DN contains invalid characters or formatting.')); } if ((strlen($map) > 0) && preg_match_all("/^([^a-z0-9_\-]*\|[a-z0-9_\-]*|[a-z0-9_\-]*\|[^a-z0-9_\-]*)$/mi", $map)) { - $form_state->setErrorByName('map', t('The Group Map contains invalid characters or formatting.')); + $form_state->setErrorByName('map', $this->t('The Group Map contains invalid characters or formatting.')); } if ($form_state->getErrors()) { @@ -357,7 +357,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { * @param string $key * The key for the value in form state. * - * @return array + * @return arraystring * The value as an array of single-line, non empty strings. */ public static function getTextFromValues(FormStateInterface $form_state, $key) { diff --git a/uwauth.info.yml b/uwauth.info.yml index f2d1ff5..14bd8a6 100644 --- a/uwauth.info.yml +++ b/uwauth.info.yml @@ -2,5 +2,5 @@ name: 'UW Auth' description: 'Provides authentication and role assignment with Shibboleth, and UW Groups or Active Directory' package: Web services type: module -version: '8.x-2.1' +version: '8.x-2.2' core: '8.x' diff --git a/uwauth.install b/uwauth.install new file mode 100644 index 0000000..dccc698 --- /dev/null +++ b/uwauth.install @@ -0,0 +1,32 @@ +getEditable('uwauth.settings'); + $config->set('mail', ['valid_domains' => ['uw.edu']]); + $config->set('auth', [ + 'allowed_attributes' => [ + 'cn', + 'employeeType', + 'givenName', + 'sn', + 'uid', + 'uwnetid', + ], + 'excluded_routes' => [ + 'user.login', + 'user.logout', + ], + 'name_id' => 'uwnetid', + 'sp_endpoint' => '/Shibboleth.sso', + ]); + $config->save(); +} diff --git a/uwauth.module b/uwauth.module index c8259e2..7b9400e 100644 --- a/uwauth.module +++ b/uwauth.module @@ -27,7 +27,7 @@ function uwauth_help($route_name, RouteMatchInterface $route_match) { $output .= '

' . t('For portability purposes, externally managed groups are mapped to Drupal roles. This mapping function allows for changes in group names, and sources without having to change permissions within Drupal.') . '

'; $output .= '

' . t('Each row within the text box represents a group to role map entry. A group can only be mapped to a single role, as the group name is used as an internal identifier. Groups cannot be mapped to builtin Drupal roles. So, if you want to give a group, admin privileges, you will need to create a role with those privileges.') . '

'; $output .= '

' . t('History') . '

'; - $output .= '

' . t('Developed by John Yocum for UW DEOHS. Source code history, releases, etc. available from ') . 'https://github.com/jtyocum/drupal8-uwauth.' . '

'; + $output .= '

' . t('Developed by John Yocum for UW DEOHS. Source code history, releases, etc. available from') . ' ' . 'https://github.com/jtyocum/drupal8-uwauth.' . '

'; return $output; } }