99/**
1010 * Class sspmod_perun_Auth_Process_ProxyFilter
1111 *
12- * This filter allows to disable/enable nested filter for particular SP
12+ * This filter allows to disable/enable nested filters for particular SP
1313 * or for users with one of (black/white)listed attribute values.
14- * Based on the mode of operation, the nested filter
15- * IS (whitelist) or IS NOT (blacklist) run when any of the attribute values matches.
14+ * Based on the mode of operation, the nested filters
15+ * ARE (whitelist) or ARE NOT (blacklist) run when any of the attribute values matches.
1616 * SPs are defined by theirs entityID in property 'filterSPs'.
1717 * User attributes are defined as a map 'attrName'=>['value1','value2']
1818 * in property 'filterAttributes'.
19- * Nested filter is defined in property config as regular filter.
19+ * Nested filters are defined in the authproc property in the same format as in config.
20+ * If only one filter is needed, it can be specified in the config property.
2021 *
2122 * example usage:
2223 *
3637 * 'class' => 'perun:ProxyFilter',
3738 * 'mode' => 'whitelist',
3839 * 'filterSPs' => ['enableSpEntityId01', 'enableSpEntityId02'],
39- * 'config' => [
40- * 'class' => 'perun:NestedFilter',
41- * // ...
40+ * 'authproc' => [
41+ * [
42+ * 'class' => 'perun:NestedFilter1',
43+ * // ...
44+ * ],
45+ * [
46+ * 'class' => 'perun:NestedFilter2',
47+ * // ...
48+ * ],
4249 * ],
4350 * ],
4451 *
@@ -53,8 +60,8 @@ class ProxyFilter extends \SimpleSAML\Auth\ProcessingFilter
5360 self ::MODE_WHITELIST ,
5461 ];
5562
56- private $ config ;
57- private $ nestedClass ;
63+ private $ authproc ;
64+ private $ nestedClasses ;
5865 private $ filterSPs ;
5966 private $ filterAttributes ;
6067 private $ mode ;
@@ -65,13 +72,20 @@ public function __construct($config, $reserved)
6572 parent ::__construct ($ config , $ reserved );
6673
6774 $ conf = Configuration::loadFromArray ($ config );
68- $ this ->config = $ conf ->getArray ('config ' , []);
69- $ this ->nestedClass = Configuration::loadFromArray ($ this ->config )->getString ('class ' );
70- unset($ this ->config ['class ' ]);
7175 $ this ->filterSPs = $ conf ->getArray ('filterSPs ' , []);
7276 $ this ->filterAttributes = $ conf ->getArray ('filterAttributes ' , []);
7377 $ this ->mode = $ conf ->getValueValidate ('mode ' , self ::MODES , self ::MODE_BLACKLIST );
7478
79+ $ this ->authproc = $ conf ->getArray ('authproc ' , []);
80+ $ this ->authproc [] = $ conf ->getArray ('config ' , []);
81+ $ this ->authproc = array_filter ($ this ->authproc );
82+ $ this ->nestedClasses = implode (', ' , array_map (
83+ function ($ config ) {
84+ return is_string ($ config ) ? $ config : $ config ['class ' ];
85+ },
86+ $ this ->authproc
87+ ));
88+
7589 $ this ->reserved = (array )$ reserved ;
7690 }
7791
@@ -86,7 +100,15 @@ public function process(&$request)
86100 }
87101
88102 if ($ shouldRun ) {
89- $ this ->runAuthProcFilter ($ request );
103+ $ this ->processState ($ request );
104+ } elseif ($ this ->mode === self ::MODE_WHITELIST ) {
105+ Logger::info (
106+ sprintf (
107+ 'perun.ProxyFilter: Not running filter %s for SP %s ' ,
108+ $ this ->nestedClasses ,
109+ $ request ['Destination ' ]['entityid ' ]
110+ )
111+ );
90112 }
91113 }
92114
@@ -99,7 +121,7 @@ private function shouldRunForSP($currentSp, $default)
99121 sprintf (
100122 'perun.ProxyFilter: %s filter %s for SP %s ' ,
101123 $ shouldRun ? 'Running ' : 'Filtering out ' ,
102- $ this ->nestedClass ,
124+ $ this ->nestedClasses ,
103125 $ currentSp
104126 )
105127 );
@@ -120,7 +142,7 @@ private function shouldRunForAttribute($attributes, $default)
120142 sprintf (
121143 'perun.ProxyFilter: %s filter %s because %s contains %s ' ,
122144 $ shouldRun ? 'Running ' : 'Filtering out ' ,
123- $ this ->nestedClass ,
145+ $ this ->nestedClasses ,
124146 $ attr ,
125147 $ value
126148 )
@@ -133,11 +155,102 @@ private function shouldRunForAttribute($attributes, $default)
133155 return $ default ;
134156 }
135157
136- private function runAuthProcFilter (&$ request )
158+ /**
159+ * Parse an array of authentication processing filters.
160+ * @see https://github.com/simplesamlphp/simplesamlphp/blob/simplesamlphp-1.17/lib/SimpleSAML/Auth/ProcessingChain.php
161+ *
162+ * @param array $filterSrc Array with filter configuration.
163+ * @return array Array of ProcessingFilter objects.
164+ */
165+ private static function parseFilterList ($ filterSrc )
166+ {
167+ assert (is_array ($ filterSrc ));
168+
169+ $ parsedFilters = [];
170+
171+ foreach ($ filterSrc as $ priority => $ filter ) {
172+ if (is_string ($ filter )) {
173+ $ filter = ['class ' => $ filter ];
174+ }
175+
176+ if (!is_array ($ filter )) {
177+ throw new \Exception ('Invalid authentication processing filter configuration: ' .
178+ 'One of the filters wasn \'t a string or an array. ' );
179+ }
180+
181+ $ parsedFilters [] = self ::parseFilter ($ filter , $ priority );
182+ }
183+
184+ return $ parsedFilters ;
185+ }
186+
187+ /**
188+ * Parse an authentication processing filter.
189+ * @see https://github.com/simplesamlphp/simplesamlphp/blob/simplesamlphp-1.17/lib/SimpleSAML/Auth/ProcessingChain.php
190+ *
191+ * @param array $config Array with the authentication processing filter configuration.
192+ * @param int $priority The priority of the current filter, (not included in the filter
193+ * definition.)
194+ * @return ProcessingFilter The parsed filter.
195+ */
196+ private static function parseFilter ($ config , $ priority )
197+ {
198+ assert (is_array ($ config ));
199+
200+ if (!array_key_exists ('class ' , $ config )) {
201+ throw new \Exception ('Authentication processing filter without name given. ' );
202+ }
203+
204+ $ className = \SimpleSAML \Module::resolveClass (
205+ $ config ['class ' ],
206+ 'Auth\Process ' ,
207+ '\SimpleSAML\Auth\ProcessingFilter '
208+ );
209+ $ config ['%priority ' ] = $ priority ;
210+ unset($ config ['class ' ]);
211+ return new $ className ($ config , null );
212+ }
213+
214+ /**
215+ * Process the given state.
216+ * @see https://github.com/simplesamlphp/simplesamlphp/blob/simplesamlphp-1.17/lib/SimpleSAML/Auth/ProcessingChain.php
217+ *
218+ * This function will only return if processing completes. If processing requires showing
219+ * a page to the user, we will not be able to return from this function. There are two ways
220+ * this can be handled:
221+ * - Redirect to a URL: We will redirect to the URL set in $state['ReturnURL'].
222+ * - Call a function: We will call the function set in $state['ReturnCall'].
223+ *
224+ * If an exception is thrown during processing, it should be handled by the caller of
225+ * this function. If the user has redirected to a different page, the exception will be
226+ * returned through the exception handler defined on the state array. See
227+ * State for more information.
228+ *
229+ * @see State
230+ * @see State::EXCEPTION_HANDLER_URL
231+ * @see State::EXCEPTION_HANDLER_FUNC
232+ *
233+ * @param array &$state The state we are processing.
234+ */
235+ private function processState (&$ state )
137236 {
138- list ($ module , $ simpleClass ) = explode (': ' , $ this ->nestedClass );
139- $ className = '\SimpleSAML\Module \\' . $ module . '\Auth\Process \\' . $ simpleClass ;
140- $ authFilter = new $ className ($ this ->config , $ this ->reserved );
141- $ authFilter ->process ($ request );
237+ $ filters = self ::parseFilterList ($ this ->authproc );
238+ try {
239+ while (count ($ filters ) > 0 ) {
240+ $ filter = array_shift ($ filters );
241+ $ filter ->process ($ state );
242+ }
243+ } catch (\SimpleSAML \Error \Exception $ e ) {
244+ // No need to convert the exception
245+ throw $ e ;
246+ } catch (\Exception $ e ) {
247+ /*
248+ * To be consistent with the exception we return after an redirect,
249+ * we convert this exception before returning it.
250+ */
251+ throw new \SimpleSAML \Error \UnserializableException ($ e );
252+ }
253+
254+ // Completed
142255 }
143256}
0 commit comments