Skip to content

Commit

Permalink
8.2.0
Browse files Browse the repository at this point in the history
- D8CORE-4129: changed labels for new sso user buttons (#115)
- D8CORE-000: Check for attribute existence on user login (#114)
- D8CORE-4083 Use API V2 for the new workgroup API (#111)
  • Loading branch information
pookmish authored May 7, 2021
2 parents e48a4e4 + 4fa6466 commit 43474d7
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 123 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Stanford SSP

8.x-2.0
--------------------------------------------------------------------------------
_Release Date: 2021-05-07_

- D8CORE-4129: changed labels for new sso user buttons (#115) (a66f0a8)
- D8CORE-000: Check for attribute existence on user login (#114) (979ff4d)
- D8CORE-4083 Use API V2 for the new workgroup API (#111) (6465c96)
- 2.x (0305353)

8.x-2.x
--------------------------------------------------------------------------------
- Replaced Workgroup API from V1 to V2
- V1 used XML, V2 uses JSON
- Workgroup API url has been removed from the config. It can now be configured in settings.php files with `$settings['stanford_ssp.workgroup_api'] = 'http://foo.bar';'`
- Workgoup API certificates for V1 do not automatically work for V2. Database updates check for a successful connection and will error out if the certificates are not valid.

8.x-1.7
--------------------------------------------------------------------------------
_Release Date: 2021-04-09_
Expand Down
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# [Stanford Simple SAML PHP](https://github.com/SU-SWS/stanford_ssp)
##### Version: 8.x-1.3
##### Version: 8.x-2.x

[![CircleCI](https://circleci.com/gh/SU-SWS/stanford_ssp.svg?style=svg)](https://circleci.com/gh/SU-SWS/stanford_ssp)
[![Maintainability](https://api.codeclimate.com/v1/badges/d597c026202dc075d677/maintainability)](https://codeclimate.com/github/SU-SWS/stanford_ssp/maintainability)
Expand Down Expand Up @@ -28,6 +28,9 @@ Configuration

The main configuration page can be found at: `/admin/config/people/simplesamlphp_auth`

To use the workgroup API, you must work with the MAIS team to get a valid certificate. V1 API certificates do not automatically
work with the V2 API.

Troubleshooting
---

Expand All @@ -41,17 +44,17 @@ You are welcome to contribute functionality, bug fixes, or documentation to this
[![CircleCI](https://circleci.com/gh/SU-SWS/stanford_ssp/tree/8.x-1.x.svg?style=svg)](https://circleci.com/gh/SU-SWS/stanford_ssp/tree/8.x-1.x)


Releases
---

Steps to build a new release:
- Checkout the latest commit from the `8.x-1.x` branch.
- Create a new branch for the release.
- Commit any necessary changes to the release branch.
- These may include, but are not necessarily limited to:
- Update the version in any `info.yml` files, including in any submodules.
- Update the CHANGELOG to reflect the changes made in the new release.
- Make a PR to merge your release branch into `master`
- Give the PR a semver-compliant label, e.g., (`patch`, `minor`, `major`). This may happen automatically via Github actions (if a labeler action is configured).
- When the PR is merged to `master`, a new tag will be created automatically, bumping the version by the semver label.
- The github action is built from: [semver-release-action](https://github.com/K-Phoen/semver-release-action), and further documentation is available there.
Releases
---

Steps to build a new release:
- Checkout the latest commit from the `8.x-2.x` branch.
- Create a new branch for the release.
- Commit any necessary changes to the release branch.
- These may include, but are not necessarily limited to:
- Update the version in any `info.yml` files, including in any submodules.
- Update the CHANGELOG to reflect the changes made in the new release.
- Make a PR to merge your release branch into `master`
- Give the PR a semver-compliant label, e.g., (`patch`, `minor`, `major`). This may happen automatically via Github actions (if a labeler action is configured).
- When the PR is merged to `master`, a new tag will be created automatically, bumping the version by the semver label.
- The github action is built from: [semver-release-action](https://github.com/K-Phoen/semver-release-action), and further documentation is available there.
2 changes: 1 addition & 1 deletion config/install/stanford_ssp.settings.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
saml_attribute: eduPersonEntitlement
workgroup_api_url: 'https://workgroupsvc.stanford.edu/v1/workgroups'

use_workgroup_api: false
hide_local_login: true
workgroup_api_cert: ''
Expand Down
3 changes: 0 additions & 3 deletions config/schema/stanford_ssp.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ stanford_ssp.settings:
saml_attribute:
type: string
label: 'Attribute in SAML that contains the role mapping values'
workgroup_api_url:
type: string
label: 'Workgroup API Base URL'
hide_local_login:
type: boolean
label: 'Hide core local login form on user page'
Expand Down
2 changes: 1 addition & 1 deletion src/Form/AddUserForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {

$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Add SSO User'),
'#value' => $this->t('Add SUNetID User'),
];
return $form;
}
Expand Down
117 changes: 55 additions & 62 deletions src/Service/StanfordSSPWorkgroupApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Site\Settings;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;

Expand All @@ -14,6 +15,8 @@
*/
class StanfordSSPWorkgroupApi implements StanfordSSPWorkgroupApiInterface {

const WORKGROUP_API = 'https://workgroupsvc.stanford.edu/workgroups/2.0';

/**
* Config factory service.
*
Expand All @@ -35,13 +38,6 @@ class StanfordSSPWorkgroupApi implements StanfordSSPWorkgroupApiInterface {
*/
protected $logger;

/**
* Keyed array of workgroup responses with the group as the key.
*
* @var array
*/
protected $workgroupResponses;

/**
* Path to cert file.
*
Expand Down Expand Up @@ -113,8 +109,7 @@ public function getKey() {
* {@inheritdoc}
*/
public function connectionSuccessful() {
$response = $this->getWorkgroupApiResponse('uit:sws');
return $response && $response->getStatusCode() == 200;
return !empty($this->callApi('uit:sws'));
}

/**
Expand All @@ -126,111 +121,109 @@ public function getRolesFromAuthname($authname) {
return $roles;
}
$config = $this->configFactory->get('simplesamlphp_auth.settings');;
$workgroup_mappings = array_filter(explode('|', $config->get('role.population') ?: ''));

// Loop through each workgroup mapping and find out if the given user exists
// within each group.
foreach ($workgroup_mappings as $workgroup_mapping) {
[$role, $mapping] = explode(':', $workgroup_mapping, 2);
$role_population = array_filter(explode('|', $config->get('role.population') ?: ''));
$workgroup_mappings = [];

// Convert the simplesamlphp_auth role mapping to a nested array of
// workgroup name => array of roles.
foreach ($role_population as $rule) {
[$role, $mapping] = explode(':', $rule, 2);
// We ignore the eduEntitlement equation since its only a yes or no if the
// user is in the group.
$workgroup = substr($mapping, strrpos($mapping, ',') + 1);
if (!in_array($role, $roles) && $this->userInGroup($workgroup, $authname)) {
$roles[] = $role;
$workgroup_mappings[$workgroup][] = $role;
}

$users_workgroups = $this->getAllUserWorkgroups($authname);

// Loop through the workgroup mappings to find the ones the current user
// is a member of and add those roles to the list.
foreach ($workgroup_mappings as $workgroup => $workgroup_roles) {
if (in_array($workgroup, $users_workgroups)) {
$roles = array_merge($roles, $workgroup_roles);
}
}
return $roles;
return array_unique($roles);
}

/**
* {@inheritdoc}
*/
public function userInGroup($workgroup, $name) {
if ($response = $this->getWorkgroupApiResponse($workgroup)) {
$dom = new \DOMDocument();
$dom->loadXML((string) $response->getBody());
$xpath = new \DOMXPath($dom);

// Use xpath to find if the sunetid is one of the members.
return $xpath->query("//members/member[@id='$name']")->length > 0;
}
return FALSE;
return in_array($workgroup, $this->getAllUserWorkgroups($name));
}

/**
* {@inheritdoc}
*/
public function userInAnyGroup(array $workgroups, $name) {
foreach ($workgroups as $workgroup) {
if ($this->userInGroup($workgroup, $name)) {
return TRUE;
}
}
return FALSE;
return !empty(array_intersect($workgroups, $this->getAllUserWorkgroups($name)));
}

/**
* {@inheritdoc}
*/
public function userInAllGroups(array $workgroups, $name) {
foreach ($workgroups as $workgroup) {
if (!$this->userInGroup($workgroup, $name)) {
return FALSE;
}
}
return TRUE;
return count(array_intersect($workgroups, $this->getAllUserWorkgroups($name))) == count($workgroups);
}

/**
* {@inheritDoc}
*/
public function isWorkgroupValid($workgroup) {
if (!$this->connectionSuccessful()) {
return FALSE;
}

$response = $this->getWorkgroupApiResponse($workgroup);
$dom = new \DOMDocument();
$dom->loadXML((string) $response->getBody());
$xpath = new \DOMXPath($dom);
return $xpath->query('//visibility')->item(0)->nodeValue != 'PRIVATE';
return !empty($this->callApi($workgroup));
}

/**
* Call the workgroup api and get the response for the workgroup.
*
* @param string $workgroup
* @param string|null $workgroup
* Workgroup name like uit:sws.
* @param string|null $sunet
* User sunetid.
*
* @return bool|\Psr\Http\Message\ResponseInterface
* API response or false if fails.
*/
protected function getWorkgroupApiResponse($workgroup) {
// We've already called the API for this group, use that result.
if (isset($this->workgroupResponses[$workgroup])) {
return $this->workgroupResponses[$workgroup];
}
protected function callApi($workgroup = NULL, $sunet = NULL) {
$options = [
'cert' => $this->getCert(),
'ssl_key' => $this->getKey(),
'verify' => TRUE,
'timeout' => 5,
'query' => [
'type' => $workgroup ? 'workgroup' : 'user',
'id' => $workgroup ?: $sunet,
],
];

$base_url = $this->configFactory->get('stanford_ssp.settings')
->get('workgroup_api_url');
$base_url = trim($base_url, '/') ?: 'https://workgroupsvc.stanford.edu/v1/workgroups';

$api_url = Settings::get('stanford_ssp.workgroup_api', self::WORKGROUP_API);
try {
$result = $this->guzzle->request('GET', "$base_url/$workgroup", $options);
$this->workgroupResponses[$workgroup] = $result;
return $result;
$result = $this->guzzle->request('GET', $api_url, $options);
return json_decode($result->getBody(), TRUE);
}
catch (GuzzleException $e) {
$this->logger->error('Unable to connect to workgroup api. @message', ['@message' => $e->getMessage()]);
}
return FALSE;
}

/**
* Get a flat list of all the given user's workgroups.
*
* @param string $authname
* Sunet id.
*
* @return array
* Array of the user's workgroups.
*/
protected function getAllUserWorkgroups($authname) {
$workgroup_names = [];
if ($user_data = $this->callApi(NULL, $authname)) {
foreach ($user_data['results'] as $user_member) {
$workgroup_names[] = $user_member['name'];
}
}
return $workgroup_names;
}

}
2 changes: 1 addition & 1 deletion stanford_ssp.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Configures SimpleSAML PHP auth to work in Stanford web environment
core: 8.x
core_version_requirement: ^8 || ^9
type: module
version: 8.x-1.7
version: 8.x-2.0
package: Stanford
dependencies:
- simplesamlphp_auth:simplesamlphp_auth
13 changes: 13 additions & 0 deletions stanford_ssp.install
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,16 @@ function stanford_ssp_update_8107() {
->clear('whitelist_users')
->save();
}

/**
* Remove workgroup api from configs since it is hardcoded.
*/
function stanford_ssp_update_8108() {
$config = \Drupal::configFactory()->getEditable('stanford_ssp.settings');
/** @var \Drupal\stanford_ssp\Service\StanfordSSPWorkgroupApiInterface $api */
$api = \Drupal::service('stanford_ssp.workgroup_api');
if ($config->get('use_workgroup_api') && !$api->connectionSuccessful()) {
throw new \Exception('Workgroup API connection is enabled but is unable to connect to the API. Please check your certificate credentials or disable the workgroup API.');
}
$config->clear('workgroup_api_url')->save();
}
2 changes: 1 addition & 1 deletion stanford_ssp.links.action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
stanford_ssp.create_user:
route_name: stanford_ssp.create_user
title: 'Add SSO User'
title: 'Add SUNetID User'
appears_on:
- user.admin_create
5 changes: 2 additions & 3 deletions stanford_ssp.module
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,8 @@ function stanford_ssp_simplesamlphp_auth_allow_login($attributes) {
* Implements hook_simplesamlphp_auth_existing_user().
*/
function stanford_ssp_simplesamlphp_auth_existing_user($attributes) {
$saml_mail = $attributes['eduPersonPrincipalName'];

if (!empty($saml_mail)) {
if (!empty($attributes['eduPersonPrincipalName'])) {
$saml_mail = $attributes['eduPersonPrincipalName'];
$saml_mail = is_array($saml_mail) ? reset($saml_mail) : $saml_mail;
$existing_users = \Drupal::service('entity_type.manager')
->getStorage('user')
Expand Down
2 changes: 1 addition & 1 deletion tests/modules/stanford_ssp_test/stanford_ssp_test.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ type: module
core_version_requirement: ^8.8 || ^9
hidden: true
package: Testing
version: 8.x-1.7-dev
version: 8.x-2.0
18 changes: 3 additions & 15 deletions tests/src/Kernel/Form/RoleSyncFormTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,8 @@ protected function setUp(): void {

public function guzzleCallback($method, $url, $options) {
$response = $this->createMock(ResponseInterface::class);
$response->method('getBody')->willReturn(json_encode(['results' => []]));

$response->method('getBody')->willReturn('<?xml version="1.0" encoding="UTF-8"?>
<workgroup>
<description>Test Workgroup</description>
<filter />
<visibility>STANFORD</visibility>
<reusable>FALSE</reusable>
<privgroup>TRUE</privgroup>
<members />
<administrators>
<member id="foo-bar" url="https://workgroupsvc.stanford.edu/v1/users/foo-bar" name="Foo, BAR" />
</administrators>
</workgroup>
');
$response->method('getStatusCode')->willReturn(200);
return $response;
}
Expand Down Expand Up @@ -207,8 +195,8 @@ public function testAddingNewWorkgroup() {
],
]);
\Drupal::formBuilder()->submitForm($this->formId, $form_state);
$this->assertEqual(\Drupal::config('simplesamlphp_auth.settings')
->get('role.population'), 'role1:eduPersonEnttitlement,=,foo:bar');
$this->assertEquals('role1:eduPersonEnttitlement,=,foo:bar', \Drupal::config('simplesamlphp_auth.settings')
->get('role.population'));
}

/**
Expand Down
Loading

0 comments on commit 43474d7

Please sign in to comment.