From b2306e36fc079cb3e727fe1d9cf2a9f2eeb58d6c Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Mon, 21 Nov 2011 13:01:55 +0000
Subject: [PATCH] authorize:Authorize: Add deny & regex flags.

This patch adds two new features to the authorize:Authorize filter:

- Add flag to change the policy from default-deny to default-allow. This
  makes it possible to block specific users.
- Add a flag that changes the matching algorithm from regex matching to
  string matching, which simplifies matching with strings that contain
  special characters.

This patch also makes it simpler to subclass this filter.

Thanks to Ryan Panning for implementing this!

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2987 44740490-163a-0410-bde0-09ae8108e29a
---
 modules/authorize/docs/authorize.txt          | 53 +++++++++++---
 .../authorize/lib/Auth/Process/Authorize.php  | 72 ++++++++++++++++---
 2 files changed, 106 insertions(+), 19 deletions(-)

diff --git a/modules/authorize/docs/authorize.txt b/modules/authorize/docs/authorize.txt
index 5664ee983..d8a0cc90d 100644
--- a/modules/authorize/docs/authorize.txt
+++ b/modules/authorize/docs/authorize.txt
@@ -8,31 +8,43 @@ authorize Module
 -->
 
   * Version: `$Id$`
-  * Author: Ernesto Revilla <erny@yaco.es>, Yaco Sistemas
+  * Author: Ernesto Revilla <erny@yaco.es>, Yaco Sistemas, Ryan Panning
   * Package: simpleSAMLphp
 
-This module provides an user authorization filter based on regular expressions for those applications that do not cleanly separate authentication from authorization and set some default permissions for authenticated users.
+This module provides a user authorization filter based on attribute matching for those applications that do not cleanly separate authentication from authorization and set some default permissions for authenticated users.
 
 
 `authorize:Authorize`
-: Authorize certain users based on regular expressions.
+: Authorize certain users based on attribute matching
 
 
 `authorize:Authorize`
 ---------------------
 
-For each attribute you can specify a regular expression string or array of strings. If one of those attributes matches (OR operator) one of the regular expression, the user is authorized successfully.
-
-You must use the preg_match format, i.e. you have to enclose it with a delimiter that does not appear inside the regex (e.g. slash (/), at sign (@), number sign (#) or underscore (`_`)).
+There are two configuration options that can be defined; deny and regex. All other filter configuration options are considered attribute matching rules.
 
 The users not authorized will be shown a 403 Forbidden page.
 
-Problems:
+### Deny ###
+The default action of the filter is to authorize only if an attribute match is found (default allow). When set to TRUE, this option reverses that rule and authorizes the user unless an attribute match is found (default deny), causing an unauthorized action.
+
+Note: This option needs to be boolean (TRUE/FALSE) else it will be considered an attribute matching rule.
+
+### Regex ###
+Turn regex pattern matching on or off for the attribute values defined. For backwards compatibility, this option defaults to TRUE, but can be turned off by setting it to FALSE.
+
+Note: This option needs to be boolean (TRUE/FALSE) else it will be considered an attribute matching rule.
+
+### Attribute Rules ###
+Each additional filter configuration option is considered an attribute matching rule. For each attribute, you can specify a string or array of strings to match. If one of those attributes match one of the rules (OR operator), the user is authorized/unauthorized (depending on the deny config option).
+
+Note: If regex is enabled, you must use the preg_match format, i.e. you have to enclose it with a delimiter that does not appear inside the regex (e.g. slash (/), at sign (@), number sign (#) or underscore (`_`)).
 
+### Problems ###
  * Once you get the forbidden page, you can't logout at the IdP directly,
    (as far as I know), you have to close the browser.
 
-
+### Examples ###
 To use this filter configure it in `config/config.php`:
 
 	'authproc.sp' => array(
@@ -45,3 +57,28 @@ To use this filter configure it in `config/config.php`:
 			'schacUserStatus' => '@urn:mace:terena.org:userStatus:' .
 				'example.org:service:active.*@',
 	)
+
+
+An alternate way of using this filter is to deny certain users. Or even use multiple filters to create a simple ACL, by first allowing a group of users but then denying a "black list" of users.
+
+	'authproc.sp' => array(
+		60 => array(
+			'class' => 'authorize:Authorize',
+			'deny'  => TRUE,
+			'uid'   =>  array(
+				'/.*@students.example.edu/',
+				'/(stu1|stu2|stu3)@example.edu/',
+			)
+	)
+
+The regex pattern matching can be turned off, allowing for exact attribute matching rules. This can be helpful in cases where you know what the value should be. An example of this is with the memberOf attribute or using the ldap:AttributeAddUsersGroups filter with the group attribute.
+
+	'authproc.sp' => array(
+		60 => array(
+			'class' => 'authorize:Authorize',
+			'regex' => FALSE,
+			'group' =>  array(
+				'CN=SimpleSAML Students,CN=Users,DC=example,DC=edu',
+				'CN=All Teachers,OU=Staff,DC=example,DC=edu',
+			)
+	)
diff --git a/modules/authorize/lib/Auth/Process/Authorize.php b/modules/authorize/lib/Auth/Process/Authorize.php
index baf94e83f..cd8155601 100644
--- a/modules/authorize/lib/Auth/Process/Authorize.php
+++ b/modules/authorize/lib/Auth/Process/Authorize.php
@@ -4,18 +4,32 @@
  * Filter to authorize only certain users.
  * See docs directory.
  *
- * @author Ernesto Revilla, Yaco Sistemas SL.
+ * @author Ernesto Revilla, Yaco Sistemas SL., Ryan Panning
  * @package simpleSAMLphp
  * @version $Id$
  */
 class sspmod_authorize_Auth_Process_Authorize extends SimpleSAML_Auth_ProcessingFilter {
 
+	/**
+	 * Flag to deny/unauthorize the user a attribute filter IS found
+	 *
+	 * @var bool
+	 */
+	protected $deny = FALSE;
+
+	/**
+	 * Flag to turn the REGEX pattern matching on or off
+	 *
+	 * @var bool
+	 */
+	protected $regex = TRUE;
+
 	/**
 	 * Array of valid users. Each element is a regular expression. You should
 	 * user \ to escape special chars, like '.' etc.
 	 *
 	 */
-	private $valid_attribute_values = array();
+	protected $valid_attribute_values = array();
 
 
 	/**
@@ -30,6 +44,20 @@ class sspmod_authorize_Auth_Process_Authorize extends SimpleSAML_Auth_Processing
 
 		assert('is_array($config)');
 
+		// Check for the deny option, get it and remove it
+		// Must be bool specifically, if not, it might be for a attrib filter below
+		if (isset($config['deny']) && is_bool($config['deny'])) {
+			$this->deny = $config['deny'];
+			unset($config['deny']);
+		}
+
+		// Check for the regex option, get it and remove it
+		// Must be bool specifically, if not, it might be for a attrib filter below
+		if (isset($config['regex']) && is_bool($config['regex'])) {
+			$this->regex = $config['regex'];
+			unset($config['regex']);
+		}
+
 		foreach ($config as $attribute => $values) {
 			if (is_string($values))
 				$values = array($values);
@@ -51,7 +79,7 @@ class sspmod_authorize_Auth_Process_Authorize extends SimpleSAML_Auth_Processing
 	 * @param array &$request  The current request
 	 */
 	public function process(&$request) {
-		$authorize = FALSE;
+		$authorize = $this->deny;
 		assert('is_array($request)');
 		assert('array_key_exists("Attributes", $request)');
 
@@ -64,8 +92,13 @@ class sspmod_authorize_Auth_Process_Authorize extends SimpleSAML_Auth_Processing
 					if (!is_array($values))
 						$values = array($values);
 					foreach ($values as $value){
-						if(preg_match($pattern, $value)) {
-							$authorize = TRUE;
+						if ($this->regex) {
+							$matched = preg_match($pattern, $value);
+						} else {
+							$matched = ($value == $pattern);
+						}
+						if ($matched) {
+							$authorize = ($this->deny ? FALSE : TRUE);
 							break 3;
 						}
 					}
@@ -73,14 +106,31 @@ class sspmod_authorize_Auth_Process_Authorize extends SimpleSAML_Auth_Processing
 			}
 		}
 		if (!$authorize){
-			/* Save state and redirect to 403 page. */
-			$id = SimpleSAML_Auth_State::saveState($request,
-				'authorize:Authorize');
-			$url = SimpleSAML_Module::getModuleURL(
-				'authorize/authorize_403.php');
-			SimpleSAML_Utilities::redirect($url, array('StateId' => $id));
+			$this->unauthorized($request);
 		}
 	}
+
+
+	/**
+	 * When the process logic determines that the user is not
+	 * authorized for this service, then forward the user to
+	 * an 403 unauthorized page.
+	 *
+	 * Separated this code into its own method so that child
+	 * classes can override it and change the action. Forward
+	 * thinking in case a "chained" ACL is needed, more complex
+	 * permission logic.
+	 *
+	 * @param array $request
+	 */
+	protected function unauthorized(&$request) {
+		/* Save state and redirect to 403 page. */
+		$id = SimpleSAML_Auth_State::saveState($request,
+			'authorize:Authorize');
+		$url = SimpleSAML_Module::getModuleURL(
+			'authorize/authorize_403.php');
+		SimpleSAML_Utilities::redirect($url, array('StateId' => $id));
+	}
 }
 
 ?>
-- 
GitLab