diff --git a/config-templates/acl.php b/config-templates/acl.php
new file mode 100644
index 0000000000000000000000000000000000000000..ce901cefeddf7c078c29affdaf3d5ee0d71b03bc
--- /dev/null
+++ b/config-templates/acl.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file defines "named" access control lists, which can
+ * be reused in several places.
+ */
+$config = array(
+
+	'adminlist' => array(
+		//array('allow', 'equals', 'mail', 'admin1@example.org'),
+		//array('allow', 'has', 'groups', 'admin'),
+		/* The default action is to deny access. */
+	),
+
+	'example-simple' => array(
+		array('allow', 'equals', 'mail', 'admin1@example.org'),
+		array('allow', 'equals', 'mail', 'admin2@example.org'),
+		/* The default action is to deny access. */
+	),
+
+	'example-deny-some' => array(
+		array('deny', 'equals', 'mail', 'eviluser@example.org'),
+		array('allow'), /* Allow everybody else. */
+	),
+
+	'example-maildomain' => array(
+		array('allow', 'equals-preg', 'mail', '/@example\.org$/'),
+		/* The default action is to deny access. */
+	),
+
+	'example-allow-employees' => array(
+		array('allow', 'has', 'eduPersonAffiliation', 'employee'),
+		/* The default action is to deny access. */
+	),
+
+	'example-allow-employees-not-students' => array(
+		array('deny', 'has', 'eduPersonAffiliation', 'student'),
+		array('allow', 'has', 'eduPersonAffiliation', 'employee'),
+		/* The default action is to deny access. */
+	),
+
+	'example-deny-student-except-one' => array(
+		array('deny', 'and',
+			array('has', 'eduPersonAffiliation', 'student'),
+			array('not', 'equals', 'mail', 'user@example.org'),
+		),
+		array('allow'),
+	),
+
+	'example-allow-or' => array(
+		array('allow', 'or',
+			array('equals', 'eduPersonAffiliation', 'student', 'member'),
+			array('equals', 'mail', 'someuser@example2.org'),
+		),
+	),
+
+	'example-allow-all' => array(
+		array('allow'),
+	),
+
+);
\ No newline at end of file
diff --git a/modules/core/lib/ACL.php b/modules/core/lib/ACL.php
new file mode 100644
index 0000000000000000000000000000000000000000..1bd1ad8dc8c691309c13a7fbdfcb3fb51598ea90
--- /dev/null
+++ b/modules/core/lib/ACL.php
@@ -0,0 +1,307 @@
+<?php
+
+/**
+ * Generic library for access control lists.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_core_ACL {
+
+	/**
+	 * The access control list, as an array.
+	 *
+	 * @var array
+	 */
+	private $acl;
+
+
+	/**
+	 * Initializer for this access control list.
+	 *
+	 * @param array|string $acl  The access control list.
+	 */
+	public function __construct($acl) {
+		assert('is_string($acl) || is_array($acl)');
+
+		if (is_string($acl)) {
+			$acl = self::getById($acl);
+		}
+
+		foreach ($acl as $rule) {
+			if (!is_array($rule)) {
+				throw new SimpleSAML_Error_Exception('Invalid rule in access control list: ' . var_export($rule, TRUE));
+			}
+			if (count($rule) === 0) {
+				throw new SimpleSAML_Error_Exception('Empty rule in access control list.');
+			}
+
+			$action = array_shift($rule);
+			if ($action !== 'allow' && $action !== 'deny') {
+				throw new SimpleSAML_Error_Exception('Invalid action in rule in access control list: ' . var_export($action, TRUE));
+			}
+
+		}
+
+		$this->acl = $acl;
+	}
+
+
+	/**
+	 * Retrieve an access control list with the given id.
+	 *
+	 * @param string $id  The id of the access control list.
+	 * @return array  The access control list array.
+	 */
+	private static function getById($id) {
+		assert('is_string($id)');
+
+		$config = SimpleSAML_Configuration::getConfig('acl.php');
+		if (!$config->hasValue($id)) {
+			throw new SimpleSAML_Error_Exception('No ACL with id ' . var_export($id, TRUE) . ' in config/acl.php.');
+		}
+
+		return $config->getArray($id);
+	}
+
+
+	/**
+	 * Match the attributes against the access control list.
+	 *
+	 * @param array $attributes  The attributes of an user.
+	 * @return boolean  TRUE if the user is allowed to access the resource, FALSE if not.
+	 */
+	public function allows(array $attributes) {
+
+		foreach ($this->acl as $rule) {
+			$action = array_shift($rule);
+
+			if (!self::match($attributes, $rule)) {
+				continue;
+			}
+
+			if ($action === 'allow') {
+				return TRUE;
+			} else {
+				return FALSE;
+			}
+		}
+	}
+
+
+	/**
+	 * Match the attributes against the given rule.
+	 *
+	 * @param array $attributes  The attributes of an user.
+	 * @param array $rule  The rule we should check.
+	 * @return boolean  TRUE if the rule matches, FALSE if not.
+	 */
+	private static function match(array $attributes, array $rule) {
+
+		$op = array_shift($rule);
+		if ($op === NULL) {
+			/* An empty rule always matches. */
+			return TRUE;
+		}
+
+		switch($op) {
+		case 'and':
+			return self::opAnd($attributes, $rule);
+		case 'equals':
+			return self::opEquals($attributes, $rule);
+		case 'equals-preg':
+			return self::opEqualsPreg($attributes, $rule);
+		case 'has':
+			return self::opHas($attributes, $rule);
+		case 'has-preg':
+			return self::opHasPreg($attributes, $rule);
+		case 'not':
+			return !self::match($attributes, $rule);
+		case 'or':
+			return self::opOr($attributes, $rule);
+		default:
+			throw new SimpleSAML_Error_Exception('Invalid ACL operation: ' . var_export($op. TRUE));
+		}
+	}
+
+
+	/**
+	 * 'and' match operator.
+	 *
+	 * @param array $attributes  The attributes of an user.
+	 * @param array $rule  The rule we should check.
+	 * @return boolean  TRUE if the rule matches, FALSE if not.
+	 */
+	private static function opAnd($attributes, $rule) {
+
+		foreach ($rule as $subRule) {
+			if (!self::match($attributes, $subRule)) {
+				return FALSE;
+			}
+		}
+
+		/* All matches. */
+		return TRUE;
+	}
+
+
+	/**
+	 * 'equals' match operator.
+	 *
+	 * @param array $attributes  The attributes of an user.
+	 * @param array $rule  The rule we should check.
+	 * @return boolean  TRUE if the rule matches, FALSE if not.
+	 */
+	private static function opEquals($attributes, $rule) {
+
+		$attributeName = array_shift($rule);
+
+		if (!array_key_exists($attributeName, $attributes)) {
+			$attributeValues = array();
+		} else {
+			$attributeValues = $attributes[$attributeName];
+		}
+
+		foreach ($rule as $value) {
+			$found = FALSE;
+			foreach ($attributeValues as $i => $v) {
+				if ($value !== $v) {
+					continue;
+				}
+				unset($attributeValues[$i]);
+				$found = TRUE;
+				break;
+			}
+			if (!$found) {
+				return FALSE;
+			}
+		}
+		if (!empty($attributeValues)) {
+			/* One of the attribute values didn't match. */
+			return FALSE;
+		}
+
+		/* All the values in the attribute matched one in the rule. */
+		return TRUE;
+	}
+
+
+	/**
+	 * 'equals-preg' match operator.
+	 *
+	 * @param array $attributes  The attributes of an user.
+	 * @param array $rule  The rule we should check.
+	 * @return boolean  TRUE if the rule matches, FALSE if not.
+	 */
+	private static function opEqualsPreg($attributes, $rule) {
+
+		$attributeName = array_shift($rule);
+
+		if (!array_key_exists($attributeName, $attributes)) {
+			$attributeValues = array();
+		} else {
+			$attributeValues = $attributes[$attributeName];
+		}
+
+		foreach ($rule as $pattern) {
+			$found = FALSE;
+			foreach ($attributeValues as $i => $v) {
+				if (!preg_match($pattern, $v)) {
+					continue;
+				}
+				unset($attributeValues[$i]);
+				$found = TRUE;
+				break;
+			}
+			if (!$found) {
+				return FALSE;
+			}
+		}
+
+		if (!empty($attributeValues)) {
+			/* One of the attribute values didn't match. */
+			return FALSE;
+		}
+
+		/* All the values in the attribute matched one in the rule. */
+		return TRUE;
+	}
+
+
+	/**
+	 * 'has' match operator.
+	 *
+	 * @param array $attributes  The attributes of an user.
+	 * @param array $rule  The rule we should check.
+	 * @return boolean  TRUE if the rule matches, FALSE if not.
+	 */
+	private static function opHas($attributes, $rule) {
+
+		$attributeName = array_shift($rule);
+
+		if (!array_key_exists($attributeName, $attributes)) {
+			$attributeValues = array();
+		} else {
+			$attributeValues = $attributes[$attributeName];
+		}
+
+		foreach ($rule as $value) {
+			if (!in_array($value, $attributeValues, TRUE)) {
+				return FALSE;
+			}
+		}
+
+		/* Found all values in the rule in the attribute. */
+		return TRUE;
+	}
+
+
+	/**
+	 * 'has-preg' match operator.
+	 *
+	 * @param array $attributes  The attributes of an user.
+	 * @param array $rule  The rule we should check.
+	 * @return boolean  TRUE if the rule matches, FALSE if not.
+	 */
+	private static function opHasPreg($attributes, $rule) {
+
+		$attributeName = array_shift($rule);
+
+		if (!array_key_exists($attributeName, $attributes)) {
+			$attributeValues = array();
+		} else {
+			$attributeValues = $attributes[$attributeName];
+		}
+
+		foreach ($rule as $pattern) {
+			$matches = preg_grep($pattern, $attributeValues);
+			if (count($matches) === 0) {
+				return FALSE;
+			}
+		}
+
+		/* Found all values in the rule in the attribute. */
+		return TRUE;
+	}
+
+
+	/**
+	 * 'or' match operator.
+	 *
+	 * @param array $attributes  The attributes of an user.
+	 * @param array $rule  The rule we should check.
+	 * @return boolean  TRUE if the rule matches, FALSE if not.
+	 */
+	private static function opOr($attributes, $rule) {
+
+		foreach ($rule as $subRule) {
+			if (self::match($attributes, $subRule)) {
+				return TRUE;
+			}
+		}
+
+		/* None matches. */
+		return FALSE;
+	}
+
+}