Skip to content
This repository was archived by the owner on Sep 19, 2022. It is now read-only.

Commit 301139a

Browse files
author
Dominik Frantisek Bucik
committed
feat: 🎸 PerunAup authProcFilter
Filter to check approved AUP and redirect to approval
1 parent b31976a commit 301139a

File tree

6 files changed

+240
-0
lines changed

6 files changed

+240
-0
lines changed

config-templates/processFilterConfigurations-example.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ Example how to enable filter AttributeMap:
164164
## ExtractRequestAttribute
165165

166166
Filter is intended to extract an attribute specified by set of keys forming the chain of keys in the `$request` variable into the configured destination attribute.
167+
167168
Configuration options:
168169
* `attr_name`: specifies attribute name, into which the extracted value will be stored
169170
* `request_keys`: string, which contains a semicolon (`;`) separated chain of keys that are examined in the state. Numeric keys are automatically treated as array indexes. For instance, value `'saml:AuthenticatingAuthority;0'` will be treated as code `$request['saml:AuthenticatingAuthority'][0]`. In case of this value being empty, exception is thrown. Otherwise, extracted value is stored into the configured destination attribute.
@@ -204,3 +205,27 @@ Configuration options:
204205
'perun_register_url' => 'https://signup.perun.cesnet.cz/fed/registrar/?vo=cesnet'
205206
],
206207
```
208+
209+
## PerunAup
210+
211+
Filter fetches the given attribute holding approved AUP and checks, if expected value is set in the attribute or not. If not, it redirects the user to specified registration component, where user will be asked to approve the AUP.
212+
213+
Configuration options:
214+
* `interface`: specifies what interface of Perun should be used to fetch data. See class `SimpleSAML\Module\perun\PerunAdapter` for more details.
215+
* `attribute`: name of the attribute, which will be fetched from Perun and holds the value of approved AUP.
216+
* `value`: value that is expected in the attribute as mark of approved AUP. Expected is a string.
217+
* `approval_url`: URL to which the user will be forwarded for registration. Leave empty to use the Perun registrar.
218+
* `callback_parameter_name`: name of the parameter wich will hold callback URL, where the user should be redirected after the AUP approval on URL configured in the `approval_url` property.
219+
* `perun_register_url`: the complete URL (including vo and group) to which user will be redirected, if `approval_url` has not been configured. Parameters targetnew, targetexisting and targetextended will be set to callback URL to continue after the AUP approval is completed.
220+
221+
```php
222+
3 => [
223+
'class' => 'perun:PerunAup',
224+
'interface' => 'LDAP',
225+
'value' => 'aup_2020_01_01',
226+
'attribute' => 'approved_aup',
227+
'approval_url' => 'https://signup.cesnet.cz/aup/',
228+
'callback_parameter_name' => 'callback',
229+
'perun_approval_url' => 'https://signup.perun.cesnet.cz/fed/registrar/?vo=cesnet&group=aup'
230+
],
231+
```

dictionaries/perun.definition.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,13 @@
102102
"register_button": {
103103
"en": "Proceed to register for an account",
104104
"cs": "Pokračovat na registraci ůčtu"
105+
},
106+
"aup_text": {
107+
"en": "Oops! It seems you have tried to access service via Perun AAI, but you have not approved the Acceptable Use Policy (AUP). Let's fix that!",
108+
"cs": "Ups! Vyzerá to, že jste se pokousil(a) přihlásit ke službě skrze Perun AAI, no neschválili jste Podmínky užití služby (AUP). Pojďme to napravit!"
109+
},
110+
"aup_button": {
111+
"en": "Proceed to approval of the AUP",
112+
"cs": "Pokračovat na potvrzení souhlasu s AUP"
105113
}
106114
}

lib/Auth/Process/PerunAup.php

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\perun\Auth\Process;
6+
7+
use SimpleSAML\Auth\ProcessingFilter;
8+
use SimpleSAML\Auth\State;
9+
use SimpleSAML\Configuration;
10+
use SimpleSAML\Error\Exception;
11+
use SimpleSAML\Logger;
12+
use SimpleSAML\Module;
13+
use SimpleSAML\Module\perun\Adapter;
14+
use SimpleSAML\Module\perun\PerunConstants;
15+
use SimpleSAML\Utils\HTTP;
16+
17+
/**
18+
* Class checks if the user has approved given aup, and forwards to approval page if not.
19+
*/
20+
class PerunAup extends ProcessingFilter
21+
{
22+
public const STAGE = 'perun:PerunAup';
23+
public const DEBUG_PREFIX = self::STAGE . ' - ';
24+
25+
public const CALLBACK = 'perun/perun_aup_callback.php';
26+
public const REDIRECT = 'perun/perun_aup.php';
27+
public const TEMPLATE = 'perun:perun-aup-tpl.php';
28+
29+
public const PARAM_STATE_ID = PerunConstants::STATE_ID;
30+
public const PARAM_APPROVAL_URL = 'approvalUrl';
31+
32+
public const INTERFACE = 'interface';
33+
public const AUP_ATTR = 'attribute';
34+
public const AUP_VALUE = 'value';
35+
public const APPROVAL_URL = 'approval_url';
36+
public const CALLBACK_PARAMETER_NAME = 'callback_parameter_name';
37+
public const PERUN_APPROVAL_URL = 'perun_approval_url';
38+
39+
private $adapter;
40+
private $aupAttr;
41+
private $aupValue;
42+
private $approvalUrl;
43+
private $callbackParameterName;
44+
private $perunApprovalUrl;
45+
private $config;
46+
private $filterConfig;
47+
48+
public function __construct($config, $reserved)
49+
{
50+
parent::__construct($config, $reserved);
51+
$this->config = $config;
52+
$this->filterConfig = Configuration::loadFromArray($config);
53+
54+
$interface = $this->filterConfig->getString(self::INTERFACE, Adapter::RPC);
55+
$this->adapter = Adapter::getInstance($interface);
56+
57+
$this->aupAttr = $this->filterConfig->getString(self::AUP_ATTR, null);
58+
if (empty($this->aupAttr)) {
59+
throw new Exception(
60+
self::DEBUG_PREFIX . 'Invalid configuration: no attribute containing approved AUP ' . 'has been configured. Use option \'' . self::AUP_ATTR . '\' to configure the name of the Perun' . 'attribute, which should contain the approved AUP version.'
61+
);
62+
}
63+
64+
$this->aupValue = $this->filterConfig->getString(self::AUP_VALUE, null);
65+
if (empty($this->aupValue)) {
66+
throw new Exception(
67+
self::DEBUG_PREFIX . 'Invalid configuration: no value signaling AUP which needs to be approved ' . 'has been configured. Use option \'' . self::AUP_VALUE . '\' to configure the value, which needs to ' . 'be present in the attribute containing the approved AUP version.'
68+
);
69+
}
70+
71+
$this->approvalUrl = $this->filterConfig->getString(self::APPROVAL_URL, null);
72+
$this->callbackParameterName = $this->filterConfig->getString(self::CALLBACK_PARAMETER_NAME, null);
73+
$this->perunApprovalUrl = $this->filterConfig->getString(self::PERUN_APPROVAL_URL, null);
74+
if (empty($this->approvalUrl) && empty($this->callbackParameterName) && empty($this->perunApprovalUrl)) {
75+
throw new Exception(
76+
self::DEBUG_PREFIX . 'Invalid configuration: no URL where user should approve the AUP ' . 'has been configured. Use option \'' . self::APPROVAL_URL . '\' to configure the URL and ' . 'option \'' . self::CALLBACK_PARAMETER_NAME . '\' to configure name of the callback parameter.
77+
. If you wish to use the Perun registrar, use the option \'' . self::PERUN_APPROVAL_URL . '\'.'
78+
);
79+
}
80+
}
81+
82+
public function process(&$request)
83+
{
84+
assert(is_array($request));
85+
assert(!empty($request[PerunConstants::PERUN][PerunConstants::USER]));
86+
87+
if (empty($request[PerunConstants::PERUN][PerunConstants::USER])) {
88+
throw new Exception(
89+
self::DEBUG_PREFIX . 'Request does not contain Perun user. Did you configure ' . PerunUser::STAGE . ' filter before this filter in the processing chain?'
90+
);
91+
}
92+
$user = $request[PerunConstants::PERUN][PerunConstants::USER];
93+
94+
$aupAttr = null;
95+
$userAttributesValues = $this->adapter->getUserAttributesValues($user, [$this->aupAttr]);
96+
if (empty($userAttributesValues) || empty($userAttributesValues[$this->aupAttr])) {
97+
Logger::warning(
98+
self::DEBUG_PREFIX . 'Attribute \'' . $this->aupAttr . '\' is empty. Probably could not be '
99+
. 'fetched. Redirecting user to approve AUP.'
100+
);
101+
} else {
102+
$aupAttr = $userAttributesValues[$this->aupAttr];
103+
}
104+
105+
if ($aupAttr === $this->aupValue) {
106+
Logger::info(
107+
self::DEBUG_PREFIX . 'User approved AUP did match the expected value, continue processing.'
108+
);
109+
110+
return;
111+
}
112+
Logger::info(
113+
self::DEBUG_PREFIX . 'User did not approve the expected AUP. Expected value \''
114+
. $this->aupValue . '\', actual value \'' . $aupAttr . '\'. Redirecting user to AUP approval page.'
115+
);
116+
$this->redirect($request);
117+
}
118+
119+
private function redirect(&$request): void
120+
{
121+
$request[PerunConstants::CONTINUE_FILTER_CONFIG] = $this->config;
122+
$stateId = State::saveState($request, self::STAGE);
123+
$callback = Module::getModuleURL(self::CALLBACK, [
124+
self::PARAM_STATE_ID => $stateId,
125+
]);
126+
if (!empty($this->approvalUrl) && !empty($this->callbackParameterName)) {
127+
Logger::debug(
128+
self::DEBUG_PREFIX . 'Redirecting to \'' . $this->approvalUrl . ', callback parameter \''
129+
. $this->callbackParameterName . '\' with value \'' . $callback . '\''
130+
);
131+
HTTP::redirectTrustedURL($this->approvalUrl, [
132+
$this->callbackParameterName => $callback,
133+
]);
134+
} elseif (!empty($this->perunApprovalUrl)) {
135+
$params[PerunConstants::TARGET_NEW] = $callback;
136+
$params[PerunConstants::TARGET_EXISTING] = $callback;
137+
$params[PerunConstants::TARGET_EXTENDED] = $callback;
138+
139+
$url = Module::getModuleURL(self::REDIRECT);
140+
$approvalUrl = HTTP::addURLParameters($this->approvalUrl, $params);
141+
Logger::debug(
142+
self::DEBUG_PREFIX . 'Redirecting to \'' . self::REDIRECT . ', approval URL \''
143+
. $approvalUrl . '\''
144+
);
145+
HTTP::redirectTrustedURL($url, [
146+
self::PARAM_APPROVAL_URL => $approvalUrl,
147+
]);
148+
} else {
149+
throw new Exception(self::DEBUG_PREFIX . 'No configuration for AUP approval enabled. Cannot proceed');
150+
}
151+
}
152+
}

templates/perun-aup-tpl.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types=1);
2+
3+
use SimpleSAML\Module\perun\Auth\Process\PerunAup;
4+
5+
$this->includeAtTemplateBase('includes/header.php');
6+
7+
?>
8+
9+
<div class="row">
10+
<div class="offset-1 col-10 offset-sm-1 col-sm-10 offset-md-2 col-md-8 offset-lg-3 col-lg-6 offset-xl-3 col-xl-6">
11+
<p><?php echo $this->t('{perun:perun:aup_text}'); ?></p>
12+
<a class="btn btn-block" href="<?php echo $this->data[PerunAup::PARAM_APPROVAL_URL]; ?>">
13+
<?php echo $this->t('{perun:perun:aup_button}'); ?>
14+
</a>
15+
</div>
16+
</div>
17+
18+
19+
<?php
20+
21+
$this->includeAtTemplateBase('includes/footer.php');

www/perun_aup.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use SimpleSAML\Configuration;
6+
use SimpleSAML\Module\perun\Auth\Process\PerunAup;
7+
use SimpleSAML\XHTML\Template;
8+
9+
$config = Configuration::getInstance();
10+
$t = new Template($config, PerunAup::TEMPLATE);
11+
$t->data[PerunAup::PARAM_APPROVAL_URL] = $_REQUEST[PerunAup::PARAM_APPROVAL_URL];
12+
13+
$t->show();

www/perun_aup_callback.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use SimpleSAML\Auth\ProcessingChain;
6+
use SimpleSAML\Auth\State;
7+
use SimpleSAML\Error\BadRequest;
8+
use SimpleSAML\Module\perun\Auth\Process\PerunAup;
9+
use SimpleSAML\Module\perun\PerunConstants;
10+
11+
if (empty($_REQUEST[PerunAup::PARAM_STATE_ID])) {
12+
throw new BadRequest('Missing required \'' . PerunAup::PARAM_STATE_ID . '\' query parameter.');
13+
}
14+
$state = State::loadState($_REQUEST[PerunAup::PARAM_STATE_ID], PerunAup::STAGE);
15+
16+
$filterConfig = $state[PerunConstants::CONTINUE_FILTER_CONFIG];
17+
$perunAup = new PerunAup($filterConfig, null);
18+
$perunAup->process($state);
19+
20+
// we have not been redirected, continue processing
21+
ProcessingChain::resumeProcessing($state);

0 commit comments

Comments
 (0)