From 84b350c4d57c3f3f1baf615633684eb9f1970648 Mon Sep 17 00:00:00 2001 From: BaranekD <0Baranek.dominik0@gmail.com> Date: Wed, 17 Feb 2021 17:11:12 +0100 Subject: [PATCH] Added extended entitlements --- CHANGELOG.md | 3 + .../processFilterConfigurations-example.md | 15 ++ lib/AdapterLdap.php | 12 +- lib/AdapterRpc.php | 4 + lib/Auth/Process/PerunEntitlement.php | 123 ++------------ lib/Auth/Process/PerunEntitlementExtended.php | 140 ++++++++++++++++ lib/EntitlementUtils.php | 154 ++++++++++++++++++ lib/model/Group.php | 12 +- 8 files changed, 352 insertions(+), 111 deletions(-) create mode 100644 lib/Auth/Process/PerunEntitlementExtended.php create mode 100644 lib/EntitlementUtils.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 498be28b..3c336126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +#### Added +- Added extended PerunEntitlements + ## [v4.1.1] #### Fixed - Fixed bad log message in PerunIdentity in mode USERONLY diff --git a/config-templates/processFilterConfigurations-example.md b/config-templates/processFilterConfigurations-example.md index 6d494939..223e4821 100644 --- a/config-templates/processFilterConfigurations-example.md +++ b/config-templates/processFilterConfigurations-example.md @@ -57,6 +57,21 @@ Example how to enable/configure filter PerunEntitlement: ), ``` +## PerunEntitlementExtended + +Example how to enable/configure filter PerunEntitlement: + +```php +33 => array( + 'class' => 'perun:PerunEntitlementExtended', + 'interface' => 'ldap', + 'outputAttrName' => 'eduPersonEntitlementExtended', + # forwarded entitlement are released by default + #'releaseForwardedEntitlement' => false, OPTIONAL + 'forwardedEduPersonEntitlement' => 'eduPersonEntitlement', +), +``` + ## ForceAup 1.Create these attributes in Perun: diff --git a/lib/AdapterLdap.php b/lib/AdapterLdap.php index 48f4e97f..3e55cf49 100644 --- a/lib/AdapterLdap.php +++ b/lib/AdapterLdap.php @@ -114,13 +114,14 @@ class AdapterLdap extends Adapter $group = $this->connector->searchForEntity( $groupDn, '(objectClass=perunGroup)', - ['perunGroupId', 'cn', 'perunUniqueGroupName', 'perunVoId', 'description'] + ['perunGroupId', 'cn', 'perunUniqueGroupName', 'perunVoId', 'uuid', 'description'] ); array_push( $groups, new Group( $group['perunGroupId'][0], $group['perunVoId'][0], + $group['uuid'][0], $group['cn'][0], $group['perunUniqueGroupName'][0], $group['description'][0] ?? '' @@ -154,13 +155,14 @@ class AdapterLdap extends Adapter $group = $this->connector->searchForEntity( 'perunGroupId=' . $groupId . ',perunVoId=' . $resource['perunVoId'][0] . ',' . $this->ldapBase, '(objectClass=perunGroup)', - ['perunGroupId', 'cn', 'perunUniqueGroupName', 'perunVoId', 'description'] + ['perunGroupId', 'cn', 'perunUniqueGroupName', 'perunVoId', 'uuid', 'description'] ); array_push( $groups, new Group( $group['perunGroupId'][0], $group['perunVoId'][0], + $group['uuid'][0], $group['cn'], $group['perunUniqueGroupName'][0], $group['description'][0] ?? '' @@ -180,7 +182,7 @@ class AdapterLdap extends Adapter $group = $this->connector->searchForEntity( 'perunVoId=' . $voId . ',' . $this->ldapBase, '(&(objectClass=perunGroup)(perunUniqueGroupName=' . $name . '))', - ['perunGroupId', 'cn', 'perunUniqueGroupName', 'perunVoId', 'description'] + ['perunGroupId', 'cn', 'perunUniqueGroupName', 'perunVoId', 'uuid', 'description'] ); if ($group === null) { throw new Exception( @@ -190,6 +192,7 @@ class AdapterLdap extends Adapter return new Group( $group['perunGroupId'][0], $group['perunVoId'][0], + $group['uuId'][0], $group['cn'][0], $group['perunUniqueGroupName'][0], $group['description'][0] ?? '' @@ -404,7 +407,7 @@ class AdapterLdap extends Adapter $groups = $this->connector->searchForEntities( $this->ldapBase, '(&(uniqueMember=perunUserId=' . $userId . ', ou=People,' . $this->ldapBase . ')' . $resourcesString . ')', - ['perunGroupId', 'cn', 'perunUniqueGroupName', 'perunVoId', 'description'] + ['perunGroupId', 'cn', 'perunUniqueGroupName', 'perunVoId', 'uuid', 'description'] ); foreach ($groups as $group) { @@ -413,6 +416,7 @@ class AdapterLdap extends Adapter new Group( $group['perunGroupId'][0], $group['perunVoId'][0], + $group['uuid'][0], $group['cn'][0], $group['perunUniqueGroupName'][0], $group['description'][0] ?? '' diff --git a/lib/AdapterRpc.php b/lib/AdapterRpc.php index 93629333..1b786511 100644 --- a/lib/AdapterRpc.php +++ b/lib/AdapterRpc.php @@ -139,6 +139,7 @@ class AdapterRpc extends Adapter new Group( $group['id'], $group['voId'], + $group['uuid'], $group['name'], $uniqueName, $group['description'] @@ -194,6 +195,7 @@ class AdapterRpc extends Adapter new Group( $group['id'], $group['voId'], + $group['uuid'], $group['name'], $uniqueName, $group['description'] @@ -219,6 +221,7 @@ class AdapterRpc extends Adapter return new Group( $group['id'], $group['voId'], + $group['uuid'], $group['name'], $uniqueName, $group['description'] @@ -360,6 +363,7 @@ class AdapterRpc extends Adapter array_push($groups, new Group( $usersGroupOnFacility['id'], $usersGroupOnFacility['voId'], + $usersGroupOnFacility['uuid'], $usersGroupOnFacility['name'], $uniqueName, $usersGroupOnFacility['description'] diff --git a/lib/Auth/Process/PerunEntitlement.php b/lib/Auth/Process/PerunEntitlement.php index 2f05148f..93b5124e 100644 --- a/lib/Auth/Process/PerunEntitlement.php +++ b/lib/Auth/Process/PerunEntitlement.php @@ -7,6 +7,7 @@ use SimpleSAML\Configuration; use \SimpleSAML\Auth\ProcessingFilter; use SimpleSAML\Module\perun\Adapter; use SimpleSAML\Logger; +use SimpleSAML\Module\perun\EntitlementUtils; /** * Class PerunEntitlement @@ -77,16 +78,25 @@ class PerunEntitlement extends ProcessingFilter if (isset($request['perun']['groups'])) { $eduPersonEntitlement = $this->getEduPersonEntitlement($request); - $capabilities = $this->getCapabilities($request); + $capabilities = EntitlementUtils::getCapabilities( + $request, + $this->adapter, + $this->entitlementPrefix, + $this->entitlementAuthority + ); } else { Logger::debug( - 'perun:PerunEntitlement: There are no user groups assign to facility.' . - '=> Skipping getEduPersonEntitlement and getResourceCapabilities' + 'perun:PerunEntitlement: There are no user groups assigned to facility.' . + '=> Skipping getEduPersonEntitlement and getCapabilities' ); } if ($this->releaseForwardedEntitlement) { - $forwardedEduPersonEntitlement = $this->getForwardedEduPersonEntitlement($request); + $forwardedEduPersonEntitlement = EntitlementUtils::getForwardedEduPersonEntitlement( + $request, + $this->adapter, + $this->forwardedEduPersonEntitlement + ); } $request['Attributes'][$this->eduPersonEntitlement] = array_unique(array_merge( @@ -126,76 +136,11 @@ class PerunEntitlement extends ProcessingFilter return $eduPersonEntitlement; } - private function getForwardedEduPersonEntitlement(&$request) - { - $forwardedEduPersonEntitlement = []; - - if (!isset($request['perun']['user'])) { - Logger::debug( - 'perun:PerunEntitlement: Object Perun User is not specified.' . - '=> Skipping getting forwardedEntitlement.' - ); - return $forwardedEduPersonEntitlement; - } - - $user = $request['perun']['user']; - - try { - $forwardedEduPersonEntitlementMap = $this->adapter->getUserAttributesValues( - $user, - [$this->forwardedEduPersonEntitlement] - ); - } catch (Exception $exception) { - Logger::error( - 'perun:PerunEntitlement: Exception ' . $exception->getMessage() . - ' was thrown in method \'getForwardedEduPersonEntitlement\'.' - ); - } - - if (!empty($forwardedEduPersonEntitlementMap)) { - $forwardedEduPersonEntitlement = array_values($forwardedEduPersonEntitlementMap)[0]; - } - - return $forwardedEduPersonEntitlement; - } - - private function getCapabilities(&$request) - { - $resourceCapabilities = []; - $facilityCapabilities = []; - $capabilitiesResult = []; - - $spEntityId = $this->getSpEntityId($request); - try { - $resourceCapabilities = $this->adapter->getResourceCapabilities($spEntityId, $request['perun']['groups']); - $facilityCapabilities = $this->adapter->getFacilityCapabilities($spEntityId); - } catch (Exception $exception) { - Logger::error( - 'perun:PerunEntitlement: Exception ' . $exception->getMessage() . - ' was thrown in method \'getCapabilities\'.' - ); - } - - $capabilities = array_unique(array_merge($resourceCapabilities, $facilityCapabilities)); - - foreach ($capabilities as $capability) { - $wrappedCapability = $this->capabilitiesWrapper($capability); - array_push($capabilitiesResult, $wrappedCapability); - } - - return $capabilitiesResult; - } - private function groupNameWrapper($groupName) { - return $this->entitlementPrefix . 'group:' . implode(':', $this->encodeName($groupName)) . - '#' . $this->entitlementAuthority; - } - - private function capabilitiesWrapper($capabilities) - { - return $this->entitlementPrefix . implode(':', $this->encodeName($capabilities)) . - '#' . $this->entitlementAuthority; + return $this->entitlementPrefix . 'group:' . + implode(':', EntitlementUtils::encodeEntitlement($groupName)) . + '#' . $this->entitlementAuthority; } /** @@ -228,38 +173,4 @@ class PerunEntitlement extends ProcessingFilter return $this->entitlementPrefix . 'group:' . $groupName; } } - - private function encodeName($name) - { - $charsToSkip = [ - '!' => '%21', - '$' => '%24', - '\'' => '%27', - '(' => '%28', - ')' => '%29', - '*' => '%2A', - ',' => '%2C', - ';' => '%3B', - '&' => '%26', - '=' => '%3D', - '@' => '%40', - ':' => '%3A', - '+' => '%2B' - ]; - - $name = array_map('rawurlencode', explode(':', $name)); - $name = str_replace(array_values($charsToSkip), array_keys($charsToSkip), $name); - - return $name; - } - - private function getSpEntityId(&$request) - { - if (isset($request['SPMetadata']['entityid'])) { - return $request['SPMetadata']['entityid']; - } else { - throw new Exception('perun:PerunEntitlement: Cannot find entityID of remote SP. ' . - 'hint: Do you have this filter in IdP context?'); - } - } } diff --git a/lib/Auth/Process/PerunEntitlementExtended.php b/lib/Auth/Process/PerunEntitlementExtended.php new file mode 100644 index 00000000..99876790 --- /dev/null +++ b/lib/Auth/Process/PerunEntitlementExtended.php @@ -0,0 +1,140 @@ +<?php + + +namespace SimpleSAML\Module\perun\Auth\Process; + +use SimpleSAML\Error\Exception; +use SimpleSAML\Configuration; +use \SimpleSAML\Auth\ProcessingFilter; +use SimpleSAML\Module\perun\Adapter; +use SimpleSAML\Logger; +use SimpleSAML\Module\perun\EntitlementUtils; + +/** + * Class PerunEntitlementExtended + * + * This filter joins extended version of eduPersonEntitlement, forwardedEduPersonEntitlement, resource capabilities + * and facility capabilities + * + * @author Dominik Baránek <baranek@ics.muni.cz> + */ +class PerunEntitlementExtended extends ProcessingFilter +{ + const CONFIG_FILE_NAME = 'module_perun.php'; + const OUTPUT_ATTR_NAME = 'outputAttrName'; + const RELEASE_FORWARDED_ENTITLEMENT = 'releaseForwardedEntitlement'; + const FORWARDED_EDU_PERSON_ENTITLEMENT = 'forwardedEduPersonEntitlement'; + const ENTITLEMENTPREFIX_ATTR = 'entitlementPrefix'; + const ENTITLEMENTAUTHORITY_ATTR = 'entitlementAuthority'; + const GROUPNAMEAARC_ATTR = 'groupNameAARC'; + const INTERFACE_PROPNAME = 'interface'; + + private $outputAttrName; + private $releaseForwardedEntitlement; + private $forwardedEduPersonEntitlement; + private $entitlementPrefix; + private $entitlementAuthority; + private $groupNameAARC; + private $adapter; + + public function __construct($config, $reserved) + { + parent::__construct($config, $reserved); + $modulePerunConfiguration = Configuration::getConfig(self::CONFIG_FILE_NAME); + assert('is_array($config)'); + + $configuration = Configuration::loadFromArray($config); + + $this->outputAttrName = $configuration->getString(self::OUTPUT_ATTR_NAME, 'eduPersonEntitlementExtended'); + $this->releaseForwardedEntitlement = $configuration->getBoolean(self::RELEASE_FORWARDED_ENTITLEMENT, true); + $this->forwardedEduPersonEntitlement = $configuration->getString( + self::FORWARDED_EDU_PERSON_ENTITLEMENT, + $this->releaseForwardedEntitlement ? Configuration::REQUIRED_OPTION : '' + ); + + $this->groupNameAARC = $modulePerunConfiguration->getBoolean(self::GROUPNAMEAARC_ATTR, false); + $this->entitlementPrefix = $modulePerunConfiguration->getString( + self::ENTITLEMENTPREFIX_ATTR, + $this->groupNameAARC ? Configuration::REQUIRED_OPTION : '' + ); + $this->entitlementAuthority = $modulePerunConfiguration->getString( + self::ENTITLEMENTAUTHORITY_ATTR, + $this->groupNameAARC ? Configuration::REQUIRED_OPTION : '' + ); + + $interface = $configuration->getValueValidate( + self::INTERFACE_PROPNAME, + [Adapter::RPC, Adapter::LDAP], + Adapter::RPC + ); + $this->adapter = Adapter::getInstance($interface); + } + + public function process(&$request) + { + $eduPersonEntitlementExtended = []; + $capabilities = []; + $forwardedEduPersonEntitlement = []; + + if (isset($request['perun']['groups'])) { + $eduPersonEntitlementExtended = $this->getEduPersonEntitlementExtended($request); + + $capabilities = EntitlementUtils::getCapabilities( + $request, + $this->adapter, + $this->entitlementPrefix, + $this->entitlementAuthority + ); + } else { + Logger::debug( + 'perun:PerunEntitlementExtended: There are no user groups assigned to facility.' . + '=> Skipping getEduPersonEntitlementExtended and getCapabilities' + ); + } + + if ($this->releaseForwardedEntitlement) { + $forwardedEduPersonEntitlement = EntitlementUtils::getForwardedEduPersonEntitlement( + $request, + $this->adapter, + $this->forwardedEduPersonEntitlement + ); + } + + $request['Attributes'][$this->outputAttrName] = array_unique(array_merge( + $eduPersonEntitlementExtended, + $forwardedEduPersonEntitlement, + $capabilities + )); + } + + private function getEduPersonEntitlementExtended(&$request) + { + $eduPersonEntitlementExtended = []; + + $groups = $request['perun']['groups']; + foreach ($groups as $group) { + $entitlement = EntitlementUtils::groupEntitlementWrapper( + $group->getUuid(), + $this->entitlementPrefix, + $this->entitlementAuthority + ); + + array_push($eduPersonEntitlementExtended, $entitlement); + + $groupName = $group->getUniqueName(); + $groupName = preg_replace('/^(\w*)\:members$/', '$1', $groupName); + + $entitlementWithAttributes = EntitlementUtils::groupEntitlementWithAttributesWrapper( + $group->getUuid(), + $groupName, + $this->entitlementPrefix, + $this->entitlementAuthority + ); + + array_push($eduPersonEntitlementExtended, $entitlementWithAttributes); + } + + natsort($eduPersonEntitlementExtended); + return $eduPersonEntitlementExtended; + } +} diff --git a/lib/EntitlementUtils.php b/lib/EntitlementUtils.php new file mode 100644 index 00000000..181683f3 --- /dev/null +++ b/lib/EntitlementUtils.php @@ -0,0 +1,154 @@ +<?php + + +namespace SimpleSAML\Module\perun; + +use SimpleSAML\Error\Exception; +use SimpleSAML\Logger; + +/** + * Class EntitlementUtils + * + * This class contains common functions of PerunEntitlement and PerunEntitlementExtended. + * + * @author Dominik Baránek <baranek@ics.muni.cz> + */ +class EntitlementUtils +{ + const GROUP = 'group'; + const GROUP_ATTRIBUTES = 'groupAttributes'; + const DISPLAY_NAME = 'displayName'; + + + public static function getForwardedEduPersonEntitlement(&$request, $adapter, $forwardedEduPersonEntitlement) + { + $result = []; + + if (!isset($request['perun']['user'])) { + Logger::debug( + 'perun:EntitlementUtils: Object Perun User is not specified.' . + '=> Skipping getting forwardedEntitlement.' + ); + return $result; + } + + $user = $request['perun']['user']; + + try { + $forwardedEduPersonEntitlementMap = $adapter->getUserAttributesValues( + $user, + [$forwardedEduPersonEntitlement] + ); + } catch (Exception $exception) { + Logger::error( + 'perun:EntitlementUtils: Exception ' . $exception->getMessage() . + ' was thrown in method \'getForwardedEduPersonEntitlement\'.' + ); + } + + if (!empty($forwardedEduPersonEntitlementMap)) { + $result = array_values($forwardedEduPersonEntitlementMap)[0]; + } + + return $result; + } + + public static function getCapabilities(&$request, $adapter, $prefix, $authority) + { + $resourceCapabilities = []; + $facilityCapabilities = []; + $capabilitiesResult = []; + + $spEntityId = self::getSpEntityId($request); + try { + $resourceCapabilities = $adapter->getResourceCapabilities($spEntityId, $request['perun']['groups']); + $facilityCapabilities = $adapter->getFacilityCapabilities($spEntityId); + } catch (Exception $exception) { + Logger::error( + 'perun:EntitlementUtils: Exception ' . $exception->getMessage() . + ' was thrown in method \'getCapabilities\'.' + ); + } + + $capabilities = array_unique(array_merge($resourceCapabilities, $facilityCapabilities)); + + foreach ($capabilities as $capability) { + $wrappedCapability = self::capabilitiesWrapper($capability, $prefix, $authority); + array_push($capabilitiesResult, $wrappedCapability); + } + + return $capabilitiesResult; + } + + public static function encodeName($name) + { + $charsToSkip = [ + '-' => '%2D', + '_' => '%5F', + '.' => '%2E', + '~' => '%7E', + '!' => '%21', + '\'' => '%27', + '(' => '%28', + ')' => '%29', + '*' => '%2A', + ]; + + $name = rawurlencode($name); + $name = str_replace(array_values($charsToSkip), array_keys($charsToSkip), $name); + + return $name; + } + + public static function encodeEntitlement($name) + { + $charsToSkip = [ + '!' => '%21', + '$' => '%24', + '\'' => '%27', + '(' => '%28', + ')' => '%29', + '*' => '%2A', + ',' => '%2C', + ';' => '%3B', + '&' => '%26', + '=' => '%3D', + '@' => '%40', + ':' => '%3A', + '+' => '%2B' + ]; + + $name = array_map('rawurlencode', explode(':', $name)); + $name = str_replace(array_values($charsToSkip), array_keys($charsToSkip), $name); + + return $name; + } + + public static function capabilitiesWrapper($capabilities, $prefix, $authority) + { + return $prefix . implode(':', self::encodeEntitlement($capabilities)) . + '#' . $authority; + } + + public static function groupEntitlementWrapper($uuid, $prefix, $authority) + { + return $prefix . self::GROUP . ':' . + EntitlementUtils::encodeName($uuid) . '#' . $authority; + } + + public static function groupEntitlementWithAttributesWrapper($uuid, $groupName, $prefix, $authority) + { + return $prefix . self::GROUP_ATTRIBUTES . ':' . $uuid . '?' . self::DISPLAY_NAME . '=' . + EntitlementUtils::encodeName($groupName) . '#' . $authority; + } + + private static function getSpEntityId(&$request) + { + if (isset($request['SPMetadata']['entityid'])) { + return $request['SPMetadata']['entityid']; + } else { + throw new Exception('perun:EntitlementUtils: Cannot find entityID of remote SP. ' . + 'hint: Do you have this filter in IdP context?'); + } + } +} diff --git a/lib/model/Group.php b/lib/model/Group.php index 43024118..96869fd6 100644 --- a/lib/model/Group.php +++ b/lib/model/Group.php @@ -10,6 +10,7 @@ class Group implements HasId { private $id; private $voId; + private $uuid; private $name; private $uniqueName; private $description; @@ -22,10 +23,11 @@ class Group implements HasId * @param $uniqueName * @param $description */ - public function __construct($id, $voId, $name, $uniqueName, $description) + public function __construct($id, $voId, $uuid, $name, $uniqueName, $description) { $this->id = $id; $this->voId = $voId; + $this->uuid = $uuid; $this->name = $name; $this->uniqueName = $uniqueName; $this->description = $description; @@ -44,6 +46,14 @@ class Group implements HasId return $this->voId; } + /** + * @return string + */ + public function getUuid() + { + return $this->uuid; + } + /** * @return string */ -- GitLab